[Scummvm-git-logs] scummvm branch-3-0 -> b5c3881238798c8d227911ff4e6149ec055b828f

sluicebox noreply at scummvm.org
Tue Dec 9 16:52:41 UTC 2025


This automated email contains information about 2 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .

Summary:
2d32906d99 PRIVATE: correctly parse N+ values using NUM_PLUS type
b5c3881238 PRIVATE: Fully implement AMRadioClip and PoliceClip


Commit: 2d32906d9967ec2728adf77783c745e350d8960c
    https://github.com/scummvm/scummvm/commit/2d32906d9967ec2728adf77783c745e350d8960c
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2025-12-09T08:51:38-08:00

Commit Message:
PRIVATE: correctly parse N+ values using NUM_PLUS type

Changed paths:
    engines/private/grammar.cpp
    engines/private/grammar.y
    engines/private/tokens.h


diff --git a/engines/private/grammar.cpp b/engines/private/grammar.cpp
index 155339afab5..576af885b2b 100644
--- a/engines/private/grammar.cpp
+++ b/engines/private/grammar.cpp
@@ -95,6 +95,8 @@ using namespace Settings;
 extern int PRIVATE_lex();
 //extern int PRIVATE_parse();
 
+int markplus();
+
 void PRIVATE_xerror(const char *str) {
 }
 
@@ -104,7 +106,7 @@ int PRIVATE_wrap() {
 
 
 
-#line 108 "engines/private/grammar.cpp"
+#line 110 "engines/private/grammar.cpp"
 
 # ifndef YY_CAST
 #  ifdef __cplusplus
@@ -138,51 +140,52 @@ enum yysymbol_kind_t
   YYSYMBOL_NAME = 3,                       /* NAME  */
   YYSYMBOL_STRING = 4,                     /* STRING  */
   YYSYMBOL_NUM = 5,                        /* NUM  */
-  YYSYMBOL_LTE = 6,                        /* LTE  */
-  YYSYMBOL_GTE = 7,                        /* GTE  */
-  YYSYMBOL_NEQ = 8,                        /* NEQ  */
-  YYSYMBOL_EQ = 9,                         /* EQ  */
-  YYSYMBOL_FALSETOK = 10,                  /* FALSETOK  */
-  YYSYMBOL_TRUETOK = 11,                   /* TRUETOK  */
-  YYSYMBOL_NULLTOK = 12,                   /* NULLTOK  */
-  YYSYMBOL_IFTOK = 13,                     /* IFTOK  */
-  YYSYMBOL_ELSETOK = 14,                   /* ELSETOK  */
-  YYSYMBOL_RECT = 15,                      /* RECT  */
-  YYSYMBOL_GOTOTOK = 16,                   /* GOTOTOK  */
-  YYSYMBOL_DEBUGTOK = 17,                  /* DEBUGTOK  */
-  YYSYMBOL_EMITCODEONTOK = 18,             /* EMITCODEONTOK  */
-  YYSYMBOL_EMITCODEOFFTOK = 19,            /* EMITCODEOFFTOK  */
-  YYSYMBOL_RESETIDTOK = 20,                /* RESETIDTOK  */
-  YYSYMBOL_DEFINETOK = 21,                 /* DEFINETOK  */
-  YYSYMBOL_SETTINGTOK = 22,                /* SETTINGTOK  */
-  YYSYMBOL_RANDOMTOK = 23,                 /* RANDOMTOK  */
-  YYSYMBOL_24_ = 24,                       /* '{'  */
-  YYSYMBOL_25_ = 25,                       /* '}'  */
-  YYSYMBOL_26_ = 26,                       /* ','  */
-  YYSYMBOL_27_ = 27,                       /* ';'  */
-  YYSYMBOL_28_ = 28,                       /* '('  */
-  YYSYMBOL_29_ = 29,                       /* ')'  */
-  YYSYMBOL_30_ = 30,                       /* '!'  */
-  YYSYMBOL_31_ = 31,                       /* '+'  */
-  YYSYMBOL_32_ = 32,                       /* '<'  */
-  YYSYMBOL_33_ = 33,                       /* '>'  */
-  YYSYMBOL_34_ = 34,                       /* '%'  */
-  YYSYMBOL_YYACCEPT = 35,                  /* $accept  */
-  YYSYMBOL_lines = 36,                     /* lines  */
-  YYSYMBOL_line = 37,                      /* line  */
-  YYSYMBOL_debug = 38,                     /* debug  */
-  YYSYMBOL_statements = 39,                /* statements  */
-  YYSYMBOL_statement = 40,                 /* statement  */
-  YYSYMBOL_body = 41,                      /* body  */
-  YYSYMBOL_end = 42,                       /* end  */
-  YYSYMBOL_if = 43,                        /* if  */
-  YYSYMBOL_cond = 44,                      /* cond  */
-  YYSYMBOL_define = 45,                    /* define  */
-  YYSYMBOL_fcall = 46,                     /* fcall  */
-  YYSYMBOL_startp = 47,                    /* startp  */
-  YYSYMBOL_params = 48,                    /* params  */
-  YYSYMBOL_value = 49,                     /* value  */
-  YYSYMBOL_expr = 50                       /* expr  */
+  YYSYMBOL_NUM_PLUS = 6,                   /* NUM_PLUS  */
+  YYSYMBOL_LTE = 7,                        /* LTE  */
+  YYSYMBOL_GTE = 8,                        /* GTE  */
+  YYSYMBOL_NEQ = 9,                        /* NEQ  */
+  YYSYMBOL_EQ = 10,                        /* EQ  */
+  YYSYMBOL_FALSETOK = 11,                  /* FALSETOK  */
+  YYSYMBOL_TRUETOK = 12,                   /* TRUETOK  */
+  YYSYMBOL_NULLTOK = 13,                   /* NULLTOK  */
+  YYSYMBOL_IFTOK = 14,                     /* IFTOK  */
+  YYSYMBOL_ELSETOK = 15,                   /* ELSETOK  */
+  YYSYMBOL_RECT = 16,                      /* RECT  */
+  YYSYMBOL_GOTOTOK = 17,                   /* GOTOTOK  */
+  YYSYMBOL_DEBUGTOK = 18,                  /* DEBUGTOK  */
+  YYSYMBOL_EMITCODEONTOK = 19,             /* EMITCODEONTOK  */
+  YYSYMBOL_EMITCODEOFFTOK = 20,            /* EMITCODEOFFTOK  */
+  YYSYMBOL_RESETIDTOK = 21,                /* RESETIDTOK  */
+  YYSYMBOL_DEFINETOK = 22,                 /* DEFINETOK  */
+  YYSYMBOL_SETTINGTOK = 23,                /* SETTINGTOK  */
+  YYSYMBOL_RANDOMTOK = 24,                 /* RANDOMTOK  */
+  YYSYMBOL_25_ = 25,                       /* '{'  */
+  YYSYMBOL_26_ = 26,                       /* '}'  */
+  YYSYMBOL_27_ = 27,                       /* ','  */
+  YYSYMBOL_28_ = 28,                       /* ';'  */
+  YYSYMBOL_29_ = 29,                       /* '('  */
+  YYSYMBOL_30_ = 30,                       /* ')'  */
+  YYSYMBOL_31_ = 31,                       /* '!'  */
+  YYSYMBOL_32_ = 32,                       /* '+'  */
+  YYSYMBOL_33_ = 33,                       /* '<'  */
+  YYSYMBOL_34_ = 34,                       /* '>'  */
+  YYSYMBOL_35_ = 35,                       /* '%'  */
+  YYSYMBOL_YYACCEPT = 36,                  /* $accept  */
+  YYSYMBOL_lines = 37,                     /* lines  */
+  YYSYMBOL_line = 38,                      /* line  */
+  YYSYMBOL_debug = 39,                     /* debug  */
+  YYSYMBOL_statements = 40,                /* statements  */
+  YYSYMBOL_statement = 41,                 /* statement  */
+  YYSYMBOL_body = 42,                      /* body  */
+  YYSYMBOL_end = 43,                       /* end  */
+  YYSYMBOL_if = 44,                        /* if  */
+  YYSYMBOL_cond = 45,                      /* cond  */
+  YYSYMBOL_define = 46,                    /* define  */
+  YYSYMBOL_fcall = 47,                     /* fcall  */
+  YYSYMBOL_startp = 48,                    /* startp  */
+  YYSYMBOL_params = 49,                    /* params  */
+  YYSYMBOL_value = 50,                     /* value  */
+  YYSYMBOL_expr = 51                       /* expr  */
 };
 typedef enum yysymbol_kind_t yysymbol_kind_t;
 
@@ -510,10 +513,10 @@ union yyalloc
 /* YYFINAL -- State number of the termination state.  */
 #define YYFINAL  12
 /* YYLAST -- Last index in YYTABLE.  */
-#define YYLAST   124
+#define YYLAST   127
 
 /* YYNTOKENS -- Number of terminals.  */
-#define YYNTOKENS  35
+#define YYNTOKENS  36
 /* YYNNTS -- Number of nonterminals.  */
 #define YYNNTS  16
 /* YYNRULES -- Number of rules.  */
@@ -522,7 +525,7 @@ union yyalloc
 #define YYNSTATES  116
 
 /* YYMAXUTOK -- Last valid token kind.  */
-#define YYMAXUTOK   278
+#define YYMAXUTOK   279
 
 
 /* YYTRANSLATE(TOKEN-NUM) -- Symbol number corresponding to TOKEN-NUM
@@ -539,16 +542,16 @@ static const yytype_int8 yytranslate[] =
        0,     2,     2,     2,     2,     2,     2,     2,     2,     2,
        2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
        2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,    30,     2,     2,     2,    34,     2,     2,
-      28,    29,     2,    31,    26,     2,     2,     2,     2,     2,
-       2,     2,     2,     2,     2,     2,     2,     2,     2,    27,
-      32,     2,    33,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,    31,     2,     2,     2,    35,     2,     2,
+      29,    30,     2,    32,    27,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,    28,
+      33,     2,    34,     2,     2,     2,     2,     2,     2,     2,
        2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
        2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
        2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
        2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
        2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,    24,     2,    25,     2,     2,     2,     2,
+       2,     2,     2,    25,     2,    26,     2,     2,     2,     2,
        2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
        2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
        2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
@@ -563,19 +566,19 @@ static const yytype_int8 yytranslate[] =
        2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
        2,     2,     2,     2,     2,     2,     1,     2,     3,     4,
        5,     6,     7,     8,     9,    10,    11,    12,    13,    14,
-      15,    16,    17,    18,    19,    20,    21,    22,    23
+      15,    16,    17,    18,    19,    20,    21,    22,    23,    24
 };
 
 #if PRIVATE_DEBUG
 /* YYRLINE[YYN] -- Source line where rule number YYN was defined.  */
 static const yytype_uint8 yyrline[] =
 {
-       0,    95,    95,    96,    99,   100,   101,   102,   103,   104,
-     108,   109,   112,   113,   116,   123,   124,   129,   137,   138,
-     141,   144,   147,   150,   151,   156,   160,   161,   164,   172,
-     173,   181,   184,   185,   186,   187,   188,   191,   192,   193,
-     194,   195,   196,   199,   200,   201,   202,   203,   204,   205,
-     206,   207,   208,   209
+       0,    98,    98,    99,   102,   103,   104,   105,   106,   107,
+     111,   112,   115,   116,   119,   126,   127,   132,   140,   141,
+     144,   147,   150,   153,   154,   159,   163,   164,   167,   175,
+     176,   184,   187,   188,   189,   190,   191,   194,   195,   196,
+     197,   198,   199,   202,   203,   204,   205,   206,   207,   208,
+     209,   210,   211,   212
 };
 #endif
 
@@ -592,13 +595,13 @@ static const char *yysymbol_name (yysymbol_kind_t yysymbol) YY_ATTRIBUTE_UNUSED;
 static const char *const yytname[] =
 {
   "\"end of file\"", "error", "\"invalid token\"", "NAME", "STRING",
-  "NUM", "LTE", "GTE", "NEQ", "EQ", "FALSETOK", "TRUETOK", "NULLTOK",
-  "IFTOK", "ELSETOK", "RECT", "GOTOTOK", "DEBUGTOK", "EMITCODEONTOK",
-  "EMITCODEOFFTOK", "RESETIDTOK", "DEFINETOK", "SETTINGTOK", "RANDOMTOK",
-  "'{'", "'}'", "','", "';'", "'('", "')'", "'!'", "'+'", "'<'", "'>'",
-  "'%'", "$accept", "lines", "line", "debug", "statements", "statement",
-  "body", "end", "if", "cond", "define", "fcall", "startp", "params",
-  "value", "expr", YY_NULLPTR
+  "NUM", "NUM_PLUS", "LTE", "GTE", "NEQ", "EQ", "FALSETOK", "TRUETOK",
+  "NULLTOK", "IFTOK", "ELSETOK", "RECT", "GOTOTOK", "DEBUGTOK",
+  "EMITCODEONTOK", "EMITCODEOFFTOK", "RESETIDTOK", "DEFINETOK",
+  "SETTINGTOK", "RANDOMTOK", "'{'", "'}'", "','", "';'", "'('", "')'",
+  "'!'", "'+'", "'<'", "'>'", "'%'", "$accept", "lines", "line", "debug",
+  "statements", "statement", "body", "end", "if", "cond", "define",
+  "fcall", "startp", "params", "value", "expr", YY_NULLPTR
 };
 
 static const char *
@@ -608,7 +611,7 @@ yysymbol_name (yysymbol_kind_t yysymbol)
 }
 #endif
 
-#define YYPACT_NINF (-75)
+#define YYPACT_NINF (-74)
 
 #define yypact_value_is_default(Yyn) \
   ((Yyn) == YYPACT_NINF)
@@ -622,18 +625,18 @@ yysymbol_name (yysymbol_kind_t yysymbol)
    STATE-NUM.  */
 static const yytype_int8 yypact[] =
 {
-      67,   -10,   -75,   -75,   -75,    12,    19,    28,    67,    26,
-      13,    27,   -75,   -75,    17,     5,    36,    51,    26,   -75,
-      24,    30,    25,   -75,    37,     4,    49,    51,    50,    52,
-     -75,     6,   -75,   -75,    47,    53,    78,   -75,   -75,    15,
-      20,   -75,    54,   -75,     1,    64,   -75,    62,   -75,   -75,
-     -75,   -75,   -75,   -75,    66,    65,    40,    63,    51,   -75,
-     -75,    88,    25,    68,    69,    70,    71,    93,   -75,    95,
-     -75,    65,    65,    65,    65,    65,    65,    65,   -75,    76,
-      89,    79,     1,   -75,     1,    80,    73,   -75,   -75,   -75,
-     -75,   -75,   -75,   -75,   -75,    20,    97,   -75,   -75,    99,
-      81,   -75,    82,    83,   -75,   -75,   106,   107,    87,    85,
-     110,   -75,    90,    91,    36,   -75
+      69,   -17,   -74,   -74,   -74,    12,    13,    19,    69,    21,
+       2,     6,   -74,   -74,    -1,    18,    40,    36,    21,   -74,
+      22,    20,    16,   -74,    25,     4,    29,    36,    42,    23,
+     -74,     7,   -74,   -74,    52,    30,    71,   -74,   -74,    17,
+      56,   -74,    46,   -74,     1,    53,   -74,    49,   -74,   -74,
+     -74,   -74,   -74,   -74,    64,    73,    28,    65,    36,   -74,
+     -74,    77,    16,    67,    70,    68,    72,    89,   -74,    95,
+     -74,    73,    73,    73,    73,    73,    73,    73,   -74,    75,
+      87,    76,     1,   -74,     1,    78,    74,   -74,   -74,   -74,
+     -74,   -74,   -74,   -74,   -74,    56,    99,   -74,   -74,   101,
+      80,   -74,    81,    84,   -74,   -74,   102,   107,    86,    85,
+     109,   -74,    88,    90,    40,   -74
 };
 
 /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM.
@@ -658,8 +661,8 @@ static const yytype_int8 yydefact[] =
 /* YYPGOTO[NTERM-NUM].  */
 static const yytype_int8 yypgoto[] =
 {
-     -75,   108,   -75,   100,   -24,   -39,    29,    21,   -75,   -75,
-     -31,   -42,   -75,   -74,   -14,    84
+     -74,   108,   -74,   103,   -24,   -39,    24,    26,   -74,   -74,
+     -31,   -42,   -74,   -73,    -8,    83
 };
 
 /* YYDEFGOTO[NTERM-NUM].  */
@@ -674,65 +677,65 @@ static const yytype_int8 yydefgoto[] =
    number is the opposite.  If YYTABLE_NINF, syntax error.  */
 static const yytype_int8 yytable[] =
 {
-      43,    59,    64,    38,    62,    49,    50,    35,    97,    20,
-      98,    51,    52,    53,     9,    10,    24,    63,    48,    49,
-      50,    42,    11,    22,    54,    51,    52,    53,    12,    14,
-      19,    55,    36,    23,    79,    24,    25,    16,    54,    20,
-      64,    70,    64,    18,    58,    55,    71,    72,    73,    74,
-      31,    17,    45,    33,    22,    32,    59,    87,    88,    89,
-      90,    91,    92,    93,    23,    34,    24,    25,    48,    49,
-      50,    75,    76,    77,    37,    51,    52,    53,    39,    41,
-      46,    47,    61,   115,     1,     2,     3,     4,     5,     6,
-      67,    68,    78,    81,    69,    82,    36,    84,    85,    83,
-      86,    94,   102,    95,   103,    96,    99,   100,   106,   107,
-     104,   108,   109,   110,   111,   112,    13,   114,    30,   113,
-       0,     0,   105,    57,   101
+      43,    59,    64,    38,    62,    49,    50,    35,     9,    97,
+      20,    98,    51,    52,    53,    10,    11,    24,    63,    12,
+      48,    49,    50,    42,    14,    54,    18,    16,    51,    52,
+      53,    17,    55,    36,    79,    71,    72,    73,    74,    22,
+      64,    54,    64,    20,    19,    33,    32,    70,    55,    31,
+      23,    41,    24,    25,    34,    37,    59,    45,    46,    22,
+      75,    76,    77,    87,    88,    89,    90,    91,    92,    93,
+      23,    39,    24,    25,    47,    61,    48,    49,    50,    68,
+      67,    58,    81,   115,    51,    52,    53,     1,     2,     3,
+       4,     5,     6,    69,    85,    78,    36,    82,    83,    84,
+      86,    94,    95,    96,   102,    99,   103,   108,   106,   100,
+     104,   107,   109,   110,   112,   111,    13,   114,   113,   101,
+       0,    30,    57,     0,     0,     0,     0,   105
 };
 
 static const yytype_int8 yycheck[] =
 {
-      31,    40,    44,    27,     3,     4,     5,     3,    82,     3,
-      84,    10,    11,    12,    24,     3,    15,    16,     3,     4,
-       5,    15,     3,     3,    23,    10,    11,    12,     0,     3,
-      25,    30,    28,    13,    58,    15,    16,    24,    23,     3,
-      82,    55,    84,    26,    24,    30,     6,     7,     8,     9,
-      26,    24,     5,    28,     3,    25,    95,    71,    72,    73,
-      74,    75,    76,    77,    13,    28,    15,    16,     3,     4,
-       5,    31,    32,    33,    25,    10,    11,    12,    28,    27,
-      27,     3,    28,   114,    17,    18,    19,    20,    21,    22,
-      26,    29,    29,     5,    28,    26,    28,    26,     5,    29,
-       5,    25,     5,    14,     5,    26,    26,    34,    26,    26,
-      29,     5,     5,    26,    29,     5,     8,    26,    18,    29,
-      -1,    -1,   101,    39,    95
+      31,    40,    44,    27,     3,     4,     5,     3,    25,    82,
+       3,    84,    11,    12,    13,     3,     3,    16,    17,     0,
+       3,     4,     5,    16,     3,    24,    27,    25,    11,    12,
+      13,    25,    31,    29,    58,     7,     8,     9,    10,     3,
+      82,    24,    84,     3,    26,    29,    26,    55,    31,    27,
+      14,    28,    16,    17,    29,    26,    95,     5,    28,     3,
+      32,    33,    34,    71,    72,    73,    74,    75,    76,    77,
+      14,    29,    16,    17,     3,    29,     3,     4,     5,    30,
+      27,    25,     5,   114,    11,    12,    13,    18,    19,    20,
+      21,    22,    23,    29,     5,    30,    29,    27,    30,    27,
+       5,    26,    15,    27,     5,    27,     5,     5,    27,    35,
+      30,    27,     5,    27,     5,    30,     8,    27,    30,    95,
+      -1,    18,    39,    -1,    -1,    -1,    -1,   101
 };
 
 /* YYSTOS[STATE-NUM] -- The symbol kind of the accessing symbol of
    state STATE-NUM.  */
 static const yytype_int8 yystos[] =
 {
-       0,    17,    18,    19,    20,    21,    22,    36,    37,    24,
-       3,     3,     0,    36,     3,    38,    24,    24,    26,    25,
-       3,    45,     3,    13,    15,    16,    39,    40,    43,    46,
-      38,    26,    25,    28,    28,     3,    28,    25,    39,    28,
-      44,    27,    15,    45,    47,     5,    27,     3,     3,     4,
-       5,    10,    11,    12,    23,    30,    49,    50,    24,    40,
-      41,    28,     3,    16,    46,    48,    50,    26,    29,    28,
-      49,     6,     7,     8,     9,    31,    32,    33,    29,    39,
-      42,     5,    26,    29,    26,     5,     5,    49,    49,    49,
-      49,    49,    49,    49,    25,    14,    26,    48,    48,    26,
-      34,    41,     5,     5,    29,    42,    26,    26,     5,     5,
-      26,    29,     5,    29,    26,    45
+       0,    18,    19,    20,    21,    22,    23,    37,    38,    25,
+       3,     3,     0,    37,     3,    39,    25,    25,    27,    26,
+       3,    46,     3,    14,    16,    17,    40,    41,    44,    47,
+      39,    27,    26,    29,    29,     3,    29,    26,    40,    29,
+      45,    28,    16,    46,    48,     5,    28,     3,     3,     4,
+       5,    11,    12,    13,    24,    31,    50,    51,    25,    41,
+      42,    29,     3,    17,    47,    49,    51,    27,    30,    29,
+      50,     7,     8,     9,    10,    32,    33,    34,    30,    40,
+      43,     5,    27,    30,    27,     5,     5,    50,    50,    50,
+      50,    50,    50,    50,    26,    15,    27,    49,    49,    27,
+      35,    42,     5,     5,    30,    43,    27,    27,     5,     5,
+      27,    30,     5,    30,    27,    46
 };
 
 /* YYR1[RULE-NUM] -- Symbol kind of the left-hand side of rule RULE-NUM.  */
 static const yytype_int8 yyr1[] =
 {
-       0,    35,    36,    36,    37,    37,    37,    37,    37,    37,
-      38,    38,    39,    39,    40,    40,    40,    40,    41,    41,
-      42,    43,    44,    45,    45,    45,    45,    45,    46,    46,
-      46,    47,    48,    48,    48,    48,    48,    49,    49,    49,
-      49,    49,    49,    50,    50,    50,    50,    50,    50,    50,
-      50,    50,    50,    50
+       0,    36,    37,    37,    38,    38,    38,    38,    38,    38,
+      39,    39,    40,    40,    41,    41,    41,    41,    42,    42,
+      43,    44,    45,    46,    46,    46,    46,    46,    47,    47,
+      47,    48,    49,    49,    49,    49,    49,    50,    50,    50,
+      50,    50,    50,    51,    51,    51,    51,    51,    51,    51,
+      51,    51,    51,    51
 };
 
 /* YYR2[RULE-NUM] -- Number of symbols on the right-hand side of rule RULE-NUM.  */
@@ -1207,50 +1210,50 @@ yyreduce:
   switch (yyn)
     {
   case 4: /* line: DEBUGTOK '{' debug '}'  */
-#line 99 "engines/private/grammar.y"
+#line 102 "engines/private/grammar.y"
                              { /* Not used in the game */ }
-#line 1213 "engines/private/grammar.cpp"
+#line 1216 "engines/private/grammar.cpp"
     break;
 
   case 5: /* line: EMITCODEONTOK  */
-#line 100 "engines/private/grammar.y"
+#line 103 "engines/private/grammar.y"
                         { /* Unclear what this is */ }
-#line 1219 "engines/private/grammar.cpp"
+#line 1222 "engines/private/grammar.cpp"
     break;
 
   case 6: /* line: EMITCODEOFFTOK  */
-#line 101 "engines/private/grammar.y"
+#line 104 "engines/private/grammar.y"
                          { /* Unclear what this is */ }
-#line 1225 "engines/private/grammar.cpp"
+#line 1228 "engines/private/grammar.cpp"
     break;
 
   case 7: /* line: RESETIDTOK  */
-#line 102 "engines/private/grammar.y"
+#line 105 "engines/private/grammar.y"
                      { /* Unclear what this is */ }
-#line 1231 "engines/private/grammar.cpp"
+#line 1234 "engines/private/grammar.cpp"
     break;
 
   case 8: /* line: DEFINETOK NAME '{' define '}'  */
-#line 103 "engines/private/grammar.y"
+#line 106 "engines/private/grammar.y"
                                         { g_private->maps.installAll((yyvsp[-3].s)); }
-#line 1237 "engines/private/grammar.cpp"
+#line 1240 "engines/private/grammar.cpp"
     break;
 
   case 9: /* line: SETTINGTOK NAME '{' statements '}'  */
-#line 104 "engines/private/grammar.y"
+#line 107 "engines/private/grammar.y"
                                              { g_setts->save((yyvsp[-3].s));
 					       g_setts->init(); }
-#line 1244 "engines/private/grammar.cpp"
+#line 1247 "engines/private/grammar.cpp"
     break;
 
   case 12: /* statements: %empty  */
-#line 112 "engines/private/grammar.y"
+#line 115 "engines/private/grammar.y"
                                { (yyval.inst) = g_vm->_progp; }
-#line 1250 "engines/private/grammar.cpp"
+#line 1253 "engines/private/grammar.cpp"
     break;
 
   case 14: /* statement: GOTOTOK NAME ';'  */
-#line 116 "engines/private/grammar.y"
+#line 119 "engines/private/grammar.y"
                             {
 	(yyval.inst) = g_vm->_progp;
 	code2(strpush, (Inst) g_private->maps.constant(STRING, 0, (yyvsp[-1].s)));
@@ -1258,99 +1261,99 @@ yyreduce:
 	code2(strpush, (Inst) g_private->maps.constant(STRING, 0, "goto"));
 	code1(funcpush);
 	}
-#line 1262 "engines/private/grammar.cpp"
+#line 1265 "engines/private/grammar.cpp"
     break;
 
   case 15: /* statement: fcall ';'  */
-#line 123 "engines/private/grammar.y"
+#line 126 "engines/private/grammar.y"
                          { (yyval.inst) = (yyvsp[-1].inst); }
-#line 1268 "engines/private/grammar.cpp"
+#line 1271 "engines/private/grammar.cpp"
     break;
 
   case 16: /* statement: if cond body end  */
-#line 124 "engines/private/grammar.y"
+#line 127 "engines/private/grammar.y"
                            {
 		/* else-less if */
 		((yyvsp[-3].inst))[1] = (Inst)(yyvsp[-1].inst);     /* thenpart */
 		((yyvsp[-3].inst))[3] = (Inst)(yyvsp[0].inst);
 		}
-#line 1278 "engines/private/grammar.cpp"
+#line 1281 "engines/private/grammar.cpp"
     break;
 
   case 17: /* statement: if cond body end ELSETOK body end  */
-#line 129 "engines/private/grammar.y"
+#line 132 "engines/private/grammar.y"
                                             {
 		/* if with else */
 		((yyvsp[-6].inst))[1] = (Inst)(yyvsp[-4].inst);     /* thenpart */
 		((yyvsp[-6].inst))[2] = (Inst)(yyvsp[-1].inst);     /* elsepart */
 		((yyvsp[-6].inst))[3] = (Inst)(yyvsp[0].inst);
 		}
-#line 1289 "engines/private/grammar.cpp"
+#line 1292 "engines/private/grammar.cpp"
     break;
 
   case 18: /* body: statement  */
-#line 137 "engines/private/grammar.y"
+#line 140 "engines/private/grammar.y"
                         { (yyval.inst) = (yyvsp[0].inst); }
-#line 1295 "engines/private/grammar.cpp"
+#line 1298 "engines/private/grammar.cpp"
     break;
 
   case 19: /* body: '{' statements '}'  */
-#line 138 "engines/private/grammar.y"
+#line 141 "engines/private/grammar.y"
                              { (yyval.inst) = (yyvsp[-1].inst); }
-#line 1301 "engines/private/grammar.cpp"
+#line 1304 "engines/private/grammar.cpp"
     break;
 
   case 20: /* end: %empty  */
-#line 141 "engines/private/grammar.y"
+#line 144 "engines/private/grammar.y"
                              { code1(STOP); (yyval.inst) = g_vm->_progp; }
-#line 1307 "engines/private/grammar.cpp"
+#line 1310 "engines/private/grammar.cpp"
     break;
 
   case 21: /* if: IFTOK  */
-#line 144 "engines/private/grammar.y"
+#line 147 "engines/private/grammar.y"
           { (yyval.inst) = code1(ifcode); code3(STOP, STOP, STOP); }
-#line 1313 "engines/private/grammar.cpp"
+#line 1316 "engines/private/grammar.cpp"
     break;
 
   case 22: /* cond: '(' expr ')'  */
-#line 147 "engines/private/grammar.y"
+#line 150 "engines/private/grammar.y"
                         { code1(STOP); (yyval.inst) = (yyvsp[-1].inst); }
-#line 1319 "engines/private/grammar.cpp"
+#line 1322 "engines/private/grammar.cpp"
     break;
 
   case 24: /* define: NAME ',' RECT '(' NUM ',' NUM ',' NUM ',' NUM ')' ',' define  */
-#line 151 "engines/private/grammar.y"
+#line 154 "engines/private/grammar.y"
                                                                         {
 	  Common::Rect *r = new Common::Rect((yyvsp[-9].sym)->u.val, (yyvsp[-7].sym)->u.val, (yyvsp[-5].sym)->u.val, (yyvsp[-3].sym)->u.val);
 	  assert(r->isValidRect());
 	  g_private->maps.defineSymbol((yyvsp[-13].s), r);
 	  }
-#line 1329 "engines/private/grammar.cpp"
+#line 1332 "engines/private/grammar.cpp"
     break;
 
   case 25: /* define: NAME ',' RECT '(' NUM ',' NUM ',' NUM ',' NUM ')'  */
-#line 156 "engines/private/grammar.y"
+#line 159 "engines/private/grammar.y"
                                                             {
 	  Common::Rect *r = new Common::Rect((yyvsp[-7].sym)->u.val, (yyvsp[-5].sym)->u.val, (yyvsp[-3].sym)->u.val, (yyvsp[-1].sym)->u.val);
 	  g_private->maps.defineSymbol((yyvsp[-11].s), r);
 	  }
-#line 1338 "engines/private/grammar.cpp"
+#line 1341 "engines/private/grammar.cpp"
     break;
 
   case 26: /* define: NAME ',' define  */
-#line 160 "engines/private/grammar.y"
+#line 163 "engines/private/grammar.y"
                           { g_private->maps.defineSymbol((yyvsp[-2].s), NULL); }
-#line 1344 "engines/private/grammar.cpp"
+#line 1347 "engines/private/grammar.cpp"
     break;
 
   case 27: /* define: NAME  */
-#line 161 "engines/private/grammar.y"
+#line 164 "engines/private/grammar.y"
                     { g_private->maps.defineSymbol((yyvsp[0].s), NULL); }
-#line 1350 "engines/private/grammar.cpp"
+#line 1353 "engines/private/grammar.cpp"
     break;
 
   case 28: /* fcall: GOTOTOK '(' NAME ')'  */
-#line 164 "engines/private/grammar.y"
+#line 167 "engines/private/grammar.y"
                                {
 			       (yyval.inst) = g_vm->_progp;
 			       code2(strpush, (Inst) g_private->maps.constant(STRING, 0, (yyvsp[-1].s)));
@@ -1358,166 +1361,166 @@ yyreduce:
 			       code2(strpush, (Inst) g_private->maps.constant(STRING, 0, "goto"));
 			       code1(funcpush);
 			       }
-#line 1362 "engines/private/grammar.cpp"
+#line 1365 "engines/private/grammar.cpp"
     break;
 
   case 29: /* fcall: RECT '(' NUM ',' NUM ',' NUM ',' NUM ')'  */
-#line 172 "engines/private/grammar.y"
+#line 175 "engines/private/grammar.y"
                                                    { (yyval.inst) = g_vm->_progp; }
-#line 1368 "engines/private/grammar.cpp"
+#line 1371 "engines/private/grammar.cpp"
     break;
 
   case 30: /* fcall: NAME '(' startp params ')'  */
-#line 173 "engines/private/grammar.y"
+#line 176 "engines/private/grammar.y"
                                       {
 			       (yyval.inst) = (yyvsp[-2].inst);
 			       code2(constpush, (Inst) g_private->maps.constant(NUM, (yyvsp[-1].narg), NULL));
 			       code2(strpush, (Inst) g_private->maps.constant(STRING, 0, (yyvsp[-4].s)));
 			       code1(funcpush);
 			       }
-#line 1379 "engines/private/grammar.cpp"
+#line 1382 "engines/private/grammar.cpp"
     break;
 
   case 31: /* startp: %empty  */
-#line 181 "engines/private/grammar.y"
+#line 184 "engines/private/grammar.y"
                     { (yyval.inst) = g_vm->_progp; }
-#line 1385 "engines/private/grammar.cpp"
+#line 1388 "engines/private/grammar.cpp"
     break;
 
   case 32: /* params: %empty  */
-#line 184 "engines/private/grammar.y"
+#line 187 "engines/private/grammar.y"
                             { (yyval.narg) = 0; }
-#line 1391 "engines/private/grammar.cpp"
+#line 1394 "engines/private/grammar.cpp"
     break;
 
   case 33: /* params: fcall ',' params  */
-#line 185 "engines/private/grammar.y"
+#line 188 "engines/private/grammar.y"
                             { (yyval.narg) = (yyvsp[0].narg) + 1; }
-#line 1397 "engines/private/grammar.cpp"
+#line 1400 "engines/private/grammar.cpp"
     break;
 
   case 34: /* params: expr ',' params  */
-#line 186 "engines/private/grammar.y"
+#line 189 "engines/private/grammar.y"
                             { (yyval.narg) = (yyvsp[0].narg) + 1; }
-#line 1403 "engines/private/grammar.cpp"
+#line 1406 "engines/private/grammar.cpp"
     break;
 
   case 35: /* params: expr  */
-#line 187 "engines/private/grammar.y"
+#line 190 "engines/private/grammar.y"
                 { (yyval.narg) = 1; }
-#line 1409 "engines/private/grammar.cpp"
+#line 1412 "engines/private/grammar.cpp"
     break;
 
   case 36: /* params: fcall  */
-#line 188 "engines/private/grammar.y"
+#line 191 "engines/private/grammar.y"
                       { (yyval.narg) = 1; }
-#line 1415 "engines/private/grammar.cpp"
+#line 1418 "engines/private/grammar.cpp"
     break;
 
   case 37: /* value: NULLTOK  */
-#line 191 "engines/private/grammar.y"
+#line 194 "engines/private/grammar.y"
                    { code2(constpush, (Inst) g_private->maps.constant(NUM, 0, NULL)); }
-#line 1421 "engines/private/grammar.cpp"
+#line 1424 "engines/private/grammar.cpp"
     break;
 
   case 38: /* value: FALSETOK  */
-#line 192 "engines/private/grammar.y"
+#line 195 "engines/private/grammar.y"
                    { code2(constpush, (Inst) g_private->maps.constant(NUM, 0, NULL)); }
-#line 1427 "engines/private/grammar.cpp"
+#line 1430 "engines/private/grammar.cpp"
     break;
 
   case 39: /* value: TRUETOK  */
-#line 193 "engines/private/grammar.y"
+#line 196 "engines/private/grammar.y"
                    { code2(constpush, (Inst) g_private->maps.constant(NUM, 1, NULL)); }
-#line 1433 "engines/private/grammar.cpp"
+#line 1436 "engines/private/grammar.cpp"
     break;
 
   case 40: /* value: NUM  */
-#line 194 "engines/private/grammar.y"
+#line 197 "engines/private/grammar.y"
                    { code2(constpush, (Inst)(yyvsp[0].sym)); }
-#line 1439 "engines/private/grammar.cpp"
+#line 1442 "engines/private/grammar.cpp"
     break;
 
   case 41: /* value: STRING  */
-#line 195 "engines/private/grammar.y"
+#line 198 "engines/private/grammar.y"
                    { code2(strpush, (Inst)(yyvsp[0].sym)); }
-#line 1445 "engines/private/grammar.cpp"
+#line 1448 "engines/private/grammar.cpp"
     break;
 
   case 42: /* value: NAME  */
-#line 196 "engines/private/grammar.y"
+#line 199 "engines/private/grammar.y"
                    { code1(varpush); code1((Inst) g_private->maps.lookupName((yyvsp[0].s))); code1(eval); }
-#line 1451 "engines/private/grammar.cpp"
+#line 1454 "engines/private/grammar.cpp"
     break;
 
   case 43: /* expr: value  */
-#line 199 "engines/private/grammar.y"
+#line 202 "engines/private/grammar.y"
                    { (yyval.inst) = (yyvsp[0].inst); }
-#line 1457 "engines/private/grammar.cpp"
+#line 1460 "engines/private/grammar.cpp"
     break;
 
   case 44: /* expr: '!' value  */
-#line 200 "engines/private/grammar.y"
+#line 203 "engines/private/grammar.y"
                           { code1(negate); (yyval.inst) = (yyvsp[0].inst); }
-#line 1463 "engines/private/grammar.cpp"
+#line 1466 "engines/private/grammar.cpp"
     break;
 
   case 45: /* expr: value EQ value  */
-#line 201 "engines/private/grammar.y"
+#line 204 "engines/private/grammar.y"
                           { code1(eq); }
-#line 1469 "engines/private/grammar.cpp"
+#line 1472 "engines/private/grammar.cpp"
     break;
 
   case 46: /* expr: value NEQ value  */
-#line 202 "engines/private/grammar.y"
+#line 205 "engines/private/grammar.y"
                           { code1(ne); }
-#line 1475 "engines/private/grammar.cpp"
+#line 1478 "engines/private/grammar.cpp"
     break;
 
   case 47: /* expr: value '+' value  */
-#line 203 "engines/private/grammar.y"
+#line 206 "engines/private/grammar.y"
                           { code1(add); }
-#line 1481 "engines/private/grammar.cpp"
+#line 1484 "engines/private/grammar.cpp"
     break;
 
   case 48: /* expr: value '<' value  */
-#line 204 "engines/private/grammar.y"
+#line 207 "engines/private/grammar.y"
                           { code1(lt); }
-#line 1487 "engines/private/grammar.cpp"
+#line 1490 "engines/private/grammar.cpp"
     break;
 
   case 49: /* expr: value '>' value  */
-#line 205 "engines/private/grammar.y"
+#line 208 "engines/private/grammar.y"
                           { code1(gt); }
-#line 1493 "engines/private/grammar.cpp"
+#line 1496 "engines/private/grammar.cpp"
     break;
 
   case 50: /* expr: value LTE value  */
-#line 206 "engines/private/grammar.y"
+#line 209 "engines/private/grammar.y"
                           { code1(le); }
-#line 1499 "engines/private/grammar.cpp"
+#line 1502 "engines/private/grammar.cpp"
     break;
 
   case 51: /* expr: value GTE value  */
-#line 207 "engines/private/grammar.y"
+#line 210 "engines/private/grammar.y"
                           { code1(ge); }
-#line 1505 "engines/private/grammar.cpp"
+#line 1508 "engines/private/grammar.cpp"
     break;
 
   case 52: /* expr: value '+'  */
-#line 208 "engines/private/grammar.y"
-                          { (yyval.inst) = (yyvsp[-1].inst); }
-#line 1511 "engines/private/grammar.cpp"
+#line 211 "engines/private/grammar.y"
+                          { code1(markplus); (yyval.inst) = (yyvsp[-1].inst); }
+#line 1514 "engines/private/grammar.cpp"
     break;
 
   case 53: /* expr: RANDOMTOK '(' NUM '%' ')'  */
-#line 209 "engines/private/grammar.y"
+#line 212 "engines/private/grammar.y"
                                     { code3(constpush, (Inst)(yyvsp[-2].sym), randbool); }
-#line 1517 "engines/private/grammar.cpp"
+#line 1520 "engines/private/grammar.cpp"
     break;
 
 
-#line 1521 "engines/private/grammar.cpp"
+#line 1524 "engines/private/grammar.cpp"
 
       default: break;
     }
@@ -1710,3 +1713,14 @@ yyreturnlab:
   return yyresult;
 }
 
+#line 215 "engines/private/grammar.y"
+
+
+int markplus() {
+	Datum d = pop();
+	if (d.type == NUM) {
+		d.type = NUM_PLUS;
+	}
+	push(d);
+	return 0;
+}
diff --git a/engines/private/grammar.y b/engines/private/grammar.y
index a176d960470..8b1615f825f 100644
--- a/engines/private/grammar.y
+++ b/engines/private/grammar.y
@@ -66,6 +66,8 @@ using namespace Settings;
 extern int PRIVATE_lex();
 //extern int PRIVATE_parse();
 
+int markplus();
+
 void PRIVATE_xerror(const char *str) {
 }
 
@@ -86,6 +88,7 @@ int PRIVATE_wrap() {
 
 %token<s> NAME
 %token<sym> STRING NUM
+%token NUM_PLUS
 %type <inst> body if startp cond end expr statements statement fcall value
 %token LTE GTE NEQ EQ FALSETOK TRUETOK NULLTOK IFTOK ELSETOK RECT GOTOTOK DEBUGTOK EMITCODEONTOK EMITCODEOFFTOK RESETIDTOK DEFINETOK SETTINGTOK RANDOMTOK
 %type<narg> params
@@ -205,6 +208,17 @@ expr:     value	   { $$ = $1; }
 	| value '>' value { code1(gt); }
 	| value LTE value { code1(le); }
 	| value GTE value { code1(ge); }
-	| value '+'       { $$ = $1; } // unclear what it should do
+	| value '+'       { code1(markplus); $$ = $1; }
 	| RANDOMTOK '(' NUM '%' ')' { code3(constpush, (Inst)$NUM, randbool); }
 	;
+
+%%
+
+int markplus() {
+	Datum d = pop();
+	if (d.type == NUM) {
+		d.type = NUM_PLUS;
+	}
+	push(d);
+	return 0;
+}
diff --git a/engines/private/tokens.h b/engines/private/tokens.h
index 40b3b61d1ff..e76fecaa635 100644
--- a/engines/private/tokens.h
+++ b/engines/private/tokens.h
@@ -65,24 +65,25 @@ extern int PRIVATE_debug;
     NAME = 258,                    /* NAME  */
     STRING = 259,                  /* STRING  */
     NUM = 260,                     /* NUM  */
-    LTE = 261,                     /* LTE  */
-    GTE = 262,                     /* GTE  */
-    NEQ = 263,                     /* NEQ  */
-    EQ = 264,                      /* EQ  */
-    FALSETOK = 265,                /* FALSETOK  */
-    TRUETOK = 266,                 /* TRUETOK  */
-    NULLTOK = 267,                 /* NULLTOK  */
-    IFTOK = 268,                   /* IFTOK  */
-    ELSETOK = 269,                 /* ELSETOK  */
-    RECT = 270,                    /* RECT  */
-    GOTOTOK = 271,                 /* GOTOTOK  */
-    DEBUGTOK = 272,                /* DEBUGTOK  */
-    EMITCODEONTOK = 273,           /* EMITCODEONTOK  */
-    EMITCODEOFFTOK = 274,          /* EMITCODEOFFTOK  */
-    RESETIDTOK = 275,              /* RESETIDTOK  */
-    DEFINETOK = 276,               /* DEFINETOK  */
-    SETTINGTOK = 277,              /* SETTINGTOK  */
-    RANDOMTOK = 278                /* RANDOMTOK  */
+    NUM_PLUS = 261,                /* NUM_PLUS  */
+    LTE = 262,                     /* LTE  */
+    GTE = 263,                     /* GTE  */
+    NEQ = 264,                     /* NEQ  */
+    EQ = 265,                      /* EQ  */
+    FALSETOK = 266,                /* FALSETOK  */
+    TRUETOK = 267,                 /* TRUETOK  */
+    NULLTOK = 268,                 /* NULLTOK  */
+    IFTOK = 269,                   /* IFTOK  */
+    ELSETOK = 270,                 /* ELSETOK  */
+    RECT = 271,                    /* RECT  */
+    GOTOTOK = 272,                 /* GOTOTOK  */
+    DEBUGTOK = 273,                /* DEBUGTOK  */
+    EMITCODEONTOK = 274,           /* EMITCODEONTOK  */
+    EMITCODEOFFTOK = 275,          /* EMITCODEOFFTOK  */
+    RESETIDTOK = 276,              /* RESETIDTOK  */
+    DEFINETOK = 277,               /* DEFINETOK  */
+    SETTINGTOK = 278,              /* SETTINGTOK  */
+    RANDOMTOK = 279                /* RANDOMTOK  */
   };
   typedef enum PRIVATE_tokentype PRIVATE_token_kind_t;
 #endif
@@ -91,7 +92,7 @@ extern int PRIVATE_debug;
 #if ! defined PRIVATE_STYPE && ! defined PRIVATE_STYPE_IS_DECLARED
 union PRIVATE_STYPE
 {
-#line 79 "engines/private/grammar.y"
+#line 81 "engines/private/grammar.y"
 
 	Private::Symbol *sym; /* symbol table pointer */
 	int (**inst)();       /* machine instruction */
@@ -99,7 +100,7 @@ union PRIVATE_STYPE
 	int *i;	       /* integer value */
 	int narg;	     /* auxiliary value to count function arguments */
 
-#line 103 "engines/private/tokens.h"
+#line 104 "engines/private/tokens.h"
 
 };
 typedef union PRIVATE_STYPE PRIVATE_STYPE;


Commit: b5c3881238798c8d227911ff4e6149ec055b828f
    https://github.com/scummvm/scummvm/commit/b5c3881238798c8d227911ff4e6149ec055b828f
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-12-09T08:51:53-08:00

Commit Message:
PRIVATE: Fully implement AMRadioClip and PoliceClip

Changed paths:
    engines/private/funcs.cpp
    engines/private/private.cpp
    engines/private/private.h
    engines/private/savegame.h


diff --git a/engines/private/funcs.cpp b/engines/private/funcs.cpp
index 6ed02a06642..2c977d1d60e 100644
--- a/engines/private/funcs.cpp
+++ b/engines/private/funcs.cpp
@@ -660,34 +660,64 @@ static void fMaskDrawn(ArgArray args) {
 	_fMask(args, true);
 }
 
-static void fAddSound(Common::String sound, const char *t, Symbol *flag = nullptr, int val = 0) {
-	if (sound == "\"\"")
+static void fAMRadioClip(ArgArray args) {
+	assert(args.size() <= 4);
+	debugC(1, kPrivateDebugScript, "AMRadioClip(%s,%d,...)", args[0].u.str, args[1].u.val);
+
+	const char *name = args[0].u.str;
+	if (strcmp(name, "\"\"") == 0) {
+		int clipCount = args[1].u.val;
+		g_private->initializeAMRadioChannels(clipCount);
 		return;
+	}
 
-	if (strcmp(t, "AMRadioClip") == 0)
-		g_private->_AMRadio.push_back(sound);
-	else if (strcmp(t, "PoliceClip") == 0)
-		g_private->_policeRadio.push_back(sound);
-	else
-		error("error: invalid sound type %s", t);
-}
+	int priority = args[1].u.val;
 
-static void fAMRadioClip(ArgArray args) {
-	assert(args.size() <= 4);
-	fAddSound(args[0].u.str, "AMRadioClip");
+	// The third and fourth parameters are numbers followed by an optional '+' character.
+	// Each number is a priority and the '+' indicates that it is to be treated as a range
+	// instead of the default behavior of requiring an exact match.
+	int disabledPriority1 = (args.size() >= 3) ? args[2].u.val : 0;
+	bool exactPriorityMatch1 = (args.size() >= 3) ? (args[2].type != NUM_PLUS) : true;
+	int disabledPriority2 = (args.size() >= 4) ? args[3].u.val : 0;
+	bool exactPriorityMatch2 = (args.size() >= 4) ? (args[3].type != NUM_PLUS) : true;
+
+	Common::String flagName = (args.size() >= 6) ? *(args[4].u.sym->name) : "";
+	int flagValue = (args.size() >= 6) ? args[5].u.val : 0;
+
+	g_private->addRadioClip(g_private->_AMRadio, name, priority,
+		disabledPriority1, exactPriorityMatch1,
+		disabledPriority2, exactPriorityMatch2,
+		flagName, flagValue);
 }
 
 static void fPoliceClip(ArgArray args) {
 	assert(args.size() <= 4 || args.size() == 6);
-	fAddSound(args[0].u.str, "PoliceClip");
-	// In the original, the variable is updated when the clip is played, but here we just update
-	// the variable when the clip is added to play. The effect for the player, is mostly the same.
-	if (args.size() == 6) {
-		assert(args[4].type == NAME);
-		assert(args[5].type == NUM);
-		Symbol *flag = g_private->maps.lookupVariable(args[4].u.sym->name);
-		setSymbol(flag, args[5].u.val);
+	debugC(1, kPrivateDebugScript, "PoliceClip(%s,%d,...)", args[0].u.str, args[1].u.val);
+
+	const char *name = args[0].u.str;
+	if (strcmp(name, "\"\"") == 0) {
+		g_private->initializePoliceRadioChannels();
+		return;
 	}
+	
+	int priority = args[1].u.val;
+	if (strcmp(name, "\"DISABLE_ONLY\"") == 0) {
+		g_private->disableRadioClips(g_private->_policeRadio, priority);
+		return;
+	}
+
+	// The third and fourth parameters are numbers followed by an optional '+' character.
+	// Each number is a priority and the '+' indicates that it is to be treated as a range
+	// instead of the default behavior of requiring an exact match.
+	int disabledPriority1 = (args.size() >= 3) ? args[2].u.val : 0;
+	bool exactPriorityMatch1 = (args.size() >= 3) ? (args[2].type != NUM_PLUS) : true;
+	int disabledPriority2 = (args.size() >= 4) ? args[3].u.val : 0;
+	bool exactPriorityMatch2 = (args.size() >= 4) ? (args[3].type != NUM_PLUS) : true;
+
+	g_private->addRadioClip(g_private->_policeRadio, name, priority,
+		disabledPriority1, exactPriorityMatch1,
+		disabledPriority2, exactPriorityMatch2,
+		"", 0);
 }
 
 static void fPhoneClip(ArgArray args) {
diff --git a/engines/private/private.cpp b/engines/private/private.cpp
index b83cffb76c0..be54706921e 100644
--- a/engines/private/private.cpp
+++ b/engines/private/private.cpp
@@ -98,8 +98,8 @@ PrivateEngine::PrivateEngine(OSystem *syst, const ADGameDescription *gd)
 	_policeRadioArea.clear();
 	_AMRadioArea.clear();
 	_phoneArea.clear();
-	// TODO: use this as a default sound for radio
-	_infaceRadioPath = "inface/radio/";
+	_AMRadio.path = "inface/radio/comm_/";
+	_policeRadio.path = "inface/radio/police/";
 	_phonePrefix = "inface/telephon/";
 	_phoneCallSound = "phone.wav";
 
@@ -1253,13 +1253,8 @@ void PrivateEngine::selectAMRadioArea(Common::Point mousePos) {
 	if (_AMRadioArea.surf == nullptr)
 		return;
 
-	if (_AMRadio.empty())
-		return;
-
 	if (inMask(_AMRadioArea.surf, mousePos)) {
-		Common::String sound = _infaceRadioPath + "comm_/" + _AMRadio.back() + ".wav";
-		playSound(sound, 1, false, false);
-		_AMRadio.pop_back();
+		playRadio(_AMRadio, false);
 	}
 }
 
@@ -1267,13 +1262,8 @@ void PrivateEngine::selectPoliceRadioArea(Common::Point mousePos) {
 	if (_policeRadioArea.surf == nullptr)
 		return;
 
-	if (_policeRadio.empty())
-		return;
-
 	if (inMask(_policeRadioArea.surf, mousePos)) {
-		Common::String sound = _infaceRadioPath + "police/" + _policeRadio.back() + ".wav";
-		playSound(sound, 1, false, false);
-		_policeRadio.pop_back();
+		playRadio(_policeRadio, true);
 	}
 }
 
@@ -1394,6 +1384,222 @@ bool PrivateEngine::selectDossierPrevSuspect(Common::Point mousePos) {
 	return false;
 }
 
+void PrivateEngine::addRadioClip(
+	Radio &radio, const Common::String &name, int priority,
+	int disabledPriority1, bool exactPriorityMatch1,
+	int disabledPriority2, bool exactPriorityMatch2,
+	const Common::String &flagName, int flagValue) {
+
+	// lookup radio clip by name
+	RadioClip *clip = nullptr;
+	for (uint i = 0; i < radio.clips.size(); i++) {
+		if (radio.clips[i].name == name) {
+			clip = &radio.clips[i];
+			break;
+		}
+	}
+
+	// add clip if new
+	if (clip == nullptr) {
+		RadioClip newClip;
+		newClip.name = name;
+		newClip.played = false;
+		newClip.priority = priority;
+		newClip.disabledPriority1 = disabledPriority1;
+		newClip.exactPriorityMatch1 = exactPriorityMatch1;
+		newClip.disabledPriority2 = disabledPriority2;
+		newClip.exactPriorityMatch2 = exactPriorityMatch2;
+		newClip.flagName = flagName;
+		newClip.flagValue = flagValue;
+		radio.clips.push_back(newClip);
+		clip = &radio.clips[radio.clips.size() - 1];
+	}
+
+	// disable other clips based on the clip's priority
+	disableRadioClips(radio, clip->priority);
+}
+
+void PrivateEngine::initializeAMRadioChannels(uint clipCount) {
+	Radio &radio = _AMRadio;
+	assert(clipCount < radio.clips.size());
+
+	// clear all channels
+	for (uint i = 0; i < ARRAYSIZE(radio.channels); i++) {
+		radio.channels[i] = -1;
+	}
+
+	// build array of playable clip indexes (up to clipCount)
+	Common::Array<uint> playableClips;
+	for (uint i = 0; i < clipCount; i++) {
+		if (!radio.clips[i].played) {
+			playableClips.push_back(i);
+		}
+	}
+
+	// place the highest priority clips in the channels (up to two)
+	uint channelCount;
+	switch (playableClips.size()) {
+	case 0: channelCount = 0; break;
+	case 1: channelCount = 1; break;
+	case 2: channelCount = 1; break;
+	case 3: channelCount = 1; break;
+	default: channelCount = 2; break;
+	}
+	uint channel = 0;
+	uint end = 0;
+	while (channel < channelCount) {
+		channel++;
+		if (channel < playableClips.size()) {
+			uint start = channel;
+			uint remainingClips = playableClips.size() - start;
+			while (remainingClips--) {
+				RadioClip &clip1 = radio.clips[playableClips[start]];
+				RadioClip &clip2 = radio.clips[playableClips[end]];
+				if (clip1.priority < clip2.priority) {
+					SWAP(playableClips[start], playableClips[end]);
+				}
+				start++;
+			}
+		}
+		radio.channels[channel - 1] = playableClips[end];
+		end++;
+	}
+
+	// build another array of playable clip indexes, starting at clipCount
+	Common::Array<uint> morePlayableClips;
+	for (uint i = clipCount; i < radio.clips.size(); i++) {
+		if (!radio.clips[i].played) {
+			morePlayableClips.push_back(i);
+		}
+	}
+
+	// shuffle second array
+	if (!morePlayableClips.empty()) {
+		for (uint i = morePlayableClips.size() - 1; i > 0; i--) {
+			uint n = _rnd->getRandomNumber(i);
+			SWAP(morePlayableClips[i], morePlayableClips[n]);
+		}
+	}
+
+	// install some of the clips from the second array into channels, starting
+	// at the end of the channel array to keep the highest priority clips.
+	uint copyCount = morePlayableClips.size();
+	if (playableClips.size() <= 3) { // not morePlayableClips
+		copyCount = MIN<uint>(copyCount, 2);
+	} else {
+		copyCount = MIN<uint>(copyCount, 1);
+	}
+	for (uint i = 0; i < copyCount; i++) {
+		radio.channels[2 - i] = morePlayableClips[i];
+	}
+
+	// shuffle channels
+	for (uint i = ARRAYSIZE(radio.channels) - 1; i > 0; i--) {
+		uint n = _rnd->getRandomNumber(i);
+		SWAP(radio.channels[i], radio.channels[n]);
+	}
+}
+
+void PrivateEngine::initializePoliceRadioChannels() {
+	Radio &radio = _policeRadio;
+
+	// clear all channels
+	for (uint i = 0; i < ARRAYSIZE(radio.channels); i++) {
+		radio.channels[i] = -1;
+	}
+
+	// build array of playable clip indexes
+	Common::Array<uint> playableClips;
+	for (uint i = 0; i < radio.clips.size(); i++) {
+		if (!radio.clips[i].played) {
+			playableClips.push_back(i);
+		}
+	}
+
+	// place the highest priority clips in the channels (up to three)
+	uint channelCount = MIN<uint>(playableClips.size(), ARRAYSIZE(radio.channels));
+	uint channel = 0;
+	uint end = 0;
+	while (channel < channelCount) {
+		channel++;
+		if (channel < playableClips.size()) {
+			uint start = channel;
+			uint remainingClips = playableClips.size() - start;
+			while (remainingClips--) {
+				RadioClip &clip1 = radio.clips[playableClips[start]];
+				RadioClip &clip2 = radio.clips[playableClips[end]];
+				if (clip1.priority < clip2.priority) {
+					SWAP(playableClips[start], playableClips[end]);
+				}
+				start++;
+			}
+		}
+		radio.channels[channel - 1] = playableClips[end];
+		end++;
+	}
+}
+
+void PrivateEngine::disableRadioClips(Radio &radio, int priority) {
+	for (uint i = 0; i < radio.clips.size(); i++) {
+		RadioClip &clip = radio.clips[i];
+		if (clip.played) {
+			continue;
+		}
+
+		if (clip.disabledPriority1) {
+			if ((clip.exactPriorityMatch1 && priority == clip.disabledPriority1) ||
+				(!clip.exactPriorityMatch1 && priority <= clip.disabledPriority1)) {
+				clip.played = true;
+			}
+		}
+		if (clip.disabledPriority2) {
+			if ((clip.exactPriorityMatch2 && priority == clip.disabledPriority2) ||
+				(!clip.exactPriorityMatch2 && priority <= clip.disabledPriority2)) {
+				clip.played = true;
+			}
+		}
+	}
+}
+
+void PrivateEngine::playRadio(Radio &radio, bool randomlyDisableClips) {
+	// search channels for first available clip
+	for (uint i = 0; i < ARRAYSIZE(radio.channels); i++) {
+		// skip empty channels
+		if (radio.channels[i] == -1) {
+			continue;
+		}
+
+		// verify that clip hasn't been already been played
+		RadioClip &clip = radio.clips[radio.channels[i]];
+		radio.channels[i] = -1;
+		if (clip.played) {
+			continue;
+		}
+
+		// the police radio randomly disables clips (!)
+		if (randomlyDisableClips) {
+			uint r = _rnd->getRandomNumber(9);
+			if (r < 3) {
+				clip.played = true;
+				break; // play radio.wav
+			}
+		}
+
+		// play the clip
+		Common::String sound = radio.path + clip.name + ".wav";
+		playSound(sound, 1, false, false);
+		clip.played = true;
+		if (!clip.flagName.empty()) {
+			Symbol *flag = maps.lookupVariable(&(clip.flagName));
+			setSymbol(flag, clip.flagValue);
+		}
+		return;
+	}
+
+	// play default radio sound
+	playSound("inface/radio/radio.wav", 1, false, false);
+}
+
 void PrivateEngine::addPhone(const Common::String &name, bool once, int startIndex, int endIndex, const Common::String &flagName, int flagValue) {
 	// lookup phone clip by name and index range
 	PhoneInfo *phone = nullptr;
@@ -1785,18 +1991,28 @@ Common::Error PrivateEngine::loadGameStream(Common::SeekableReadStream *stream)
 	_policeBustPreviousSetting = stream->readString();
 
 	// Radios
-	size = stream->readUint32LE();
-	_AMRadio.clear();
-
-	for (uint32 i = 0; i < size; ++i) {
-		_AMRadio.push_back(stream->readString());
-	}
-
-	size = stream->readUint32LE();
-	_policeRadio.clear();
-
-	for (uint32 i = 0; i < size; ++i) {
-		_policeRadio.push_back(stream->readString());
+	Radio *radios[] = { &_AMRadio, &_policeRadio };
+	for (uint r = 0; r < ARRAYSIZE(radios); r++) {
+		Radio *radio = radios[r];
+		radio->clear();
+
+		size = stream->readUint32LE();
+		for (uint32 i = 0; i < size; ++i) {
+			RadioClip clip;
+			clip.name = stream->readString();
+			clip.played = (stream->readByte() == 1);
+			clip.priority = stream->readSint32LE();
+			clip.disabledPriority1 = stream->readSint32LE();
+			clip.exactPriorityMatch1 = (stream->readByte() == 1);
+			clip.disabledPriority2 = stream->readSint32LE();
+			clip.exactPriorityMatch2 = (stream->readByte() == 1);
+			clip.flagName = stream->readString();
+			clip.flagValue = stream->readSint32LE();
+			radio->clips.push_back(clip);
+		}
+		for (uint i = 0; i < ARRAYSIZE(radio->channels); i++) {
+			radio->channels[i] = stream->readSint32LE();
+		}
 	}
 
 	size = stream->readUint32LE();
@@ -1925,15 +2141,27 @@ Common::Error PrivateEngine::saveGameStream(Common::WriteStream *stream, bool is
 	stream->writeByte(0);
 
 	// Radios
-	stream->writeUint32LE(_AMRadio.size());
-	for (SoundList::const_iterator it = _AMRadio.begin(); it != _AMRadio.end(); ++it) {
-		stream->writeString(*it);
-		stream->writeByte(0);
-	}
-	stream->writeUint32LE(_policeRadio.size());
-	for (SoundList::const_iterator it = _policeRadio.begin(); it != _policeRadio.end(); ++it) {
-		stream->writeString(*it);
-		stream->writeByte(0);
+	Radio *radios[] = { &_AMRadio, &_policeRadio };
+	for (uint r = 0; r < ARRAYSIZE(radios); r++) {
+		Radio *radio = radios[r];
+		stream->writeUint32LE(radio->clips.size());
+		for (uint i = 0; i < radio->clips.size(); i++) {
+			RadioClip &clip = radio->clips[i];
+			stream->writeString(clip.name);
+			stream->writeByte(0);
+			stream->writeByte(clip.played ? 1 : 0);
+			stream->writeSint32LE(clip.priority);
+			stream->writeSint32LE(clip.disabledPriority1);
+			stream->writeByte(clip.exactPriorityMatch1 ? 1 : 0);
+			stream->writeSint32LE(clip.disabledPriority2);
+			stream->writeByte(clip.exactPriorityMatch2 ? 1 : 0);
+			stream->writeString(clip.flagName);
+			stream->writeByte(0);
+			stream->writeSint32LE(clip.flagValue);
+		}
+		for (uint i = 0; i < ARRAYSIZE(radio->channels); i++) {
+			stream->writeSint32LE(radio->channels[i]);
+		}
 	}
 
 	// Phone
diff --git a/engines/private/private.h b/engines/private/private.h
index b1261a2878b..41debcccf7f 100644
--- a/engines/private/private.h
+++ b/engines/private/private.h
@@ -139,6 +139,35 @@ typedef struct PhoneInfo {
 	Common::Array<Common::String> sounds;
 } PhoneInfo;
 
+typedef struct RadioClip {
+	Common::String name;
+	bool played;
+	int priority;
+	int disabledPriority1; // 0 == none
+	bool exactPriorityMatch1;
+	int disabledPriority2; // 0 == none
+	bool exactPriorityMatch2;
+	Common::String flagName;
+	int flagValue;
+} RadioClip;
+
+typedef struct Radio {
+	Common::String path;
+	Common::Array<RadioClip> clips;
+	int channels[3];
+
+	Radio() {
+		clear();
+	}
+
+	void clear() {
+		clips.clear();
+		for (uint i = 0; i < ARRAYSIZE(channels); i++) {
+			channels[i] = -1;
+		}
+	}
+} Radio;
+
 typedef struct DossierInfo {
 	Common::String page1;
 	Common::String page2;
@@ -181,7 +210,6 @@ extern const FuncTable funcTable[];
 
 typedef Common::List<ExitInfo> ExitList;
 typedef Common::List<MaskInfo> MaskList;
-typedef Common::List<Common::String> SoundList;
 typedef Common::List<PhoneInfo> PhoneList;
 typedef Common::List<InventoryItem> InvList;
 typedef Common::List<Common::Rect *> RectList;
@@ -426,11 +454,19 @@ public:
 	Common::String _sirenSound;
 
 	// Radios
-	Common::String _infaceRadioPath;
 	MaskInfo _AMRadioArea;
 	MaskInfo _policeRadioArea;
-	SoundList _AMRadio;
-	SoundList _policeRadio;
+	Radio _AMRadio;
+	Radio _policeRadio;
+	void addRadioClip(
+		Radio &radio, const Common::String &name, int priority,
+		int disabledPriority1, bool exactPriorityMatch1,
+		int disabledPriority2, bool exactPriorityMatch2,
+		const Common::String &flagName, int flagValue);
+	void initializeAMRadioChannels(uint clipCount);
+	void initializePoliceRadioChannels();
+	void disableRadioClips(Radio &radio, int priority);
+	void playRadio(Radio &radio, bool randomlyDisableClips);
 	void selectAMRadioArea(Common::Point);
 	void selectPoliceRadioArea(Common::Point);
 
diff --git a/engines/private/savegame.h b/engines/private/savegame.h
index ecf2f291873..f606d9fb823 100644
--- a/engines/private/savegame.h
+++ b/engines/private/savegame.h
@@ -36,13 +36,14 @@ namespace Private {
 //
 // Version - new/changed feature
 // =============================
+//       3 - Radio detailed state (December 2025)
 //       2 - Phone clip detailed state (December 2025)
 //       1 - Metadata header and more game state (November 2025)
 //
 // Earlier versions did not have a header and not supported.
 
-const uint16 kCurrentSavegameVersion = 2;
-const uint16 kMinimumSavegameVersion = 2;
+const uint16 kCurrentSavegameVersion = 3;
+const uint16 kMinimumSavegameVersion = 3;
 
 struct SavegameMetadata {
 	uint16 version;




More information about the Scummvm-git-logs mailing list