From 3bfcc3501f3c7757ee9d5a9f4948456a4bc2821d Mon Sep 17 00:00:00 2001 From: DCurrent Date: Thu, 20 Aug 2026 16:52:28 -0400 Subject: [PATCH 1/4] Fix script mapping for 64-bit animation identifiers Update dynamic animation table declarations in scriptcommon.h from int pointers to animation_id_t pointers, matching their definitions in openbor.c. The stale 32-bit declarations caused the script constant mapper to walk 64-bit animation tables in 32-bit steps. This returned the upper zero half of an entry for every second lookup and shifted the remaining animation constants to incorrect identifiers. This affected ANI_FOLLOW and other configurable animation families used through openborconstant(). --- engine/openbor.c | 106 +++++++++++++++++++++------- engine/openbor.h | 14 ++-- engine/source/openborscript/model.c | 3 +- 3 files changed, 90 insertions(+), 33 deletions(-) diff --git a/engine/openbor.c b/engine/openbor.c index 5ad67de3b..80a11a7ca 100644 --- a/engine/openbor.c +++ b/engine/openbor.c @@ -4084,6 +4084,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 an +* unsigned 64-bit integer using the bounded token parser. +*/ +static bool command_argument_get_uint64(const char* argument, uint64_t* result) { + s_command_token token; + + assert(argument); + assert(result); + + token.text = argument; + token.length = strlen(argument); + + return command_token_get_uint64(&token, result); +} + /* * Caskey, Damon V. * 2026-08-06 @@ -15743,7 +15762,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_uint64(GET_ARG(1), &newchar->score)) + { + borShutdown( + 1, + "Invalid unsigned 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 +24610,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_uint64(GET_ARG(1), &next.score)) { - next.score = 0; // So negative values cannot be added + borShutdown( + 1, + "Invalid unsigned 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 +25420,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 +26247,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 +26652,19 @@ void update_loading(s_loadingbar *s, int value, int max) } } -void addscore(int playerindex, int add) +/* +* Caskey, Damon V. +* 2026-08-20 +* +* Add an unsigned 64-bit score award to a player. Saturate +* at UINT64_MAX, award crossed life thresholds without an +* unbounded loop, and execute the player's score script. +*/ +void addscore(int playerindex, uint64_t add) { + 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 +26675,42 @@ 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; - - s += add; - if(s > 999999999) + if(add > UINT64_MAX - s) { - s = 999999999; + s = UINT64_MAX; + } + else + { + s += add; } - while(s > next1up) + /* + * Awarded life 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,8 +26719,8 @@ 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_UINTEGER64); + var.ullVal = add; Script_Set_Local_Variant(cs, "score", &var); Script_Execute(cs); ScriptVariant_Clear(&var); @@ -52118,7 +52176,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 +52239,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 +52341,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..03c916dbd 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. ~~ + uint64_t score; // Points given to 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 + uint64_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,7 @@ 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, uint64_t add); void free_ent(entity *e); void free_ents(); int alloc_ents(); @@ -5048,7 +5048,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 +5071,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..8d180a741 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_UINTEGER64 }, {.property = MODEL_PROPERTY_SCROLL, .id_string = "MODEL_PROPERTY_SCROLL", @@ -556,4 +556,3 @@ HRESULT openbor_set_model_property(ScriptVariant** varlist, ScriptVariant** cons - From caed49f1d4174f362521e935c60dbfea3754a8eb Mon Sep 17 00:00:00 2001 From: DCurrent Date: Thu, 20 Aug 2026 17:34:34 -0400 Subject: [PATCH 2/4] Model items may have negative score value - total score remaisn a usighend 64 bit. --- engine/openbor.c | 52 +++++++++++++++++++---------- engine/openbor.h | 6 ++-- engine/source/openborscript/model.c | 3 +- 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/engine/openbor.c b/engine/openbor.c index 80a11a7ca..25c0449cc 100644 --- a/engine/openbor.c +++ b/engine/openbor.c @@ -4088,10 +4088,10 @@ static bool command_token_get_uint64(const s_command_token* token, uint64_t* res * Caskey, Damon V. * 2026-08-20 * -* Convert a null-terminated command argument to an -* unsigned 64-bit integer using the bounded token parser. +* Convert a null-terminated command argument to a +* signed 64-bit integer using the bounded token parser. */ -static bool command_argument_get_uint64(const char* argument, uint64_t* result) { +static bool command_argument_get_int64(const char* argument, int64_t* result) { s_command_token token; assert(argument); @@ -4100,7 +4100,7 @@ static bool command_argument_get_uint64(const char* argument, uint64_t* result) token.text = argument; token.length = strlen(argument); - return command_token_get_uint64(&token, result); + return command_token_get_int64(&token, result); } /* @@ -15762,11 +15762,11 @@ s_model *load_cached_model(char *name, char *owner, char unload) } break; case CMD_MODEL_SCORE: - if(!command_argument_get_uint64(GET_ARG(1), &newchar->score)) + if(!command_argument_get_int64(GET_ARG(1), &newchar->score)) { borShutdown( 1, - "Invalid unsigned 64-bit score '%s' in %s, line %zu.\n", + "Invalid signed 64-bit score '%s' in %s, line %zu.\n", GET_ARG(1), filename, line @@ -24610,11 +24610,11 @@ void load_level(char *filename) break; case CMD_LEVEL_SCORE: // So score can be overriden in the levels .txt file - if(!command_argument_get_uint64(GET_ARG(1), &next.score)) + if(!command_argument_get_int64(GET_ARG(1), &next.score)) { borShutdown( 1, - "Invalid unsigned 64-bit score '%s' in %s, line %d.\n", + "Invalid signed 64-bit score '%s' in %s, line %d.\n", GET_ARG(1), filename, line @@ -26656,12 +26656,14 @@ void update_loading(s_loadingbar *s, int value, int max) * Caskey, Damon V. * 2026-08-20 * -* Add an unsigned 64-bit score award to a player. Saturate -* at UINT64_MAX, award crossed life thresholds without an -* unbounded loop, and execute the player's score script. +* 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, uint64_t add) +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; @@ -26679,17 +26681,31 @@ void addscore(int playerindex, uint64_t add) s = old_score; cs = score_script + playerindex; - if(add > UINT64_MAX - s) + if(add >= 0) { - s = UINT64_MAX; + add_magnitude = (uint64_t)add; + + if(add_magnitude > UINT64_MAX - s) + { + s = UINT64_MAX; + } + else + { + s += add_magnitude; + } } else { - s += add; + add_magnitude = add == INT64_MIN + ? (uint64_t)INT64_MAX + UINT64_C(1) + : (uint64_t)-add; + + s = add_magnitude > s ? 0 : s - add_magnitude; } /* - * Awarded life after score exceeds the boundary. + * Preserve the legacy strict threshold comparison: a + * life is awarded only after score exceeds the boundary. */ if(lifescore > 0 && s > old_score) { @@ -26719,8 +26735,8 @@ void addscore(int playerindex, uint64_t add) if(Script_IsInitialized(cs)) { ScriptVariant_Init(&var); - ScriptVariant_ChangeType(&var, VT_UINTEGER64); - var.ullVal = add; + ScriptVariant_ChangeType(&var, VT_INTEGER64); + var.llVal = add; Script_Set_Local_Variant(cs, "score", &var); Script_Execute(cs); ScriptVariant_Clear(&var); diff --git a/engine/openbor.h b/engine/openbor.h index 03c916dbd..4ab5dab42 100644 --- a/engine/openbor.h +++ b/engine/openbor.h @@ -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. ~~ - uint64_t 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 - uint64_t 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,7 @@ 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, uint64_t add); +void addscore(int playerindex, int64_t add); void free_ent(entity *e); void free_ents(); int alloc_ents(); diff --git a/engine/source/openborscript/model.c b/engine/source/openborscript/model.c index 8d180a741..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_UINTEGER64 }, + .type = VT_INTEGER64 }, {.property = MODEL_PROPERTY_SCROLL, .id_string = "MODEL_PROPERTY_SCROLL", @@ -555,4 +555,3 @@ HRESULT openbor_set_model_property(ScriptVariant** varlist, ScriptVariant** cons } - From 09d5fe4806a33656a7dae73b40da2a9e3faa29ed Mon Sep 17 00:00:00 2001 From: DCurrent Date: Thu, 20 Aug 2026 17:49:36 -0400 Subject: [PATCH 3/4] Add scoreall.c event. Fires after score#.c for all players. --- engine/openbor.c | 41 +++++++++++++++++++++++++++++++++++++++++ engine/openbor.h | 1 + 2 files changed, 42 insertions(+) diff --git a/engine/openbor.c b/engine/openbor.c index 25c0449cc..1b1a138cc 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++) @@ -2502,6 +2510,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; @@ -26742,6 +26781,8 @@ void addscore(int playerindex, int64_t add) ScriptVariant_Clear(&var); Script_Set_Local_Variant(cs, "score", &var); } + + execute_score_script_all(playerindex, add); } diff --git a/engine/openbor.h b/engine/openbor.h index 4ab5dab42..cf46ee4ed 100644 --- a/engine/openbor.h +++ b/engine/openbor.h @@ -4749,6 +4749,7 @@ void ent_set_colourmap(entity *ent, uint64_t which); void predrawstatus(); void drawstatus(); 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(); From 928ef0b1ebb985fab5caa48ea91e6586b732999c Mon Sep 17 00:00:00 2001 From: DCurrent Date: Thu, 20 Aug 2026 18:45:11 -0400 Subject: [PATCH 4/4] Fix spawnscript Y access misname. --- engine/openbor.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/engine/openbor.c b/engine/openbor.c index 1b1a138cc..36774f6e0 100644 --- a/engine/openbor.c +++ b/engine/openbor.c @@ -2430,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); @@ -2443,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); } }