diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..11937d16c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,372 @@ +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +max_line_length = 120 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = true +ij_smart_tabs = false +ij_visual_guides = +ij_wrap_on_typing = false + +[{*.ats,*.cts,*.mts,*.ts}] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 2 +ij_visual_guides = 120 +ij_typescript_align_imports = false +ij_typescript_align_multiline_array_initializer_expression = false +ij_typescript_align_multiline_binary_operation = false +ij_typescript_align_multiline_chained_methods = false +ij_typescript_align_multiline_extends_list = false +ij_typescript_align_multiline_for = true +ij_typescript_align_multiline_parameters = false +ij_typescript_align_multiline_parameters_in_calls = false +ij_typescript_align_multiline_ternary_operation = false +ij_typescript_align_object_properties = 0 +ij_typescript_align_union_types = false +ij_typescript_align_var_statements = 0 +ij_typescript_array_initializer_new_line_after_left_brace = false +ij_typescript_array_initializer_right_brace_on_new_line = false +ij_typescript_array_initializer_wrap = normal +ij_typescript_assignment_wrap = off +ij_typescript_binary_operation_sign_on_next_line = false +ij_typescript_binary_operation_wrap = off +ij_typescript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** +ij_typescript_blank_lines_after_imports = 1 +ij_typescript_blank_lines_around_class = 1 +ij_typescript_blank_lines_around_field = 0 +ij_typescript_blank_lines_around_field_in_interface = 0 +ij_typescript_blank_lines_around_function = 0 +ij_typescript_blank_lines_around_method = 1 +ij_typescript_blank_lines_around_method_in_interface = 1 +ij_typescript_block_brace_style = end_of_line +ij_typescript_block_comment_add_space = false +ij_typescript_block_comment_at_first_column = true +ij_typescript_call_parameters_new_line_after_left_paren = false +ij_typescript_call_parameters_right_paren_on_new_line = false +ij_typescript_call_parameters_wrap = normal +ij_typescript_catch_on_new_line = false +ij_typescript_chained_call_dot_on_new_line = true +ij_typescript_class_brace_style = end_of_line +ij_typescript_class_decorator_wrap = split_into_lines +ij_typescript_class_field_decorator_wrap = normal +ij_typescript_class_method_decorator_wrap = normal +ij_typescript_comma_on_new_line = false +ij_typescript_do_while_brace_force = always +ij_typescript_else_on_new_line = false +ij_typescript_enforce_trailing_comma = remove +ij_typescript_enum_constants_wrap = normal +ij_typescript_extends_keyword_wrap = off +ij_typescript_extends_list_wrap = off +ij_typescript_field_prefix = _ +ij_typescript_file_name_style = relaxed +ij_typescript_finally_on_new_line = false +ij_typescript_for_brace_force = always +ij_typescript_for_statement_new_line_after_left_paren = false +ij_typescript_for_statement_right_paren_on_new_line = false +ij_typescript_for_statement_wrap = off +ij_typescript_force_quote_style = false +ij_typescript_force_semicolon_style = false +ij_typescript_function_expression_brace_style = end_of_line +ij_typescript_function_parameter_decorator_wrap = normal +ij_typescript_if_brace_force = always +ij_typescript_import_merge_members = global +ij_typescript_import_prefer_absolute_path = global +ij_typescript_import_sort_members = true +ij_typescript_import_sort_module_name = false +ij_typescript_import_use_node_resolution = true +ij_typescript_imports_wrap = off +ij_typescript_indent_case_from_switch = true +ij_typescript_indent_chained_calls = false +ij_typescript_indent_package_children = 0 +ij_typescript_jsdoc_include_types = false +ij_typescript_jsx_attribute_value = braces +ij_typescript_keep_blank_lines_in_code = 1 +ij_typescript_keep_first_column_comment = true +ij_typescript_keep_indents_on_empty_lines = false +ij_typescript_keep_line_breaks = false +ij_typescript_keep_simple_blocks_in_one_line = true +ij_typescript_keep_simple_methods_in_one_line = true +ij_typescript_line_comment_add_space = true +ij_typescript_line_comment_at_first_column = false +ij_typescript_method_brace_style = end_of_line +ij_typescript_method_call_chain_wrap = normal +ij_typescript_method_parameters_new_line_after_left_paren = false +ij_typescript_method_parameters_right_paren_on_new_line = false +ij_typescript_method_parameters_wrap = normal +ij_typescript_object_literal_wrap = split_into_lines +ij_typescript_object_types_wrap = split_into_lines +ij_typescript_parentheses_expression_new_line_after_left_paren = false +ij_typescript_parentheses_expression_right_paren_on_new_line = false +ij_typescript_place_assignment_sign_on_next_line = false +ij_typescript_prefer_as_type_cast = true +ij_typescript_prefer_explicit_types_function_expression_returns = false +ij_typescript_prefer_explicit_types_function_returns = false +ij_typescript_prefer_explicit_types_vars_fields = false +ij_typescript_prefer_parameters_wrap = false +ij_typescript_property_prefix = +ij_typescript_reformat_c_style_comments = true +ij_typescript_space_after_colon = true +ij_typescript_space_after_comma = true +ij_typescript_space_after_dots_in_rest_parameter = false +ij_typescript_space_after_generator_mult = true +ij_typescript_space_after_property_colon = true +ij_typescript_space_after_quest = true +ij_typescript_space_after_type_colon = true +ij_typescript_space_after_unary_not = false +ij_typescript_space_before_async_arrow_lparen = true +ij_typescript_space_before_catch_keyword = true +ij_typescript_space_before_catch_left_brace = true +ij_typescript_space_before_catch_parentheses = false +ij_typescript_space_before_class_lbrace = true +ij_typescript_space_before_class_left_brace = true +ij_typescript_space_before_colon = true +ij_typescript_space_before_comma = false +ij_typescript_space_before_do_left_brace = true +ij_typescript_space_before_else_keyword = true +ij_typescript_space_before_else_left_brace = true +ij_typescript_space_before_finally_keyword = true +ij_typescript_space_before_finally_left_brace = true +ij_typescript_space_before_for_left_brace = true +ij_typescript_space_before_for_parentheses = true +ij_typescript_space_before_for_semicolon = false +ij_typescript_space_before_function_left_parenth = false +ij_typescript_space_before_generator_mult = false +ij_typescript_space_before_if_left_brace = true +ij_typescript_space_before_if_parentheses = true +ij_typescript_space_before_method_call_parentheses = false +ij_typescript_space_before_method_left_brace = true +ij_typescript_space_before_method_parentheses = false +ij_typescript_space_before_property_colon = false +ij_typescript_space_before_quest = true +ij_typescript_space_before_switch_left_brace = true +ij_typescript_space_before_switch_parentheses = false +ij_typescript_space_before_try_left_brace = true +ij_typescript_space_before_type_colon = false +ij_typescript_space_before_unary_not = false +ij_typescript_space_before_while_keyword = true +ij_typescript_space_before_while_left_brace = true +ij_typescript_space_before_while_parentheses = true +ij_typescript_spaces_around_additive_operators = true +ij_typescript_spaces_around_arrow_function_operator = true +ij_typescript_spaces_around_assignment_operators = true +ij_typescript_spaces_around_bitwise_operators = true +ij_typescript_spaces_around_equality_operators = true +ij_typescript_spaces_around_logical_operators = true +ij_typescript_spaces_around_multiplicative_operators = true +ij_typescript_spaces_around_relational_operators = true +ij_typescript_spaces_around_shift_operators = true +ij_typescript_spaces_around_unary_operator = false +ij_typescript_spaces_within_array_initializer_brackets = false +ij_typescript_spaces_within_brackets = false +ij_typescript_spaces_within_catch_parentheses = false +ij_typescript_spaces_within_for_parentheses = false +ij_typescript_spaces_within_if_parentheses = false +ij_typescript_spaces_within_imports = true +ij_typescript_spaces_within_interpolation_expressions = false +ij_typescript_spaces_within_method_call_parentheses = false +ij_typescript_spaces_within_method_parentheses = false +ij_typescript_spaces_within_object_literal_braces = true +ij_typescript_spaces_within_object_type_braces = true +ij_typescript_spaces_within_parentheses = false +ij_typescript_spaces_within_switch_parentheses = false +ij_typescript_spaces_within_type_assertion = false +ij_typescript_spaces_within_union_types = true +ij_typescript_spaces_within_while_parentheses = false +ij_typescript_special_else_if_treatment = true +ij_typescript_ternary_operation_signs_on_next_line = false +ij_typescript_ternary_operation_wrap = normal +ij_typescript_union_types_wrap = normal +ij_typescript_use_chained_calls_group_indents = false +ij_typescript_use_double_quotes = false +ij_typescript_use_explicit_js_extension = auto +ij_typescript_use_import_type = always +ij_typescript_use_path_mapping = always +ij_typescript_use_public_modifier = false +ij_typescript_use_semicolon_after_statement = false +ij_typescript_var_declaration_wrap = normal +ij_typescript_while_brace_force = always +ij_typescript_while_on_new_line = false +ij_typescript_wrap_comments = false + +[{*.cjs,*.es6,*.js,*.mjs}] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 2 +ij_javascript_align_imports = false +ij_javascript_align_multiline_array_initializer_expression = false +ij_javascript_align_multiline_binary_operation = false +ij_javascript_align_multiline_chained_methods = false +ij_javascript_align_multiline_extends_list = false +ij_javascript_align_multiline_for = true +ij_javascript_align_multiline_parameters = false +ij_javascript_align_multiline_parameters_in_calls = false +ij_javascript_align_multiline_ternary_operation = false +ij_javascript_align_object_properties = 0 +ij_javascript_align_union_types = false +ij_javascript_align_var_statements = 0 +ij_javascript_array_initializer_new_line_after_left_brace = false +ij_javascript_array_initializer_right_brace_on_new_line = false +ij_javascript_array_initializer_wrap = normal +ij_javascript_assignment_wrap = normal +ij_javascript_binary_operation_sign_on_next_line = false +ij_javascript_binary_operation_wrap = off +ij_javascript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** +ij_javascript_blank_lines_after_imports = 1 +ij_javascript_blank_lines_around_class = 1 +ij_javascript_blank_lines_around_field = 0 +ij_javascript_blank_lines_around_function = 0 +ij_javascript_blank_lines_around_method = 1 +ij_javascript_block_brace_style = end_of_line +ij_javascript_block_comment_add_space = false +ij_javascript_block_comment_at_first_column = true +ij_javascript_call_parameters_new_line_after_left_paren = false +ij_javascript_call_parameters_right_paren_on_new_line = false +ij_javascript_call_parameters_wrap = normal +ij_javascript_catch_on_new_line = false +ij_javascript_chained_call_dot_on_new_line = true +ij_javascript_class_brace_style = end_of_line +ij_javascript_class_decorator_wrap = split_into_lines +ij_javascript_class_field_decorator_wrap = off +ij_javascript_class_method_decorator_wrap = off +ij_javascript_comma_on_new_line = false +ij_javascript_do_while_brace_force = always +ij_javascript_else_on_new_line = false +ij_javascript_enforce_trailing_comma = remove +ij_javascript_extends_keyword_wrap = off +ij_javascript_extends_list_wrap = off +ij_javascript_field_prefix = _ +ij_javascript_file_name_style = relaxed +ij_javascript_finally_on_new_line = false +ij_javascript_for_brace_force = always +ij_javascript_for_statement_new_line_after_left_paren = false +ij_javascript_for_statement_right_paren_on_new_line = false +ij_javascript_for_statement_wrap = off +ij_javascript_force_quote_style = true +ij_javascript_force_semicolon_style = true +ij_javascript_function_expression_brace_style = end_of_line +ij_javascript_function_parameter_decorator_wrap = off +ij_javascript_if_brace_force = always +ij_javascript_import_merge_members = global +ij_javascript_import_prefer_absolute_path = global +ij_javascript_import_sort_members = true +ij_javascript_import_sort_module_name = false +ij_javascript_import_use_node_resolution = true +ij_javascript_imports_wrap = off +ij_javascript_indent_case_from_switch = true +ij_javascript_indent_chained_calls = false +ij_javascript_indent_package_children = 0 +ij_javascript_jsx_attribute_value = braces +ij_javascript_keep_blank_lines_in_code = 1 +ij_javascript_keep_first_column_comment = true +ij_javascript_keep_indents_on_empty_lines = false +ij_javascript_keep_line_breaks = false +ij_javascript_keep_simple_blocks_in_one_line = false +ij_javascript_keep_simple_methods_in_one_line = true +ij_javascript_line_comment_add_space = true +ij_javascript_line_comment_at_first_column = false +ij_javascript_method_brace_style = end_of_line +ij_javascript_method_call_chain_wrap = normal +ij_javascript_method_parameters_new_line_after_left_paren = false +ij_javascript_method_parameters_right_paren_on_new_line = false +ij_javascript_method_parameters_wrap = normal +ij_javascript_object_literal_wrap = split_into_lines +ij_javascript_object_types_wrap = on_every_item +ij_javascript_parentheses_expression_new_line_after_left_paren = false +ij_javascript_parentheses_expression_right_paren_on_new_line = false +ij_javascript_place_assignment_sign_on_next_line = false +ij_javascript_prefer_as_type_cast = false +ij_javascript_prefer_explicit_types_function_expression_returns = false +ij_javascript_prefer_explicit_types_function_returns = false +ij_javascript_prefer_explicit_types_vars_fields = false +ij_javascript_prefer_parameters_wrap = false +ij_javascript_property_prefix = +ij_javascript_reformat_c_style_comments = true +ij_javascript_space_after_colon = true +ij_javascript_space_after_comma = true +ij_javascript_space_after_dots_in_rest_parameter = false +ij_javascript_space_after_generator_mult = true +ij_javascript_space_after_property_colon = true +ij_javascript_space_after_quest = true +ij_javascript_space_after_type_colon = true +ij_javascript_space_after_unary_not = false +ij_javascript_space_before_async_arrow_lparen = true +ij_javascript_space_before_catch_keyword = true +ij_javascript_space_before_catch_left_brace = true +ij_javascript_space_before_catch_parentheses = false +ij_javascript_space_before_class_lbrace = true +ij_javascript_space_before_class_left_brace = true +ij_javascript_space_before_colon = true +ij_javascript_space_before_comma = false +ij_javascript_space_before_do_left_brace = true +ij_javascript_space_before_else_keyword = true +ij_javascript_space_before_else_left_brace = true +ij_javascript_space_before_finally_keyword = true +ij_javascript_space_before_finally_left_brace = true +ij_javascript_space_before_for_left_brace = true +ij_javascript_space_before_for_parentheses = true +ij_javascript_space_before_for_semicolon = false +ij_javascript_space_before_function_left_parenth = false +ij_javascript_space_before_generator_mult = false +ij_javascript_space_before_if_left_brace = true +ij_javascript_space_before_if_parentheses = true +ij_javascript_space_before_method_call_parentheses = false +ij_javascript_space_before_method_left_brace = true +ij_javascript_space_before_method_parentheses = false +ij_javascript_space_before_property_colon = false +ij_javascript_space_before_quest = true +ij_javascript_space_before_switch_left_brace = true +ij_javascript_space_before_switch_parentheses = false +ij_javascript_space_before_try_left_brace = true +ij_javascript_space_before_type_colon = false +ij_javascript_space_before_unary_not = false +ij_javascript_space_before_while_keyword = true +ij_javascript_space_before_while_left_brace = true +ij_javascript_space_before_while_parentheses = true +ij_javascript_spaces_around_additive_operators = true +ij_javascript_spaces_around_arrow_function_operator = true +ij_javascript_spaces_around_assignment_operators = true +ij_javascript_spaces_around_bitwise_operators = true +ij_javascript_spaces_around_equality_operators = true +ij_javascript_spaces_around_logical_operators = true +ij_javascript_spaces_around_multiplicative_operators = true +ij_javascript_spaces_around_relational_operators = true +ij_javascript_spaces_around_shift_operators = true +ij_javascript_spaces_around_unary_operator = false +ij_javascript_spaces_within_array_initializer_brackets = false +ij_javascript_spaces_within_brackets = false +ij_javascript_spaces_within_catch_parentheses = false +ij_javascript_spaces_within_for_parentheses = false +ij_javascript_spaces_within_if_parentheses = false +ij_javascript_spaces_within_imports = true +ij_javascript_spaces_within_interpolation_expressions = false +ij_javascript_spaces_within_method_call_parentheses = false +ij_javascript_spaces_within_method_parentheses = false +ij_javascript_spaces_within_object_literal_braces = true +ij_javascript_spaces_within_object_type_braces = true +ij_javascript_spaces_within_parentheses = false +ij_javascript_spaces_within_switch_parentheses = false +ij_javascript_spaces_within_type_assertion = false +ij_javascript_spaces_within_union_types = true +ij_javascript_spaces_within_while_parentheses = false +ij_javascript_special_else_if_treatment = true +ij_javascript_ternary_operation_signs_on_next_line = false +ij_javascript_ternary_operation_wrap = off +ij_javascript_union_types_wrap = on_every_item +ij_javascript_use_chained_calls_group_indents = false +ij_javascript_use_double_quotes = false +ij_javascript_use_explicit_js_extension = auto +ij_javascript_use_import_type = auto +ij_javascript_use_path_mapping = always +ij_javascript_use_public_modifier = false +ij_javascript_use_semicolon_after_statement = false +ij_javascript_var_declaration_wrap = normal +ij_javascript_while_brace_force = always +ij_javascript_while_on_new_line = false +ij_javascript_wrap_comments = false diff --git a/Dockerfile b/Dockerfile index 89338e1c6..a65a86f28 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,8 @@ RUN mv GameData/Config/dedicated_cfg.txt dedicated_cfg.txt.bk && \ mkdir -p Tracksbk/MatchSettings && \ mkdir -p Tracksbk/Campaigns/Nations && \ mv GameData/Tracks/MatchSettings/Nations/NationsBlue.txt Tracksbk/MatchSettings/MatchSettings.txt && \ - mv GameData/Tracks/Campaigns/Nations/Blue Tracksbk/Campaigns/Nations/ + mv GameData/Tracks/Campaigns/Nations/Blue Tracksbk/Campaigns/Nations/ && \ + rm -rf GameData/Tracks # expose volumes VOLUME /app/server/GameData/Config VOLUME /app/server/GameData/Tracks diff --git a/Plugins.ts b/Plugins.ts index b6dcf05a1..c247c8c57 100644 --- a/Plugins.ts +++ b/Plugins.ts @@ -20,3 +20,4 @@ import './plugins/info_messages/InfoMessages.js' import './plugins/skip_endscreen/SkipEndscreen.js' import './plugins/ultimania/Ultimania.js' import './plugins/live_checkpoints/LiveCheckpoints.js' +import './plugins/dynamic_time_limit/DynamicTimeLimit.js' diff --git a/Update.js b/Update.js index c66d182ad..7ed417d82 100644 --- a/Update.js +++ b/Update.js @@ -57,9 +57,18 @@ async function hashFile(path) { async function generateHashes() { console.log('Generating hashes for current files...') - const entries = (await fs.readdir('./src', { recursive: true, withFileTypes: true })) - .concat(await fs.readdir('./plugins', { recursive: true, withFileTypes: true })) - .concat(await fs.readdir('./config', { recursive: true, withFileTypes: true })) + const entries = (await fs.readdir('./src', { + recursive: true, + withFileTypes: true + })) + .concat(await fs.readdir('./plugins', { + recursive: true, + withFileTypes: true + })) + .concat(await fs.readdir('./config', { + recursive: true, + withFileTypes: true + })) const res = new Map( await Promise.all(entries.filter(a => a.isFile() && (a.name.endsWith('.js') || a.name.endsWith('.ts'))) @@ -118,6 +127,7 @@ async function doUpdate(fromPath, newHashes, oldHashes = null) { errorOccurred = true } } + // copy new hashes so you only have to update once try { await fs.copyFile(process.argv[2], '.hashes.json') @@ -130,27 +140,35 @@ async function doUpdate(fromPath, newHashes, oldHashes = null) { console.log('Errors occurred during update, exiting...') process.exit(1) } - let log = 'Update successful!' + + let log = `Update log to version ${JSON.parse( + await fs.readFile(path.join(fromPath, 'package.json'), { encoding: 'utf-8' }))['version']}\n\n` if (conflicts.length > 0) { console.log('!!! Update conflicts, make sure to fix them by comparing your files to files ending in .new !!!') console.log('If you do not fix these conflicts, the controller might fail to start or crash.') console.log('Affected files: ') - log = 'Update did not succeed because of conflicts. Please merge the following files manually:\n' + log += 'Update did not succeed because of conflicts. Please merge the following files manually:\n' conflicts.forEach(name => { console.log(name) log += name + '\n' }) console.log('_____________________________________') + } else { + log += 'Update successful!' } + try { await fs.writeFile('update.log', log, { encoding: 'utf-8' }) } catch(e) { console.log('Failed to write update log.') console.log(e) } - if (conflicts.length > 0) { process.exit(2) } + + if (conflicts.length > 0) { + process.exit(2) + } if (updatePerformed) { - console.log('Update successful.') + console.log('Update successful!') } else { console.log('Update not necessary.') } diff --git a/config/Config.js b/config/Config.js index 36857885d..b30359ce1 100644 --- a/config/Config.js +++ b/config/Config.js @@ -35,7 +35,7 @@ export default { kick: 1, forceSpectator: 1, addMap: 1, - removeMap: 1, + removeMap: 1 }, /** * Enable this for Trakman to take over handling maps. The controller will read maps from a folder @@ -51,16 +51,16 @@ export default { /** Path to the `GameData/Tracks/` directory of the server. MUST end with a slash '/'! * Default value: "../GameData/Tracks/" assuming the Trakman directory is on the same level as the server. */ - mapsDirectoryPrefix: "../GameData/Tracks/", + mapsDirectoryPrefix: '../GameData/Tracks/', /** Relative path to all the maps. Read recursively from `GameData/Tracks/`. * MUST end with a slash '/' when a directory is specified (i.e. if not left empty)! * Default value: "" */ - mapsDirectory: "", + mapsDirectory: '', /** Ignore non-stadium maps */ stadiumOnly: undefined, /** Amount of maps to load into the server. Default value: 5 */ - preloadMaps: 5, + preloadMaps: 5 }, /** * When adding a large amount of maps, it is better to add them in smaller chunks. This is the size @@ -77,12 +77,14 @@ export default { defaultTimeAttackTimeLimit: 300000, /** Minimal time value to which the dynamic timer can be set (in milliseconds) */ dynamicTimerSubtractionLimit: 30000, + /** CallVotes are skip/restart votes that can be done through the ingame UI (ESC>Advanced Options), setting it to -1 disables CallVotes entirely */ + callVoteRatio: -1, /** Relative path (/GameData/Config/) to the blacklist file */ - blacklistFile: "blacklist.txt", + blacklistFile: 'blacklist.txt', /** Relative path (/GameData/Config/) to the guestlist file */ - guestlistFile: "guestlist.txt", - /** Relative path (/GameData/Tracks/) to the matchsettings file */ - matchSettingsFile: "MatchSettings/MatchSettings.txt", + guestlistFile: 'guestlist.txt', + /** Relative path (/GameData/Tracks/) to the match settings file, ignored if manualMapLoading is enabled */ + matchSettingsFile: 'MatchSettings/MatchSettings.txt', /** Default message sent as the reason for administrative actions if nothing was specified by the admin */ defaultReasonMessage: 'No reason specified', /** Things that will be interpreted as true for the boolean command parameter */ @@ -92,10 +94,12 @@ export default { /** Object with options for ufuzzy, see https://github.com/leeoniya/uFuzzy#options */ searchOptions: { intraMode: 1, - alpha: "a-zа-яё" + alpha: 'a-zа-яё' }, - /** Current controller version */ - version: "1.6.1", - /** Controller repository link */ - repo: "github.com/lythx/trakman", + /** When using `matchString()` util with `stripTrackmaniaFormatting = true`, values are cached to optimize performance */ + matchStringCacheSize: 10000, + /** If haystack size exceeds this threshold `matchString()` will skip expensive haystack processing to optimize performance */ + matchStringReduxModeThreshold: 5000, + /** Custom map environments. If a map has an environment that is not in this list, it will be marked as "Stadium" */ + customEnvironments: ["Highlands", "NewSnowCar"] } diff --git a/docker-compose.yml b/docker-compose.yml index fd78efadd..767b725d1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -53,6 +53,8 @@ services: DISCORD_EMBED_TITLE_URL: "" DISCORD_EMBED_TITLE_ICON_URL: "" DISCORD_TAGGED_USERS: "" + # Setup + UPDATE_UTILITY_DISABLED: "NO" ports: # Don't forget to change the ports # if you already have another server running # **The ports MUST match** because of the way the server works diff --git a/docker_run.sh b/docker_run.sh index 1b2ede525..301ae351b 100644 --- a/docker_run.sh +++ b/docker_run.sh @@ -5,7 +5,7 @@ # create and copy dedicated config if find /app/server/GameData/Config -mindepth 1 -maxdepth 1 | read; then echo 'Server config exists, skipping initial setup.' - rm dedicated_cfg.txt.bk + rm dedicated_cfg.txt.bk 2>/dev/null else echo 'Setting up server...' # ugly xml replacement @@ -17,35 +17,63 @@ else xml ed -L -u "/dedicated/masterserver_account/password" -v "$SERVER_ACC_PASSWORD" dedicated_cfg.txt.bk xml ed -L -u "/dedicated/masterserver_account/validation_key" -v "$SERVER_ACC_KEY" dedicated_cfg.txt.bk xml ed -L -u "/dedicated/server_options/name" -v "$SERVER_NAME" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/server_options/comment" -v "$SERVER_COMMENT" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/server_options/hide_server" -v "${SERVER_HIDE_SERVER=0}" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/server_options/max_players" -v "${SERVER_MAX_PLAYERS=32}" dedicated_cfg.txt.bk xml ed -L -u "/dedicated/server_options/password" -v "$SERVER_PASSWORD" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/server_options/max_spectators" -v "${SERVER_MAX_SPECTATORS=32}" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/server_options/password_spectator" -v "$SERVER_PASSWORD_SPECTATOR" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/server_options/ladder_mode" -v "${SERVER_LADDER_MODE=1}" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/server_options/ladder_serverlimit_min" -v "${SERVER_LADDER_SERVERLIMIT_MIN=0}" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/server_options/ladder_serverlimit_max" -v "${SERVER_LADDER_SERVERLIMIT_MAX=50000}" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/server_options/enable_p2p_upload" -v "${SERVER_ENABLE_P2P_UPLOAD=True}" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/server_options/enable_p2p_download" -v "${SERVER_ENABLE_P2P_DOWNLOAD=True}" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/system_config/connection_uploadrate" -v "${SERVER_CONNECTION_UPLOADRATE=512}" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/system_config/connection_downloadrate" -v "${SERVER_CONNECTION_DOWNLOADRATE=8192}" dedicated_cfg.txt.bk xml ed -L -u "/dedicated/system_config/server_port" -v "$SERVER_NET_PORT" dedicated_cfg.txt.bk xml ed -L -u "/dedicated/system_config/server_p2p_port" -v "$SERVER_P2P_PORT" dedicated_cfg.txt.bk xml ed -L -u "/dedicated/system_config/xmlrpc_port" -v "$SERVER_PORT" dedicated_cfg.txt.bk - xml ed -L -u "/dedicated/system_config/packmask" -v "$SERVER_PACKMASK" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/system_config/xmlrpc_allowremote" -v "${REMOTE_XMLRPC=False}" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/system_config/packmask" -v "${SERVER_PACKMASK=nations}" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/system_config/disable_coherence_checks" -v "${SERVER_DISABLE_COHERENCE_CHECKS=laps}" dedicated_cfg.txt.bk mv /app/server/dedicated_cfg.txt.bk /app/server/GameData/Config/dedicated_cfg.txt fi # copy over default tracks if find /app/server/GameData/Tracks -mindepth 1 -maxdepth 1 | read; then echo 'Tracks exist, skipping initial setup.' - rm -r Tracksbk + rm -r Tracksbk 2>/dev/null else echo 'Setting up tracks...' mv /app/server/Tracksbk/* /app/server/GameData/Tracks/ fi # update and copy over Trakman directory if find /app/server/trakman -mindepth 1 -maxdepth 1 | read; then - echo 'Trakman exists. Attempting update...' - cd trakman || exit - cp ../trakmanbk/Update.js . - node Update.js /app/server/trakmanbk/.hashes.json - if [ $? -gt 0 ]; then - chown server:server update.log - echo 'Update not fully successful, please stop the container.' - sleep 1m # wait a minute for user to read the message, or to realise something's wrong - exit + if [ "$UPDATE_UTILITY_DISABLED" = "YES" ]; then + echo 'Trakman exists, skipping initial setup.' + else + echo 'Trakman exists. Attempting update...' + if ! cd trakman 2>/dev/null + then + echo 'Trakman actually does not exist. This makes no sense. Aborting and trying again.' + exit + fi + if ! cp ../trakmanbk/Update.js . 2>/dev/null + then + # this runs if trakmanbk doesn't exist so we have nothing to update from + echo "Update not available." + else + if ! node Update.js /app/server/trakmanbk/.hashes.json + then + # this runs if trakmanbk exists and the update script fails + chown server:server update.log + echo 'Update not fully successful, please stop the container.' + sleep 1m # wait a minute for the user to read the message, or to realise something's wrong + exit 1 + fi + fi + cd .. fi - cd .. - rm -r trakmanbk + rm -r trakmanbk 2>/dev/null else echo 'Setting up Trakman...' mv /app/server/trakmanbk/* /app/server/trakman/ @@ -63,6 +91,7 @@ touch trakman/logs/trace.log mkdir -p .pm2/logs touch .pm2/logs/Trakman-error.log touch .pm2/logs/Trakman-out.log +touch .pm2/logs/docker.log mkdir -p trakman/temp touch trakman/temp/rank_coherence.txt mkdir -p trakman/plugins/server_links/temp @@ -72,16 +101,18 @@ chown -R server:server /app/server echo "#!/bin/sh (while true; do /app/server/TrackmaniaServer /game_settings=MatchSettings/MatchSettings.txt /dedicated_cfg=dedicated_cfg.txt /nodaemon - echo 'Server exited with code ' $? + echo [\$(date +'%d %b %Y %T.%3N')] Server exited with code \$? | tee -a /app/server/.pm2/logs/docker.log echo 'Restarting...' -done) & -npm i --prefix /app/server/trakman -npm run build --prefix /app/server/trakman +done) &" > run.sh +if [ "$INSTALL_ON_STARTUP" != "NO" ]; then + echo "npm i --prefix /app/server/trakman" >> run.sh +fi +echo "npm run build --prefix /app/server/trakman chmod -R a+w /app/server cd trakman trap 'echo Terminating; npx pm2 stop 0; npx pm2 kill; exit' SIGTERM SIGINT -npx pm2 start ./built/src/Main.js --name Trakman -wait $!" > run.sh +npm run daemon +wait \$!" >> run.sh chown server:server run.sh chmod 766 run.sh exec su-exec server ./run.sh diff --git a/package-lock.json b/package-lock.json index d47eadc82..6ffad55db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,20 @@ { "name": "trakman", + "version": "1.6.1", "lockfileVersion": 3, "requires": true, "packages": { "": { + "name": "trakman", + "version": "1.6.1", + "license": "MIT", "dependencies": { "@leeoniya/ufuzzy": "^1.0.18", "dotenv": "^16.5.0", "node-fetch": "^3.3.2", "pg": "^8.16.0", "pg-copy-streams": "^6.0.6", - "pm2": "^6.0.6", + "pm2": "^6.0.8", "typescript": "^5.8.3", "xml2js": "^0.5.0" }, @@ -309,9 +313,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "18.19.110", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.110.tgz", - "integrity": "sha512-WW2o4gTmREtSnqKty9nhqF/vA0GKd0V/rbC0OyjSk9Bz6bzlsXKT+i7WDdS/a0z74rfT2PO4dArVCSnapNLA5Q==", + "version": "18.19.112", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.112.tgz", + "integrity": "sha512-i+Vukt9POdS/MBI7YrrkkI5fMfwFtOjphSmt4WXYLfwqsfr6z/HdCx7LqT9M7JktGob8WNgj8nFB4TbGNE4Cog==", "dev": true, "license": "MIT", "dependencies": { @@ -352,9 +356,9 @@ } }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -426,12 +430,12 @@ } }, "node_modules/ansis": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.0.0.tgz", - "integrity": "sha512-P8nrHI1EyW9OfBt1X7hMSwGN2vwRuqHSKJAT1gbLWZRzDa24oHjYwGHvEgHeBepupzk878yS/HBZ0NMPYtbolw==", + "version": "4.0.0-node10", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.0.0-node10.tgz", + "integrity": "sha512-BRrU0Bo1X9dFGw6KgGz6hWrqQuOlVEDOzkb0QSLZY9sXHqA7pNj7yHPVJRz7y/rj4EOJ3d/D5uxH+ee9leYgsg==", "license": "ISC", "engines": { - "node": ">=14" + "node": ">=10" } }, "node_modules/anymatch": { @@ -1309,22 +1313,22 @@ "license": "MIT" }, "node_modules/pg": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.0.tgz", - "integrity": "sha512-7SKfdvP8CTNXjMUzfcVTaI+TDzBEeaUnVwiVGZQD1Hh33Kpev7liQba9uLd4CfN8r9mCVsD0JIpq03+Unpz+kg==", + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.1.tgz", + "integrity": "sha512-5n6e7MgF5ABRsssOsX9xC95p+NUuhgDQDBSsrKSZJjYVqZHGyrmJuknym2IbVhGtzV9siBdzH9SEIQAuWF+sdg==", "license": "MIT", "dependencies": { - "pg-connection-string": "^2.9.0", - "pg-pool": "^3.10.0", - "pg-protocol": "^1.10.0", + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.1", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "engines": { - "node": ">= 8.0.0" + "node": ">= 16.0.0" }, "optionalDependencies": { - "pg-cloudflare": "^1.2.5" + "pg-cloudflare": "^1.2.6" }, "peerDependencies": { "pg-native": ">=3.0.1" @@ -1336,16 +1340,16 @@ } }, "node_modules/pg-cloudflare": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.5.tgz", - "integrity": "sha512-OOX22Vt0vOSRrdoUPKJ8Wi2OpE/o/h9T8X1s4qSkCedbNah9ei2W2765be8iMVxQUsvgT7zIAT2eIa9fs5+vtg==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.6.tgz", + "integrity": "sha512-uxmJAnmIgmYgnSFzgOf2cqGQBzwnRYcrEgXuFjJNEkpedEIPBSEzxY7ph4uA9k1mI+l/GR0HjPNS6FKNZe8SBQ==", "license": "MIT", "optional": true }, "node_modules/pg-connection-string": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.0.tgz", - "integrity": "sha512-P2DEBKuvh5RClafLngkAuGe9OUlFV7ebu8w1kmaaOgPcpJd1RIFh7otETfI6hAR8YupOLFTY7nuvvIn7PLciUQ==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", "license": "MIT" }, "node_modules/pg-copy-streams": { @@ -1367,18 +1371,18 @@ } }, "node_modules/pg-pool": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.0.tgz", - "integrity": "sha512-DzZ26On4sQ0KmqnO34muPcmKbhrjmyiO4lCCR0VwEd7MjmiKf5NTg/6+apUEu0NF7ESa37CGzFxH513CoUmWnA==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", "license": "MIT", "peerDependencies": { "pg": ">=8.0" } }, "node_modules/pg-protocol": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.0.tgz", - "integrity": "sha512-IpdytjudNuLv8nhlHs/UrVBhU0e78J0oIS/0AVdTbWxSOkFUVdsHC/NrorO6nXsQNDTT1kzDSOMJubBQviX18Q==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.1.tgz", + "integrity": "sha512-9YS3ZonDj0Lxny//aF0ITPdfrEPgKWCJvONsSXAaIUhgpzlzl5JgaZNlbTFxvYNfm2terGEnHeOSUlF6qRGBzw==", "license": "MIT" }, "node_modules/pg-types": { @@ -1431,16 +1435,16 @@ } }, "node_modules/pm2": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/pm2/-/pm2-6.0.6.tgz", - "integrity": "sha512-6t+I+Gc232dKe0OjwFYlU59JoY8MWBqsBmcZMSfKollByL4q8APUFwWOt7OtPH4lGO4Sb6GiFAqu7xT42QeRBA==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/pm2/-/pm2-6.0.8.tgz", + "integrity": "sha512-y7sO+UuGjfESK/ChRN+efJKAsHrBd95GY2p1GQfjVTtOfFtUfiW0NOuUhP5dN5QTF2F0EWcepgkLqbF32j90Iw==", "license": "AGPL-3.0", "dependencies": { "@pm2/agent": "~2.1.1", "@pm2/io": "~6.1.0", "@pm2/js-api": "~0.8.0", "@pm2/pm2-version-check": "latest", - "ansis": "4.0.0", + "ansis": "4.0.0-node10", "async": "~3.2.6", "blessed": "0.1.81", "chokidar": "^3.5.3", @@ -1773,9 +1777,9 @@ } }, "node_modules/socks": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", - "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.5.tgz", + "integrity": "sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==", "license": "MIT", "dependencies": { "ip-address": "^9.0.5", @@ -1859,9 +1863,9 @@ } }, "node_modules/systeminformation": { - "version": "5.27.1", - "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.27.1.tgz", - "integrity": "sha512-FgkVpT6GgATtNvADgtEzDxI/SVaBisfnQ4fmgQZhCJ4335noTgt9q6O81ioHwzs9HgnJaaFSdHSEMIkneZ55iA==", + "version": "5.27.5", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.27.5.tgz", + "integrity": "sha512-Kw8gAew7cvOMbCYISLeyf7e18p8/XaMz9lxARPlIsDatROB3yIS3xulfzD+lYHWe63vsddOCzDZBHt4E2yuq0Q==", "license": "MIT", "optional": true, "os": [ diff --git a/package.json b/package.json index b5c05dfc7..c7d8ca289 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,16 @@ { + "name": "trakman", + "version": "1.6.2", + "private": "true", + "license": "MIT", + "homepage": "http://github.com/lythx/trakman", "dependencies": { "@leeoniya/ufuzzy": "^1.0.18", "dotenv": "^16.5.0", "node-fetch": "^3.3.2", "pg": "^8.16.0", "pg-copy-streams": "^6.0.6", - "pm2": "^6.0.6", + "pm2": "^6.0.8", "typescript": "^5.8.3", "xml2js": "^0.5.0" }, diff --git a/plugins/actions/Actions.ts b/plugins/actions/Actions.ts index 29bd9e77c..0c34dc7fb 100644 --- a/plugins/actions/Actions.ts +++ b/plugins/actions/Actions.ts @@ -11,7 +11,13 @@ interface CallerInfo { const sendNoPrivilegeMessage = (info: CallerInfo): void => tm.sendMessage(config.noPermission, info.login) -let eraseObject: { id: string, admin: { login: string, nickname: string } } | undefined +let eraseObject: { + id: string, + admin: { + login: string, + nickname: string + } +} | undefined tm.addListener('BeginMap', (info): void => { if (info.isRestart) { return @@ -26,7 +32,7 @@ let toSkip = false tm.addListener('ServerStateChanged', (state): void => { if (toSkip) { - tm.client.callNoRes(`NextChallenge`, tm.getGameMode() === 'Cup' ? [{boolean: true }] : undefined) + tm.client.callNoRes(`NextChallenge`, tm.getGameMode() === 'Cup' ? [{ boolean: true }] : undefined) toSkip = false } }) @@ -42,7 +48,10 @@ export const actions = { * @param info Player information * @param voteValue Vote value */ - addVote: (info: { login: string, nickname: string }, voteValue: -3 | -2 | -1 | 1 | 2 | 3) => { + addVote: (info: { + login: string, + nickname: string + }, voteValue: -3 | -2 | -1 | 1 | 2 | 3) => { if (voteValue === undefined || voteValue === tm.karma.current.find(a => a.login === info.login)?.vote) { return } @@ -63,13 +72,15 @@ export const actions = { sendNoPrivilegeMessage(info) return } - const reasonString: string = reason === undefined ? '' : ` ${tm.utils.strVar(config.kick.reason, { reason: reason })}.` + const reasonString: string = reason === undefined ? '' : + ` ${tm.utils.strVar(config.kick.reason, { reason: reason })}.` tm.sendMessage(tm.utils.strVar(config.kick.text, { title: info.title, adminName: tm.utils.strip(info.nickname), name: tm.utils.strip(player.nickname) }) + `${reasonString}`, config.kick.public ? undefined : info.login) - tm.client.callNoRes(`Kick`, [{ string: player.login }, { string: reason === undefined ? 'No reason specified' : reason }]) + tm.client.callNoRes(`Kick`, + [{ string: player.login }, { string: reason === undefined ? 'No reason specified' : reason }]) }, /** * Mutes a player and sends a chat message @@ -85,8 +96,10 @@ export const actions = { } const expireDate: Date | undefined = duration === undefined ? undefined : new Date(Date.now() + duration) await tm.admin.mute(player.login, info, player.nickname, reason, expireDate) - const reasonString: string = reason === undefined ? '' : ` ${tm.utils.strVar(config.mute.reason, { reason: reason })}` - const durationString: string = duration === undefined ? '' : ` for ${tm.utils.palette.highlight}${tm.utils.getVerboseTime(duration)}` + const reasonString: string = reason === undefined ? '' : + ` ${tm.utils.strVar(config.mute.reason, { reason: reason })}` + const durationString: string = duration === undefined ? '' : + ` for ${tm.utils.palette.highlight}${tm.utils.getVerboseTime(duration)}` tm.sendMessage(tm.utils.strVar(config.mute.text, { title: info.title, adminName: tm.utils.strip(info.nickname), @@ -131,16 +144,13 @@ export const actions = { sendNoPrivilegeMessage(info) return } - const res = await tm.client.call('system.multicall', - [{ - method: 'ForceSpectator', - params: [{ string: player.login }, { int: 1 }] - }, - { - method: 'ForceSpectator', - params: [{ string: player.login }, { int: 0 }] - }] - ) + const res = await tm.client.call('system.multicall', [{ + method: 'ForceSpectator', + params: [{ string: player.login }, { int: 1 }] + }, { + method: 'ForceSpectator', + params: [{ string: player.login }, { int: 0 }] + }]) const name = tm.utils.strip(player.nickname) if (res instanceof Error || res[0] instanceof Error) { tm.sendMessage(tm.utils.strVar(config.forcespec.tooManySpecs, { name }), info.login) @@ -162,16 +172,13 @@ export const actions = { sendNoPrivilegeMessage(info) return } - const res = await tm.client.call('system.multicall', - [{ - method: 'ForceSpectator', - params: [{ string: player.login }, { int: 2 }] - }, - { - method: 'ForceSpectator', - params: [{ string: player.login }, { int: 0 }] - }] - ) + const res = await tm.client.call('system.multicall', [{ + method: 'ForceSpectator', + params: [{ string: player.login }, { int: 2 }] + }, { + method: 'ForceSpectator', + params: [{ string: player.login }, { int: 0 }] + }]) const name = tm.utils.strip(player.nickname) if (res instanceof Error || res[0] instanceof Error) { tm.sendMessage(tm.utils.strVar(config.forceplay.tooManyPlayers, { name }), info.login) @@ -197,8 +204,10 @@ export const actions = { } const expireDate: Date | undefined = duration === undefined ? undefined : new Date(Date.now() + duration) await tm.admin.ban(player.ip, player.login, info, player.nickname, reason, expireDate) - const reasonString: string = reason === undefined ? '' : ` ${tm.utils.strVar(config.ban.reason, { reason: reason })}` - const durationString: string = duration === undefined ? '' : ` for ${tm.utils.palette.highlight}${tm.utils.getVerboseTime(duration)}` + const reasonString: string = reason === undefined ? '' : + ` ${tm.utils.strVar(config.ban.reason, { reason: reason })}` + const durationString: string = duration === undefined ? '' : + ` for ${tm.utils.palette.highlight}${tm.utils.getVerboseTime(duration)}` tm.sendMessage(tm.utils.strVar(config.ban.text, { title: info.title, adminName: tm.utils.strip(info.nickname), @@ -253,8 +262,10 @@ export const actions = { tm.sendMessage(tm.utils.strVar(config.blacklist.error, { login: player.login }), info.login) return } - const reasonString: string = reason === undefined ? '' : ` ${tm.utils.strVar(config.blacklist.reason, { reason: reason })}` - const durationString: string = duration === undefined ? '' : ` for ${tm.utils.palette.highlight}${tm.utils.getVerboseTime(duration)}` + const reasonString: string = reason === undefined ? '' : + ` ${tm.utils.strVar(config.blacklist.reason, { reason: reason })}` + const durationString: string = duration === undefined ? '' : + ` for ${tm.utils.palette.highlight}${tm.utils.getVerboseTime(duration)}` tm.sendMessage(tm.utils.strVar(config.blacklist.text, { title: info.title, adminName: tm.utils.strip(info.nickname), @@ -280,7 +291,8 @@ export const actions = { return } if (result === 'Player not blacklisted') { - tm.sendMessage(tm.utils.strVar(config.unblacklist.notBlacklisted, { login: tm.utils.strip(player.nickname) }), info.login) + tm.sendMessage(tm.utils.strVar(config.unblacklist.notBlacklisted, { login: tm.utils.strip(player.nickname) }), + info.login) return } tm.sendMessage(tm.utils.strVar(config.unblacklist.text, { @@ -307,7 +319,8 @@ export const actions = { return } if (result === 'Already guest') { - tm.sendMessage(tm.utils.strVar(config.addguest.alreadyGuest, { login: tm.utils.strip(player.nickname) }), info.login) + tm.sendMessage(tm.utils.strVar(config.addguest.alreadyGuest, { login: tm.utils.strip(player.nickname) }), + info.login) return } tm.sendMessage(tm.utils.strVar(config.addguest.text, { @@ -344,14 +357,11 @@ export const actions = { }), config.rmguest.public ? undefined : info.login) }, publicAdd: async (login: string, nickname: string, title: string, mapName: string): Promise => { - const voteWindow: VoteWindow = new VoteWindow( - login, - config.publicAdd.voteGoal, - tm.utils.strVar(config.publicAdd.voteText, { mapName }), - tm.utils.strVar(config.publicAdd.voteStart, { nickname: tm.utils.strip(nickname, true), mapName }), - config.publicAdd.voteTime, - config.publicAdd.voteIcon - ) + const voteWindow: VoteWindow = new VoteWindow(login, config.publicAdd.voteGoal, + tm.utils.strVar(config.publicAdd.voteText, { mapName }), tm.utils.strVar(config.publicAdd.voteStart, { + nickname: tm.utils.strip(nickname, true), + mapName + }), config.publicAdd.voteTime, config.publicAdd.voteIcon) const result = await voteWindow.startAndGetResult(tm.players.list) if (result === undefined) { tm.sendMessage(config.publicAdd.alreadyRunning) @@ -429,7 +439,8 @@ export const actions = { await tm.admin.setPrivilege(target.login, privilege, caller) } }, - addMap: async (login: string, nickname: string, title: string, id: number | string, tmxSite?: string, fromUrl = false) => { + addMap: async (login: string, nickname: string, title: string, id: number | string, tmxSite?: string, + fromUrl = false) => { const tmxSites: tm.TMXSite[] = ['TMNF', 'TMN', 'TMO', 'TMS', 'TMU'] const site: tm.TMXSite | undefined = tmxSites.find(a => a === tmxSite?.toUpperCase()) let file: { @@ -449,7 +460,10 @@ export const actions = { tm.sendMessage(config.addMap.fetchError, login) return } - const obj = await tm.maps.writeFileAndAdd(file.name, file.content, { login, nickname }) + const obj = await tm.maps.writeFileAndAdd(file.name, file.content, { + login, + nickname + }) if (obj instanceof Error) { tm.log.warn(obj.message) tm.sendMessage(config.addMap.addError, login) @@ -478,7 +492,13 @@ export const actions = { return } id = tm.maps.current.id - eraseObject = { id: id, admin: { login, nickname } } + eraseObject = { + id: id, + admin: { + login, + nickname + } + } tm.sendMessage(tm.utils.strVar(config.removeMap.removeThis, { title: title, nickname: tm.utils.strip(nickname, true), @@ -496,17 +516,23 @@ export const actions = { nickname: tm.utils.strip(nickname, true), map: tm.utils.strip(map.name, true) }), config.removeMap.public ? undefined : login) - void tm.maps.remove(map.id, { login, nickname }) + void tm.maps.remove(map.id, { + login, + nickname + }) }, requeueMap: async (login: string, nickname: string, title: string): Promise => { - const status = await tm.jukebox.add(tm.maps.current.id, { login, nickname }, true) + const status = await tm.jukebox.add(tm.maps.current.id, { + login, + nickname + }, true) if (!status || status instanceof Error) { tm.sendMessage(config.requeueMap.error, login) return } tm.sendMessage(tm.utils.strVar(config.requeueMap.text, { title: title, - adminName: tm.utils.strip(nickname, true), + adminName: tm.utils.strip(nickname, true) })) }, skipMap: (): void => { @@ -517,7 +543,7 @@ export const actions = { toSkip = true break case 'race': - tm.client.callNoRes(`NextChallenge`, tm.getGameMode() === 'Cup' ? [{boolean: true }] : undefined) + tm.client.callNoRes(`NextChallenge`, tm.getGameMode() === 'Cup' ? [{ boolean: true }] : undefined) break } } diff --git a/plugins/actions/Config.js b/plugins/actions/Config.js index e7c9aed35..90483671c 100644 --- a/plugins/actions/Config.js +++ b/plugins/actions/Config.js @@ -27,75 +27,75 @@ export default { text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has muted ${p.highlight}#{name}${p.admin}#{duration}.`, reason: `${p.admin}Reason${p.highlight}: #{reason}${p.admin}.`, public: true, - privilege: tm.config.controller.privileges.mute, + privilege: tm.config.controller.privileges.mute }, unmute: { text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has unmuted ${p.highlight}#{name}${p.admin}.`, error: `${p.error}Could not unmute ${p.highlight}#{login}.`, notMuted: `${p.error}#{login} is not muted.`, public: true, - privilege: tm.config.controller.privileges.mute, + privilege: tm.config.controller.privileges.mute }, forcespec: { text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has forced ${p.highlight}#{name} ${p.admin}into spectator mode.`, error: `${p.error}Player is not on the server.`, tooManySpecs: `${p.error}Could not forcespec ${p.highlight}#{name}${p.error} because there are too many spectators.`, public: true, - privilege: tm.config.controller.privileges.forceSpectator, + privilege: tm.config.controller.privileges.forceSpectator }, forceplay: { text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has forced ${p.highlight}#{name} ${p.admin}into player mode.`, error: `${p.error}Player is not on the server.`, tooManyPlayers: `${p.error}Could not forceplay ${p.highlight}#{name}${p.error} because there are too many players.`, public: true, - privilege: tm.config.controller.privileges.forceSpectator, + privilege: tm.config.controller.privileges.forceSpectator }, kickghost: { text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has kicked ${p.highlight}#{name}${p.admin}.`, public: true, - privilege: tm.config.controller.privileges.kick, + privilege: tm.config.controller.privileges.kick }, ban: { text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has banned ${p.highlight}#{name}${p.admin}#{duration}.`, error: `${p.error}Could not ban ${p.highlight}#{login}${p.error}.`, reason: `${p.admin}Reason${p.highlight}: #{reason}${p.admin}.`, public: true, - privilege: tm.config.controller.privileges.ban, + privilege: tm.config.controller.privileges.ban }, unban: { text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has unbanned ${p.highlight}#{name}${p.admin}.`, error: `${p.error}Could not unban ${p.highlight}#{login}${p.error}.`, notBanned: `${p.error}#{login} is not banned.`, public: true, - privilege: tm.config.controller.privileges.ban, + privilege: tm.config.controller.privileges.ban }, blacklist: { text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has blacklisted ${p.highlight}#{name}${p.admin}#{duration}.`, error: `${p.error}Could not blacklist ${p.highlight}#{login}${p.error}.`, reason: `${p.admin}Reason${p.highlight}: #{reason}${p.admin}.`, public: true, - privilege: tm.config.controller.privileges.blacklist, + privilege: tm.config.controller.privileges.blacklist }, unblacklist: { text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has removed ${p.highlight}#{name} ${p.admin}from the blacklist.`, error: `${p.error}Could not remove ${p.highlight}#{login} ${p.error}from the blacklist.`, notBlacklisted: `${p.highlight}#{login} ${p.error}is not blacklisted.`, public: true, - privilege: tm.config.controller.privileges.blacklist, + privilege: tm.config.controller.privileges.blacklist }, addguest: { text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has added ${p.highlight}#{name} ${p.admin}to the guestlist.`, error: `${p.error}Could not add ${p.highlight}#{login} ${p.error}to the guestlist.`, alreadyGuest: `${p.highlight}#{login} ${p.error}is already in the guestlist.`, public: true, - privilege: tm.config.controller.privileges.addGuest, + privilege: tm.config.controller.privileges.addGuest }, rmguest: { text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has removed ${p.highlight}#{name} ${p.admin}from the guestlist.`, error: `${p.error}Could not remove ${p.highlight}#{login} ${p.error}from the guestlist.`, notGuest: `${p.highlight}#{login} ${p.error}is not in the guestlist.`, public: true, - privilege: tm.config.controller.privileges.addGuest, + privilege: tm.config.controller.privileges.addGuest }, publicAdd: { voteGoal: 0.51, @@ -113,20 +113,17 @@ export default { setPlayerPrivilege: { unknownPlayer: `${p.error}Unknown player.`, noPrivilege: `${p.error}You cannot control privileges of a person who has equal or higher privilege than you.`, - promote: `${p.admin}#{title} ${p.highlight}#{adminNickname}${p.admin} has promoted ` + - `${p.highlight}#{nickname}${p.admin} to #{rank}.`, - demote: `${p.admin}#{title} ${p.highlight}#{adminNickname}${p.admin} has demoted ` + - `${p.highlight}#{nickname}${p.admin} to #{rank}.`, + promote: `${p.admin}#{title} ${p.highlight}#{adminNickname}${p.admin} has promoted ` + `${p.highlight}#{nickname}${p.admin} to #{rank}.`, + demote: `${p.admin}#{title} ${p.highlight}#{adminNickname}${p.admin} has demoted ` + `${p.highlight}#{nickname}${p.admin} to #{rank}.`, rightsRemoved: `${p.admin}#{title} ${p.highlight}#{adminNickname}${p.admin} has removed privileges of ${p.highlight}#{nickname}${p.admin}.`, - alreadyIs: `${p.highlight}#{nickname}${p.error} is already #{rank}.`, + alreadyIs: `${p.highlight}#{nickname}${p.error} is already #{rank}.` }, addMap: { fetchError: `${p.error}Failed to fetch the map file from TMX.`, addError: `${p.error}Failed to add the map.`, - alreadyAdded: `${p.admin}Map ${p.highlight}#{map}${p.admin} added by ${p.highlight}#{nickname}${p.admin} is already on the server, ` + - `it will be ${p.highlight}queued ${p.admin}instead.`, + alreadyAdded: `${p.admin}Map ${p.highlight}#{map}${p.admin} added by ${p.highlight}#{nickname}${p.admin} is already on the server, ` + `it will be ${p.highlight}queued ${p.admin}instead.`, added: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has added and queued ${p.highlight}#{map}${p.admin} from TMX.`, - public: true, + public: true }, removeMap: { text: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has removed map ${p.highlight}#{map} ${p.admin}from the server.`, @@ -134,11 +131,11 @@ export default { alreadyRemoved: `${p.error}This map is already getting removed.`, error: `${p.error}Error while removing the map.`, onlyMap: `${p.error}There are no more maps in the server playlist.`, - public: true, + public: true }, requeueMap: { text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has requeued the ongoing map.`, error: `${p.error}This map has already been requeued.`, - public: true, + public: true } } diff --git a/plugins/betting/Betting.ts b/plugins/betting/Betting.ts index b6367f79c..161ddd86a 100644 --- a/plugins/betting/Betting.ts +++ b/plugins/betting/Betting.ts @@ -15,11 +15,10 @@ const returnCoppers = (login: string) => { if (prize === undefined) { throw new Error(`Prize undefined while returning coppers in betting plugin`) } - void tm.utils.payCoppers(login, tm.utils.getCoppersAfterTax(prize), tm.utils.strVar(config.copperReturnMessage, - { - amount: prize, - serverName: tm.utils.strip(tm.config.server.name, false) - })) + void tm.utils.payCoppers(login, tm.utils.getCoppersAfterTax(prize), tm.utils.strVar(config.copperReturnMessage, { + amount: prize, + serverName: tm.utils.strip(tm.config.server.name, false) + })) } const onTimeRunOut = (wasInterrupted = false) => { @@ -65,7 +64,7 @@ const acceptBet = async (player: tm.Player) => { if (paymentStatus === true) { betLogins.push(player.login) tm.sendMessage(tm.utils.strVar(config.messages.accept, { - name: tm.utils.strip(player.nickname), + name: tm.utils.strip(player.nickname) })) betPlaceWindow.hideToPlayer(player.login) } @@ -78,8 +77,9 @@ betPlaceWindow.onBetAccept = acceptBet if (config.isEnabled) { tm.addListener('Startup', () => { const msg = isActive ? config.messages.startupEnabled : config.messages.startupDisabled - for (const e of tm.players.list.filter(a => a.privilege >= config.activatePrivilege)) + for (const e of tm.players.list.filter(a => a.privilege >= config.activatePrivilege)) { tm.sendMessage(msg, e.login) + } }) tm.addListener('ServerStateChanged', (state) => { @@ -130,11 +130,10 @@ if (config.isEnabled) { return } tm.utils.payCoppers(bestRecord.login, tm.utils.getCoppersAfterTax(prize * betLogins.length), - tm.utils.strVar(config.winMessage, - { - amount: prize * betLogins.length, - serverName: tm.utils.strip(tm.config.server.name, false) - })) + tm.utils.strVar(config.winMessage, { + amount: prize * betLogins.length, + serverName: tm.utils.strip(tm.config.server.name, false) + })) tm.sendMessage(tm.utils.strVar(config.messages.win, { name: tm.utils.strip(bestRecord.nickname), prize: prize * betLogins.length @@ -178,7 +177,11 @@ if (config.isEnabled) { tm.commands.add({ aliases: config.bet.aliases, help: config.bet.help, - params: [{ name: 'prize', type: 'int', optional: true }], + params: [{ + name: 'prize', + type: 'int', + optional: true + }], callback(info, newPrize?: number) { if (!isOpen) { tm.sendMessage(config.messages.closed, info.login) @@ -189,8 +192,7 @@ if (config.isEnabled) { acceptBet(info) } else if (newPrize !== undefined) { if (newPrize < config.minimumAmount) { - tm.sendMessage(tm.utils.strVar(config.messages.amountTooLow, - { minimum: config.minimumAmount }), info.login) + tm.sendMessage(tm.utils.strVar(config.messages.amountTooLow, { minimum: config.minimumAmount }), info.login) return } startBet(info, newPrize) diff --git a/plugins/betting/Config.js b/plugins/betting/Config.js index 70f12c814..85abe7751 100644 --- a/plugins/betting/Config.js +++ b/plugins/betting/Config.js @@ -2,8 +2,7 @@ const p = tm.utils.palette export default { // If false plugin cant be activated - isEnabled: false, - // This can be changed using an ingame command + isEnabled: false, // This can be changed using an ingame command isActive: false, betTimeSeconds: 30, minimumAmount: 50, @@ -14,10 +13,8 @@ export default { start: `${p.highlight}#{name} ${p.donation}has started a bet with ${p.highlight}#{prize} ${p.donation}coppers.`, begin: `${p.donation}Bets are open. Use ${p.highlight}/bet ${p.donation}to place a bet.`, accept: `${p.highlight}#{name} ${p.donation}has accepted the bet.`, - startupEnabled: `${p.donation}Betting plugin is ${p.highlight}enabled${p.donation}.` - + ` Use ${p.highlight}//dbet ${p.donation}if you want to disable it.`, - startupDisabled: `${p.donation}Betting plugin is ${p.highlight}disabled${p.donation}.` - + ` Use ${p.highlight}//ebet ${p.donation}if you want to enable it.`, + startupEnabled: `${p.donation}Betting plugin is ${p.highlight}enabled${p.donation}.` + ` Use ${p.highlight}//dbet ${p.donation}if you want to disable it.`, + startupDisabled: `${p.donation}Betting plugin is ${p.highlight}disabled${p.donation}.` + ` Use ${p.highlight}//ebet ${p.donation}if you want to enable it.`, amountTooLow: `${p.error}Amount of bet coppers too low. Minimum amount is ${p.highlight}#{minimum}${p.error}.`, closed: `${p.error}Bets are closed.` }, @@ -32,8 +29,7 @@ export default { public: true, privilege: 0, prizeNeeded: `${p.error}You need to specify the bet prize.`, - noPrizeNeeded: `${p.error}Specified bet prize was ignored because somebody ` + - `else has already placed a bet. Prize is ${p.highlight}#{prize}${p.error}.`, + noPrizeNeeded: `${p.error}Specified bet prize was ignored because somebody ` + `else has already placed a bet. Prize is ${p.highlight}#{prize}${p.error}.` }, activate: { aliases: ['ebet', 'enablebets'], @@ -49,4 +45,4 @@ export default { success: `${p.donation}#{title} ${p.highlight}#{name} ${p.donation}has ${p.highlight}disabled ${p.donation}the betting plugin.`, alreadyNotActive: `${p.error}Betting plugin is already disabled.` } -} \ No newline at end of file +} diff --git a/plugins/betting/ui/BetInfoWidget.component.ts b/plugins/betting/ui/BetInfoWidget.component.ts index ebd6eddb9..66ec83e56 100644 --- a/plugins/betting/ui/BetInfoWidget.component.ts +++ b/plugins/betting/ui/BetInfoWidget.component.ts @@ -1,6 +1,4 @@ -import { - DynamicComponent, StaticHeader, componentIds, centeredText, components -} from '../../ui/UI.js' +import { centeredText, componentIds, components, DynamicComponent, StaticHeader } from '../../ui/UI.js' import config from './BetInfoWidget.config.js' export default class BetInfoWidget extends DynamicComponent { @@ -13,8 +11,7 @@ export default class BetInfoWidget extends DynamicComponent { super(componentIds.betInfoWidget) this.updatePosYAndHeight() this.header = new StaticHeader('race', { - rectangleWidth: StaticHeader.racePreset.rectangleWidth - - (config.prizeWidth + config.margin) + rectangleWidth: StaticHeader.racePreset.rectangleWidth - (config.prizeWidth + config.margin) }) tm.addListener('EndMap', () => this.hide()) } @@ -30,8 +27,7 @@ export default class BetInfoWidget extends DynamicComponent { } const side = config.side === true ? 'right' : 'left' const data = components.staticHeights[tm.getGameMode()] - this.posY = config.topBorder - data[side].reduce((acc, cur) => - acc += cur.getHeight() + config.marginBig, 0) + this.posY = config.topBorder - data[side].reduce((acc, cur) => acc += cur.getHeight() + config.marginBig, 0) } displayToPlayer(login: string): void { diff --git a/plugins/betting/ui/BetInfoWidget.config.js b/plugins/betting/ui/BetInfoWidget.config.js index 11edfebb4..ac5c15df5 100644 --- a/plugins/betting/ui/BetInfoWidget.config.js +++ b/plugins/betting/ui/BetInfoWidget.config.js @@ -1,9 +1,8 @@ -import { icons, raceConfig } from "../../ui/UI.js" +import { icons, raceConfig } from '../../ui/UI.js' export default { posX: raceConfig.rightPosition, - side: true, - // If true overrides posY prop and places the widget as last component + side: true, // If true overrides posY prop and places the widget as last component placeAsLastComponent: true, posY: 0, icon: icons.cash, @@ -14,4 +13,4 @@ export default { prizeColour: tm.utils.palette.green, topBorder: raceConfig.topBorder, // used for Y positioning marginBig: raceConfig.marginBig -} \ No newline at end of file +} diff --git a/plugins/betting/ui/BetPlaceWindow.component.ts b/plugins/betting/ui/BetPlaceWindow.component.ts index 3ca6a372a..8811c2929 100644 --- a/plugins/betting/ui/BetPlaceWindow.component.ts +++ b/plugins/betting/ui/BetPlaceWindow.component.ts @@ -1,7 +1,4 @@ -import { - DynamicComponent, StaticHeader, Grid, type GridCellFunction, - centeredText, icons, addManialinkListener, componentIds, components -} from '../../ui/UI.js' +import { addManialinkListener, centeredText, componentIds, components, DynamicComponent, Grid, type GridCellFunction, icons, StaticHeader } from '../../ui/UI.js' import config from './BetPlaceWindow.config.js' export default class BetPlaceWindow extends DynamicComponent { @@ -17,15 +14,13 @@ export default class BetPlaceWindow extends DynamicComponent { constructor() { super(componentIds.betPlaceWindow) - this.headerRectW = config.width - (StaticHeader.racePreset.squareWidth + - config.timerWidth + StaticHeader.racePreset.margin * 2) + this.headerRectW = config.width - (StaticHeader.racePreset.squareWidth + config.timerWidth + StaticHeader.racePreset.margin * 2) this.header = new StaticHeader('race', { rectangleWidth: this.headerRectW }) this.updatePosYAndHeight() - addManialinkListener(this.id + config.actionIdOffset, - config.options.length, (info, offset) => { - const amount = config.options[offset] - this.onBetStart(info, amount) - }) + addManialinkListener(this.id + config.actionIdOffset, config.options.length, (info, offset) => { + const amount = config.options[offset] + this.onBetStart(info, amount) + }) addManialinkListener(this.id + config.actionIdOffset + config.options.length, async (info) => this.onBetAccept(info)) } @@ -38,16 +33,19 @@ export default class BetPlaceWindow extends DynamicComponent { const side = config.relativePos.side === true ? 'right' : 'left' const data = components.staticHeights[tm.getGameMode()] this.height = config.staticPos.height ?? data[side][config.relativePos.widgetNumber]?.getHeight() ?? 0 - this.posY = config.staticPos.posY ?? - (config.topBorder - data[side].slice(0, config.relativePos.widgetNumber) - .reduce((acc, cur) => acc += cur.getHeight() + config.marginBig, 0)) + this.posY = config.staticPos.posY ?? (config.topBorder - data[side].slice(0, config.relativePos.widgetNumber) + .reduce((acc, cur) => acc += cur.getHeight() + config.marginBig, 0)) } - displayToPlayer(login: string, params: { seconds: number, placedBet: boolean }): void { - const grid = new Grid(config.width + config.margin * 2, - this.height + config.margin - StaticHeader.raceHeight, [1, 1, 1], [1, 1], { - background: config.background, margin: config.margin - }) + displayToPlayer(login: string, params: { + seconds: number, + placedBet: boolean + }): void { + const grid = new Grid(config.width + config.margin * 2, this.height + config.margin - StaticHeader.raceHeight, + [1, 1, 1], [1, 1], { + background: config.background, + margin: config.margin + }) const cell: GridCellFunction = (i, j, w, h) => { return ` ${centeredText(config.options[j + (i * 3)].toString(), w, h)}` @@ -59,7 +57,10 @@ export default class BetPlaceWindow extends DynamicComponent { bgcolor="${config.background}" action="${this.id + config.options.length + config.actionIdOffset}"/> ${centeredText(`Bet accepted`, config.width, h / 2, config.betAcceptedText)} ${centeredText(`Prize: $${tm.utils.palette.green}${(this.prize ?? 0) * this.betLogins.length}C`, config.width, - h / 2, { yOffset: h / 2 + config.prizeText.yOffset, textScale: config.prizeText.textScale })}` + h / 2, { + yOffset: h / 2 + config.prizeText.yOffset, + textScale: config.prizeText.textScale + })}` } else if (this.prize === undefined) { const cells: GridCellFunction[] = [] for (let i = 0; i < config.options.length; i++) { cells.push(cell) } @@ -81,8 +82,8 @@ export default class BetPlaceWindow extends DynamicComponent { - ${centeredText('$' + countdownColour + params.seconds.toString(), - topRightW, StaticHeader.raceHeight, config.countdownText)} + ${centeredText('$' + countdownColour + params.seconds.toString(), topRightW, StaticHeader.raceHeight, + config.countdownText)} ${content} diff --git a/plugins/betting/ui/BetPlaceWindow.config.js b/plugins/betting/ui/BetPlaceWindow.config.js index d67536963..dae1bb125 100644 --- a/plugins/betting/ui/BetPlaceWindow.config.js +++ b/plugins/betting/ui/BetPlaceWindow.config.js @@ -1,17 +1,15 @@ -import { raceConfig, StaticHeader } from "../../ui/UI.js" +import { raceConfig, StaticHeader } from '../../ui/UI.js' const p = tm.utils.palette const width = 10 export default { posX: raceConfig.rightPosition - (width + raceConfig.marginBig), - headerText: `Bet`, - // To position the widget next to side ui widgets set these properties and keep staticPos props null + headerText: `Bet`, // To position the widget next to side ui widgets set these properties and keep staticPos props null relativePos: { side: true, widgetNumber: 3 - }, - // To set fixed position set these props to numbers + }, // To set fixed position set these props to numbers staticPos: { posY: null, height: null @@ -44,4 +42,4 @@ export default { }, topBorder: raceConfig.topBorder, // used for Y positioning marginBig: raceConfig.marginBig -} \ No newline at end of file +} diff --git a/plugins/chat_routing_additions/CustomBracket.config.js b/plugins/chat_routing_additions/CustomBracket.config.js index ede22af71..4752f8643 100644 --- a/plugins/chat_routing_additions/CustomBracket.config.js +++ b/plugins/chat_routing_additions/CustomBracket.config.js @@ -2,10 +2,14 @@ const p = tm.utils.palette export default { isEnabled: true, - brackets: [ - { logins: ["login1"], left: `$${p.purple}[$g`, right: `$z$s$${p.purple}]$g ` }, - { logins: ["login1", "login2", "login3"], left: `$${p.red}[$g`, right: `$z$s$${p.red}]$g ` } - ], - // If there is another function modifying chat nickname with higher importance it will overwrite this one + brackets: [{ + logins: ['login1'], + left: `$${p.purple}[$g`, + right: `$z$s$${p.purple}]$g ` + }, { + logins: ['login1', 'login2', 'login3'], + left: `$${p.red}[$g`, + right: `$z$s$${p.red}]$g ` + }], // If there is another function modifying chat nickname with higher importance it will overwrite this one importance: 1 } diff --git a/plugins/chat_routing_additions/FinishCounter.config.js b/plugins/chat_routing_additions/FinishCounter.config.js index edc7b8c61..0ff7c6530 100644 --- a/plugins/chat_routing_additions/FinishCounter.config.js +++ b/plugins/chat_routing_additions/FinishCounter.config.js @@ -2,11 +2,21 @@ const p = tm.utils.palette export default { isEnabled: false, - colours: [ - { amount: 0, colour: '$fff' }, - { amount: 10, colour: '$' + p.green }, - { amount: 30, colour: '$' + p.purple }, - { amount: 100, colour: '$' + p.yellow }, - { amount: 200, colour: ['$03A', '$319', '$519', '$709', '$81A'] } // Gradient + colours: [{ + amount: 0, + colour: '$fff' + }, { + amount: 10, + colour: '$' + p.green + }, { + amount: 30, + colour: '$' + p.purple + }, { + amount: 100, + colour: '$' + p.yellow + }, { + amount: 200, + colour: ['$03A', '$319', '$519', '$709', '$81A'] + } // Gradient ] -} \ No newline at end of file +} diff --git a/plugins/chat_routing_additions/UrlFixer.ts b/plugins/chat_routing_additions/UrlFixer.ts index a14eb0637..b59430a4b 100644 --- a/plugins/chat_routing_additions/UrlFixer.ts +++ b/plugins/chat_routing_additions/UrlFixer.ts @@ -5,9 +5,8 @@ if (config.isEnabled) { if (info.text.match(config.matchRegex)) { const [text, fixedText]: string[][] = [info.text.split(` `), [``]] for (const t of text) { - t.match(config.matchRegex) - ? fixedText.push(`$L${tm.utils.fixProtocol(tm.utils.strip(t))}$L`) - : fixedText.push(t) + t.match(config.matchRegex) ? fixedText.push(`$L${tm.utils.fixProtocol(tm.utils.strip(t))}$L`) : + fixedText.push(t) } return fixedText.join(` `).slice(1) } diff --git a/plugins/checkpoint_records/CheckpointDB.ts b/plugins/checkpoint_records/CheckpointDB.ts index fd37284c9..e86a982ca 100644 --- a/plugins/checkpoint_records/CheckpointDB.ts +++ b/plugins/checkpoint_records/CheckpointDB.ts @@ -13,8 +13,7 @@ const createQueries = [`CREATE TABLE IF NOT EXISTS best_checkpoint_records( CONSTRAINT fk_map_id FOREIGN KEY(map_id) REFERENCES map_ids(id) -);`, -`CREATE TABLE IF NOT EXISTS checkpoint_records( +);`, `CREATE TABLE IF NOT EXISTS checkpoint_records( map_id INT4 NOT NULL, player_id INT4 NOT NULL, checkpoints INT4[] NOT NULL, @@ -42,20 +41,30 @@ for (const e of createQueries) { export const allCpsDB = { - async get(mapDBId: number, ...playerLogins: string[]): - Promise<{ checkpoints: (number | undefined)[], login: string, nickname: string }[] | Error> { + async get(mapDBId: number, ...playerLogins: string[]): Promise<{ + checkpoints: (number | undefined)[], + login: string, + nickname: string + }[] | Error> { if (playerLogins.length === 0) { return [] } const playerDBIds = await tm.db.getPlayerId(playerLogins) const query = `SELECT checkpoints, login, nickname FROM checkpoint_records JOIN players ON players.id=checkpoint_records.player_id WHERE map_id=$1 AND (${playerDBIds.map((_: any, i: number) => `player_id=$${i + 2} OR `).join('').slice(0, -3)});` - const res: { checkpoints: number[], login: string, nickname: string }[] | Error = - (await queryDB(query, mapDBId, ...playerDBIds.map(a => a.id))) + const res: { + checkpoints: number[], + login: string, + nickname: string + }[] | Error = (await queryDB(query, mapDBId, ...playerDBIds.map(a => a.id))) if (res instanceof Error) { tm.log.error(`Error when fetching checkpoints records of players ${playerDBIds} on map ${mapDBId}`, res.message) return [] } - return res.map(a => ({ login: a.login, nickname: a.nickname, checkpoints: a.checkpoints.map(b => b === -1 ? undefined : b) })) + return res.map(a => ({ + login: a.login, + nickname: a.nickname, + checkpoints: a.checkpoints.map(b => b === -1 ? undefined : b) + })) }, async add(mapId: number, login: string, checkpoints: number[]): Promise { @@ -76,11 +85,21 @@ export const allCpsDB = { export const bestCpsDB = { - async get(mapId: string | number): Promise<{ checkpoint: number, date: Date, login: string, nickname: string }[] | Error> { + async get(mapId: string | number): Promise<{ + checkpoint: number, + date: Date, + login: string, + nickname: string + }[] | Error> { const mapDBId = typeof mapId === 'number' ? mapId : await tm.db.getMapId(mapId) if (mapDBId === undefined) { return [] } - const res: { checkpoint: number, date: Date, index: number, login: string, nickname: string }[] | Error = - (await queryDB(`SELECT checkpoint, index, date, login, nickname FROM best_checkpoint_records + const res: { + checkpoint: number, + date: Date, + index: number, + login: string, + nickname: string + }[] | Error = (await queryDB(`SELECT checkpoint, index, date, login, nickname FROM best_checkpoint_records JOIN players ON players.id=best_checkpoint_records.player_id WHERE map_id=$1;`, mapId)) if (res instanceof Error) { @@ -89,12 +108,18 @@ export const bestCpsDB = { } const ret: Omit[] = [] for (const e of res) { - ret[e.index] = { checkpoint: e.checkpoint, date: e.date, login: e.login, nickname: e.nickname } + ret[e.index] = { + checkpoint: e.checkpoint, + date: e.date, + login: e.login, + nickname: e.nickname + } } return ret }, - async add(mapId: string | number, playerId: string | number, index: number, checkpoint: number, date: Date): Promise { + async add(mapId: string | number, playerId: string | number, index: number, checkpoint: number, + date: Date): Promise { const mapDBId = typeof mapId === 'number' ? mapId : await tm.db.getMapId(mapId) const playerDBId = typeof playerId === 'number' ? playerId : await tm.db.getPlayerId(playerId) if (mapDBId === undefined || playerDBId === undefined) { return } @@ -102,7 +127,8 @@ export const bestCpsDB = { await queryDB(query, mapDBId, playerDBId, index, checkpoint, date) }, - async update(mapId: string | number, playerId: string | number, index: number, checkpoint: number, date: Date): Promise { + async update(mapId: string | number, playerId: string | number, index: number, checkpoint: number, + date: Date): Promise { const mapDBId = typeof mapId === 'number' ? mapId : await tm.db.getMapId(mapId) const playerDBId = typeof playerId === 'number' ? playerId : await tm.db.getPlayerId(playerId) if (mapDBId === undefined || playerDBId === undefined) { return } diff --git a/plugins/checkpoint_records/CheckpointEvents.ts b/plugins/checkpoint_records/CheckpointEvents.ts index 9a5c6f551..b79e7ae2f 100644 --- a/plugins/checkpoint_records/CheckpointEvents.ts +++ b/plugins/checkpoint_records/CheckpointEvents.ts @@ -12,8 +12,9 @@ const nicknameUpdateListeners: Function[] = [] * @param event Event name * @param callback Callback function to execute */ -const addListener = (event: T, callback: CheckpointEventFunctions[T]): void => { - switch (event) { +const addListener = (event: T, + callback: CheckpointEventFunctions[T]): void => { + switch(event) { case 'BestCheckpoint': bestCheckpointListeners.push(callback as any) return @@ -34,9 +35,10 @@ const addListener = (event: T, callbac } } -const emitEvent = async (event: T, ...params: Parameters) => { +const emitEvent = async (event: T, + ...params: Parameters) => { let listeners: any - switch (event) { + switch(event) { case 'BestCheckpoint': listeners = bestCheckpointListeners break diff --git a/plugins/checkpoint_records/CheckpointListeners.ts b/plugins/checkpoint_records/CheckpointListeners.ts index a8ecdad2f..45713baeb 100644 --- a/plugins/checkpoint_records/CheckpointListeners.ts +++ b/plugins/checkpoint_records/CheckpointListeners.ts @@ -1,9 +1,9 @@ import type { BestCheckpoints, PlayerCheckpoints } from './CheckpointTypes.js' -import { bestCpsDB, allCpsDB } from './CheckpointDB.js' +import { allCpsDB, bestCpsDB } from './CheckpointDB.js' import { emitEvent } from './CheckpointEvents.js' import config from './Config.js' -let currentBestCps: BestCheckpoints +let currentBestCps: BestCheckpoints = [] let currentMapDBId: number const currentPlayerCps: PlayerCheckpoints[] = [] @@ -19,7 +19,9 @@ const onMapStart = async (): Promise => { await tm.log.fatal(`Failed to fetch best checkpoints for map ${tm.maps.current.id}`, res.message) return } - if (res === undefined) return + if (res === undefined) { + return + } currentBestCps = res const playerCps = await allCpsDB.get(currentMapDBId, ...tm.players.list.map(a => a.login)) if (playerCps instanceof Error) { @@ -42,7 +44,10 @@ if (config.isEnabled) { }, true) tm.addListener('PlayerDataUpdated', (info) => { - const changedObjects: { login: string; nickname: string; }[] = [] + const changedObjects: { + login: string; + nickname: string; + }[] = [] for (const e of currentBestCps) { if (e === undefined) { continue } const newNickname = info.find(a => a.login === e.login)?.nickname @@ -65,18 +70,34 @@ if (config.isEnabled) { }) tm.addListener('PlayerCheckpoint', (info: tm.CheckpointInfo) => { + if (currentMapDBId === undefined) { + tm.log.error(`Player ${info.player.login} got a checkpoint before the map started`) + return + } const date = new Date() const playerCheckpoints = currentPlayerCps.find(a => a.login === info.player.login) const isStunts = tm.getGameMode() === 'Stunts' if (playerCheckpoints === undefined) { - currentPlayerCps.push({ login: info.player.login, nickname: info.player.nickname, checkpoints: [info.time] }) + currentPlayerCps.push({ + login: info.player.login, + nickname: info.player.nickname, + checkpoints: [info.time] + }) void allCpsDB.add(currentMapDBId, info.player.login, [info.time]) - emitEvent('PlayerCheckpoint', { login: info.player.login, nickname: info.player.nickname, index: info.index }) - } else if (((playerCheckpoints.checkpoints[info.index] ?? Infinity) > info.time && !isStunts) || - ((playerCheckpoints.checkpoints[info.index] ?? -1) < info.time && isStunts)) { + emitEvent('PlayerCheckpoint', { + login: info.player.login, + nickname: info.player.nickname, + index: info.index + }) + } else if (((playerCheckpoints.checkpoints[info.index] ?? Infinity) > info.time && !isStunts) || ((playerCheckpoints.checkpoints[info.index] ?? -1) < info.time && isStunts)) { playerCheckpoints.checkpoints[info.index] = info.time - void allCpsDB.update(currentMapDBId, info.player.login, playerCheckpoints.checkpoints.map(a => a === undefined ? -1 : a)) - emitEvent('PlayerCheckpoint', { login: info.player.login, nickname: info.player.nickname, index: info.index }) + void allCpsDB.update(currentMapDBId, info.player.login, + playerCheckpoints.checkpoints.map(a => a === undefined ? -1 : a)) + emitEvent('PlayerCheckpoint', { + login: info.player.login, + nickname: info.player.nickname, + index: info.index + }) } const cp = currentBestCps[info.index]?.checkpoint if (cp === undefined || (cp > info.time && !isStunts) || (cp < info.time && isStunts)) { @@ -86,109 +107,160 @@ if (config.isEnabled) { checkpoint: info.time, date: date } - cp === undefined ? void bestCpsDB.add(currentMapDBId, info.player.login, info.index, info.time, date) - : void bestCpsDB.update(currentMapDBId, info.player.login, info.index, info.time, date) - emitEvent('BestCheckpoint', { login: info.player.login, nickname: info.player.nickname, index: info.index, date }) + cp === undefined ? void bestCpsDB.add(currentMapDBId, info.player.login, info.index, info.time, date) : + void bestCpsDB.update(currentMapDBId, info.player.login, info.index, info.time, date) + emitEvent('BestCheckpoint', { + login: info.player.login, + nickname: info.player.nickname, + index: info.index, + date + }) } }) tm.addListener('PlayerJoin', async (info) => { const playerCps = await allCpsDB.get(currentMapDBId, info.login) if (playerCps instanceof Error) { - await tm.log.fatal(`Failed to fetch player ${info.login} checkpoint records for map ${tm.maps.current.id}`, playerCps.message) + await tm.log.fatal(`Failed to fetch player ${info.login} checkpoint records for map ${tm.maps.current.id}`, + playerCps.message) return } currentPlayerCps.push(...playerCps) }) - tm.commands.add( - { - aliases: config.commands.deletemycp.aliases, - help: config.commands.deletemycp.help, - params: [{ name: 'cpIndex', type: 'int', optional: true }], - callback(info, cpIndex?: number) { - const cps = currentPlayerCps.find(a => a.login === info.login) - if (cps === undefined) { - tm.sendMessage(config.noCpRecords, info.login) + tm.commands.add({ + aliases: config.commands.deletemycp.aliases, + help: config.commands.deletemycp.help, + params: [{ + name: 'cpIndex', + type: 'int', + optional: true + }], + callback(info, cpIndex?: number) { + const cps = currentPlayerCps.find(a => a.login === info.login) + if (cps === undefined) { + tm.sendMessage(config.noCpRecords, info.login) + return + } + if (cpIndex === undefined) { + const arr: number[] = cps.checkpoints.filter(a => a !== undefined) as any + cps.checkpoints.length = 0 + tm.sendMessage(config.allPlayerCpsRemoved, info.login) + emitEvent('DeletePlayerCheckpoint', { + ...info, + deletedCheckpoints: arr.map((a, i) => ({ + time: a, + index: i + })) + }) + void allCpsDB.update(currentMapDBId, info.login, cps.checkpoints.map(a => a === undefined ? -1 : a)) + } else { + if (cpIndex < 1 || cpIndex > tm.maps.current.checkpointsAmount) { + tm.sendMessage(config.outOfRange, info.login) return } - if (cpIndex === undefined) { - const arr: number[] = cps.checkpoints.filter(a => a !== undefined) as any - cps.checkpoints.length = 0 - tm.sendMessage(config.allPlayerCpsRemoved, info.login) - emitEvent('DeletePlayerCheckpoint', { ...info, deletedCheckpoints: arr.map((a, i) => ({ time: a, index: i })) }) - void allCpsDB.update(currentMapDBId, info.login, cps.checkpoints.map(a => a === undefined ? -1 : a)) - } else { - if (cpIndex < 1 || cpIndex > tm.maps.current.checkpointsAmount) { - tm.sendMessage(config.outOfRange, info.login) - return - } - const deleted = cps.checkpoints[cpIndex - 1] - cps.checkpoints[cpIndex - 1] = undefined - tm.sendMessage(tm.utils.strVar(config.playerCpRemoved, { index: tm.utils.getOrdinalSuffix(cpIndex) }), info.login) - if (deleted !== undefined) { - emitEvent('DeletePlayerCheckpoint', { ...info, deletedCheckpoints: [{ time: deleted, index: cpIndex }] }) - } - void allCpsDB.update(currentMapDBId, info.login, cps.checkpoints.map(a => a === undefined ? -1 : a)) + const deleted = cps.checkpoints[cpIndex - 1] + cps.checkpoints[cpIndex - 1] = undefined + tm.sendMessage(tm.utils.strVar(config.playerCpRemoved, { index: tm.utils.getOrdinalSuffix(cpIndex) }), + info.login) + if (deleted !== undefined) { + emitEvent('DeletePlayerCheckpoint', { + ...info, + deletedCheckpoints: [{ + time: deleted, + index: cpIndex + }] + }) } - }, - privilege: config.commands.deletemycp.privilege + void allCpsDB.update(currentMapDBId, info.login, cps.checkpoints.map(a => a === undefined ? -1 : a)) + } }, - { - aliases: config.commands.deletecp.aliases, - help: config.commands.deletecp.help, - params: [{ name: 'cpIndex', type: 'int', optional: true }], - callback(info, cpIndex?: number) { - if (cpIndex === undefined) { - const arr: { - login: string, - nickname: string, - checkpoint: number, - date: Date - }[] = currentBestCps.filter(a => a !== undefined) as any - currentBestCps.length = 0 - tm.sendMessage(tm.utils.strVar(config.allBestCpsRemoved, - { title: info.title, nickname: tm.utils.strip(info.nickname, true) })) - emitEvent('DeleteBestCheckpoint', arr.map((a, i) => ({ ...a, index: i }))) - void bestCpsDB.delete(currentMapDBId) - } else { - if (cpIndex < 1 || cpIndex > tm.maps.current.checkpointsAmount) { - tm.sendMessage(config.outOfRange, info.login) - return - } - const deleted = currentBestCps[cpIndex - 1] - currentBestCps[cpIndex - 1] = undefined - tm.sendMessage(tm.utils.strVar(config.bestCpRemoved, { - title: info.title, - nickname: tm.utils.strip(info.nickname, true), - index: tm.utils.getOrdinalSuffix(cpIndex) - }), config.commands.deletecp.public ? undefined : info.login) - if (deleted !== undefined) { - emitEvent('DeleteBestCheckpoint', [{ ...deleted, index: cpIndex }]) - } - void bestCpsDB.delete(currentMapDBId, cpIndex - 1) + privilege: config.commands.deletemycp.privilege + }, { + aliases: config.commands.deletecp.aliases, + help: config.commands.deletecp.help, + params: [{ + name: 'cpIndex', + type: 'int', + optional: true + }], + callback(info, cpIndex?: number) { + if (cpIndex === undefined) { + const arr: { + login: string, + nickname: string, + checkpoint: number, + date: Date + }[] = currentBestCps.filter(a => a !== undefined) as any + currentBestCps.length = 0 + tm.sendMessage(tm.utils.strVar(config.allBestCpsRemoved, { + title: info.title, + nickname: tm.utils.strip(info.nickname, true) + })) + emitEvent('DeleteBestCheckpoint', arr.map((a, i) => ({ + ...a, + index: i + }))) + void bestCpsDB.delete(currentMapDBId) + } else { + if (cpIndex < 1 || cpIndex > tm.maps.current.checkpointsAmount) { + tm.sendMessage(config.outOfRange, info.login) + return } - }, - privilege: config.commands.deletecp.privilege - } - ) + const deleted = currentBestCps[cpIndex - 1] + currentBestCps[cpIndex - 1] = undefined + tm.sendMessage(tm.utils.strVar(config.bestCpRemoved, { + title: info.title, + nickname: tm.utils.strip(info.nickname, true), + index: tm.utils.getOrdinalSuffix(cpIndex) + }), config.commands.deletecp.public ? undefined : info.login) + if (deleted !== undefined) { + emitEvent('DeleteBestCheckpoint', [{ + ...deleted, + index: cpIndex + }]) + } + void bestCpsDB.delete(currentMapDBId, cpIndex - 1) + } + }, + privilege: config.commands.deletecp.privilege + }) } -const getMapCheckpoints = (): ({ login: string, nickname: string, checkpoint: number, date: Date } | null)[] => { - const arr: ({ login: string, nickname: string, checkpoint: number, date: Date } | null)[] = new Array(tm.maps.current.checkpointsAmount - 1).fill(null) +const getMapCheckpoints = (): ({ + login: string, + nickname: string, + checkpoint: number, + date: Date +} | null)[] => { + const arr: ({ + login: string, + nickname: string, + checkpoint: number, + date: Date + } | null)[] = new Array(tm.maps.current.checkpointsAmount - 1).fill(null) for (const [i, e] of currentBestCps.entries()) { arr[i] = e ?? null } return arr } -const getPlayerCheckpoints = (): ({ login: string, nickname: string, checkpoints: (number | null)[] })[] => { - const arr: ({ login: string, nickname: string, checkpoints: (number | null)[] })[] = [] +const getPlayerCheckpoints = (): ({ + login: string, + nickname: string, + checkpoints: (number | null)[] +})[] => { + const arr: ({ + login: string, + nickname: string, + checkpoints: (number | null)[] + })[] = [] for (const [i, e] of tm.players.list.entries()) { arr[i] = { login: e.login, nickname: e.nickname, - checkpoints: new Array(tm.maps.current.checkpointsAmount - 1).fill(null).map((a, i) => currentPlayerCps.find(a => a.login === e.login)?.checkpoints[i] ?? null) + checkpoints: new Array(tm.maps.current.checkpointsAmount - 1).fill(null) + .map((a, i) => currentPlayerCps.find(a => a.login === e.login)?.checkpoints[i] ?? null) } } return arr diff --git a/plugins/checkpoint_records/CheckpointRecords.ts b/plugins/checkpoint_records/CheckpointRecords.ts index b39d0f564..fd8bbd033 100644 --- a/plugins/checkpoint_records/CheckpointRecords.ts +++ b/plugins/checkpoint_records/CheckpointRecords.ts @@ -14,15 +14,23 @@ export const checkpointRecords = { /** * @returns an array of current map best checkpoint records or null if checkpoint record doesn't exist */ - get mapCheckpoints(): ({ login: string, nickname: string, checkpoint: number, date: Date } | null)[] { + get mapCheckpoints(): ({ + login: string, + nickname: string, + checkpoint: number, + date: Date + } | null)[] { return getMapCheckpoints() }, - /** * @returns an array of current map online players and their checkpoint records */ - get playerCheckpoints(): ({ login: string, nickname: string, checkpoints: (number | null)[] })[] { + get playerCheckpoints(): ({ + login: string, + nickname: string, + checkpoints: (number | null)[] + })[] { return getPlayerCheckpoints() }, diff --git a/plugins/checkpoint_records/CheckpointTypes.ts b/plugins/checkpoint_records/CheckpointTypes.ts index c13e19d21..1cb1942cf 100644 --- a/plugins/checkpoint_records/CheckpointTypes.ts +++ b/plugins/checkpoint_records/CheckpointTypes.ts @@ -12,13 +12,35 @@ export interface PlayerCheckpoints { } export interface CheckpointEventFunctions { - 'BestCheckpoint': ((bestCheckpoint: Readonly<{ login: string, nickname: string, index: number, date: Date }>) => void) - 'CheckpointsFetch': ((bestCheckpoints: Readonly, playerCheckpoints: readonly Readonly[]) => void) - 'DeleteBestCheckpoint': ((deletedCheckpoints: readonly Readonly<{ index: number, login: string, checkpoint: number, date: Date }>[]) => void) + 'BestCheckpoint': ((bestCheckpoint: Readonly<{ + login: string, + nickname: string, + index: number, + date: Date + }>) => void) + 'CheckpointsFetch': ((bestCheckpoints: Readonly, + playerCheckpoints: readonly Readonly[]) => void) + 'DeleteBestCheckpoint': ((deletedCheckpoints: readonly Readonly<{ + index: number, + login: string, + checkpoint: number, + date: Date + }>[]) => void) 'DeletePlayerCheckpoint': ((player: Readonly<{ - login: string, nickname: string, - deletedCheckpoints: readonly Readonly<{ index: number, time: number }>[] + login: string, + nickname: string, + deletedCheckpoints: readonly Readonly<{ + index: number, + time: number + }>[] + }>) => void) + 'PlayerCheckpoint': ((playerCheckpoint: Readonly<{ + login: string, + nickname: string, + index: number }>) => void) - 'PlayerCheckpoint': ((playerCheckpoint: Readonly<{ login: string, nickname: string, index: number }>) => void) - 'NicknameUpdated': ((players: Readonly[]>) => void) + 'NicknameUpdated': ((players: Readonly[]>) => void) } \ No newline at end of file diff --git a/plugins/checkpoint_records/Config.js b/plugins/checkpoint_records/Config.js index 918bf2980..a314c1593 100644 --- a/plugins/checkpoint_records/Config.js +++ b/plugins/checkpoint_records/Config.js @@ -1,18 +1,15 @@ -import { palette as p } from "../../config/PrefixesAndPalette.js" +import { palette as p } from '../../config/PrefixesAndPalette.js' export default { - isEnabled: true, - // Using DB client makes the plugin a bit faster due to high amount of database queries + isEnabled: true, // Using DB client makes the plugin a bit faster due to high amount of database queries // Program can run only run limited amount of clients, the process will hang otherwise useDBClient: true, noCpRecords: `${p.error}You have no checkpoint records on the ongoing map.`, allPlayerCpsRemoved: `${p.servermsg}Your checkpoints on the ongoing map were removed.`, outOfRange: `${p.error}Checkpoint index needs to be > 0 and <= to the ongoing map's checkpoint count.`, playerCpRemoved: `${p.servermsg}Your ${p.highlight}#{index}${p.servermsg} checkpoint was removed.`, - allBestCpsRemoved: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has removed ` - + `${p.highlight}all checkpoint records${p.admin} on the ongoing map.`, - bestCpRemoved: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has removed the ` - + `${p.highlight}#{index}${p.admin} checkpoint record on the ongoing map.`, + allBestCpsRemoved: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has removed ` + `${p.highlight}all checkpoint records${p.admin} on the ongoing map.`, + bestCpRemoved: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has removed the ` + `${p.highlight}#{index}${p.admin} checkpoint record on the ongoing map.`, commands: { deletecp: { aliases: ['delcp', 'deletecp'], @@ -26,4 +23,4 @@ export default { privilege: 0 } } -} \ No newline at end of file +} diff --git a/plugins/checkpoint_records/ui/CheckpointRecords.component.ts b/plugins/checkpoint_records/ui/CheckpointRecords.component.ts index ece77f8e8..13110a5b4 100644 --- a/plugins/checkpoint_records/ui/CheckpointRecords.component.ts +++ b/plugins/checkpoint_records/ui/CheckpointRecords.component.ts @@ -3,7 +3,7 @@ * @since 0.3 */ -import { closeButton, componentIds, Grid, centeredText, Paginator, type GridCellFunction, type GridCellObject, PopupWindow } from '../../ui/UI.js' +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, Paginator, PopupWindow } from '../../ui/UI.js' import { checkpointRecords } from '../../checkpoint_records/CheckpointRecords.js' import config from './CheckpointRecords.config.js' @@ -18,9 +18,12 @@ class CheckpointRecords extends PopupWindow { } constructor() { - super(componentIds.checkpointRecords, config.icon, config.title, (tm.getGameMode() === 'Stunts' ? config.stuntsNavbar : config.navbar)) - this.grid = new Grid(this.contentWidth, this.contentHeight, config.columnProportions, new Array(config.entries + 1).fill(1), config.grid) - this.paginator = new Paginator(this.openId, this.contentWidth, this.footerHeight, Math.ceil(tm.maps.current.checkpointsAmount / config.entries)) + super(componentIds.checkpointRecords, config.icon, config.title, + (tm.getGameMode() === 'Stunts' ? config.stuntsNavbar : config.navbar)) + this.grid = new Grid(this.contentWidth, this.contentHeight, config.columnProportions, + new Array(config.entries + 1).fill(1), config.grid) + this.paginator = new Paginator(this.openId, this.contentWidth, this.footerHeight, + Math.ceil(tm.maps.current.checkpointsAmount / config.entries)) this.paginator.onPageChange = (login: string, page: number) => { this.displayToPlayer(login, { page }, `${page}/${this.paginator.pageCount}`) } @@ -61,15 +64,13 @@ class CheckpointRecords extends PopupWindow { this.displayToPlayer(login, { page }, `${page}/${this.paginator.pageCount}`) } - protected async constructContent(login: string, params: { page: number }): Promise { - const headers: GridCellFunction[] = [ - (i, j, w, h) => centeredText(' Index ', w, h), - (i, j, w, h) => centeredText(' Nickname ', w, h), - (i, j, w, h) => centeredText(' Login ', w, h), - (i, j, w, h) => centeredText(' Date ', w, h), - (i, j, w, h) => centeredText(' Best ', w, h), - (i, j, w, h) => centeredText(' Personal ', w, h) - ] + protected async constructContent(login: string, params: { + page: number + }): Promise { + const headers: GridCellFunction[] = [(i, j, w, h) => centeredText(' Index ', w, h), + (i, j, w, h) => centeredText(' Nickname ', w, h), (i, j, w, h) => centeredText(' Login ', w, h), + (i, j, w, h) => centeredText(' Date ', w, h), (i, j, w, h) => centeredText(' Best ', w, h), + (i, j, w, h) => centeredText(' Personal ', w, h)] const cps = checkpointRecords.mapCheckpoints const cpIndex = config.entries * (params.page - 1) const personalCps = checkpointRecords.playerCheckpoints @@ -90,8 +91,7 @@ class CheckpointRecords extends PopupWindow { const dateCell: GridCellFunction = (i, j, w, h) => { const cp = cps[i + cpIndex - 1] - return centeredText((cp === undefined || cp === null) ? '' : - tm.utils.formatDate(cp.date, true), w, h) + return centeredText((cp === undefined || cp === null) ? '' : tm.utils.formatDate(cp.date, true), w, h) } const bestSectorCell: GridCellFunction = (i, j, w, h) => { @@ -121,7 +121,8 @@ class CheckpointRecords extends PopupWindow { } else if (difference === 0) { differenceString = `($${this.diffColours.equal + tm.utils.getTimeString(difference)}$FFF)` } else { - differenceString = `($${this.diffColours.worse}${worseSign}${tm.utils.getTimeString(Math.abs(difference))}$FFF)` + differenceString = `($${this.diffColours.worse}${worseSign}${tm.utils.getTimeString( + Math.abs(difference))}$FFF)` } } return centeredText(differenceString + ' ' + tm.utils.getTimeString(cp), w, h) diff --git a/plugins/checkpoint_records/ui/CheckpointRecords.config.js b/plugins/checkpoint_records/ui/CheckpointRecords.config.js index 1cd0cd395..5f199a558 100644 --- a/plugins/checkpoint_records/ui/CheckpointRecords.config.js +++ b/plugins/checkpoint_records/ui/CheckpointRecords.config.js @@ -1,34 +1,43 @@ -import { icons, componentIds as ids } from '../../ui/UI.js' +import { componentIds as ids, icons } from '../../ui/UI.js' export default { - title: " Checkpoint Records ", + title: ' Checkpoint Records ', icon: icons.clock, entries: 14, - columnProportions: [ - 0.7, - 2, - 2, - 2, - 1, - 1.5 - ], - navbar: [ - { name: 'Sector Records', actionId: ids.sectorRecords }, - { name: 'Dedi Checkpoints', actionId: ids.dediCps }, - { name: 'Dedi Sectors', actionId: ids.dediSectors }, - { name: 'Local Checkpoints', actionId: ids.localCps }, - { name: 'Local Sectors', actionId: ids.localSectors }, - ], - stuntsNavbar: [ - { name: 'Sector Records', actionId: ids.sectorRecords }, - { name: 'Ulti Records', actionId: ids.ultiRecords }, - { name: 'Local Checkpoints', actionId: ids.localCps }, - { name: 'Local Sectors', actionId: ids.localSectors }, - ], + columnProportions: [0.7, 2, 2, 2, 1, 1.5], + navbar: [{ + name: 'Sector Records', + actionId: ids.sectorRecords + }, { + name: 'Dedi Checkpoints', + actionId: ids.dediCps + }, { + name: 'Dedi Sectors', + actionId: ids.dediSectors + }, { + name: 'Local Checkpoints', + actionId: ids.localCps + }, { + name: 'Local Sectors', + actionId: ids.localSectors + }], + stuntsNavbar: [{ + name: 'Sector Records', + actionId: ids.sectorRecords + }, { + name: 'Ulti Records', + actionId: ids.ultiRecords + }, { + name: 'Local Checkpoints', + actionId: ids.localCps + }, { + name: 'Local Sectors', + actionId: ids.localSectors + }], grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, selfColour: tm.utils.palette.green, command: { @@ -38,4 +47,4 @@ export default { }, noTimeText: '--:--.-', stuntsNoTimeText: '--' -} \ No newline at end of file +} diff --git a/plugins/commands/config/ChatCommands.config.js b/plugins/commands/config/ChatCommands.config.js index eb24988dc..d630a79f7 100644 --- a/plugins/commands/config/ChatCommands.config.js +++ b/plugins/commands/config/ChatCommands.config.js @@ -4,49 +4,56 @@ const prefix = `$i` // Prefix all "fake" player messages with this (eg. $i, $t, export default { defaultValue: `everyone`, // This value will be used for the name if you don't specify anything in e.g. /hi hi: { - text: `$g[#{nickname}$z$s$g] ${prefix}Hello, #{name}!`, + text: `$g[#{nickname}$z$s$g] ${prefix}Hello, #{name}$g!`, public: true, privilege: 0, aliases: ['hi', 'hey', 'hello'], help: `Greet a certain someone.` }, + greet: { + text: `$g[#{nickname}$z$s$g] ${prefix}Hello, #{nickname_joiner}$g!`, + public: true, + privilege: 0, + aliases: ['greet'], + help: `Greet the latest joiner.` + }, bye: { - text: `$g[#{nickname}$z$s$g] ${prefix}Goodbye, #{name}!`, + text: `$g[#{nickname}$z$s$g] ${prefix}Goodbye, #{name}$g!`, public: true, privilege: 0, aliases: ['bb', 'bye'], help: `Bid your farewell.` }, thx: { - text: `$g[#{nickname}$z$s$g] ${prefix}Thanks, #{name}!`, + text: `$g[#{nickname}$z$s$g] ${prefix}Thanks, #{name}$g!`, public: true, privilege: 0, aliases: ['ty', 'tx', 'thx', 'thanks'], help: `Express your gratitude.` }, gg: { - text: `$g[#{nickname}$z$s$g] ${prefix}Good game, #{name}!`, + text: `$g[#{nickname}$z$s$g] ${prefix}Good game, #{name}$g!`, public: true, privilege: 0, aliases: ['gg', 'goodgame'], help: `Inform others that you\'ve enjoyed the race.` }, bg: { - text: `$g[#{nickname}$z$s$g] ${prefix}Bad game, #{name}!`, + text: `$g[#{nickname}$z$s$g] ${prefix}Bad game, #{name}$g!`, public: true, privilege: 0, aliases: ['bg', 'badgame'], help: `Allow others to find out about your disenjoyment of the round.` }, n1: { - text: `$g[#{nickname}$z$s$g] ${prefix}Nice one, #{name}!`, + text: `$g[#{nickname}$z$s$g] ${prefix}Nice one, #{name}$g!`, public: true, privilege: 0, aliases: ['n1', 'nice1', 'niceone'], help: `Rain your blessings upon the few selected by thy divine ritual.` }, gr: { - text: `$g[#{nickname}$z$s$g] ${prefix}Good race, #{name}!`, + text: `$g[#{nickname}$z$s$g] ${prefix}Good race, #{name}$g!`, public: true, privilege: 0, aliases: ['gr', 'goodrace'], @@ -138,16 +145,14 @@ export default { help: `Find out about the current map playtime.` }, laston: { - text: `${p.servermsg}Player ${p.highlight}#{name} ${p.servermsg}was last` + - ` active on${p.highlight} #{time}${p.servermsg}.`, + text: `${p.servermsg}Player ${p.highlight}#{name} ${p.servermsg}was last` + ` active on${p.highlight} #{time}${p.servermsg}.`, error: `${p.error}Unknown player ${p.highlight}#{name}${p.error}.`, privilege: 0, aliases: ['lo', 'laston'], help: `Enlighten yourself with the last visit date of the specifed specimen.` }, sessiontime: { - text: `${p.servermsg}Current session time of ${p.highlight}#{name}${p.servermsg}` + - ` is${p.highlight} #{time}${p.servermsg}.`, + text: `${p.servermsg}Current session time of ${p.highlight}#{name}${p.servermsg}` + ` is${p.highlight} #{time}${p.servermsg}.`, selfText: `${p.servermsg}Your current session time is ${p.highlight}#{time}${p.servermsg}.`, error: `${p.error}Player ${p.highlight}#{name}${p.error} is not online.`, privilege: 0, @@ -165,8 +170,7 @@ export default { admin: { text: `${p.error}Use //[command] for admin commands.`, privilege: 0, - aliases: ['admin', 'a'], - // help: `` // not needed here + aliases: ['admin', 'a'] // help: `` // not needed here }, coppers: { text: `${p.admin}Current server coppers amount is ${p.highlight}#{value}C${p.admin}.`, @@ -177,4 +181,4 @@ export default { aliases: ['ccs', 'coppers', 'checkcoppers'], help: `Check the amount of coppers the server account currently has.` } -} \ No newline at end of file +} diff --git a/plugins/commands/config/GameCommands.config.js b/plugins/commands/config/GameCommands.config.js index 514548a96..fd378d932 100644 --- a/plugins/commands/config/GameCommands.config.js +++ b/plugins/commands/config/GameCommands.config.js @@ -115,5 +115,5 @@ export default { privilege: 3, aliases: ['drp', 'disablerespawn'], help: `Set whether checkpoint respawning is enabled.` - }, -} \ No newline at end of file + } +} diff --git a/plugins/commands/config/MapCommands.config.js b/plugins/commands/config/MapCommands.config.js index 9f0c2cb12..0414ce380 100644 --- a/plugins/commands/config/MapCommands.config.js +++ b/plugins/commands/config/MapCommands.config.js @@ -17,8 +17,7 @@ export default { addrandom: { fetchError: `${p.error}Failed to fetch random map from TMX.`, addError: `${p.error}Failed to add the map.`, - added: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has added and queued` + - ` ${p.highlight}#{map}${p.admin} from TMX random.`, + added: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has added and queued` + ` ${p.highlight}#{map}${p.admin} from TMX random.`, public: true, privilege: tm.config.controller.privileges.addMap, aliases: ['ar', 'addr', 'addrandom'], @@ -32,8 +31,7 @@ export default { addfromurl: { fetchError: `${p.error}Failed to fetch the map file.`, addError: `${p.error}Failed to add the map.`, - alreadyAdded: `${p.admin}Map ${p.highlight}#{map}${p.admin} added by ${p.highlight}#{nickname}${p.admin} is already on the server, ` + - `it will be ${p.highlight}queued ${p.admin}instead.`, + alreadyAdded: `${p.admin}Map ${p.highlight}#{map}${p.admin} added by ${p.highlight}#{nickname}${p.admin} is already on the server, ` + `it will be ${p.highlight}queued ${p.admin}instead.`, added: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has added and queued ${p.highlight}#{map}${p.admin} from url.`, public: true, privilege: tm.config.controller.privileges.addMap, @@ -50,12 +48,11 @@ export default { notAvailable: `${p.error}Public ${p.highlight}/add ${p.error} is not available on this server.`, fetchError: `${p.error}Failed to fetch the map file from TMX.`, addError: `${p.error}Failed to add the map.`, - alreadyAdded: `${p.admin}Map ${p.highlight}#{map}${p.admin} added by ${p.highlight}#{nickname}${p.admin} is already on the server, ` + - `it will be ${p.highlight}queued ${p.admin}instead.`, + alreadyAdded: `${p.admin}Map ${p.highlight}#{map}${p.admin} added by ${p.highlight}#{nickname}${p.admin} is already on the server, ` + `it will be ${p.highlight}queued ${p.admin}instead.`, added: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has added and queued ${p.highlight}#{map}${p.admin} from TMX.`, public: true, privilege: 0, aliases: ['add', 'am', 'addmap'], help: `Add a map from TMX.` } -} \ No newline at end of file +} diff --git a/plugins/commands/config/RecordsCommands.config.js b/plugins/commands/config/RecordsCommands.config.js index 898dc629e..9edd628a7 100644 --- a/plugins/commands/config/RecordsCommands.config.js +++ b/plugins/commands/config/RecordsCommands.config.js @@ -16,5 +16,5 @@ export default { privilege: 3, aliases: ['pr', 'prunerecs', 'prunerecords'], help: `Remove all records on the ongoing map.` - }, -} \ No newline at end of file + } +} diff --git a/plugins/commands/config/ServerCommands.config.js b/plugins/commands/config/ServerCommands.config.js index 9cc0f9be1..15d174523 100644 --- a/plugins/commands/config/ServerCommands.config.js +++ b/plugins/commands/config/ServerCommands.config.js @@ -58,10 +58,8 @@ export default { help: `Set the referee mode.` }, pay: { - selfText: `${p.admin}You withdrew ${p.highlight}#{coppers}` + - ` ${p.admin}coppers from the server.`, - text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}paid ${p.highlight}#{coppers}` + - ` ${p.admin}coppers to ${p.highlight}#{target}${p.admin}.`, + selfText: `${p.admin}You withdrew ${p.highlight}#{coppers}` + ` ${p.admin}coppers from the server.`, + text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}paid ${p.highlight}#{coppers}` + ` ${p.admin}coppers to ${p.highlight}#{target}${p.admin}.`, defaultMessage: `You received #{coppers} coppers from #{server}$z$s.`, error: `${p.error}Failed to pay coppers to ${p.highlight}#{login}${p.error}.`, public: false, @@ -179,4 +177,4 @@ export default { aliases: ['call'], help: `Execute a dedicated server method. Params need to be specified in a valid json format or in "" for strings.` } -} \ No newline at end of file +} diff --git a/plugins/commands/src/AdminCommands.ts b/plugins/commands/src/AdminCommands.ts index 6a12b75a1..6c610f7b2 100644 --- a/plugins/commands/src/AdminCommands.ts +++ b/plugins/commands/src/AdminCommands.ts @@ -1,187 +1,225 @@ import config from '../config/AdminCommands.config.js' import { actions } from '../../actions/Actions.js' -const commands: tm.Command[] = [ - { - aliases: config.kick.aliases, - help: config.kick.help, - params: [{ name: 'login', type: 'player' }, { name: 'reason', type: 'multiword', optional: true }], - callback: actions.kick, - privilege: config.kick.privilege - }, - { - aliases: config.mute.aliases, - help: config.mute.help, - params: [{ name: 'login', type: 'offlinePlayer' }, { name: 'duration', type: 'time', optional: true }, { - name: 'reason', - type: 'multiword', - optional: true - }], - callback: actions.mute, - privilege: config.mute.privilege - }, - { - aliases: config.unmute.aliases, - help: config.unmute.help, - params: [{ name: 'login', type: 'offlinePlayer' }], - callback: actions.unmute, - privilege: config.unmute.privilege - }, - { - aliases: config.forcespec.aliases, - help: config.forcespec.help, - params: [{ name: 'login', type: 'player' }], - callback: actions.forceSpectator, - privilege: config.forcespec.privilege - }, - { - aliases: config.forceplay.aliases, - help: config.forceplay.help, - params: [{ name: 'login', type: 'player' }], - callback: actions.forcePlay, - privilege: config.forceplay.privilege - }, - { - aliases: config.kickghost.aliases, - help: config.kickghost.help, - params: [{ name: 'login' }], - callback: (info: tm.MessageInfo, login: string): void => { - tm.sendMessage(tm.utils.strVar(config.kickghost.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), - name: login - }), config.kickghost.public ? undefined : info.login) - tm.client.callNoRes(`Kick`, [{ string: login }]) - }, - privilege: config.kickghost.privilege - }, - { - aliases: config.ban.aliases, - help: config.ban.help, - params: [{ name: 'login', type: 'player' }, { name: 'duration', type: 'time', optional: true }, { - name: 'reason', - type: 'multiword', - optional: true - }], - callback: actions.ban, - privilege: config.ban.privilege - }, - { - aliases: config.unban.aliases, - help: config.unban.help, - params: [{ name: 'login', type: 'offlinePlayer' }], - callback: actions.unban, - privilege: config.unban.privilege - }, - { - aliases: config.blacklist.aliases, - help: config.blacklist.help, - params: [{ name: 'login', type: 'offlinePlayer' }, { name: 'duration', type: 'time', optional: true }, { - name: 'reason', - type: 'multiword', - optional: true - }], - callback: actions.blacklist, - privilege: config.blacklist.privilege - }, - { - aliases: config.unblacklist.aliases, - help: config.unblacklist.help, - params: [{ name: 'login', type: 'offlinePlayer' }], - callback: actions.unblacklist, - privilege: config.unblacklist.privilege - }, - { - aliases: config.addguest.aliases, - help: config.addguest.help, - params: [{ name: 'login', type: 'offlinePlayer' }], - callback: actions.addGuest, - privilege: config.addguest.privilege - }, - { - aliases: config.rmguest.aliases, - help: config.rmguest.help, - params: [{ name: 'login', type: 'offlinePlayer' }], - callback: actions.removeGuest, - privilege: config.rmguest.privilege - }, - { - aliases: config.loadmatchsettings.aliases, - help: config.loadmatchsettings.help, - params: [{ name: 'fileName', type: 'multiword', }], - callback: async (info: tm.MessageInfo, fileName: string) => { - const res = await tm.client.call(`LoadMatchSettings`, [{ string: fileName }]) - if (res instanceof Error) { - tm.sendMessage(config.loadmatchsettings.error, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.loadmatchsettings.text, - { - title: info.title, - adminName: tm.utils.strip(info.nickname), - file: tm.utils.strip(fileName) - } - ), config.loadmatchsettings.public ? undefined : info.login) - }, - privilege: config.loadmatchsettings.privilege - }, - { - aliases: config.savematchsettings.aliases, - help: config.savematchsettings.help, - params: [{ name: 'fileName', type: 'multiword', optional: true }], - callback: async (info: tm.MessageInfo, fileName?: string) => { - fileName = fileName || tm.config.controller.matchSettingsFile - const res = await tm.client.call(`SaveMatchSettings`, [{ string: fileName }]) - if (res instanceof Error) { - tm.sendMessage(config.savematchsettings.error, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.savematchsettings.text, - { - title: info.title, - adminName: tm.utils.strip(info.nickname), - file: tm.utils.strip(fileName) - } - ), config.savematchsettings.public ? undefined : info.login) - }, - privilege: config.savematchsettings.privilege - }, - { - aliases: config.updatemaps.aliases, - help: config.updatemaps.help, - callback: async (info: tm.MessageInfo) => { - await tm.maps.updateMaps() - tm.sendMessage(tm.utils.strVar(config.updatemaps.text, - { - title: info.title, - adminName: tm.utils.strip(info.nickname) - }), config.updatemaps.public ? undefined : info.login) - }, - privilege: config.updatemaps.privilege - }, - { - aliases: config.recalculateranks.aliases, - help: config.recalculateranks.help, - callback: async (info: tm.MessageInfo) => { - await tm.records.recalculateRanks() - tm.sendMessage(tm.utils.strVar(config.recalculateranks.text, { - title: info.title, adminName: tm.utils.strip(info.nickname) - }), config.recalculateranks.public ? undefined : info.login) - }, - privilege: config.recalculateranks.privilege - }, - { - aliases: config.servermessage.aliases, - help: config.servermessage.help, - params: [{ name: 'text', type: 'multiword', optional: true }], - callback: async (info: tm.MessageInfo, text: string = ''): Promise => { - tm.sendMessage(tm.utils.strVar(config.servermessage.message, { - server: tm.config.server.name, // No strip - message: text - }), config.servermessage.public ? undefined : info.login, false) - }, - privilege: config.servermessage.privilege - } -] +const commands: tm.Command[] = [{ + aliases: config.kick.aliases, + help: config.kick.help, + params: [{ + name: 'login', + type: 'player' + }, { + name: 'reason', + type: 'multiword', + optional: true + }], + callback: actions.kick, + privilege: config.kick.privilege +}, { + aliases: config.mute.aliases, + help: config.mute.help, + params: [{ + name: 'login', + type: 'offlinePlayer' + }, { + name: 'duration', + type: 'time', + optional: true + }, { + name: 'reason', + type: 'multiword', + optional: true + }], + callback: actions.mute, + privilege: config.mute.privilege +}, { + aliases: config.unmute.aliases, + help: config.unmute.help, + params: [{ + name: 'login', + type: 'offlinePlayer' + }], + callback: actions.unmute, + privilege: config.unmute.privilege +}, { + aliases: config.forcespec.aliases, + help: config.forcespec.help, + params: [{ + name: 'login', + type: 'player' + }], + callback: actions.forceSpectator, + privilege: config.forcespec.privilege +}, { + aliases: config.forceplay.aliases, + help: config.forceplay.help, + params: [{ + name: 'login', + type: 'player' + }], + callback: actions.forcePlay, + privilege: config.forceplay.privilege +}, { + aliases: config.kickghost.aliases, + help: config.kickghost.help, + params: [{ name: 'login' }], + callback: (info: tm.MessageInfo, login: string): void => { + tm.sendMessage(tm.utils.strVar(config.kickghost.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + name: login + }), config.kickghost.public ? undefined : info.login) + tm.client.callNoRes(`Kick`, [{ string: login }]) + }, + privilege: config.kickghost.privilege +}, { + aliases: config.ban.aliases, + help: config.ban.help, + params: [{ + name: 'login', + type: 'player' + }, { + name: 'duration', + type: 'time', + optional: true + }, { + name: 'reason', + type: 'multiword', + optional: true + }], + callback: actions.ban, + privilege: config.ban.privilege +}, { + aliases: config.unban.aliases, + help: config.unban.help, + params: [{ + name: 'login', + type: 'offlinePlayer' + }], + callback: actions.unban, + privilege: config.unban.privilege +}, { + aliases: config.blacklist.aliases, + help: config.blacklist.help, + params: [{ + name: 'login', + type: 'offlinePlayer' + }, { + name: 'duration', + type: 'time', + optional: true + }, { + name: 'reason', + type: 'multiword', + optional: true + }], + callback: actions.blacklist, + privilege: config.blacklist.privilege +}, { + aliases: config.unblacklist.aliases, + help: config.unblacklist.help, + params: [{ + name: 'login', + type: 'offlinePlayer' + }], + callback: actions.unblacklist, + privilege: config.unblacklist.privilege +}, { + aliases: config.addguest.aliases, + help: config.addguest.help, + params: [{ + name: 'login', + type: 'offlinePlayer' + }], + callback: actions.addGuest, + privilege: config.addguest.privilege +}, { + aliases: config.rmguest.aliases, + help: config.rmguest.help, + params: [{ + name: 'login', + type: 'offlinePlayer' + }], + callback: actions.removeGuest, + privilege: config.rmguest.privilege +}, { + aliases: config.loadmatchsettings.aliases, + help: config.loadmatchsettings.help, + params: [{ + name: 'fileName', + type: 'multiword' + }], + callback: async (info: tm.MessageInfo, fileName: string) => { + const res = await tm.client.call(`LoadMatchSettings`, [{ string: fileName }]) + if (res instanceof Error) { + tm.sendMessage(config.loadmatchsettings.error, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.loadmatchsettings.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + file: tm.utils.strip(fileName) + }), config.loadmatchsettings.public ? undefined : info.login) + }, + privilege: config.loadmatchsettings.privilege +}, { + aliases: config.savematchsettings.aliases, + help: config.savematchsettings.help, + params: [{ + name: 'fileName', + type: 'multiword', + optional: true + }], + callback: async (info: tm.MessageInfo, fileName?: string) => { + fileName = fileName || tm.config.controller.matchSettingsFile + const res = await tm.client.call(`SaveMatchSettings`, [{ string: fileName }]) + if (res instanceof Error) { + tm.sendMessage(config.savematchsettings.error, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.savematchsettings.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + file: tm.utils.strip(fileName) + }), config.savematchsettings.public ? undefined : info.login) + }, + privilege: config.savematchsettings.privilege +}, { + aliases: config.updatemaps.aliases, + help: config.updatemaps.help, + callback: async (info: tm.MessageInfo) => { + await tm.maps.updateMaps() + tm.sendMessage(tm.utils.strVar(config.updatemaps.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + }), config.updatemaps.public ? undefined : info.login) + }, + privilege: config.updatemaps.privilege +}, { + aliases: config.recalculateranks.aliases, + help: config.recalculateranks.help, + callback: async (info: tm.MessageInfo) => { + await tm.records.recalculateRanks() + tm.sendMessage(tm.utils.strVar(config.recalculateranks.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + }), config.recalculateranks.public ? undefined : info.login) + }, + privilege: config.recalculateranks.privilege +}, { + aliases: config.servermessage.aliases, + help: config.servermessage.help, + params: [{ + name: 'text', + type: 'multiword', + optional: true + }], + callback: async (info: tm.MessageInfo, text: string = ''): Promise => { + tm.sendMessage(tm.utils.strVar(config.servermessage.message, { + server: tm.config.server.name, // No strip + message: text + }), config.servermessage.public ? undefined : info.login, false) + }, + privilege: config.servermessage.privilege +}] tm.commands.add(...commands) diff --git a/plugins/commands/src/ChatCommands.ts b/plugins/commands/src/ChatCommands.ts index 1886bbbff..57a65aacc 100644 --- a/plugins/commands/src/ChatCommands.ts +++ b/plugins/commands/src/ChatCommands.ts @@ -1,310 +1,372 @@ import config from '../config/ChatCommands.config.js' -const commands: tm.Command[] = [ - { - aliases: config.hi.aliases, - help: config.hi.help, - params: [{ name: 'name', type: 'multiword', optional: true }], - callback: (info: tm.MessageInfo, name?: string): void => { - tm.sendMessage(tm.utils.strVar(config.hi.text, { nickname: info.nickname, name: name ?? config.defaultValue }), config.hi.public ? undefined : info.login, false) - }, - privilege: config.hi.privilege, - disableForMuted: true +const commands: tm.Command[] = [{ + aliases: config.hi.aliases, + help: config.hi.help, + params: [{ + name: 'name', + type: 'multiword', + optional: true + }], + callback: (info: tm.MessageInfo, name?: string): void => { + tm.sendMessage(tm.utils.strVar(config.hi.text, { + nickname: info.nickname, + name: name ?? config.defaultValue + }), config.hi.public ? undefined : info.login, false) }, - { - aliases: config.bye.aliases, - help: config.bye.help, - params: [{ name: 'name', type: 'multiword', optional: true }], - callback: (info: tm.MessageInfo, name?: string): void => { - tm.sendMessage(tm.utils.strVar(config.bye.text, { nickname: info.nickname, name: name ?? config.defaultValue }), config.bye.public ? undefined : info.login, false) - }, - privilege: config.bye.privilege, - disableForMuted: true + privilege: config.hi.privilege, + disableForMuted: true +}, { + aliases: config.greet.aliases, + help: config.greet.help, + params: [], + callback: (info: tm.MessageInfo): void => { + let timestamp = tm.players.list[0]; + tm.players.list.slice(1).forEach(player => { + if (player.joinTimestamp > timestamp.joinTimestamp) { + timestamp = player + } + }) + tm.sendMessage(tm.utils.strVar(config.greet.text, { + nickname: info.nickname, + nickname_joiner: timestamp.nickname + }), config.greet.public ? undefined : info.login, false) }, - { - aliases: config.thx.aliases, - help: config.thx.help, - params: [{ name: 'name', type: 'multiword', optional: true }], - callback: (info: tm.MessageInfo, name?: string): void => { - tm.sendMessage(tm.utils.strVar(config.thx.text, { nickname: info.nickname, name: name ?? config.defaultValue }), config.thx.public ? undefined : info.login, false) - }, - privilege: config.thx.privilege, - disableForMuted: true + privilege: config.greet.privilege, + disableForMuted: true +}, { + aliases: config.bye.aliases, + help: config.bye.help, + params: [{ + name: 'name', + type: 'multiword', + optional: true + }], + callback: (info: tm.MessageInfo, name?: string): void => { + tm.sendMessage(tm.utils.strVar(config.bye.text, { + nickname: info.nickname, + name: name ?? config.defaultValue + }), config.bye.public ? undefined : info.login, false) }, - { - aliases: config.gg.aliases, - help: config.gg.help, - params: [{ name: 'name', type: 'multiword', optional: true }], - callback: (info: tm.MessageInfo, name?: string): void => { - tm.sendMessage(tm.utils.strVar(config.gg.text, { nickname: info.nickname, name: name ?? config.defaultValue }), config.gg.public ? undefined : info.login, false) - }, - privilege: config.gg.privilege, - disableForMuted: true + privilege: config.bye.privilege, + disableForMuted: true +}, { + aliases: config.thx.aliases, + help: config.thx.help, + params: [{ + name: 'name', + type: 'multiword', + optional: true + }], + callback: (info: tm.MessageInfo, name?: string): void => { + tm.sendMessage(tm.utils.strVar(config.thx.text, { + nickname: info.nickname, + name: name ?? config.defaultValue + }), config.thx.public ? undefined : info.login, false) }, - { - aliases: config.bg.aliases, - help: config.bg.help, - params: [{ name: 'name', type: 'multiword', optional: true }], - callback: (info: tm.MessageInfo, name?: string): void => { - tm.sendMessage(tm.utils.strVar(config.bg.text, { nickname: info.nickname, name: name ?? config.defaultValue }), config.bg.public ? undefined : info.login, false) - }, - privilege: config.bg.privilege, - disableForMuted: true + privilege: config.thx.privilege, + disableForMuted: true +}, { + aliases: config.gg.aliases, + help: config.gg.help, + params: [{ + name: 'name', + type: 'multiword', + optional: true + }], + callback: (info: tm.MessageInfo, name?: string): void => { + tm.sendMessage(tm.utils.strVar(config.gg.text, { + nickname: info.nickname, + name: name ?? config.defaultValue + }), config.gg.public ? undefined : info.login, false) }, - { - aliases: config.n1.aliases, - help: config.n1.help, - params: [{ name: 'name', type: 'multiword' }], - callback: (info: tm.MessageInfo, name: string): void => { - tm.sendMessage(tm.utils.strVar(config.n1.text, { nickname: info.nickname, name: name }), config.n1.public ? undefined : info.login, false) - }, - privilege: config.n1.privilege, - disableForMuted: true + privilege: config.gg.privilege, + disableForMuted: true +}, { + aliases: config.bg.aliases, + help: config.bg.help, + params: [{ + name: 'name', + type: 'multiword', + optional: true + }], + callback: (info: tm.MessageInfo, name?: string): void => { + tm.sendMessage(tm.utils.strVar(config.bg.text, { + nickname: info.nickname, + name: name ?? config.defaultValue + }), config.bg.public ? undefined : info.login, false) }, - { - aliases: config.gr.aliases, - help: config.gr.help, - params: [{ name: 'name', type: 'multiword', optional: true }], - callback: (info: tm.MessageInfo, name?: string): void => { - tm.sendMessage(tm.utils.strVar(config.gr.text, { nickname: info.nickname, name: name ?? config.defaultValue }), config.gr.public ? undefined : info.login, false) - }, - privilege: config.gr.privilege, - disableForMuted: true + privilege: config.bg.privilege, + disableForMuted: true +}, { + aliases: config.n1.aliases, + help: config.n1.help, + params: [{ + name: 'name', + type: 'multiword' + }], + callback: (info: tm.MessageInfo, name: string): void => { + tm.sendMessage(tm.utils.strVar(config.n1.text, { + nickname: info.nickname, + name: name + }), config.n1.public ? undefined : info.login, false) }, - { - aliases: config.bgm.aliases, - help: config.bgm.help, - callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.bgm.text, { nickname: info.nickname }), config.bgm.public ? undefined : info.login, false) - }, - privilege: config.bgm.privilege, - disableForMuted: true + privilege: config.n1.privilege, + disableForMuted: true +}, { + aliases: config.gr.aliases, + help: config.gr.help, + params: [{ + name: 'name', + type: 'multiword', + optional: true + }], + callback: (info: tm.MessageInfo, name?: string): void => { + tm.sendMessage(tm.utils.strVar(config.gr.text, { + nickname: info.nickname, + name: name ?? config.defaultValue + }), config.gr.public ? undefined : info.login, false) }, - { - aliases: config.brb.aliases, - help: config.brb.help, - callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.brb.text, { nickname: info.nickname }), config.brb.public ? undefined : info.login, false) - }, - privilege: config.brb.privilege, - disableForMuted: true + privilege: config.gr.privilege, + disableForMuted: true +}, { + aliases: config.bgm.aliases, + help: config.bgm.help, + callback: (info: tm.MessageInfo): void => { + tm.sendMessage(tm.utils.strVar(config.bgm.text, { nickname: info.nickname }), + config.bgm.public ? undefined : info.login, false) }, - { - aliases: config.afk.aliases, - help: config.afk.help, - callback: async (info: tm.MessageInfo): Promise => { - const res = await tm.client.call('system.multicall', - [{ - method: 'ForceSpectator', - params: [{ string: info.login }, { int: 1 }] - }, - { - method: 'ForceSpectator', - params: [{ string: info.login }, { int: 0 }] - }]) - if (res instanceof Error || res[0] instanceof Error) { - tm.sendMessage(config.afk.tooManySpecs, info.login) - } else { - tm.sendMessage(tm.utils.strVar(config.afk.text, { nickname: info.nickname }), config.afk.public ? undefined : info.login, false) - tm.client.callNoRes('SpectatorReleasePlayerSlot', [{ string: info.login }]) - } - }, - privilege: config.afk.privilege + privilege: config.bgm.privilege, + disableForMuted: true +}, { + aliases: config.brb.aliases, + help: config.brb.help, + callback: (info: tm.MessageInfo): void => { + tm.sendMessage(tm.utils.strVar(config.brb.text, { nickname: info.nickname }), + config.brb.public ? undefined : info.login, false) }, - { - aliases: config.me.aliases, - help: config.me.help, - params: [{ name: 'thoughts', type: 'multiword', optional: true }], - callback: (info: tm.MessageInfo, thoughts?: string): void => { - tm.sendMessage(tm.utils.strVar(config.me.text, { nickname: info.nickname, message: thoughts ?? '' }), config.me.public ? undefined : info.login, false) - }, - privilege: config.me.privilege, - disableForMuted: true + privilege: config.brb.privilege, + disableForMuted: true +}, { + aliases: config.afk.aliases, + help: config.afk.help, + callback: async (info: tm.MessageInfo): Promise => { + const res = await tm.client.call('system.multicall', [{ + method: 'ForceSpectator', + params: [{ string: info.login }, { int: 1 }] + }, { + method: 'ForceSpectator', + params: [{ string: info.login }, { int: 0 }] + }]) + if (res instanceof Error || res[0] instanceof Error) { + tm.sendMessage(config.afk.tooManySpecs, info.login) + } else { + tm.sendMessage(tm.utils.strVar(config.afk.text, { nickname: info.nickname }), + config.afk.public ? undefined : info.login, false) + tm.client.callNoRes('SpectatorReleasePlayerSlot', [{ string: info.login }]) + } }, - { - aliases: config.lol.aliases, - help: config.lol.help, - callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.lol.text, { nickname: info.nickname }), config.lol.public ? undefined : info.login, false) - }, - privilege: config.lol.privilege, - disableForMuted: true + privilege: config.afk.privilege +}, { + aliases: config.me.aliases, + help: config.me.help, + params: [{ + name: 'thoughts', + type: 'multiword', + optional: true + }], + callback: (info: tm.MessageInfo, thoughts?: string): void => { + tm.sendMessage(tm.utils.strVar(config.me.text, { + nickname: info.nickname, + message: thoughts ?? '' + }), config.me.public ? undefined : info.login, false) }, - { - aliases: config.lool.aliases, - help: config.lool.help, - callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.lool.text, { nickname: info.nickname }), config.lool.public ? undefined : info.login, false) - }, - privilege: config.lool.privilege, - disableForMuted: true + privilege: config.me.privilege, + disableForMuted: true +}, { + aliases: config.lol.aliases, + help: config.lol.help, + callback: (info: tm.MessageInfo): void => { + tm.sendMessage(tm.utils.strVar(config.lol.text, { nickname: info.nickname }), + config.lol.public ? undefined : info.login, false) }, - { - aliases: config.loool.aliases, - help: config.loool.help, - callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.loool.text, { nickname: info.nickname }), config.loool.public ? undefined : info.login, false) - }, - privilege: config.loool.privilege, - disableForMuted: true + privilege: config.lol.privilege, + disableForMuted: true +}, { + aliases: config.lool.aliases, + help: config.lool.help, + callback: (info: tm.MessageInfo): void => { + tm.sendMessage(tm.utils.strVar(config.lool.text, { nickname: info.nickname }), + config.lool.public ? undefined : info.login, false) }, - { - aliases: config.time.aliases, - help: config.time.help, - callback: (info: tm.MessageInfo): void => { - const date = new Date() - tm.sendMessage(tm.utils.strVar(config.time.text, - { - time: date.toLocaleString('EU') - }), info.login) - }, - privilege: config.time.privilege + privilege: config.lool.privilege, + disableForMuted: true +}, { + aliases: config.loool.aliases, + help: config.loool.help, + callback: (info: tm.MessageInfo): void => { + tm.sendMessage(tm.utils.strVar(config.loool.text, { nickname: info.nickname }), + config.loool.public ? undefined : info.login, false) }, - { - aliases: config.bootme.aliases, - help: config.bootme.help, - callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.bootme.text, { nickname: tm.utils.strip(info.nickname, true) }), config.bootme.public ? undefined : info.login) - tm.client.callNoRes('Kick', [{ string: info.login }, { string: config.bootme.leaveText }]) - }, - privilege: config.bootme.privilege + privilege: config.loool.privilege, + disableForMuted: true +}, { + aliases: config.time.aliases, + help: config.time.help, + callback: (info: tm.MessageInfo): void => { + const date = new Date() + tm.sendMessage(tm.utils.strVar(config.time.text, { + time: date.toLocaleString('EU') + }), info.login) }, - { - aliases: config.rq.aliases, - help: config.rq.help, - callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.rq.text, { nickname: tm.utils.strip(info.nickname, true) }), config.rq.public ? undefined : info.login) - tm.client.callNoRes('Kick', [{ string: info.login }, { string: config.rq.leaveText }]) - }, - privilege: config.rq.privilege + privilege: config.time.privilege +}, { + aliases: config.bootme.aliases, + help: config.bootme.help, + callback: (info: tm.MessageInfo): void => { + tm.sendMessage(tm.utils.strVar(config.bootme.text, { nickname: tm.utils.strip(info.nickname, true) }), + config.bootme.public ? undefined : info.login) + tm.client.callNoRes('Kick', [{ string: info.login }, { string: config.bootme.leaveText }]) }, - { - aliases: config.pm.aliases, - help: config.pm.help, - params: [{ name: 'login' }, { name: 'text', type: 'multiword', optional: true }], - callback: (info: tm.MessageInfo, login: string, text: string = ''): void => { - const playerInfo: tm.Player | undefined = tm.players.get(login) - if (playerInfo === undefined) { - tm.sendMessage(config.pm.error, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.pm.text, - { - sender: tm.utils.strip(info.nickname, false), - recipient: tm.utils.strip(playerInfo.nickname, false), - message: text - }), [info.login, playerInfo.login].join()) - }, - privilege: config.pm.privilege + privilege: config.bootme.privilege +}, { + aliases: config.rq.aliases, + help: config.rq.help, + callback: (info: tm.MessageInfo): void => { + tm.sendMessage(tm.utils.strVar(config.bootme.text, { nickname: tm.utils.strip(info.nickname, true) }), + config.rq.public ? undefined : info.login) + tm.client.callNoRes('Kick', [{ string: info.login }, { string: config.rq.leaveText }]) }, - { - aliases: config.playtime.aliases, - help: config.playtime.help, - callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.playtime.text, - { time: tm.utils.getVerboseTime(Date.now() - tm.timer.mapStartTimestamp) }), - info.login) - }, - privilege: config.playtime.privilege + privilege: config.rq.privilege +}, { + aliases: config.pm.aliases, + help: config.pm.help, + params: [{ name: 'login' }, { + name: 'text', + type: 'multiword', + optional: true + }], + callback: (info: tm.MessageInfo, login: string, text: string = ''): void => { + const playerInfo: tm.Player | undefined = tm.players.get(login) + if (playerInfo === undefined) { + tm.sendMessage(config.pm.error, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.pm.text, { + sender: tm.utils.strip(info.nickname, false), + recipient: tm.utils.strip(playerInfo.nickname, false), + message: text + }), [info.login, playerInfo.login].join()) }, - { - aliases: config.laston.aliases, - help: config.laston.help, - params: [{ name: 'login' }], - callback: async (info: tm.MessageInfo, login: string): Promise => { - login = login.toLowerCase() - const player = await tm.players.fetch(login) - if (player?.lastOnline === undefined) { - tm.sendMessage(tm.utils.strVar(config.laston.error, { name: login }), info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.laston.text, { - name: tm.utils.strip(player.nickname), - time: new Date(player.lastOnline).toLocaleString('EU') + privilege: config.pm.privilege +}, { + aliases: config.playtime.aliases, + help: config.playtime.help, + callback: (info: tm.MessageInfo): void => { + tm.sendMessage( + tm.utils.strVar(config.playtime.text, { time: tm.utils.getVerboseTime(Date.now() - tm.timer.mapStartTimestamp) }), + info.login) + }, + privilege: config.playtime.privilege +}, { + aliases: config.laston.aliases, + help: config.laston.help, + params: [{ name: 'login' }], + callback: async (info: tm.MessageInfo, login: string): Promise => { + login = login.toLowerCase() + const player = await tm.players.fetch(login) + if (player?.lastOnline === undefined) { + tm.sendMessage(tm.utils.strVar(config.laston.error, { name: login }), info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.laston.text, { + name: tm.utils.strip(player.nickname), + time: new Date(player.lastOnline).toLocaleString('EU') + }), info.login) + }, + privilege: config.laston.privilege +}, { + aliases: config.sessiontime.aliases, + help: config.sessiontime.help, + params: [{ + name: 'login', + optional: true + }], + callback: async (info: tm.MessageInfo, login?: string): Promise => { + if (login === undefined || login === info.login) { + tm.sendMessage(tm.utils.strVar(config.sessiontime.selfText, { + time: tm.utils.getVerboseTime(Date.now() - info.joinTimestamp) + }), info.login) + return + } + const player = tm.players.get(login) + if (player === undefined) { + tm.sendMessage(tm.utils.strVar(config.sessiontime.error, { + name: login }), info.login) - }, - privilege: config.laston.privilege + return + } + tm.sendMessage(tm.utils.strVar(config.sessiontime.text, { + name: tm.utils.strip(player.nickname), + time: tm.utils.getVerboseTime(Date.now() - player.joinTimestamp) + }), info.login) }, - { - aliases: config.sessiontime.aliases, - help: config.sessiontime.help, - params: [{ name: 'login', optional: true }], - callback: async (info: tm.MessageInfo, login?: string): Promise => { - if (login === undefined || login === info.login) { - tm.sendMessage(tm.utils.strVar(config.sessiontime.selfText, { - time: tm.utils.getVerboseTime(Date.now() - info.joinTimestamp) - }), info.login) - return + privilege: config.sessiontime.privilege +}, { + aliases: config.man.aliases, + help: config.man.help, + params: [{ name: 'commandName' }], + callback: (info: tm.MessageInfo, commandName: string): void => { + const command = tm.commands.list.filter(a => a.aliases.some(a => a === commandName)) + let str = '' + for (const e of command) { + if (e.help === undefined || e.privilege > info.privilege) { + continue } - const player = tm.players.get(login) - if (player === undefined) { - tm.sendMessage(tm.utils.strVar(config.sessiontime.error, { - name: login - }), info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.sessiontime.text, { - name: tm.utils.strip(player.nickname), - time: tm.utils.getVerboseTime(Date.now() - player.joinTimestamp) + str += '\n' + const par = tm.utils.stringifyCommandParams(e.params) + str += tm.utils.strVar(config.man.text, { + name: commandName, + params: par.length === 0 ? '' : `(${par}) `, + help: e.help + }) + } + str = str.slice(1) + if (str.length === 0) { + tm.sendMessage(tm.utils.strVar(config.man.error, { + name: commandName }), info.login) - }, - privilege: config.sessiontime.privilege + return + } + tm.sendMessage(str, info.login) }, - { - aliases: config.man.aliases, - help: config.man.help, - params: [{ name: 'commandName' }], - callback: (info: tm.MessageInfo, commandName: string): void => { - const command = tm.commands.list.filter(a => a.aliases.some(a => a === commandName)) - let str = '' - for (const e of command) { - if (e.help === undefined || e.privilege > info.privilege) { continue } - str += '\n' - const par = tm.utils.stringifyCommandParams(e.params) - str += tm.utils.strVar(config.man.text, { - name: commandName, - params: par.length === 0 ? '' : `(${par}) `, - help: e.help - }) - } - str = str.slice(1) - if (str.length === 0) { - tm.sendMessage(tm.utils.strVar(config.man.error, { - name: commandName - }), info.login) - return - } - tm.sendMessage(str, info.login) - }, - privilege: config.man.privilege + privilege: config.man.privilege +}, { + aliases: ['admin', 'a'], + callback: (info: tm.MessageInfo): void => { + if (info.privilege > 0) { + tm.sendMessage(config.admin.text, info.login) + } }, - { - aliases: ['admin', 'a'], - callback: (info: tm.MessageInfo): void => { - if (info.privilege > 0) { - tm.sendMessage(config.admin.text, info.login) - } - }, - privilege: config.admin.privilege + privilege: config.admin.privilege +}, { + aliases: config.coppers.aliases, + help: config.coppers.help, + callback: async (info: tm.MessageInfo): Promise => { + if (!tm.config.server.isUnited) { + tm.sendMessage(config.coppers.notUnited, info.login) + return + } + const coppers: any | Error = await tm.client.call('GetServerCoppers') + if (coppers instanceof Error) { + tm.log.error(`Couldn't retrieve the coppers amount.`, coppers.message) + tm.sendMessage(config.coppers.error, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.coppers.text, { value: coppers }), + config.coppers.public ? undefined : info.login) }, - { - aliases: config.coppers.aliases, - help: config.coppers.help, - callback: async (info: tm.MessageInfo): Promise => { - if (!tm.config.server.isUnited) { - tm.sendMessage(config.coppers.notUnited, info.login) - return - } - const coppers: any | Error = await tm.client.call('GetServerCoppers') - if (coppers instanceof Error) { - tm.log.error(`Couldn't retrieve the coppers amount.`, coppers.message) - tm.sendMessage(config.coppers.error, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.coppers.text, { value: coppers }), config.coppers.public ? undefined : info.login) - }, - privilege: config.coppers.privilege - } -] + privilege: config.coppers.privilege +}] tm.commands.add(...commands) \ No newline at end of file diff --git a/plugins/commands/src/GameCommands.ts b/plugins/commands/src/GameCommands.ts index 70b5be3fe..b46957ee7 100644 --- a/plugins/commands/src/GameCommands.ts +++ b/plugins/commands/src/GameCommands.ts @@ -1,266 +1,321 @@ import config from '../config/GameCommands.config.js' -const commands: tm.Command[] = [ - { - aliases: config.setgamemode.aliases, - help: config.setgamemode.help, - params: [{ - name: 'mode', validValues: ['round', 'ta', 'team', 'lap', 'stunt', 'cup', - 'rounds', 'timeattack', 'teams', 'laps', 'stunts'] - }], - callback: (info: tm.MessageInfo, mode: string): void => { - let modeInt = 1 - switch (mode.toLowerCase()) { - case 'rounds': case 'round': modeInt = 0 - break - case 'timeattack': case 'ta': modeInt = 1 - break - case 'teams': case 'team': modeInt = 2 - break - case 'laps': case 'lap': modeInt = 3 - break - case 'stunts': case 'stunt': modeInt = 4 - break - case 'cup': modeInt = 5 - } - tm.sendMessage(tm.utils.strVar(config.setgamemode.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), mode: mode.toUpperCase() - }), - config.setgamemode.public ? undefined : info.login) - tm.client.callNoRes(`SetGameMode`, [{ int: modeInt }]) - }, - privilege: config.setgamemode.privilege +const commands: tm.Command[] = [{ + aliases: config.setgamemode.aliases, + help: config.setgamemode.help, + params: [{ + name: 'mode', + validValues: ['round', 'ta', 'team', 'lap', 'stunt', 'cup', 'rounds', 'timeattack', 'teams', 'laps', 'stunts'] + }], + callback: (info: tm.MessageInfo, mode: string): void => { + let modeInt = 1 + switch(mode.toLowerCase()) { + case 'rounds': + case 'round': + modeInt = 0 + break + case 'timeattack': + case 'ta': + modeInt = 1 + break + case 'teams': + case 'team': + modeInt = 2 + break + case 'laps': + case 'lap': + modeInt = 3 + break + case 'stunts': + case 'stunt': + modeInt = 4 + break + case 'cup': + modeInt = 5 + } + tm.sendMessage(tm.utils.strVar(config.setgamemode.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + mode: mode.toUpperCase() + }), config.setgamemode.public ? undefined : info.login) + tm.client.callNoRes(`SetGameMode`, [{ int: modeInt }]) }, - { - aliases: config.settimelimit.aliases, - help: config.settimelimit.help, - params: [{ name: 'time', type: 'time' }], - callback: (info: tm.MessageInfo, time: number): void => { - tm.sendMessage(tm.utils.strVar(config.settimelimit.text, - { - title: info.title, adminName: tm.utils.strip(info.nickname), - time: tm.utils.getVerboseTime(time) - }), - config.settimelimit.public ? undefined : info.login) - tm.timer.setTimeLimit(time) - //tm.client.callNoRes(`SetTimeAttackLimit`, [{ int: time }]) - }, - privilege: config.settimelimit.privilege + privilege: config.setgamemode.privilege +}, { + aliases: config.settimelimit.aliases, + help: config.settimelimit.help, + params: [{ + name: 'time', + type: 'time' + }], + callback: (info: tm.MessageInfo, time: number): void => { + tm.sendMessage(tm.utils.strVar(config.settimelimit.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + time: tm.utils.getVerboseTime(time) + }), config.settimelimit.public ? undefined : info.login) + tm.timer.setTimeLimit(time) + //tm.client.callNoRes(`SetTimeAttackLimit`, [{ int: time }]) }, - { - aliases: config.setchattime.aliases, - help: config.setchattime.help, - params: [{ name: 'time', type: 'time' }], - callback: (info: tm.MessageInfo, time: number): void => { - tm.sendMessage(tm.utils.strVar(config.setchattime.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), time: tm.utils.getVerboseTime(time) - }), config.setchattime.public ? undefined : info.login) - tm.client.callNoRes(`SetChatTime`, [{ int: time }]) - }, - privilege: config.setchattime.privilege + privilege: config.settimelimit.privilege +}, { + aliases: config.setchattime.aliases, + help: config.setchattime.help, + params: [{ + name: 'time', + type: 'time' + }], + callback: (info: tm.MessageInfo, time: number): void => { + tm.sendMessage(tm.utils.strVar(config.setchattime.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + time: tm.utils.getVerboseTime(time) + }), config.setchattime.public ? undefined : info.login) + tm.client.callNoRes(`SetChatTime`, [{ int: time }]) }, - { - aliases: config.setwarmup.aliases, - help: config.setwarmup.help, - params: [{ name: 'enabled', type: 'boolean' }], - callback: (info: tm.MessageInfo, enabled: boolean): void => { - tm.sendMessage(tm.utils.strVar(config.setwarmup.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), state: enabled ? 'enabled' : 'disabled' - }), - config.setwarmup.public ? undefined : info.login) - tm.client.callNoRes(`SetWarmUp`, [{ boolean: enabled }]) - }, - privilege: config.setwarmup.privilege + privilege: config.setchattime.privilege +}, { + aliases: config.setwarmup.aliases, + help: config.setwarmup.help, + params: [{ + name: 'enabled', + type: 'boolean' + }], + callback: (info: tm.MessageInfo, enabled: boolean): void => { + tm.sendMessage(tm.utils.strVar(config.setwarmup.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + state: enabled ? 'enabled' : 'disabled' + }), config.setwarmup.public ? undefined : info.login) + tm.client.callNoRes(`SetWarmUp`, [{ boolean: enabled }]) }, - { - aliases: config.setlapsamount.aliases, - help: config.setlapsamount.help, - params: [{ name: 'amount', type: 'int' }], - callback: (info: tm.MessageInfo, amount: number): void => { - if (amount <= 0) { - tm.sendMessage(config.setlapsamount.insufficientLaps, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.setlapsamount.text, { + privilege: config.setwarmup.privilege +}, { + aliases: config.setlapsamount.aliases, + help: config.setlapsamount.help, + params: [{ + name: 'amount', + type: 'int' + }], + callback: (info: tm.MessageInfo, amount: number): void => { + if (amount <= 0) { + tm.sendMessage(config.setlapsamount.insufficientLaps, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.setlapsamount.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + amount: amount + }), config.setlapsamount.public ? undefined : info.login) + tm.client.callNoRes(`SetNbLaps`, [{ int: amount }]) + }, + privilege: config.setlapsamount.privilege +}, { + aliases: config.setroundslapsamount.aliases, + help: config.setroundslapsamount.help, + params: [{ + name: 'amount', + type: 'int' + }], + callback: (info: tm.MessageInfo, amount: number): void => { + if (amount < 0) { + tm.sendMessage(config.setroundslapsamount.insufficientLaps, info.login) + return + } + if (amount === 0) { + tm.sendMessage(tm.utils.strVar(config.setroundslapsamount.resetText, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + amount: amount + }), config.setroundslapsamount.public ? undefined : info.login) + } else { + tm.sendMessage(tm.utils.strVar(config.setroundslapsamount.text, { title: info.title, - adminName: tm.utils.strip(info.nickname), amount: amount - }), - config.setlapsamount.public ? undefined : info.login) - tm.client.callNoRes(`SetNbLaps`, [{ int: amount }]) - }, - privilege: config.setlapsamount.privilege + adminName: tm.utils.strip(info.nickname), + amount: amount + }), config.setroundslapsamount.public ? undefined : info.login) + } + tm.client.callNoRes(`SetRoundForcedLaps`, [{ int: amount }]) }, - { - aliases: config.setroundslapsamount.aliases, - help: config.setroundslapsamount.help, - params: [{ name: 'amount', type: 'int' }], - callback: (info: tm.MessageInfo, amount: number): void => { - if (amount < 0) { - tm.sendMessage(config.setroundslapsamount.insufficientLaps, info.login) - return - } - if (amount === 0) { - tm.sendMessage(tm.utils.strVar(config.setroundslapsamount.resetText, { - title: info.title, - adminName: tm.utils.strip(info.nickname), amount: amount - }), config.setroundslapsamount.public ? undefined : info.login) - } else { - tm.sendMessage(tm.utils.strVar(config.setroundslapsamount.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), amount: amount - }), config.setroundslapsamount.public ? undefined : info.login) - } - tm.client.callNoRes(`SetRoundForcedLaps`, [{ int: amount }]) - }, - privilege: config.setroundslapsamount.privilege + privilege: config.setroundslapsamount.privilege +}, { + aliases: config.setroundspointlimit.aliases, + help: config.setroundspointlimit.help, + params: [{ + name: 'amount', + type: 'int' + }], + callback: (info: tm.MessageInfo, amount: number): void => { + if (amount <= 0) { + tm.sendMessage(config.setroundspointlimit.insufficientPoints, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.setroundspointlimit.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + amount: amount + }), config.setroundspointlimit.public ? undefined : info.login) + tm.client.callNoRes(`SetRoundPointsLimit`, [{ int: amount }]) }, - { - aliases: config.setroundspointlimit.aliases, - help: config.setroundspointlimit.help, - params: [{ name: 'amount', type: 'int' }], - callback: (info: tm.MessageInfo, amount: number): void => { - if (amount <= 0) { - tm.sendMessage(config.setroundspointlimit.insufficientPoints, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.setroundspointlimit.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), amount: amount - }), - config.setroundspointlimit.public ? undefined : info.login) - tm.client.callNoRes(`SetRoundPointsLimit`, [{ int: amount }]) - }, - privilege: config.setroundspointlimit.privilege + privilege: config.setroundspointlimit.privilege +}, { + aliases: config.setteamspointlimit.aliases, + help: config.setteamspointlimit.help, + params: [{ + name: 'amount', + type: 'int' + }], + callback: (info: tm.MessageInfo, amount: number): void => { + if (amount < 0) { + tm.sendMessage(config.setteamspointlimit.insufficientPoints, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.setteamspointlimit.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + amount: amount + }), config.setteamspointlimit.public ? undefined : info.login) + tm.client.callNoRes(`SetTeamPointsLimit`, [{ int: amount }]) }, - { - aliases: config.setteamspointlimit.aliases, - help: config.setteamspointlimit.help, - params: [{ name: 'amount', type: 'int' }], - callback: (info: tm.MessageInfo, amount: number): void => { - if (amount < 0) { - tm.sendMessage(config.setteamspointlimit.insufficientPoints, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.setteamspointlimit.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), amount: amount - }), - config.setteamspointlimit.public ? undefined : info.login) - tm.client.callNoRes(`SetTeamPointsLimit`, [{ int: amount }]) - }, - privilege: config.setteamspointlimit.privilege + privilege: config.setteamspointlimit.privilege +}, { + aliases: config.setteamsmaxpoints.aliases, + help: config.setteamsmaxpoints.help, + params: [{ + name: 'amount', + type: 'int' + }], + callback: (info: tm.MessageInfo, amount: number): void => { + if (amount < 0) { + tm.sendMessage(config.setteamsmaxpoints.insufficientPoints, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.setteamsmaxpoints.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + amount: amount + }), config.setteamsmaxpoints.public ? undefined : info.login) + tm.client.callNoRes(`SetTeamMaxPoints`, [{ int: amount }]) }, - { - aliases: config.setteamsmaxpoints.aliases, - help: config.setteamsmaxpoints.help, - params: [{ name: 'amount', type: 'int' }], - callback: (info: tm.MessageInfo, amount: number): void => { - if (amount < 0) { - tm.sendMessage(config.setteamsmaxpoints.insufficientPoints, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.setteamsmaxpoints.text, - { title: info.title, adminName: tm.utils.strip(info.nickname), amount: amount }), - config.setteamsmaxpoints.public ? undefined : info.login) - tm.client.callNoRes(`SetTeamMaxPoints`, [{ int: amount }]) - }, - privilege: config.setteamsmaxpoints.privilege + privilege: config.setteamsmaxpoints.privilege +}, { + aliases: config.setcuppointlimit.aliases, + help: config.setcuppointlimit.help, + params: [{ + name: 'amount', + type: 'int' + }], + callback: (info: tm.MessageInfo, amount: number): void => { + if (amount <= 0) { + tm.sendMessage(config.setcuppointlimit.insufficientPoints, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.setcuppointlimit.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + amount: amount + }), config.setcuppointlimit.public ? undefined : info.login) + tm.client.callNoRes(`SetCupPointsLimit`, [{ int: amount }]) }, - { - aliases: config.setcuppointlimit.aliases, - help: config.setcuppointlimit.help, - params: [{ name: 'amount', type: 'int' }], - callback: (info: tm.MessageInfo, amount: number): void => { - if (amount <= 0) { - tm.sendMessage(config.setcuppointlimit.insufficientPoints, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.setcuppointlimit.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), amount: amount - }), - config.setcuppointlimit.public ? undefined : info.login) - tm.client.callNoRes(`SetCupPointsLimit`, [{ int: amount }]) - }, - privilege: config.setcuppointlimit.privilege + privilege: config.setcuppointlimit.privilege +}, { + aliases: config.setcuproundspermap.aliases, + help: config.setcuproundspermap.help, + params: [{ + name: 'amount', + type: 'int' + }], + callback: (info: tm.MessageInfo, amount: number): void => { + if (amount < 0) { + tm.sendMessage(config.setcuproundspermap.insufficientRounds, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.setcuproundspermap.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + amount: amount + }), config.setcuproundspermap.public ? undefined : info.login) + tm.client.callNoRes(`SetCupRoundsPerChallenge`, [{ int: amount }]) }, - { - aliases: config.setcuproundspermap.aliases, - help: config.setcuproundspermap.help, - params: [{ name: 'amount', type: 'int' }], - callback: (info: tm.MessageInfo, amount: number): void => { - if (amount < 0) { - tm.sendMessage(config.setcuproundspermap.insufficientRounds, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.setcuproundspermap.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), amount: amount - }), config.setcuproundspermap.public ? undefined : info.login) - tm.client.callNoRes(`SetCupRoundsPerChallenge`, [{ int: amount }]) - }, - privilege: config.setcuproundspermap.privilege + privilege: config.setcuproundspermap.privilege +}, { + aliases: config.setcupwarmuprounds.aliases, + help: config.setcupwarmuprounds.help, + params: [{ + name: 'amount', + type: 'int' + }], + callback: (info: tm.MessageInfo, amount: number): void => { + if (amount < 0) { + tm.sendMessage(config.setcupwarmuprounds.insufficientRounds, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.setcupwarmuprounds.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + amount: amount + }), config.setcupwarmuprounds.public ? undefined : info.login) + tm.client.callNoRes(`SetCupWarmUpDuration`, [{ int: amount }]) }, - { - aliases: config.setcupwarmuprounds.aliases, - help: config.setcupwarmuprounds.help, - params: [{ name: 'amount', type: 'int' }], - callback: (info: tm.MessageInfo, amount: number): void => { - if (amount < 0) { - tm.sendMessage(config.setcupwarmuprounds.insufficientRounds, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.setcupwarmuprounds.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), amount: amount - }), config.setcupwarmuprounds.public ? undefined : info.login) - tm.client.callNoRes(`SetCupWarmUpDuration`, [{ int: amount }]) - }, - privilege: config.setcupwarmuprounds.privilege + privilege: config.setcupwarmuprounds.privilege +}, { + aliases: config.setcupwinnersamount.aliases, + help: config.setcupwinnersamount.help, + params: [{ + name: 'amount', + type: 'int' + }], + callback: (info: tm.MessageInfo, amount: number): void => { + if (amount <= 0) { + tm.sendMessage(config.setcupwinnersamount.insufficientWinners, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.setcupwinnersamount.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + amount: amount + }), config.setcupwinnersamount.public ? undefined : info.login) + tm.client.callNoRes(`SetCupNbWinners`, [{ int: amount }]) }, - { - aliases: config.setcupwinnersamount.aliases, - help: config.setcupwinnersamount.help, - params: [{ name: 'amount', type: 'int' }], - callback: (info: tm.MessageInfo, amount: number): void => { - if (amount <= 0) { - tm.sendMessage(config.setcupwinnersamount.insufficientWinners, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.setcupwinnersamount.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), amount: amount - }), - config.setcupwinnersamount.public ? undefined : info.login) - tm.client.callNoRes(`SetCupNbWinners`, [{ int: amount }]) - }, - privilege: config.setcupwinnersamount.privilege + privilege: config.setcupwinnersamount.privilege +}, { + aliases: config.forceshowopp.aliases, + help: config.forceshowopp.help, + params: [{ + name: 'status', + type: 'boolean' + }, { + name: 'amount', + type: 'int', + optional: true + }], + callback: (info: tm.MessageInfo, status: boolean, amount?: number): void => { + let n: number + if (!status) { n = 0 } else if (amount !== undefined) { n = amount } else { n = 1 } + tm.sendMessage(tm.utils.strVar(config.forceshowopp.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + value: status ? 'enabled' : 'disabled' + }), config.forceshowopp.public ? undefined : info.login) + tm.client.callNoRes(`SetForceShowAllOpponents`, [{ int: n }]) }, - { - aliases: config.forceshowopp.aliases, - help: config.forceshowopp.help, - params: [{ name: 'status', type: 'boolean' }, { name: 'amount', type: 'int', optional: true }], - callback: (info: tm.MessageInfo, status: boolean, amount?: number): void => { - let n: number - if (!status) { n = 0 } else if (amount !== undefined) { n = amount } else { n = 1 } - tm.sendMessage(tm.utils.strVar(config.forceshowopp.text, { title: info.title, adminName: tm.utils.strip(info.nickname), value: status ? 'enabled' : 'disabled' }), config.forceshowopp.public ? undefined : info.login) - tm.client.callNoRes(`SetForceShowAllOpponents`, [{ int: n }]) - }, - privilege: config.forceshowopp.privilege + privilege: config.forceshowopp.privilege +}, { + aliases: config.disablerespawn.aliases, + help: config.disablerespawn.help, + params: [{ + name: 'status', + type: 'boolean' + }], + callback: (info: tm.MessageInfo, status: boolean): void => { + tm.sendMessage(tm.utils.strVar(config.disablerespawn.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + value: status ? 'disabled' : 'enabled' + }), config.disablerespawn.public ? undefined : info.login) + tm.client.callNoRes(`SetDisableRespawn`, [{ boolean: status }]) }, - { - aliases: config.disablerespawn.aliases, - help: config.disablerespawn.help, - params: [{ name: 'status', type: 'boolean' }], - callback: (info: tm.MessageInfo, status: boolean): void => { - tm.sendMessage(tm.utils.strVar(config.disablerespawn.text, { title: info.title, adminName: tm.utils.strip(info.nickname), value: status ? 'disabled' : 'enabled' }), config.disablerespawn.public ? undefined : info.login) - tm.client.callNoRes(`SetDisableRespawn`, [{ boolean: status }]) - }, - privilege: config.disablerespawn.privilege - } -] + privilege: config.disablerespawn.privilege +}] tm.commands.add(...commands) \ No newline at end of file diff --git a/plugins/commands/src/JukeboxCommands.ts b/plugins/commands/src/JukeboxCommands.ts index 0ab7f2dbf..35899399a 100644 --- a/plugins/commands/src/JukeboxCommands.ts +++ b/plugins/commands/src/JukeboxCommands.ts @@ -1,56 +1,66 @@ import config from '../config/JukeboxCommands.config.js' -const commands: tm.Command[] = [ - { - aliases: config.dropjukebox.aliases, - help: config.dropjukebox.help, - params: [{ name: 'index', type: 'int' }], - callback: (info: tm.MessageInfo, index: number): void => { - const map: tm.Map | undefined = tm.jukebox.juked[index - 1]?.map - if (map === undefined) { - tm.sendMessage(config.dropjukebox.error, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.dropjukebox.text, { title: info.title, adminName: tm.utils.strip(info.nickname), name: tm.utils.strip(map.name) }), config.dropjukebox.public ? undefined : info.login) - tm.jukebox.remove(map.id, info) - }, - privilege: config.dropjukebox.privilege +const commands: tm.Command[] = [{ + aliases: config.dropjukebox.aliases, + help: config.dropjukebox.help, + params: [{ + name: 'index', + type: 'int' + }], + callback: (info: tm.MessageInfo, index: number): void => { + const map: tm.Map | undefined = tm.jukebox.juked[index - 1]?.map + if (map === undefined) { + tm.sendMessage(config.dropjukebox.error, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.dropjukebox.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + name: tm.utils.strip(map.name) + }), config.dropjukebox.public ? undefined : info.login) + tm.jukebox.remove(map.id, info) }, - { - aliases: config.clearjukebox.aliases, - help: config.clearjukebox.help, - callback: (info: tm.MessageInfo): void => { - if (tm.jukebox.juked.length === 0) { - tm.sendMessage(config.clearjukebox.error, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.clearjukebox.text, { title: info.title, adminName: tm.utils.strip(info.nickname) }), config.clearjukebox.public ? undefined : info.login) - for (const map of tm.jukebox.juked) { - tm.jukebox.remove(map.map.id, info) - } - }, - privilege: config.clearjukebox.privilege + privilege: config.dropjukebox.privilege +}, { + aliases: config.clearjukebox.aliases, + help: config.clearjukebox.help, + callback: (info: tm.MessageInfo): void => { + if (tm.jukebox.juked.length === 0) { + tm.sendMessage(config.clearjukebox.error, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.clearjukebox.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + }), config.clearjukebox.public ? undefined : info.login) + for (const map of tm.jukebox.juked) { + tm.jukebox.remove(map.map.id, info) + } }, - { - aliases: config.shuffle.aliases, - help: config.shuffle.help, - callback: async (info: tm.MessageInfo): Promise => { - tm.sendMessage(tm.utils.strVar(config.shuffle.text, { title: info.title, adminName: tm.utils.strip(info.nickname) }), config.shuffle.public ? undefined : info.login) - tm.jukebox.shuffle(info) - }, - privilege: config.shuffle.privilege + privilege: config.clearjukebox.privilege +}, { + aliases: config.shuffle.aliases, + help: config.shuffle.help, + callback: async (info: tm.MessageInfo): Promise => { + tm.sendMessage(tm.utils.strVar(config.shuffle.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + }), config.shuffle.public ? undefined : info.login) + tm.jukebox.shuffle(info) }, - { - aliases: config.clearhistory.aliases, - help: config.clearhistory.help, - callback: async (info: tm.MessageInfo): Promise => { - tm.sendMessage(tm.utils.strVar(config.clearhistory.text, { title: info.title, adminName: tm.utils.strip(info.nickname) }), config.clearhistory.public ? undefined : info.login) - tm.jukebox.clearHistory(info) - }, - privilege: config.clearhistory.privilege - + privilege: config.shuffle.privilege +}, { + aliases: config.clearhistory.aliases, + help: config.clearhistory.help, + callback: async (info: tm.MessageInfo): Promise => { + tm.sendMessage(tm.utils.strVar(config.clearhistory.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + }), config.clearhistory.public ? undefined : info.login) + tm.jukebox.clearHistory(info) + }, + privilege: config.clearhistory.privilege - } -] +}] tm.commands.add(...commands) \ No newline at end of file diff --git a/plugins/commands/src/KarmaCommands.ts b/plugins/commands/src/KarmaCommands.ts index 762400f93..c436dbac7 100644 --- a/plugins/commands/src/KarmaCommands.ts +++ b/plugins/commands/src/KarmaCommands.ts @@ -19,13 +19,11 @@ tm.addListener('PlayerChat', (info): void => { processVote(info.text as keyof typeof options, info) }) -tm.commands.add( - { - aliases: config.aliases, - help: config.help, - callback: async (info): Promise => { - processVote(info.aliasUsed as keyof typeof options, info) - }, - privilege: 0, - } -) +tm.commands.add({ + aliases: config.aliases, + help: config.help, + callback: async (info): Promise => { + processVote(info.aliasUsed as keyof typeof options, info) + }, + privilege: 0 +}) diff --git a/plugins/commands/src/MapCommands.ts b/plugins/commands/src/MapCommands.ts index 263c5e74c..16a8a04e1 100644 --- a/plugins/commands/src/MapCommands.ts +++ b/plugins/commands/src/MapCommands.ts @@ -2,175 +2,194 @@ import config from '../config/MapCommands.config.js' import fetch from 'node-fetch' import { actions } from '../../actions/Actions.js' -const commands: tm.Command[] = [ - { - aliases: config.add.aliases, - help: config.add.help, - params: [{ name: 'id', type: 'int' }, { name: 'tmxSite', optional: true }], - callback: async (info: tm.MessageInfo, id: number, tmxSite?: string): Promise => { - await actions.addMap(info.login, info.nickname, info.title, id, tmxSite) - }, - privilege: config.add.privilege +const commands: tm.Command[] = [{ + aliases: config.add.aliases, + help: config.add.help, + params: [{ + name: 'id', + type: 'int' + }, { + name: 'tmxSite', + optional: true + }], + callback: async (info: tm.MessageInfo, id: number, tmxSite?: string): Promise => { + await actions.addMap(info.login, info.nickname, info.title, id, tmxSite) }, - { - aliases: config.addlocal.aliases, - help: config.addlocal.help, - params: [{ name: 'filename' }], - callback: async (info: tm.MessageInfo, filename: string): Promise => { - const map: tm.Map | Error = await tm.maps.add(filename, info) - if (map instanceof Error) { - tm.sendMessage(config.addlocal.addError, info.login) + privilege: config.add.privilege +}, { + aliases: config.addlocal.aliases, + help: config.addlocal.help, + params: [{ name: 'filename' }], + callback: async (info: tm.MessageInfo, filename: string): Promise => { + const map: tm.Map | Error = await tm.maps.add(filename, info) + if (map instanceof Error) { + tm.sendMessage(config.addlocal.addError, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.addlocal.added, { + title: info.title, + map: tm.utils.strip(map.name, true), + nickname: tm.utils.strip(info.nickname, true) + }), config.addlocal.public ? undefined : info.login) + }, + privilege: config.addlocal.privilege +}, { + aliases: config.addrandom.aliases, + help: config.addrandom.help, + params: [{ + name: 'tmxSite', + optional: true + }], + callback: async (info: tm.MessageInfo, tmxSite?: string): Promise => { + let obj: { + map?: tm.Map; + wasAlreadyAdded: boolean; + } | Error + let iteration = 0 + do { + const tmxSites: tm.TMXSite[] = ['TMNF', 'TMN', 'TMO', 'TMS', 'TMU'] + const site: tm.TMXSite | undefined = tmxSites.find(a => a === tmxSite?.toUpperCase()) + const file: { + name: string, + content: Buffer + } | Error = await tm.tmx.fetchRandomMapFile(site).catch((err: Error) => err) + if (file instanceof Error) { + tm.sendMessage(config.addrandom.fetchError, info.login) return } - tm.sendMessage(tm.utils.strVar(config.addlocal.added, { + obj = await tm.maps.writeFileAndAdd(file.name, file.content, info, { cancelIfAlreadyAdded: true }) + iteration++ + } while (!(obj instanceof Error) && obj.wasAlreadyAdded && iteration < 10) + if (obj instanceof Error) { + tm.log.warn(obj.message) + tm.sendMessage(config.addrandom.addError, info.login) + return + } else if (obj.map === undefined) { + tm.sendMessage(config.addrandom.addError, info.login) + return + } else { + tm.sendMessage(tm.utils.strVar(config.addrandom.added, { title: info.title, - map: tm.utils.strip(map.name, true), + map: tm.utils.strip(obj.map.name, true), nickname: tm.utils.strip(info.nickname, true) - }), config.addlocal.public ? undefined : info.login) - }, - privilege: config.addlocal.privilege + }), config.addrandom.public ? undefined : info.login) + } }, - { - aliases: config.addrandom.aliases, - help: config.addrandom.help, - params: [{ name: 'tmxSite', optional: true }], - callback: async (info: tm.MessageInfo, tmxSite?: string): Promise => { - let obj: { map?: tm.Map; wasAlreadyAdded: boolean; } | Error - let iteration = 0 - do { - const tmxSites: tm.TMXSite[] = ['TMNF', 'TMN', 'TMO', 'TMS', 'TMU'] - const site: tm.TMXSite | undefined = tmxSites.find(a => a === tmxSite?.toUpperCase()) - const file: { name: string, content: Buffer } | Error = - await tm.tmx.fetchRandomMapFile(site).catch((err: Error) => err) - if (file instanceof Error) { - tm.sendMessage(config.addrandom.fetchError, info.login) - return - } - obj = await tm.maps.writeFileAndAdd(file.name, file.content, info, { cancelIfAlreadyAdded: true }) - iteration++ - } while (!(obj instanceof Error) && obj.wasAlreadyAdded && iteration < 10) - if (obj instanceof Error) { - tm.log.warn(obj.message) - tm.sendMessage(config.addrandom.addError, info.login) - return - } else if (obj.map === undefined) { - tm.sendMessage(config.addrandom.addError, info.login) - return - } else { - tm.sendMessage(tm.utils.strVar(config.addrandom.added, { - title: info.title, - map: tm.utils.strip(obj.map.name, true), - nickname: tm.utils.strip(info.nickname, true) - }), config.addrandom.public ? undefined : info.login) - } - }, - privilege: config.addrandom.privilege + privilege: config.addrandom.privilege +}, { + aliases: config.remove.aliases, + help: config.remove.help, + callback: async (info): Promise => { + await actions.removeMap(info.login, info.nickname, info.title) }, - { - aliases: config.remove.aliases, - help: config.remove.help, - callback: async (info): Promise => { - await actions.removeMap(info.login, info.nickname, info.title) - }, - privilege: config.remove.privilege + privilege: config.remove.privilege +}, { + aliases: config.addfromurl.aliases, + help: config.addfromurl.help, + params: [{ name: 'url' }, { + name: 'filename', + optional: true + }], + callback: async (info: tm.MessageInfo, url: string, filename?: string): Promise => { + const file = await fetch(url).catch((err: Error) => err) + if (file instanceof Error || !file.ok) { + tm.sendMessage(config.addfromurl.fetchError, info.login) + return + } + const arrayBuffer = await file.arrayBuffer() + const buffer = Buffer.from(arrayBuffer) + const obj = await tm.maps.writeFileAndAdd(filename ?? url, buffer, info) + if (obj instanceof Error) { + tm.log.warn(obj.message) + tm.sendMessage(config.addfromurl.addError, info.login) + return + } else if (obj.wasAlreadyAdded) { + tm.sendMessage(tm.utils.strVar(config.addfromurl.alreadyAdded, { + map: tm.utils.strip(tm.utils.decodeURI(obj.map.name), true), + nickname: tm.utils.strip(info.nickname, true) + }), config.addfromurl.public ? undefined : info.login) + } else { + tm.sendMessage(tm.utils.strVar(config.addfromurl.added, { + title: info.title, + map: tm.utils.strip(tm.utils.decodeURI(obj.map.name), true), + nickname: tm.utils.strip(info.nickname, true) + }), config.addfromurl.public ? undefined : info.login) + } }, - { - aliases: config.addfromurl.aliases, - help: config.addfromurl.help, - params: [{ name: 'url' }, { name: 'filename', optional: true }], - callback: async (info: tm.MessageInfo, url: string, filename?: string): Promise => { - const file = await fetch(url).catch((err: Error) => err) - if (file instanceof Error || !file.ok) { - tm.sendMessage(config.addfromurl.fetchError, info.login) - return - } - const arrayBuffer = await file.arrayBuffer() - const buffer = Buffer.from(arrayBuffer) - const obj = await tm.maps.writeFileAndAdd(filename ?? url, buffer, info) - if (obj instanceof Error) { - tm.log.warn(obj.message) - tm.sendMessage(config.addfromurl.addError, info.login) - return - } else if (obj.wasAlreadyAdded) { - tm.sendMessage(tm.utils.strVar(config.addfromurl.alreadyAdded, { - map: tm.utils.strip(tm.utils.decodeURI(obj.map.name), true), - nickname: tm.utils.strip(info.nickname, true) - }), config.addfromurl.public ? undefined : info.login) - } else { - tm.sendMessage(tm.utils.strVar(config.addfromurl.added, { - title: info.title, - map: tm.utils.strip(tm.utils.decodeURI(obj.map.name), true), - nickname: tm.utils.strip(info.nickname, true) - }), config.addfromurl.public ? undefined : info.login) - } - }, - privilege: config.addfromurl.privilege + privilege: config.addfromurl.privilege +}, { + aliases: config.addallfromdb.aliases, + help: config.addallfromdb.help, + callback: async (info: tm.MessageInfo): Promise => { + const res: any[] | Error = await tm.db.query('SELECT * FROM maps;') + if (res instanceof Error) { + tm.sendMessage(config.addallfromdb.error, info.login) + return + } + for (const map of res) { + if (tm.maps.list.some(a => a.id === map.id)) { continue } + tm.maps.add(map.filename) + } }, - { - aliases: config.addallfromdb.aliases, - help: config.addallfromdb.help, - callback: async (info: tm.MessageInfo): Promise => { - const res: any[] | Error = await tm.db.query('SELECT * FROM maps;') - if (res instanceof Error) { - tm.sendMessage(config.addallfromdb.error, info.login) - return - } - for (const map of res) { - if (tm.maps.list.some(a => a.id === map.id)) { continue } - tm.maps.add(map.filename) + privilege: config.addallfromdb.privilege +}, { + aliases: config.publicadd.aliases, + help: config.publicadd.help, + params: [{ + name: 'id', + type: 'int' + }, { + name: 'tmxSite', + optional: true + }], + callback: async (info: tm.MessageInfo, id: number, tmxSite?: string): Promise => { + if (!tm.config.controller.allowPublicAdd) { + tm.sendMessage(config.publicadd.notAvailable, info.login) + return + } + const tmxSites: tm.TMXSite[] = ['TMNF', 'TMN', 'TMO', 'TMS', 'TMU'] + const site: tm.TMXSite | undefined = tmxSites.find(a => a === tmxSite?.toUpperCase()) + let file: { + name: string, + content: Buffer + } | Error = await tm.tmx.fetchMapFile(id, site).catch((err: Error) => err) + if (file instanceof Error) { + const remainingSites: tm.TMXSite[] = tmxSites.filter(a => a !== tmxSite) + for (const e of remainingSites) { + file = await tm.tmx.fetchMapFile(id, e).catch((err: Error) => err) + if (!(file instanceof Error)) { break } } - }, - privilege: config.addallfromdb.privilege - }, - { - aliases: config.publicadd.aliases, - help: config.publicadd.help, - params: [{ name: 'id', type: 'int' }, { name: 'tmxSite', optional: true }], - callback: async (info: tm.MessageInfo, id: number, tmxSite?: string): Promise => { - if (!tm.config.controller.allowPublicAdd) { - tm.sendMessage(config.publicadd.notAvailable, info.login) - return - } - const tmxSites: tm.TMXSite[] = ['TMNF', 'TMN', 'TMO', 'TMS', 'TMU'] - const site: tm.TMXSite | undefined = tmxSites.find(a => a === tmxSite?.toUpperCase()) - let file: { name: string, content: Buffer } | Error = await tm.tmx.fetchMapFile(id, site).catch((err: Error) => err) - if (file instanceof Error) { - const remainingSites: tm.TMXSite[] = tmxSites.filter(a => a !== tmxSite) - for (const e of remainingSites) { - file = await tm.tmx.fetchMapFile(id, e).catch((err: Error) => err) - if (!(file instanceof Error)) { break } - } - } - if (file instanceof Error) { - tm.sendMessage(config.publicadd.fetchError, info.login) + } + if (file instanceof Error) { + tm.sendMessage(config.publicadd.fetchError, info.login) + return + } + if (tm.config.controller.voteOnPublicAdd) { + const voteResult: boolean = await actions.publicAdd(info.login, info.nickname, info.title, + file.name.slice(0, -14)) + if (!voteResult) { return } - if (tm.config.controller.voteOnPublicAdd) { - const voteResult: boolean = await actions.publicAdd(info.login, info.nickname, info.title, file.name.slice(0, -14)) - if (!voteResult) { - return - } - } - const obj = await tm.maps.writeFileAndAdd(file.name, file.content, info) - if (obj instanceof Error) { - tm.log.warn(obj.message) - tm.sendMessage(config.publicadd.addError, info.login) - return - } else if (obj.wasAlreadyAdded) { - tm.sendMessage(tm.utils.strVar(config.publicadd.alreadyAdded, { - map: tm.utils.strip(tm.utils.decodeURI(obj.map.name), true), - nickname: tm.utils.strip(info.nickname, true) - }), config.publicadd.public ? undefined : info.login) - } else { - tm.sendMessage(tm.utils.strVar(config.publicadd.added, { - title: info.title, - map: tm.utils.strip(tm.utils.decodeURI(obj.map.name), true), - nickname: tm.utils.strip(info.nickname, true) - }), config.publicadd.public ? undefined : info.login) - } - }, - privilege: config.publicadd.privilege - } -] + } + const obj = await tm.maps.writeFileAndAdd(file.name, file.content, info) + if (obj instanceof Error) { + tm.log.warn(obj.message) + tm.sendMessage(config.publicadd.addError, info.login) + return + } else if (obj.wasAlreadyAdded) { + tm.sendMessage(tm.utils.strVar(config.publicadd.alreadyAdded, { + map: tm.utils.strip(tm.utils.decodeURI(obj.map.name), true), + nickname: tm.utils.strip(info.nickname, true) + }), config.publicadd.public ? undefined : info.login) + } else { + tm.sendMessage(tm.utils.strVar(config.publicadd.added, { + title: info.title, + map: tm.utils.strip(tm.utils.decodeURI(obj.map.name), true), + nickname: tm.utils.strip(info.nickname, true) + }), config.publicadd.public ? undefined : info.login) + } + }, + privilege: config.publicadd.privilege +}] tm.commands.add(...commands) \ No newline at end of file diff --git a/plugins/commands/src/PrivilegesCommands.ts b/plugins/commands/src/PrivilegesCommands.ts index a18a09605..b19f6ea5a 100644 --- a/plugins/commands/src/PrivilegesCommands.ts +++ b/plugins/commands/src/PrivilegesCommands.ts @@ -1,43 +1,50 @@ import config from '../config/PrivilegesCommands.config.js' import { actions } from '../../actions/Actions.js' -const commands: tm.Command[] = [ - { - aliases: config.masteradmin.aliases, - help: config.masteradmin.help, - params: [{ name: 'player', type: 'offlinePlayer' }], - callback: async (info: tm.MessageInfo, player: tm.OfflinePlayer): Promise => { - await actions.setPlayerPrivilege(player.login, info, 3) - }, - privilege: config.masteradmin.privilege +const commands: tm.Command[] = [{ + aliases: config.masteradmin.aliases, + help: config.masteradmin.help, + params: [{ + name: 'player', + type: 'offlinePlayer' + }], + callback: async (info: tm.MessageInfo, player: tm.OfflinePlayer): Promise => { + await actions.setPlayerPrivilege(player.login, info, 3) }, - { - aliases: config.admin.aliases, - help: config.admin.help, - params: [{ name: 'player', type: 'offlinePlayer' }], - callback: async (info: tm.MessageInfo, player: tm.OfflinePlayer): Promise => { - await actions.setPlayerPrivilege(player.login, info, 2) - }, - privilege: 3 + privilege: config.masteradmin.privilege +}, { + aliases: config.admin.aliases, + help: config.admin.help, + params: [{ + name: 'player', + type: 'offlinePlayer' + }], + callback: async (info: tm.MessageInfo, player: tm.OfflinePlayer): Promise => { + await actions.setPlayerPrivilege(player.login, info, 2) }, - { - aliases: config.operator.aliases, - help: config.operator.help, - params: [{ name: 'player', type: 'offlinePlayer' }], - callback: async (info: tm.MessageInfo, player: tm.OfflinePlayer): Promise => { - await actions.setPlayerPrivilege(player.login, info, 1) - }, - privilege: 2 + privilege: 3 +}, { + aliases: config.operator.aliases, + help: config.operator.help, + params: [{ + name: 'player', + type: 'offlinePlayer' + }], + callback: async (info: tm.MessageInfo, player: tm.OfflinePlayer): Promise => { + await actions.setPlayerPrivilege(player.login, info, 1) }, - { - aliases: config.user.aliases, - help: config.user.help, - params: [{ name: 'player', type: 'offlinePlayer' }], - callback: async (info: tm.MessageInfo, player: tm.OfflinePlayer): Promise => { - await actions.setPlayerPrivilege(player.login, info, 0) - }, - privilege: 2 + privilege: 2 +}, { + aliases: config.user.aliases, + help: config.user.help, + params: [{ + name: 'player', + type: 'offlinePlayer' + }], + callback: async (info: tm.MessageInfo, player: tm.OfflinePlayer): Promise => { + await actions.setPlayerPrivilege(player.login, info, 0) }, -] + privilege: 2 +}] tm.commands.add(...commands) diff --git a/plugins/commands/src/RaceCommands.ts b/plugins/commands/src/RaceCommands.ts index 095b91c51..9447d5678 100644 --- a/plugins/commands/src/RaceCommands.ts +++ b/plugins/commands/src/RaceCommands.ts @@ -1,104 +1,104 @@ import config from '../config/RaceCommands.config.js' import { actions } from '../../actions/Actions.js' -const commands: tm.Command[] = [ - { - aliases: config.skip.aliases, - help: config.skip.help, - callback: (info: tm.MessageInfo): void => { - if (tm.getState() === 'result') { return } - tm.sendMessage(tm.utils.strVar(config.skip.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname) - }), config.skip.public ? undefined : info.login) - actions.skipMap() - }, - privilege: config.skip.privilege +const commands: tm.Command[] = [{ + aliases: config.skip.aliases, + help: config.skip.help, + callback: (info: tm.MessageInfo): void => { + if (tm.getState() === 'result') { return } + tm.sendMessage(tm.utils.strVar(config.skip.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + }), config.skip.public ? undefined : info.login) + actions.skipMap() }, - { - aliases: config.res.aliases, - help: config.res.help, - callback: (info: tm.MessageInfo): void => { - if (tm.getState() === 'result') { return } - tm.sendMessage(tm.utils.strVar(config.res.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname) - }), config.res.public ? undefined : info.login) - tm.client.callNoRes(`RestartChallenge`, tm.getGameMode() === 'Cup' ? [{ boolean: true }] : undefined) - }, - privilege: config.res.privilege + privilege: config.skip.privilege +}, { + aliases: config.res.aliases, + help: config.res.help, + callback: (info: tm.MessageInfo): void => { + if (tm.getState() === 'result') { return } + tm.sendMessage(tm.utils.strVar(config.res.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + }), config.res.public ? undefined : info.login) + tm.client.callNoRes(`RestartChallenge`, tm.getGameMode() === 'Cup' ? [{ boolean: true }] : undefined) }, - { - aliases: config.prev.aliases, - help: config.prev.help, - callback: async (info: tm.MessageInfo): Promise => { - if (tm.jukebox.history[0] === undefined) { - tm.sendMessage(config.prev.error, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.prev.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname) - }), config.prev.public ? undefined : info.login) - await tm.jukebox.add(tm.jukebox.history[0].id, info) - }, - privilege: config.prev.privilege + privilege: config.res.privilege +}, { + aliases: config.prev.aliases, + help: config.prev.help, + callback: async (info: tm.MessageInfo): Promise => { + if (tm.jukebox.history[0] === undefined) { + tm.sendMessage(config.prev.error, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.prev.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + }), config.prev.public ? undefined : info.login) + await tm.jukebox.add(tm.jukebox.history[0].id, info) }, - { - aliases: config.replay.aliases, - help: config.replay.help, - callback: (info: tm.MessageInfo): void => { - actions.requeueMap(info.login, info.nickname, info.title) - }, - privilege: config.replay.privilege + privilege: config.prev.privilege +}, { + aliases: config.replay.aliases, + help: config.replay.help, + callback: (info: tm.MessageInfo): void => { + actions.requeueMap(info.login, info.nickname, info.title) }, - { - aliases: config.endround.aliases, - help: config.endround.help, - callback: (info: tm.MessageInfo): void => { - if (tm.config.game.gameMode === 1 || tm.config.game.gameMode === 4) { // TimeAttack & Stunts - tm.sendMessage(config.endround.error, info.login) - return - } - tm.client.callNoRes('ForceEndRound') - tm.sendMessage(tm.utils.strVar(config.endround.text, { title: info.title, adminName: tm.utils.strip(info.nickname) }), config.endround.public ? undefined : info.login) - }, - privilege: config.endround.privilege + privilege: config.replay.privilege +}, { + aliases: config.endround.aliases, + help: config.endround.help, + callback: (info: tm.MessageInfo): void => { + if (tm.config.game.gameMode === 1 || tm.config.game.gameMode === 4) { // TimeAttack & Stunts + tm.sendMessage(config.endround.error, info.login) + return + } + tm.client.callNoRes('ForceEndRound') + tm.sendMessage(tm.utils.strVar(config.endround.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + }), config.endround.public ? undefined : info.login) }, - { - aliases: config.forceteam.aliases, - help: config.forceteam.help, - params: [{ name: 'player' }, { name: 'team', validValues: ['blue', 'red'] }], - callback: async (info: tm.MessageInfo, player: string, team: string): Promise => { - if (tm.config.game.gameMode === 1 || tm.config.game.gameMode === 4) { // TimeAttack & Stunts - tm.sendMessage(config.forceteam.notRounds, info.login) - return - } - const playerInfo: tm.Player | undefined = tm.players.get(player) - if (playerInfo === undefined) { - tm.sendMessage(config.forceteam.playerOffline, info.login) - return - } - let teamInt: number = 0 - let teamColour: string = '' - switch (team.toLowerCase()) { - case 'blue': - teamInt = 0 - teamColour = `${tm.utils.colours.blue}` - break - case 'red': - teamInt = 1 - teamColour = `${tm.utils.colours.red}` - } - tm.sendMessage(tm.utils.strVar(config.forceteam.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), name: tm.utils.strip(playerInfo.nickname), - team: (teamColour + team.toUpperCase()) - }), config.forceteam.public ? undefined : info.login) - tm.client.callNoRes(`ForcePlayerTeam`, [{ string: player }, { int: teamInt }]) - }, - privilege: config.forceteam.privilege - } -] + privilege: config.endround.privilege +}, { + aliases: config.forceteam.aliases, + help: config.forceteam.help, + params: [{ name: 'player' }, { + name: 'team', + validValues: ['blue', 'red'] + }], + callback: async (info: tm.MessageInfo, player: string, team: string): Promise => { + if (tm.config.game.gameMode === 1 || tm.config.game.gameMode === 4) { // TimeAttack & Stunts + tm.sendMessage(config.forceteam.notRounds, info.login) + return + } + const playerInfo: tm.Player | undefined = tm.players.get(player) + if (playerInfo === undefined) { + tm.sendMessage(config.forceteam.playerOffline, info.login) + return + } + let teamInt: number = 0 + let teamColour: string = '' + switch(team.toLowerCase()) { + case 'blue': + teamInt = 0 + teamColour = `${tm.utils.colours.blue}` + break + case 'red': + teamInt = 1 + teamColour = `${tm.utils.colours.red}` + } + tm.sendMessage(tm.utils.strVar(config.forceteam.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + name: tm.utils.strip(playerInfo.nickname), + team: (teamColour + team.toUpperCase()) + }), config.forceteam.public ? undefined : info.login) + tm.client.callNoRes(`ForcePlayerTeam`, [{ string: player }, { int: teamInt }]) + }, + privilege: config.forceteam.privilege +}] tm.commands.add(...commands) \ No newline at end of file diff --git a/plugins/commands/src/RecordsCommands.ts b/plugins/commands/src/RecordsCommands.ts index 83f385f33..f44f3a534 100644 --- a/plugins/commands/src/RecordsCommands.ts +++ b/plugins/commands/src/RecordsCommands.ts @@ -21,17 +21,25 @@ const commands: tm.Command[] = [{ return } } - tm.sendMessage(tm.utils.strVar(config.delrec.text, { title: info.title, adminName: tm.utils.strip(info.nickname), nickname: tm.utils.strip(playerRecord.nickname) }), config.delrec.public ? undefined : info.login) - tm.records.remove(playerRecord, tm.maps.current.id, info, tm.maps.current.isInLapsMode ? tm.maps.current.lapsAmount : undefined) + tm.sendMessage(tm.utils.strVar(config.delrec.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + nickname: tm.utils.strip(playerRecord.nickname) + }), config.delrec.public ? undefined : info.login) + tm.records.remove(playerRecord, tm.maps.current.id, info, + tm.maps.current.isInLapsMode ? tm.maps.current.lapsAmount : undefined) }, privilege: config.delrec.privilege -}, -{ +}, { aliases: config.prunerecs.aliases, help: config.prunerecs.help, callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.prunerecs.text, { title: info.title, adminName: tm.utils.strip(info.nickname) }), config.prunerecs.public ? undefined : info.login) - tm.records.removeAll(tm.maps.current.id, info, tm.maps.current.isInLapsMode ? tm.maps.current.lapsAmount : undefined) + tm.sendMessage(tm.utils.strVar(config.prunerecs.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + }), config.prunerecs.public ? undefined : info.login) + tm.records.removeAll(tm.maps.current.id, info, + tm.maps.current.isInLapsMode ? tm.maps.current.lapsAmount : undefined) }, privilege: config.prunerecs.privilege }] diff --git a/plugins/commands/src/ServerCommands.ts b/plugins/commands/src/ServerCommands.ts index 713422250..f29e7d470 100644 --- a/plugins/commands/src/ServerCommands.ts +++ b/plugins/commands/src/ServerCommands.ts @@ -32,393 +32,489 @@ const resumeTimer = (info: tm.Player) => { })) } -const commands: tm.Command[] = [ - { - aliases: config.timelimit.aliases, - help: config.timelimit.help, - params: [{ name: 'action' }], - callback: (info, actionStr: string): void => { - if (tm.getState() !== 'race') { - tm.sendMessage(config.timelimit.notRaceMode, info.login) - return - } - if (!tm.timer.isDynamic) { - tm.sendMessage(config.timelimit.notDynamic, info.login) - return - } - if (actionStr === 'pause' || actionStr === 'stop') { - pauseTimer(info) - return - } - if (actionStr === 'resume' || actionStr === 'unpause' || actionStr === 'start') { - resumeTimer(info) - return - } - let action: 'set' | 'add' | 'subtract' - let time: number | Error - if (actionStr.startsWith('+')) { - action = 'add' - time = tm.utils.parseTimeString(actionStr.slice(1)) - } else if (actionStr.startsWith('-')) { - action = 'subtract' - time = tm.utils.parseTimeString(actionStr.slice(1)) - } else { - action = 'set' - time = tm.utils.parseTimeString(actionStr) - } - if (time instanceof Error) { - tm.sendMessage(config.timelimit.invalidParam, info.login) - return - } - let wasSet = true - if (action === 'set') { - wasSet = tm.timer.setTime(time) - } else if (action === 'add') { - tm.timer.addTime(time) // can always add time - } else if (action === 'subtract') { - wasSet = tm.timer.subtractTime(time) - } - if (wasSet) { - tm.sendMessage(tm.utils.strVar(config.timelimit.set, { - title: info.title, - adminName: tm.utils.strip(info.nickname), - time: tm.utils.getVerboseTime(tm.timer.remainingRaceTime) - })) - } else { - tm.sendMessage(config.timelimit.tooLow, info.login) - } - }, - privilege: config.timelimit.privilege - }, - { - aliases: config.pauseTimer.aliases, - help: config.pauseTimer.help, - callback: (info): void => pauseTimer(info), - privilege: config.pauseTimer.privilege - }, - { - aliases: config.resumeTimer.aliases, - help: config.resumeTimer.help, - callback: (info): void => resumeTimer(info), - privilege: config.resumeTimer.privilege - }, - { - aliases: config.enabledynamictimer.aliases, - help: config.enabledynamictimer.help, - callback: (info): void => { - if (tm.timer.isDynamicOnNextRound) { - tm.sendMessage(config.enabledynamictimer.alreadyEnabled, info.login) - return - } - tm.timer.enableDynamic() - tm.sendMessage(tm.utils.strVar(config.enabledynamictimer.text, { +const commands: tm.Command[] = [{ + aliases: config.timelimit.aliases, + help: config.timelimit.help, + params: [{ name: 'action' }], + callback: (info, actionStr: string): void => { + if (tm.getState() !== 'race') { + tm.sendMessage(config.timelimit.notRaceMode, info.login) + return + } + if (!tm.timer.isDynamic) { + tm.sendMessage(config.timelimit.notDynamic, info.login) + return + } + if (actionStr === 'pause' || actionStr === 'stop') { + pauseTimer(info) + return + } + if (actionStr === 'resume' || actionStr === 'unpause' || actionStr === 'start') { + resumeTimer(info) + return + } + let action: 'set' | 'add' | 'subtract' + let time: number | Error + if (actionStr.startsWith('+')) { + action = 'add' + time = tm.utils.parseTimeString(actionStr.slice(1)) + } else if (actionStr.startsWith('-')) { + action = 'subtract' + time = tm.utils.parseTimeString(actionStr.slice(1)) + } else { + action = 'set' + time = tm.utils.parseTimeString(actionStr) + } + if (time instanceof Error) { + tm.sendMessage(config.timelimit.invalidParam, info.login) + return + } + let wasSet = true + if (action === 'set') { + wasSet = tm.timer.setTime(time) + } else if (action === 'add') { + tm.timer.addTime(time) // can always add time + } else if (action === 'subtract') { + wasSet = tm.timer.subtractTime(time) + } + if (wasSet) { + tm.sendMessage(tm.utils.strVar(config.timelimit.set, { title: info.title, - adminName: tm.utils.strip(info.nickname) + adminName: tm.utils.strip(info.nickname), + time: tm.utils.getVerboseTime(tm.timer.remainingRaceTime) })) - }, - privilege: config.enabledynamictimer.privilege + } else { + tm.sendMessage(config.timelimit.tooLow, info.login) + } }, - { - aliases: config.disabledynamictimer.aliases, - help: config.disabledynamictimer.help, - callback: (info): void => { - if (!tm.timer.isDynamicOnNextRound) { - tm.sendMessage(config.disabledynamictimer.alreadyDisabled, info.login) - return - } - tm.timer.disableDynamic() - tm.sendMessage(tm.utils.strVar(config.disabledynamictimer.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname) - })) - }, - privilege: config.disabledynamictimer.privilege + privilege: config.timelimit.privilege +}, { + aliases: config.pauseTimer.aliases, + help: config.pauseTimer.help, + callback: (info): void => pauseTimer(info), + privilege: config.pauseTimer.privilege +}, { + aliases: config.resumeTimer.aliases, + help: config.resumeTimer.help, + callback: (info): void => resumeTimer(info), + privilege: config.resumeTimer.privilege +}, { + aliases: config.enabledynamictimer.aliases, + help: config.enabledynamictimer.help, + callback: (info): void => { + if (tm.timer.isDynamicOnNextRound) { + tm.sendMessage(config.enabledynamictimer.alreadyEnabled, info.login) + return + } + tm.timer.enableDynamic() + tm.sendMessage(tm.utils.strVar(config.enabledynamictimer.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + })) }, - { - aliases: config.setrefpwd.aliases, - help: config.setrefpwd.help, - params: [{ name: 'password', type: 'multiword', optional: true }], - callback: (info: tm.MessageInfo, password?: string): void => { - const regex: RegExp = /[\p{ASCII}]+/u // Passwords outside of ASCII range cannot be entered in the field - if (password !== undefined && !regex.test(password)) { - tm.sendMessage(config.setrefpwd.error, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.setrefpwd.text, { title: info.title, adminName: tm.utils.strip(info.nickname), password: password !== undefined ? password : 'none (disabled)' }), config.setrefpwd.public ? undefined : info.login) - tm.client.callNoRes(`SetRefereePassword`, [{ string: password === undefined ? '' : password }]) - }, - privilege: config.setrefpwd.privilege + privilege: config.enabledynamictimer.privilege +}, { + aliases: config.disabledynamictimer.aliases, + help: config.disabledynamictimer.help, + callback: (info): void => { + if (!tm.timer.isDynamicOnNextRound) { + tm.sendMessage(config.disabledynamictimer.alreadyDisabled, info.login) + return + } + tm.timer.disableDynamic() + tm.sendMessage(tm.utils.strVar(config.disabledynamictimer.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + })) }, - { - aliases: config.setrefmode.aliases, - help: config.setrefmode.help, - params: [{ name: 'mode', type: 'boolean' }], - callback: (info: tm.MessageInfo, mode: boolean): void => { - tm.sendMessage(tm.utils.strVar(config.setrefmode.text, { title: info.title, adminName: tm.utils.strip(info.nickname), mode: mode ? 'ALL' : 'TOP3' }), config.setrefmode.public ? undefined : info.login) - tm.client.call(`SetRefereeMode`, [{ int: mode ? 1 : 0 }]) - }, - privilege: config.setrefmode.privilege + privilege: config.disabledynamictimer.privilege +}, { + aliases: config.setrefpwd.aliases, + help: config.setrefpwd.help, + params: [{ + name: 'password', + type: 'multiword', + optional: true + }], + callback: (info: tm.MessageInfo, password?: string): void => { + const regex: RegExp = /[\p{ASCII}]+/u // Passwords outside of ASCII range cannot be entered in the field + if (password !== undefined && !regex.test(password)) { + tm.sendMessage(config.setrefpwd.error, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.setrefpwd.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + password: password !== undefined ? password : 'none (disabled)' + }), config.setrefpwd.public ? undefined : info.login) + tm.client.callNoRes(`SetRefereePassword`, [{ string: password === undefined ? '' : password }]) }, - { - aliases: config.pay.aliases, - help: config.pay.help, - params: [{ name: 'amount', type: 'int' }, { name: 'login', optional: true }, { name: 'message', optional: true }], - callback: async (info: tm.MessageInfo, amount: number, login?: string, message?: string): Promise => { - const status = await tm.utils.payCoppers(login ?? info.login, amount, - message ?? tm.utils.strVar(config.pay.defaultMessage, { coppers: amount, server: tm.config.server.name })) - if (status instanceof Error) { - tm.sendMessage(tm.utils.strVar(config.pay.error, { login: login ?? info.login }), info.login) + privilege: config.setrefpwd.privilege +}, { + aliases: config.setrefmode.aliases, + help: config.setrefmode.help, + params: [{ + name: 'mode', + type: 'boolean' + }], + callback: (info: tm.MessageInfo, mode: boolean): void => { + tm.sendMessage(tm.utils.strVar(config.setrefmode.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + mode: mode ? 'ALL' : 'TOP3' + }), config.setrefmode.public ? undefined : info.login) + tm.client.call(`SetRefereeMode`, [{ int: mode ? 1 : 0 }]) + }, + privilege: config.setrefmode.privilege +}, { + aliases: config.pay.aliases, + help: config.pay.help, + params: [{ + name: 'amount', + type: 'int' + }, { + name: 'login', + optional: true + }, { + name: 'message', + optional: true + }], + callback: async (info: tm.MessageInfo, amount: number, login?: string, message?: string): Promise => { + const status = await tm.utils.payCoppers(login ?? info.login, amount, + message ?? tm.utils.strVar(config.pay.defaultMessage, { + coppers: amount, + server: tm.config.server.name + })) + if (status instanceof Error) { + tm.sendMessage(tm.utils.strVar(config.pay.error, { login: login ?? info.login }), info.login) + } else { + if (login === undefined) { + tm.sendMessage(tm.utils.strVar(config.pay.selfText, { + coppers: amount + }), info.login) } else { - if (login === undefined) { - tm.sendMessage(tm.utils.strVar(config.pay.selfText, { - coppers: amount, - }), info.login) - } else { - const player: tm.OfflinePlayer | undefined = tm.players.get(login) ?? await tm.players.fetch(login) - tm.sendMessage(tm.utils.strVar(config.pay.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname, true), - coppers: amount, - target: tm.utils.strip(player?.nickname ?? login, true) - }), config.pay.public ? undefined : info.login) - } + const player: tm.OfflinePlayer | undefined = tm.players.get(login) ?? await tm.players.fetch(login) + tm.sendMessage(tm.utils.strVar(config.pay.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname, true), + coppers: amount, + target: tm.utils.strip(player?.nickname ?? login, true) + }), config.pay.public ? undefined : info.login) } - }, - privilege: config.pay.privilege + } }, - { - aliases: config.setservername.aliases, - help: config.setservername.help, - params: [{ name: 'name', type: 'multiword' }], - callback: (info: tm.MessageInfo, name: string): void => { - tm.sendMessage(tm.utils.strVar(config.setservername.text, { title: info.title, adminName: tm.utils.strip(info.nickname), value: name.length === 0 ? `the server login` : name }), config.setservername.public ? undefined : info.login) - tm.client.callNoRes(`SetServerName`, [{ string: name }]) - }, - privilege: config.setservername.privilege + privilege: config.pay.privilege +}, { + aliases: config.setservername.aliases, + help: config.setservername.help, + params: [{ + name: 'name', + type: 'multiword' + }], + callback: (info: tm.MessageInfo, name: string): void => { + tm.sendMessage(tm.utils.strVar(config.setservername.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + value: name.length === 0 ? `the server login` : name + }), config.setservername.public ? undefined : info.login) + tm.client.callNoRes(`SetServerName`, [{ string: name }]) }, - { - aliases: config.setcomment.aliases, - help: config.setcomment.help, - params: [{ name: 'comment', type: 'multiword' }], - callback: (info: tm.MessageInfo, comment: string): void => { - tm.sendMessage(tm.utils.strVar(config.setcomment.text, { title: info.title, adminName: tm.utils.strip(info.nickname), value: comment.length === 0 ? `absolutely nothing` : comment }), config.setcomment.public ? undefined : info.login) - tm.client.callNoRes(`SetServerComment`, [{ string: comment }]) - }, - privilege: config.setcomment.privilege + privilege: config.setservername.privilege +}, { + aliases: config.setcomment.aliases, + help: config.setcomment.help, + params: [{ + name: 'comment', + type: 'multiword' + }], + callback: (info: tm.MessageInfo, comment: string): void => { + tm.sendMessage(tm.utils.strVar(config.setcomment.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + value: comment.length === 0 ? `absolutely nothing` : comment + }), config.setcomment.public ? undefined : info.login) + tm.client.callNoRes(`SetServerComment`, [{ string: comment }]) }, - { - aliases: config.setpassword.aliases, - help: config.setpassword.help, - params: [{ name: 'password', type: 'multiword', optional: true }], - callback: (info: tm.MessageInfo, password?: string): void => { - const regex: RegExp = /[\p{ASCII}]+/u // Passwords outside of ASCII range cannot be entered in the field - if (password !== undefined && !regex.test(password)) { - tm.sendMessage(config.setpassword.error, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.setpassword.text, { title: info.title, adminName: tm.utils.strip(info.nickname), value: password !== undefined ? password : 'none (disabled)' }), config.setpassword.public ? undefined : info.login) - tm.client.callNoRes(`SetServerPassword`, [{ string: password === undefined ? '' : password }]) - }, - privilege: config.setpassword.privilege + privilege: config.setcomment.privilege +}, { + aliases: config.setpassword.aliases, + help: config.setpassword.help, + params: [{ + name: 'password', + type: 'multiword', + optional: true + }], + callback: (info: tm.MessageInfo, password?: string): void => { + const regex: RegExp = /[\p{ASCII}]+/u // Passwords outside of ASCII range cannot be entered in the field + if (password !== undefined && !regex.test(password)) { + tm.sendMessage(config.setpassword.error, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.setpassword.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + value: password !== undefined ? password : 'none (disabled)' + }), config.setpassword.public ? undefined : info.login) + tm.client.callNoRes(`SetServerPassword`, [{ string: password === undefined ? '' : password }]) }, - { - aliases: config.setspecpassword.aliases, - help: config.setspecpassword.help, - params: [{ name: 'password', type: 'multiword', optional: true }], - callback: (info: tm.MessageInfo, password?: string): void => { - const regex: RegExp = /[\p{ASCII}]+/u // Passwords outside of ASCII range cannot be entered in the field - if (password !== undefined && !regex.test(password)) { - tm.sendMessage(config.setspecpassword.error, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.setspecpassword.text, { title: info.title, adminName: tm.utils.strip(info.nickname), value: password !== undefined ? password : 'none (disabled)' }), config.setspecpassword.public ? undefined : info.login) - tm.client.callNoRes(`SetServerPasswordForSpectator`, [{ string: password === undefined ? '' : password }]) - }, - privilege: config.setspecpassword.privilege + privilege: config.setpassword.privilege +}, { + aliases: config.setspecpassword.aliases, + help: config.setspecpassword.help, + params: [{ + name: 'password', + type: 'multiword', + optional: true + }], + callback: (info: tm.MessageInfo, password?: string): void => { + const regex: RegExp = /[\p{ASCII}]+/u // Passwords outside of ASCII range cannot be entered in the field + if (password !== undefined && !regex.test(password)) { + tm.sendMessage(config.setspecpassword.error, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.setspecpassword.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + value: password !== undefined ? password : 'none (disabled)' + }), config.setspecpassword.public ? undefined : info.login) + tm.client.callNoRes(`SetServerPasswordForSpectator`, [{ string: password === undefined ? '' : password }]) }, - { - aliases: config.setmaxplayers.aliases, - help: config.setmaxplayers.help, - params: [{ name: 'amount', type: 'int' }], - callback: (info: tm.MessageInfo, amount: number): void => { - tm.sendMessage(tm.utils.strVar(config.setmaxplayers.text, { title: info.title, adminName: tm.utils.strip(info.nickname), value: amount }), config.setmaxplayers.public ? undefined : info.login) - tm.client.callNoRes(`SetMaxPlayers`, [{ int: amount }]) - }, - privilege: config.setmaxplayers.privilege + privilege: config.setspecpassword.privilege +}, { + aliases: config.setmaxplayers.aliases, + help: config.setmaxplayers.help, + params: [{ + name: 'amount', + type: 'int' + }], + callback: (info: tm.MessageInfo, amount: number): void => { + tm.sendMessage(tm.utils.strVar(config.setmaxplayers.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + value: amount + }), config.setmaxplayers.public ? undefined : info.login) + tm.client.callNoRes(`SetMaxPlayers`, [{ int: amount }]) }, - { - aliases: config.setmaxspecs.aliases, - help: config.setmaxspecs.help, - params: [{ name: 'amount', type: 'int' }], - callback: (info: tm.MessageInfo, amount: number): void => { - tm.sendMessage(tm.utils.strVar(config.setmaxspecs.text, { title: info.title, adminName: tm.utils.strip(info.nickname), value: amount }), config.setmaxspecs.public ? undefined : info.login) - tm.client.callNoRes(`SetMaxSpectators`, [{ int: amount }]) - }, - privilege: config.setmaxspecs.privilege + privilege: config.setmaxplayers.privilege +}, { + aliases: config.setmaxspecs.aliases, + help: config.setmaxspecs.help, + params: [{ + name: 'amount', + type: 'int' + }], + callback: (info: tm.MessageInfo, amount: number): void => { + tm.sendMessage(tm.utils.strVar(config.setmaxspecs.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + value: amount + }), config.setmaxspecs.public ? undefined : info.login) + tm.client.callNoRes(`SetMaxSpectators`, [{ int: amount }]) }, - { - aliases: config.sendnotice.aliases, - help: config.sendnotice.help, - params: [{ name: 'time', type: 'time' }, { name: 'notice', type: 'multiword' }], - callback: (info: tm.MessageInfo, time: number, notice: string): void => { - const s: string[] = notice.split(' ').filter(a => a !== '') - const player = tm.players.get(s[s.length - 1]) - let loginAvatar: string = '' - if (player !== undefined) { - notice = s.slice(0, -1).join(' ') - loginAvatar = player.login - } - tm.sendMessage(tm.utils.strVar(config.sendnotice.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), value: notice - }), config.sendnotice.public ? undefined : info.login) - tm.client.callNoRes(`SendNotice`, [{ string: notice }, { string: loginAvatar }, { int: time }]) - }, - privilege: config.sendnotice.privilege + privilege: config.setmaxspecs.privilege +}, { + aliases: config.sendnotice.aliases, + help: config.sendnotice.help, + params: [{ + name: 'time', + type: 'time' + }, { + name: 'notice', + type: 'multiword' + }], + callback: (info: tm.MessageInfo, time: number, notice: string): void => { + const s: string[] = notice.split(' ').filter(a => a !== '') + const player = tm.players.get(s[s.length - 1]) + let loginAvatar: string = '' + if (player !== undefined) { + notice = s.slice(0, -1).join(' ') + loginAvatar = player.login + } + tm.sendMessage(tm.utils.strVar(config.sendnotice.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + value: notice + }), config.sendnotice.public ? undefined : info.login) + tm.client.callNoRes(`SendNotice`, [{ string: notice }, { string: loginAvatar }, { int: time }]) }, - { - aliases: config.allowmapdownload.aliases, - help: config.allowmapdownload.help, - params: [{ name: 'status', type: 'boolean' }], - callback: (info: tm.MessageInfo, status: boolean): void => { - tm.sendMessage(tm.utils.strVar(config.allowmapdownload.text, { title: info.title, adminName: tm.utils.strip(info.nickname), value: status ? 'allowed' : 'disallowed' }), config.allowmapdownload.public ? undefined : info.login) - tm.client.callNoRes(`AllowChallengeDownload`, [{ boolean: status }]) - }, - privilege: config.allowmapdownload.privilege + privilege: config.sendnotice.privilege +}, { + aliases: config.allowmapdownload.aliases, + help: config.allowmapdownload.help, + params: [{ + name: 'status', + type: 'boolean' + }], + callback: (info: tm.MessageInfo, status: boolean): void => { + tm.sendMessage(tm.utils.strVar(config.allowmapdownload.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + value: status ? 'allowed' : 'disallowed' + }), config.allowmapdownload.public ? undefined : info.login) + tm.client.callNoRes(`AllowChallengeDownload`, [{ boolean: status }]) }, - { - aliases: config.sethideserver.aliases, - help: config.sethideserver.help, - params: [{ name: 'value', validValues: ['hidden', 'visible', 'notmnf'] }], - callback: (info: tm.MessageInfo, value: string): void => { - let status: string = '' - let hideInt: number = 0 - switch (value) { - case 'visible': - status = config.sethideserver.status.visible - hideInt = 0 - break - case 'hidden': - status = config.sethideserver.status.hidden - hideInt = 1 - break - case 'notmnf': - status = config.sethideserver.status.noTmnf - hideInt = 2 - } - tm.sendMessage(tm.utils.strVar(config.sethideserver.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), status: status - }), config.sethideserver.public ? undefined : info.login) - tm.client.callNoRes(`SetHideServer`, [{ int: hideInt }]) - }, - privilege: config.sethideserver.privilege + privilege: config.allowmapdownload.privilege +}, { + aliases: config.sethideserver.aliases, + help: config.sethideserver.help, + params: [{ + name: 'value', + validValues: ['hidden', 'visible', 'notmnf'] + }], + callback: (info: tm.MessageInfo, value: string): void => { + let status: string = '' + let hideInt: number = 0 + switch(value) { + case 'visible': + status = config.sethideserver.status.visible + hideInt = 0 + break + case 'hidden': + status = config.sethideserver.status.hidden + hideInt = 1 + break + case 'notmnf': + status = config.sethideserver.status.noTmnf + hideInt = 2 + } + tm.sendMessage(tm.utils.strVar(config.sethideserver.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + status: status + }), config.sethideserver.public ? undefined : info.login) + tm.client.callNoRes(`SetHideServer`, [{ int: hideInt }]) }, - { - aliases: config.autosavereplays.aliases, - help: config.autosavereplays.help, - params: [{ name: 'status', type: 'boolean' }], - callback: (info: tm.MessageInfo, status: boolean): void => { - tm.sendMessage(tm.utils.strVar(config.autosavereplays.text, { title: info.title, adminName: tm.utils.strip(info.nickname), value: status ? 'enabled' : 'disabled' }), config.autosavereplays.public ? undefined : info.login) - tm.client.callNoRes(`AutoSaveReplays`, [{ boolean: status }]) - }, - privilege: config.autosavereplays.privilege + privilege: config.sethideserver.privilege +}, { + aliases: config.autosavereplays.aliases, + help: config.autosavereplays.help, + params: [{ + name: 'status', + type: 'boolean' + }], + callback: (info: tm.MessageInfo, status: boolean): void => { + tm.sendMessage(tm.utils.strVar(config.autosavereplays.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + value: status ? 'enabled' : 'disabled' + }), config.autosavereplays.public ? undefined : info.login) + tm.client.callNoRes(`AutoSaveReplays`, [{ boolean: status }]) }, - { - aliases: config.autosavevalreplays.aliases, - help: config.autosavevalreplays.help, - params: [{ name: 'status', type: 'boolean' }], - callback: (info: tm.MessageInfo, status: boolean): void => { - tm.sendMessage(tm.utils.strVar(config.autosavevalreplays.text, { title: info.title, adminName: tm.utils.strip(info.nickname), value: status ? 'enabled' : 'disabled' }), config.autosavevalreplays.public ? undefined : info.login) - tm.client.callNoRes(`AutoSaveValidationReplays`, [{ boolean: status }]) - }, - privilege: config.autosavevalreplays.privilege + privilege: config.autosavereplays.privilege +}, { + aliases: config.autosavevalreplays.aliases, + help: config.autosavevalreplays.help, + params: [{ + name: 'status', + type: 'boolean' + }], + callback: (info: tm.MessageInfo, status: boolean): void => { + tm.sendMessage(tm.utils.strVar(config.autosavevalreplays.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + value: status ? 'enabled' : 'disabled' + }), config.autosavevalreplays.public ? undefined : info.login) + tm.client.callNoRes(`AutoSaveValidationReplays`, [{ boolean: status }]) }, - { - aliases: config.killcontroller.aliases, - help: config.killcontroller.help, - callback: (info: tm.MessageInfo): never => { - tm.sendMessage(tm.utils.strVar(config.killcontroller.text, { title: info.title, adminName: tm.utils.strip(info.nickname) }), config.killcontroller.public ? undefined : info.login) - process.exit(0) - }, - privilege: config.killcontroller.privilege + privilege: config.autosavevalreplays.privilege +}, { + aliases: config.killcontroller.aliases, + help: config.killcontroller.help, + callback: (info: tm.MessageInfo): never => { + tm.sendMessage(tm.utils.strVar(config.killcontroller.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + }), config.killcontroller.public ? undefined : info.login) + process.exit(0) }, - { - aliases: config.setbuddynotif.aliases, - help: config.setbuddynotif.help, - params: [{ name: 'status', type: 'boolean' }], - callback: (info: tm.MessageInfo, status: boolean): void => { - tm.sendMessage(tm.utils.strVar(config.setbuddynotif.text, { title: info.title, adminName: tm.utils.strip(info.nickname), status: status ? 'enabled' : 'disabled' }), config.setbuddynotif.public ? undefined : info.login) - tm.client.callNoRes(`SetBuddyNotification`, [{ string: '' }, { boolean: status }]) - }, - privilege: config.setbuddynotif.privilege + privilege: config.killcontroller.privilege +}, { + aliases: config.setbuddynotif.aliases, + help: config.setbuddynotif.help, + params: [{ + name: 'status', + type: 'boolean' + }], + callback: (info: tm.MessageInfo, status: boolean): void => { + tm.sendMessage(tm.utils.strVar(config.setbuddynotif.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + status: status ? 'enabled' : 'disabled' + }), config.setbuddynotif.public ? undefined : info.login) + tm.client.callNoRes(`SetBuddyNotification`, [{ string: '' }, { boolean: status }]) }, - { - aliases: config.shutdown.aliases, - help: config.shutdown.help, - callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.shutdown.text, { title: info.title, adminName: tm.utils.strip(info.nickname) }), config.shutdown.public ? undefined : info.login) - tm.client.callNoRes(`StopServer`) - }, - privilege: config.shutdown.privilege + privilege: config.setbuddynotif.privilege +}, { + aliases: config.shutdown.aliases, + help: config.shutdown.help, + callback: (info: tm.MessageInfo): void => { + tm.sendMessage(tm.utils.strVar(config.shutdown.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + }), config.shutdown.public ? undefined : info.login) + tm.client.callNoRes(`StopServer`) }, - { - aliases: config.call.aliases, - help: config.call.help, - params: [{ name: 'method' }, { name: 'params', type: 'multiword', optional: true }], - callback: async (info: tm.MessageInfo, method: string, params?: string): Promise => { - if (params === undefined) { params = '' } - const paramsArr = params.split(' ').map(a => a.trim()).filter(a => a !== '') - let quot: [number, number] | undefined - for (let i = 0; i < paramsArr.length; i++) { - for (let j = 0; j < paramsArr[i].length; j++) { - if (paramsArr[i][j] === '"' && paramsArr[i][j - 1] !== "\\") { - if (quot === undefined) { - quot = [i, j] - } else { - const str = paramsArr.slice(quot[0], i + 1).join('') - const endIndex = str.length + j + 1 - paramsArr[i].length - const sliced = str.slice(quot[1], endIndex) - paramsArr.splice(quot[0], (i + 1) - quot[0], sliced) - quot = undefined - break - } + privilege: config.shutdown.privilege +}, { + aliases: config.call.aliases, + help: config.call.help, + params: [{ name: 'method' }, { + name: 'params', + type: 'multiword', + optional: true + }], + callback: async (info: tm.MessageInfo, method: string, params?: string): Promise => { + if (params === undefined) { params = '' } + const paramsArr = params.split(' ').map(a => a.trim()).filter(a => a !== '') + let quot: [number, number] | undefined + for (let i = 0; i < paramsArr.length; i++) { + for (let j = 0; j < paramsArr[i].length; j++) { + if (paramsArr[i][j] === '"' && paramsArr[i][j - 1] !== "\\") { + if (quot === undefined) { + quot = [i, j] + } else { + const str = paramsArr.slice(quot[0], i + 1).join('') + const endIndex = str.length + j + 1 - paramsArr[i].length + const sliced = str.slice(quot[1], endIndex) + paramsArr.splice(quot[0], (i + 1) - quot[0], sliced) + quot = undefined + break } } } - const parsedParams: tm.CallParams[] = [] - for (const e of paramsArr) { - if (e[0] === '"' && e[e.length - 1] === '"') { - parsedParams.push({ string: e.slice(1, -1) }) - continue - } - try { - const parsed = JSON.parse(e) - if (Number.isInteger(parsed)) { - parsedParams.push({ int: parsed }) - } else if (typeof parsed === 'number') { - parsedParams.push({ double: parsed }) - } else if (typeof parsed === 'boolean') { - parsedParams.push({ boolean: parsed }) - } else if (Array.isArray(parsed)) { - parsedParams.push({ array: parsed }) - } else if (typeof parsed === 'object') { - parsedParams.push({ struct: parsed }) - } - } catch (err: any) { - tm.sendMessage(`${tm.utils.palette.error}Parse error: ${err.message}`, info.login) - return - } + } + const parsedParams: tm.CallParams[] = [] + for (const e of paramsArr) { + if (e[0] === '"' && e[e.length - 1] === '"') { + parsedParams.push({ string: e.slice(1, -1) }) + continue } - const response = await tm.client.call(method, parsedParams) - if (response instanceof Error) { - tm.sendMessage(`${tm.utils.palette.error}Error: ${response.message}`, info.login) - } else { - tm.sendMessage(JSON.stringify(response), info.login) + try { + const parsed = JSON.parse(e) + if (Number.isInteger(parsed)) { + parsedParams.push({ int: parsed }) + } else if (typeof parsed === 'number') { + parsedParams.push({ double: parsed }) + } else if (typeof parsed === 'boolean') { + parsedParams.push({ boolean: parsed }) + } else if (Array.isArray(parsed)) { + parsedParams.push({ array: parsed }) + } else if (typeof parsed === 'object') { + parsedParams.push({ struct: parsed }) + } + } catch(err: any) { + tm.sendMessage(`${tm.utils.palette.error}Parse error: ${err.message}`, info.login) + return } - }, - privilege: config.call.privilege - } -] + } + const response = await tm.client.call(method, parsedParams) + if (response instanceof Error) { + tm.sendMessage(`${tm.utils.palette.error}Error: ${response.message}`, info.login) + } else { + tm.sendMessage(JSON.stringify(response), info.login) + } + }, + privilege: config.call.privilege +}] tm.commands.add(...commands) \ No newline at end of file diff --git a/plugins/dedimania/Config.js b/plugins/dedimania/Config.js index 34600f58d..5ee37d23b 100644 --- a/plugins/dedimania/Config.js +++ b/plugins/dedimania/Config.js @@ -1,8 +1,7 @@ const p = tm.utils.palette export default { - isEnabled: true, - // Maximum amount of fetched records + isEnabled: true, // Maximum amount of fetched records // (Dedimania normally stores 30, there are exceptions such as Nadeo maps with 50-80 records) dediCount: 30, syncName: true, // If true, sets player nicknames in the database to those fetched from Dedimania @@ -10,6 +9,5 @@ export default { host: 'dedimania.net', reconnectTimeout: 120, // Timeout for Dedimania reconnection attempts, in seconds updateInterval: 240, // Interval for Dedimania server update, in seconds (recommended to be kept between 2-5 minutes) - modifiedLapsMessage: `${p.dedimessage}Dedimania records will be sent in ${p.highlight}Time Attack` + - ` ${p.dedimessage}instead of ${p.highlight}Rounds ${p.dedimessage}mode due to modified lap amount.` + modifiedLapsMessage: `${p.dedimessage}Dedimania records will be sent in ${p.highlight}Time Attack` + ` ${p.dedimessage}instead of ${p.highlight}Rounds ${p.dedimessage}mode due to modified lap amount.` } diff --git a/plugins/dedimania/Dedimania.ts b/plugins/dedimania/Dedimania.ts index 27f01f578..f12253519 100644 --- a/plugins/dedimania/Dedimania.ts +++ b/plugins/dedimania/Dedimania.ts @@ -66,7 +66,8 @@ const initialize = async (): Promise => { if (uploadLaps && tm.getGameMode() !== 'Laps') { tm.sendMessage(config.modifiedLapsMessage) } - await getRecords(current.id, current.name, (tm.utils.environmentToNadeoEnvironment(current.environment) as string), current.author) + await getRecords(current.id, current.name, (tm.utils.environmentToNadeoEnvironment(current.environment) as string), + current.author) tm.log.trace('Connected to Dedimania') } @@ -93,7 +94,8 @@ const reinitialize = async (): Promise => { if (uploadLaps && tm.getGameMode() !== 'Laps') { tm.sendMessage(config.modifiedLapsMessage) } - await getRecords(current.id, current.name, (tm.utils.environmentToNadeoEnvironment(current.environment) as string), current.author) + await getRecords(current.id, current.name, (tm.utils.environmentToNadeoEnvironment(current.environment) as string), + current.author) } const getRecords = async (id: string, name: string, environment: string, author: string): Promise => { @@ -120,80 +122,65 @@ const getRecords = async (id: string, name: string, environment: string, author: const nextIds: string[] = tm.jukebox.queue.slice(0, 5).map(a => a.id) const players = tm.players.list const rawDedis: any[] | Error = await client.call('dedimania.CurrentChallenge', - [ - { string: id }, - { string: name }, - { string: environment }, - { string: author }, - { string: 'TMF' }, - { int: recordModeEnum[leaderboard] }, - { - struct: { - SrvName: { string: cfg.name }, - Comment: { string: cfg.comment }, - Private: { boolean: cfg.password !== '' }, - SrvIP: { string: '127.0.0.1' }, // Can actually get the real server IP via cfg.ipAddress - SrvPort: { string: '5000' }, - XmlRpcPort: { string: '5000' }, - NumPlayers: { int: players.filter(a => !a.isSpectator).length }, - MaxPlayers: { int: cfg.currentMaxPlayers }, - NumSpecs: { int: players.filter(a => a.isSpectator).length }, - MaxSpecs: { int: cfg.currentMaxPlayers }, - LadderMode: { int: cfg.currentLadderMode }, - NextFiveUID: { string: nextIds.join('/') } - } - }, - { int: config.dediCount }, - { array: getPlayersArray() } - ]) + [{ string: id }, { string: name }, { string: environment }, { string: author }, { string: 'TMF' }, + { int: recordModeEnum[leaderboard] }, { + struct: { + SrvName: { string: cfg.name }, + Comment: { string: cfg.comment }, + Private: { boolean: cfg.password !== '' }, + SrvIP: { string: '127.0.0.1' }, // Can actually get the real server IP via cfg.ipAddress + SrvPort: { string: '5000' }, + XmlRpcPort: { string: '5000' }, + NumPlayers: { int: players.filter(a => !a.isSpectator).length }, + MaxPlayers: { int: cfg.currentMaxPlayers }, + NumSpecs: { int: players.filter(a => a.isSpectator).length }, + MaxSpecs: { int: cfg.currentMaxPlayers }, + LadderMode: { int: cfg.currentLadderMode }, + NextFiveUID: { string: nextIds.join('/') } + } + }, { int: config.dediCount }, { array: getPlayersArray() }]) if (rawDedis instanceof Error) { - tm.log.error(`Failed to fetch dedimania records for map ${tm.utils.strip(name)} (${id}), received error:`, rawDedis.message) + tm.log.error(`Failed to fetch dedimania records for map ${tm.utils.strip(name)} (${id}), received error:`, + rawDedis.message) return - } - else if (rawDedis?.[0]?.Records === undefined) { + } else if (rawDedis?.[0]?.Records === undefined) { tm.log.error(`Failed to fetch dedimania records for map ${tm.utils.strip(name)} (${id}), received empty response`) return } - currentDedis = rawDedis[0].Records.map((a: any): DediRecord => - ({ - login: a.Login, nickname: a.NickName, time: a.Best, - checkpoints: a.Checks.slice(0, a.Checks.length - 1), leaderboard, - isLapRecord: uploadLaps - })) + currentDedis = rawDedis[0].Records.map((a: any): DediRecord => ({ + login: a.Login, + nickname: a.NickName, + time: a.Best, + checkpoints: a.Checks.slice(0, a.Checks.length - 1), + leaderboard, + isLapRecord: uploadLaps + })) if (config.syncName && currentDedis.length > 0) { void tm.updatePlayerInfo(...currentDedis) } emitFetchEvent(currentDedis) } -const sendRecords = async (mapId: string, name: string, environment: string, author: string, checkpointsAmount: number): Promise => { +const sendRecords = async (mapId: string, name: string, environment: string, author: string, + checkpointsAmount: number): Promise => { if (!client.connected || newDedis.length === 0) { return } const recordsArray: any = [] for (const d of newDedis) { - recordsArray.push( - { - struct: { - Login: { string: d.login }, - Best: { int: d.time }, - Checks: { string: [...d.checkpoints, d.time].join(',') } - } + recordsArray.push({ + struct: { + Login: { string: d.login }, + Best: { int: d.time }, + Checks: { string: [...d.checkpoints, d.time].join(',') } } - ) + }) } const status: any[] | Error = await client.call('dedimania.ChallengeRaceTimes', - [ - { string: mapId }, - { string: name }, - { string: environment }, - { string: author }, - { string: 'TMF' }, - { int: recordModeEnum[leaderboard] }, - { int: checkpointsAmount }, - { int: config.dediCount }, - { array: recordsArray } - ] - ) - if (status instanceof Error) { tm.log.error(`Failed to send dedimania records for map ${tm.utils.strip(name)} (${mapId})`, status.message) } + [{ string: mapId }, { string: name }, { string: environment }, { string: author }, { string: 'TMF' }, + { int: recordModeEnum[leaderboard] }, { int: checkpointsAmount }, { int: config.dediCount }, + { array: recordsArray }]) + if (status instanceof Error) { + tm.log.error(`Failed to send dedimania records for map ${tm.utils.strip(name)} (${mapId})`, status.message) + } } const addRecord = (player: Omit, @@ -203,21 +190,30 @@ const addRecord = (player: Omit a.time <= time).length + 1 if (position > config.dediCount || time > (pb ?? Infinity)) { return } if (pb === undefined) { - const dediRecordInfo: NewDediRecord = constructRecordObject(player, checkpoints, time, undefined, position, undefined) - currentDedis.splice(position - 1, 0, - { - login: player.login, time: time, nickname: player.nickname, - checkpoints: [...checkpoints], leaderboard, isLapRecord: uploadLaps - }) + const dediRecordInfo: NewDediRecord = constructRecordObject(player, checkpoints, time, undefined, position, + undefined) + currentDedis.splice(position - 1, 0, { + login: player.login, + time: time, + nickname: player.nickname, + checkpoints: [...checkpoints], + leaderboard, + isLapRecord: uploadLaps + }) newDedis.push({ - login: player.login, time: time, nickname: player.nickname, - checkpoints: [...checkpoints], leaderboard, isLapRecord: uploadLaps + login: player.login, + time: time, + nickname: player.nickname, + checkpoints: [...checkpoints], + leaderboard, + isLapRecord: uploadLaps }) tm.log.info(getLogString(undefined, position, undefined, time, player)) emitRecordEvent(dediRecordInfo) } else if (time === pb) { const previousPosition: number = currentDedis.findIndex(a => a.login === player.login) + 1 - const dediRecordInfo: NewDediRecord = constructRecordObject(player, checkpoints, time, time, previousPosition, previousPosition) + const dediRecordInfo: NewDediRecord = constructRecordObject(player, checkpoints, time, time, previousPosition, + previousPosition) tm.log.info(getLogString(previousPosition, previousPosition, time, time, player)) emitRecordEvent(dediRecordInfo) } else if (time < pb) { @@ -227,17 +223,25 @@ const addRecord = (player: Omit a.login === player.login) + 1) + const dediRecordInfo: NewDediRecord = constructRecordObject(player, checkpoints, time, previousTime, position, + currentDedis.findIndex(a => a.login === player.login) + 1) currentDedis = currentDedis.filter(a => a.login !== player.login) - currentDedis.splice(position - 1, 0, - { - login: player.login, time: time, nickname: player.nickname, - checkpoints: [...checkpoints], leaderboard, isLapRecord: uploadLaps - }) + currentDedis.splice(position - 1, 0, { + login: player.login, + time: time, + nickname: player.nickname, + checkpoints: [...checkpoints], + leaderboard, + isLapRecord: uploadLaps + }) newDedis = newDedis.filter(a => a.login !== player.login) newDedis.push({ - login: player.login, time: time, nickname: player.nickname, - checkpoints: [...checkpoints], leaderboard, isLapRecord: uploadLaps + login: player.login, + time: time, + nickname: player.nickname, + checkpoints: [...checkpoints], + leaderboard, + isLapRecord: uploadLaps }) tm.log.info(getLogString(previousIndex + 1, position, previousTime, time, player)) emitRecordEvent(dediRecordInfo) @@ -251,28 +255,22 @@ const updateServerPlayers = (): void => { const nextIds: string[] = tm.jukebox.queue.slice(0, 5).map(a => a.id) const players = tm.players.list const status: any[] | Error = await client.call('dedimania.UpdateServerPlayers', - [ - { string: 'TMF' }, - { int: tm.config.game.gameMode }, - { - struct: { - SrvName: { string: cfg.name }, - Comment: { string: cfg.comment }, - Private: { boolean: cfg.password !== '' }, - SrvIP: { string: '127.0.0.1' }, - SrvPort: { string: '5000' }, - XmlRpcPort: { string: '5000' }, - NumPlayers: { int: players.filter(a => !a.isSpectator).length }, - MaxPlayers: { int: cfg.currentMaxPlayers }, - NumSpecs: { int: players.filter(a => a.isSpectator).length }, - MaxSpecs: { int: cfg.currentMaxPlayers }, - LadderMode: { int: cfg.currentLadderMode }, - NextFiveUID: { string: nextIds.join('/') } - } - }, - { array: getPlayersArray() } - ] - ) + [{ string: 'TMF' }, { int: tm.config.game.gameMode }, { + struct: { + SrvName: { string: cfg.name }, + Comment: { string: cfg.comment }, + Private: { boolean: cfg.password !== '' }, + SrvIP: { string: '127.0.0.1' }, + SrvPort: { string: '5000' }, + XmlRpcPort: { string: '5000' }, + NumPlayers: { int: players.filter(a => !a.isSpectator).length }, + MaxPlayers: { int: cfg.currentMaxPlayers }, + NumSpecs: { int: players.filter(a => a.isSpectator).length }, + MaxSpecs: { int: cfg.currentMaxPlayers }, + LadderMode: { int: cfg.currentLadderMode }, + NextFiveUID: { string: nextIds.join('/') } + } + }, { array: getPlayersArray() }]) if (status instanceof Error) { tm.log.error('Failed to update dedimania status', status.message) } }, config.updateInterval * 1000) } @@ -281,85 +279,100 @@ const updateServerPlayers = (): void => { * Updates the player information and server player list on the dedimania website * @param player Player object */ -const playerJoin = async (player: - { login: string, nickname: string, region: string, isSpectator: boolean, ladderRank: number }): Promise => { +const playerJoin = async (player: { + login: string, + nickname: string, + region: string, + isSpectator: boolean, + ladderRank: number +}): Promise => { if (!client.connected) { return } const status: any[] | Error = await client.call('dedimania.PlayerArrive', - [ - { string: 'TMF' }, - { string: player.login }, - { string: player.nickname }, - { string: tm.utils.countryToCode(player.region.split('|')[0]) }, - { string: '' }, // TEAMNAME - { int: player.ladderRank }, - { boolean: player.isSpectator }, - { boolean: false } // OFFICIAL MODE ALWAYS FALSE - ] - ) - if (status instanceof Error) { tm.log.error(`Failed to update dedimania player information for ${tm.utils.strip(player.nickname)} (${player.login})`, status.message) } + [{ string: 'TMF' }, { string: player.login }, { string: player.nickname }, + { string: tm.utils.countryToCode(player.region.split('|')[0]) }, { string: '' }, // TEAMNAME + { int: player.ladderRank }, { boolean: player.isSpectator }, { boolean: false } // OFFICIAL MODE ALWAYS FALSE + ]) + if (status instanceof Error) { + tm.log.error( + `Failed to update dedimania player information for ${tm.utils.strip(player.nickname)} (${player.login})`, + status.message) + } } /** * Updates the server player list on the dedimania website * @param player Player object */ -const playerLeave = async (player: { login: string, nickname: string }): Promise => { +const playerLeave = async (player: { + login: string, + nickname: string +}): Promise => { if (!client.connected) { return } const status: any[] | Error = await client.call('dedimania.PlayerLeave', - [ - { string: 'TMF' }, - { string: player.login } - ]) - if (status instanceof Error) { tm.log.error(`Failed to update player information for ${tm.utils.strip(player.nickname)} (${player.login})`, status.message) } + [{ string: 'TMF' }, { string: player.login }]) + if (status instanceof Error) { + tm.log.error(`Failed to update player information for ${tm.utils.strip(player.nickname)} (${player.login})`, + status.message) + } } const getPlayersArray = (): any[] => { const players: tm.Player[] = tm.players.list const arr: any[] = [] for (const player of players) { - arr.push( - [ - { - struct: { - Login: { string: player.login }, - Nation: { string: player.countryCode }, - TeamName: { string: '' }, - TeamId: { int: -1 }, - IsSpec: { boolean: player.isSpectator }, - Ranking: { int: player.ladderRank }, - IsOff: { boolean: false } // OFFICIAL MODE ALWAYS FALSE - } - } - ] - ) + arr.push([{ + struct: { + Login: { string: player.login }, + Nation: { string: player.countryCode }, + TeamName: { string: '' }, + TeamId: { int: -1 }, + IsSpec: { boolean: player.isSpectator }, + Ranking: { int: player.ladderRank }, + IsOff: { boolean: false } // OFFICIAL MODE ALWAYS FALSE + } + }]) } return arr } const constructRecordObject = (player: Omit, - checkpoints: number[], time: number, previousTime: number | undefined, position: number, previousPosition: number | undefined): NewDediRecord => { + checkpoints: number[], time: number, previousTime: number | undefined, position: number, + previousPosition: number | undefined): NewDediRecord => { return { ...player, time, checkpoints, position, - previous: (previousTime && previousPosition) ? { time: previousTime, position: previousPosition } : undefined, + previous: (previousTime && previousPosition) ? { + time: previousTime, + position: previousPosition + } : undefined, leaderboard, isLapRecord: uploadLaps } } -const getLogString = (previousPosition: number | undefined, position: number, - previousTime: number | undefined, time: number, player: { login: string, nickname: string }): string[] => { - const rs = tm.utils.getRankingString({ position, time }, (previousPosition && previousTime) ? - { time: previousTime, position: previousPosition } : undefined) - return [`${tm.utils.strip(player.nickname)} (${player.login}) has ${rs.status} the ${tm.utils.getOrdinalSuffix(position)} dedimania record. Time: ${tm.utils.getTimeString(time)}${rs.difference !== undefined ? ` (-${rs.difference})` : ``}`] +const getLogString = (previousPosition: number | undefined, position: number, previousTime: number | undefined, + time: number, player: { + login: string, + nickname: string + }): string[] => { + const rs = tm.utils.getRankingString({ + position, + time + }, (previousPosition && previousTime) ? { + time: previousTime, + position: previousPosition + } : undefined) + return [`${tm.utils.strip(player.nickname)} (${player.login}) has ${rs.status} the ${tm.utils.getOrdinalSuffix( + position)} dedimania record. Time: ${tm.utils.getTimeString(time)}${rs.difference !== undefined ? + ` (-${rs.difference})` : ``}`] } if (config.isEnabled) { tm.addListener('Startup', (): void => { - if(tm.getGameMode() !== 'Stunts') { + if (tm.getGameMode() !== 'Stunts') { tm.log.trace('Connecting to Dedimania...') void initialize() } @@ -371,7 +384,7 @@ if (config.isEnabled) { if (prevLb === 'Disabled' && leaderboard !== 'Disabled') { void initialize() return - } + } if (prevLb !== 'Disabled' && leaderboard === 'Disabled') { currentDedis.length = 0 newDedis.length = 0 @@ -381,12 +394,14 @@ if (config.isEnabled) { if (uploadLaps && tm.getGameMode() !== 'Laps') { tm.sendMessage(config.modifiedLapsMessage) } - void getRecords(info.id, info.name, (tm.utils.environmentToNadeoEnvironment(info.environment) as string), info.author) + void getRecords(info.id, info.name, (tm.utils.environmentToNadeoEnvironment(info.environment) as string), + info.author) }, true) tm.addListener('EndMap', (info): void => { const cpAmount = uploadLaps ? info.checkpointsPerLap : info.checkpointsAmount - void sendRecords(info.id, info.name, (tm.utils.environmentToNadeoEnvironment(info.environment) as string), info.author, cpAmount) + void sendRecords(info.id, info.name, (tm.utils.environmentToNadeoEnvironment(info.environment) as string), + info.author, cpAmount) }) tm.addListener('PlayerJoin', (info): void => { @@ -542,7 +557,7 @@ export const dedimania = { return client.connected }, - /** + /** * Whether the plugin is uploading lap times instead of total race time on the current map. * True if the server is in Laps mode or if it's in Cup/Rounds/Teams mode with modified laps amount. */ @@ -551,7 +566,7 @@ export const dedimania = { }, /** - * Dedimania leaderboard ('Rounds' or 'TimeAttack') from which the current records were + * Dedimania leaderboard ('Rounds' or 'TimeAttack') from which the current records were * fetched and to which the new records will be uploaded. * The plugin fetches and uploads records to the Rounds Mode leaderboard in Cup/Rounds/Teams mode * if the laps amount is not modified, otherwise to the TimeAttack leaderboard. diff --git a/plugins/dedimania/DedimaniaClient.ts b/plugins/dedimania/DedimaniaClient.ts index 50f8e12a2..d461033ad 100644 --- a/plugins/dedimania/DedimaniaClient.ts +++ b/plugins/dedimania/DedimaniaClient.ts @@ -12,7 +12,10 @@ export class DedimaniaClient { private _connected: boolean = false private password: string | undefined - async connect(host: string, port: number): Promise { + async connect(host: string, port: number): Promise { if (process.env.DEDIMANIA_PASSWORD === undefined) { return { isAuthenticationError: true, @@ -27,7 +30,10 @@ export class DedimaniaClient { const packmask: string | Error = await tm.client.call('GetServerPackMask') as any if (packmask instanceof Error) { tm.log.error('Failed to fetch server packmask', packmask.message) - return { error: packmask, isAuthenticationError: false } + return { + error: packmask, + isAuthenticationError: false + } } const request: DedimaniaRequest = new DedimaniaRequest('dedimania.Authenticate', [{ struct: { @@ -50,7 +56,8 @@ export class DedimaniaClient { this.receivingResponse = false if (this.response.isError !== null) { resolve({ - error: new Error(`Dedimania server responded with an error ${this.response.errorString} Code: ${this.response.errorCode}`), + error: new Error( + `Dedimania server responded with an error ${this.response.errorString} Code: ${this.response.errorCode}`), isAuthenticationError: false }) } else { @@ -109,7 +116,8 @@ export class DedimaniaClient { const poll = (): void => { if (this.response.status === 'completed') { if (this.response.isError === true) { - resolve(new Error(`Dedimania server responded with an error ${this.response.errorString} Code: ${this.response.errorCode}`)) + resolve(new Error( + `Dedimania server responded with an error ${this.response.errorString} Code: ${this.response.errorCode}`)) } else { resolve(this.response.json) } diff --git a/plugins/dedimania/DedimaniaRequest.ts b/plugins/dedimania/DedimaniaRequest.ts index a6d838628..125504e06 100644 --- a/plugins/dedimania/DedimaniaRequest.ts +++ b/plugins/dedimania/DedimaniaRequest.ts @@ -5,21 +5,14 @@ export class DedimaniaRequest { private sessionKey?: string private xmlBuffer: Buffer private _buffer: Buffer = Buffer.from( - 'POST /Dedimania HTTP/1.1\n' + - 'Host: dedimania.net\n' + - 'User-Agent: XMLaccess\n' + - 'Cache-Control: no-cache\n' + - 'Accept-Encoding: gzip\n' + - 'Content-Encoding: gzip\n' + - 'Content-type: text/xml; charset=UTF-8\n' - ) + 'POST /Dedimania HTTP/1.1\n' + 'Host: dedimania.net\n' + 'User-Agent: XMLaccess\n' + 'Cache-Control: no-cache\n' + 'Accept-Encoding: gzip\n' + 'Content-Encoding: gzip\n' + 'Content-type: text/xml; charset=UTF-8\n') /** - * Prepares XML string for a dedimania request. - * @param method dedimania method - * @param params parameters - * @param sessionKey dedimania session key - */ + * Prepares XML string for a dedimania request. + * @param method dedimania method + * @param params parameters + * @param sessionKey dedimania session key + */ constructor(method: string, params: tm.CallParams[], sessionKey?: string) { let xml: string = `${method}` for (const param of params) { @@ -32,17 +25,12 @@ export class DedimaniaRequest { this.sessionKey = sessionKey const gzip: Buffer = zlib.gzipSync(this.xmlBuffer) if (this.sessionKey === undefined) { - this._buffer = Buffer.concat([ - this._buffer, - Buffer.from(`Content-length: ${gzip.length}\r\nKeep-Alive: timeout=600, max=2000\r\nConnection: Keep-Alive\r\n\r\n`), - gzip - ]) + this._buffer = Buffer.concat([this._buffer, Buffer.from( + `Content-length: ${gzip.length}\r\nKeep-Alive: timeout=600, max=2000\r\nConnection: Keep-Alive\r\n\r\n`), gzip]) } else { - this._buffer = Buffer.concat([ - this._buffer, - Buffer.from(`Content-length: ${gzip.length}\r\nKeep-Alive: timeout=600, max=2000\r\nConnection: Keep-Alive\r\nCookie: PHPSESSID=${this.sessionKey}\r\n\r\n`), - gzip - ]) + this._buffer = Buffer.concat([this._buffer, Buffer.from( + `Content-length: ${gzip.length}\r\nKeep-Alive: timeout=600, max=2000\r\nConnection: Keep-Alive\r\nCookie: PHPSESSID=${this.sessionKey}\r\n\r\n`), + gzip]) } } @@ -53,7 +41,7 @@ export class DedimaniaRequest { if (value === undefined) { return new Error(`Received undefined while creating dedimania XML request, expected ${type}.`) } - switch (type) { + switch(type) { case 'boolean': return `${value === true ? '1' : '0'}` case 'int': diff --git a/plugins/dedimania/DedimaniaResponse.ts b/plugins/dedimania/DedimaniaResponse.ts index 36da44439..410be00b4 100644 --- a/plugins/dedimania/DedimaniaResponse.ts +++ b/plugins/dedimania/DedimaniaResponse.ts @@ -98,10 +98,11 @@ export class DedimaniaResponse { const changeType: any = (value: any, type: string): any => { const arr: any[] = [] const obj: any = {} - switch (type) { + switch(type) { case 'boolean': return value === '1' - case 'int': case 'i4': + case 'int': + case 'i4': return parseInt(value) case 'double': return parseFloat(value) diff --git a/plugins/dedimania/ui/DediCps.component.ts b/plugins/dedimania/ui/DediCps.component.ts index 97687a55b..da2373a1b 100644 --- a/plugins/dedimania/ui/DediCps.component.ts +++ b/plugins/dedimania/ui/DediCps.component.ts @@ -4,7 +4,7 @@ */ import { dedimania } from "../../dedimania/Dedimania.js" -import { componentIds, Paginator, Grid, centeredText, closeButton, getCpTypes, type GridCellFunction, PopupWindow } from '../../ui/UI.js' +import { centeredText, closeButton, componentIds, getCpTypes, Grid, type GridCellFunction, Paginator, PopupWindow } from '../../ui/UI.js' import config from './DediCps.config.js' export default class DediCps extends PopupWindow { @@ -24,8 +24,10 @@ export default class DediCps extends PopupWindow { constructor() { super(componentIds.dediCps, config.icon, config.title, config.navbar) const records = dedimania.records - this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, Math.ceil(records.length / this.entries)) - this.cpPaginator = new Paginator(this.openId + 10, this.windowWidth, this.footerHeight, this.calculateCpPages(), 1, true) + this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, + Math.ceil(records.length / this.entries)) + this.cpPaginator = new Paginator(this.openId + 10, this.windowWidth, this.footerHeight, this.calculateCpPages(), 1, + true) this.paginator.onPageChange = (login: string): void => { this.getPagesAndOpen(login) } @@ -54,7 +56,10 @@ export default class DediCps extends PopupWindow { this.getPagesAndOpen(info.login) } - protected constructContent(login: string, params: { page: number, cpPage: number }): string { + protected constructContent(login: string, params: { + page: number, + cpPage: number + }): string { const records = dedimania.records const [cpIndex, cpsToDisplay] = this.getCpIndexAndAmount(params.cpPage) const playerIndex: number = (params.page - 1) * this.entries - 1 @@ -94,41 +99,44 @@ export default class DediCps extends PopupWindow { let grid: Grid let headers: GridCellFunction[] = [] if (params.cpPage === 1) { - headers = [ - (i, j, w, h) => centeredText(' Lp. ', w, h), - (i, j, w, h) => centeredText(' Nickname ', w, h), - (i, j, w, h) => centeredText(' Login ', w, h), - ...new Array(cpsToDisplay).fill((i: number, j: number, w: number, h: number): string => centeredText((j - this.startCellsOnFirstPage).toString(), w, h)), - (i, j, w, h) => centeredText(' Finish ', w, h), - ...new Array(this.cpsOnFirstPage - cpsToDisplay).fill((i: number, j: number, w: number, h: number): string => '') - ] - grid = new Grid(this.contentWidth, this.contentHeight, [this.indexCellWidth, - ...new Array(this.startCellsOnFirstPage).fill(this.startCellWidth), - ...new Array(this.cpsOnFirstPage + 1).fill(1)], new Array(this.entries + 1).fill(1), config.grid) + headers = [(i, j, w, h) => centeredText(' Lp. ', w, h), (i, j, w, h) => centeredText(' Nickname ', w, h), + (i, j, w, h) => centeredText(' Login ', w, h), ...new Array(cpsToDisplay).fill( + (i: number, j: number, w: number, h: number): string => centeredText( + (j - this.startCellsOnFirstPage).toString(), w, h)), (i, j, w, h) => centeredText(' Finish ', w, h), + ...new Array(this.cpsOnFirstPage - cpsToDisplay).fill( + (i: number, j: number, w: number, h: number): string => '')] + grid = new Grid(this.contentWidth, this.contentHeight, + [this.indexCellWidth, ...new Array(this.startCellsOnFirstPage).fill(this.startCellWidth), + ...new Array(this.cpsOnFirstPage + 1).fill(1)], new Array(this.entries + 1).fill(1), config.grid) } else { - headers = [ - (i, j, w, h) => centeredText(' Lp. ', w, h), + headers = [(i, j, w, h) => centeredText(' Lp. ', w, h), (i: number, j: number, w: number, h: number): string => centeredText(' Nickname ', w, h), - ...new Array(cpsToDisplay).fill((i: number, j: number, w: number, h: number): string => centeredText((j + cpIndex - (this.startCellsOnNextPages)).toString(), w, h)), + ...new Array(cpsToDisplay).fill((i: number, j: number, w: number, h: number): string => centeredText( + (j + cpIndex - (this.startCellsOnNextPages)).toString(), w, h)), (i: number, j: number, w: number, h: number): string => centeredText(' Finish ', w, h), - ...new Array(this.cpsOnNextPages - cpsToDisplay).fill((i: number, j: number, w: number, h: number): string => '') - ] - grid = new Grid(this.contentWidth, this.contentHeight, [this.indexCellWidth, - ...new Array(this.startCellsOnNextPages).fill(this.startCellWidth), - ...new Array(this.cpsOnNextPages + 1).fill(1)], new Array(this.entries + 1).fill(1), config.grid) + ...new Array(this.cpsOnNextPages - cpsToDisplay).fill( + (i: number, j: number, w: number, h: number): string => '')] + grid = new Grid(this.contentWidth, this.contentHeight, + [this.indexCellWidth, ...new Array(this.startCellsOnNextPages).fill(this.startCellWidth), + ...new Array(this.cpsOnNextPages + 1).fill(1)], new Array(this.entries + 1).fill(1), config.grid) } const arr = [...headers] for (let i = 0; i < entriesToDisplay; i++) { if (params.cpPage === 1) { - arr.push(indexCell, nickNameCell, loginCell, ...new Array(cpsToDisplay).fill(cell), finishCell, ...new Array(this.cpsOnFirstPage - cpsToDisplay).fill(emptyCell)) + arr.push(indexCell, nickNameCell, loginCell, ...new Array(cpsToDisplay).fill(cell), finishCell, + ...new Array(this.cpsOnFirstPage - cpsToDisplay).fill(emptyCell)) } else { - arr.push(indexCell, nickNameCell, ...new Array(cpsToDisplay).fill(cell), finishCell, ...new Array(this.cpsOnNextPages - cpsToDisplay).fill(emptyCell)) + arr.push(indexCell, nickNameCell, ...new Array(cpsToDisplay).fill(cell), finishCell, + ...new Array(this.cpsOnNextPages - cpsToDisplay).fill(emptyCell)) } } return grid.constructXml(arr) } - protected constructFooter(login: string, params: { page: number, cpPage: number }): string { + protected constructFooter(login: string, params: { + page: number, + cpPage: number + }): string { const w = (this.cpPaginator.buttonW + this.cpPaginator.margin) * this.cpPaginator.buttonCount + config.cpPaginatorMargin return `${closeButton(this.closeId, this.windowWidth, this.headerHeight - this.margin)} ${this.paginator.constructXml(params.page)} @@ -138,8 +146,8 @@ export default class DediCps extends PopupWindow { } private getCpIndexAndAmount(cpPage: number): [number, number] { - const cpAmount = dedimania.isUploadingLaps ? - tm.maps.current.checkpointsPerLap - 1 : tm.maps.current.checkpointsAmount - 1 + const cpAmount = dedimania.isUploadingLaps ? tm.maps.current.checkpointsPerLap - 1 : + tm.maps.current.checkpointsAmount - 1 let cpsToDisplay: number = Math.min(cpAmount, this.cpsOnFirstPage) let cpIndex = 0 if (cpPage > 1) { @@ -156,7 +164,10 @@ export default class DediCps extends PopupWindow { const page = this.paginator.getPageByLogin(login) const cpPage = this.cpPaginator.getPageByLogin(login) const pageCount: number = this.paginator.pageCount - this.displayToPlayer(login, { page, cpPage }, `${page}/${Math.max(1, pageCount)}`) + this.displayToPlayer(login, { + page, + cpPage + }, `${page}/${Math.max(1, pageCount)}`) } private reRender(): void { @@ -168,8 +179,8 @@ export default class DediCps extends PopupWindow { private calculateCpPages(): number { let cpPages = 1 - const cpAmount = dedimania.isUploadingLaps ? - tm.maps.current.checkpointsPerLap - 1 : tm.maps.current.checkpointsAmount - 1 + const cpAmount = dedimania.isUploadingLaps ? tm.maps.current.checkpointsPerLap - 1 : + tm.maps.current.checkpointsAmount - 1 for (let i = 1; i < cpAmount; i++) { if (cpPages === 1 && i >= this.cpsOnFirstPage) { cpPages++ diff --git a/plugins/dedimania/ui/DediCps.config.js b/plugins/dedimania/ui/DediCps.config.js index 3b43b57c5..a1d2ffe0a 100644 --- a/plugins/dedimania/ui/DediCps.config.js +++ b/plugins/dedimania/ui/DediCps.config.js @@ -1,35 +1,45 @@ -import { icons, componentIds as ids } from '../../ui/UI.js' +import { componentIds as ids, icons } from '../../ui/UI.js' + const p = tm.utils.palette export default { entries: 15, cpsOnFirstPage: 10, icon: icons.chartDedi, - title: "Dedi Checkpoints", + title: 'Dedi Checkpoints', selfColour: `${p.green}F`, cpColours: { best: `${p.green}F`, worst: `${p.red}F`, equal: `${p.yellow}F` }, - navbar: [ - { name: 'Dedi Sectors', actionId: ids.dediSectors }, - { name: 'Local Checkpoints', actionId: ids.localCps }, - { name: 'Local Sectors', actionId: ids.localSectors }, - { name: 'Live Checkpoints', actionId: ids.liveCps }, - { name: 'Live Sectors', actionId: ids.liveSectors } - ], + navbar: [{ + name: 'Dedi Sectors', + actionId: ids.dediSectors + }, { + name: 'Local Checkpoints', + actionId: ids.localCps + }, { + name: 'Local Sectors', + actionId: ids.localSectors + }, { + name: 'Live Checkpoints', + actionId: ids.liveCps + }, { + name: 'Live Sectors', + actionId: ids.liveSectors + }], cpPaginatorMargin: 0.25, startCellWidth: 2, indexCellWidth: 1, grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, command: { aliases: ['dcp', 'dedicptms', 'dedirecs'], help: `Display current map dedimania checkpoints.`, privilege: 0 } -} \ No newline at end of file +} diff --git a/plugins/dedimania/ui/DediSectors.component.ts b/plugins/dedimania/ui/DediSectors.component.ts index 44b687103..d116925c1 100644 --- a/plugins/dedimania/ui/DediSectors.component.ts +++ b/plugins/dedimania/ui/DediSectors.component.ts @@ -4,7 +4,7 @@ */ import { dedimania, type DediRecord } from '../../dedimania/Dedimania.js' -import { componentIds, Paginator, Grid, centeredText, closeButton, getCpTypes, type GridCellFunction, PopupWindow } from '../../ui/UI.js' +import { centeredText, closeButton, componentIds, getCpTypes, Grid, type GridCellFunction, Paginator, PopupWindow } from '../../ui/UI.js' import config from './DediSectors.config.js' export default class DediSectors extends PopupWindow { @@ -24,8 +24,10 @@ export default class DediSectors extends PopupWindow { constructor() { super(componentIds.dediSectors, config.icon, config.title, config.navbar) const records = dedimania.records - this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, Math.ceil(records.length / this.entries)) - this.cpPaginator = new Paginator(this.openId + 10, this.windowWidth, this.footerHeight, this.calculateCpPages(), 1, true) + this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, + Math.ceil(records.length / this.entries)) + this.cpPaginator = new Paginator(this.openId + 10, this.windowWidth, this.footerHeight, this.calculateCpPages(), 1, + true) this.paginator.onPageChange = (login: string): void => { this.getPagesAndOpen(login) } @@ -54,10 +56,16 @@ export default class DediSectors extends PopupWindow { this.getPagesAndOpen(info.login) } - protected constructContent(login: string, params: { page: number, cpPage: number }): string { + protected constructContent(login: string, params: { + page: number, + cpPage: number + }): string { const records: DediRecord[] = [] for (const e of dedimania.records) { - records.push({ ...e, checkpoints: [...e.checkpoints, e.time].map((a, i, arr) => i === 0 ? a : a - arr[i - 1]) }) + records.push({ + ...e, + checkpoints: [...e.checkpoints, e.time].map((a, i, arr) => i === 0 ? a : a - arr[i - 1]) + }) } const [cpIndex, cpsToDisplay] = this.getCpIndexAndAmount(params.cpPage) const playerIndex: number = (params.page - 1) * this.entries - 1 @@ -97,42 +105,44 @@ export default class DediSectors extends PopupWindow { let grid: Grid let headers: GridCellFunction[] = [] if (params.cpPage === 1) { - headers = [ - (i, j, w, h) => centeredText(' Lp. ', w, h), - (i, j, w, h) => centeredText(' Nickname ', w, h), - (i, j, w, h) => centeredText(' Login ', w, h), - ...new Array(cpsToDisplay).fill((i: number, j: number, w: number, h: number): string => centeredText((j - this.startCellsOnFirstPage).toString(), w, h)), - (i, j, w, h) => centeredText(' Finish ', w, h), - ...new Array(this.cpsOnFirstPage - cpsToDisplay).fill((i: number, j: number, w: number, h: number): string => '') - ] - grid = new Grid(this.contentWidth, this.contentHeight, [this.indexCellWidth, - ...new Array(this.startCellsOnFirstPage).fill(this.startCellWidth), - ...new Array(this.cpsOnFirstPage + 1).fill(1)], new Array(this.entries + 1).fill(1), config.grid) + headers = [(i, j, w, h) => centeredText(' Lp. ', w, h), (i, j, w, h) => centeredText(' Nickname ', w, h), + (i, j, w, h) => centeredText(' Login ', w, h), ...new Array(cpsToDisplay).fill( + (i: number, j: number, w: number, h: number): string => centeredText( + (j - this.startCellsOnFirstPage).toString(), w, h)), (i, j, w, h) => centeredText(' Finish ', w, h), + ...new Array(this.cpsOnFirstPage - cpsToDisplay).fill( + (i: number, j: number, w: number, h: number): string => '')] + grid = new Grid(this.contentWidth, this.contentHeight, + [this.indexCellWidth, ...new Array(this.startCellsOnFirstPage).fill(this.startCellWidth), + ...new Array(this.cpsOnFirstPage + 1).fill(1)], new Array(this.entries + 1).fill(1), config.grid) } else { - headers = [ - (i, j, w, h) => centeredText(' Lp. ', w, h), + headers = [(i, j, w, h) => centeredText(' Lp. ', w, h), (i: number, j: number, w: number, h: number): string => centeredText(' Nickname ', w, h), - ...new Array(cpsToDisplay).fill((i: number, j: number, w: number, h: number): string => - centeredText((j + cpIndex - (this.startCellsOnNextPages)).toString(), w, h)), + ...new Array(cpsToDisplay).fill((i: number, j: number, w: number, h: number): string => centeredText( + (j + cpIndex - (this.startCellsOnNextPages)).toString(), w, h)), (i: number, j: number, w: number, h: number): string => centeredText(' Finish ', w, h), - ...new Array(this.cpsOnNextPages - cpsToDisplay).fill((i: number, j: number, w: number, h: number): string => '') - ] - grid = new Grid(this.contentWidth, this.contentHeight, [this.indexCellWidth, - ...new Array(this.startCellsOnNextPages).fill(this.startCellWidth), ...new Array(this.cpsOnNextPages + 1).fill(1)], - new Array(this.entries + 1).fill(1), config.grid) + ...new Array(this.cpsOnNextPages - cpsToDisplay).fill( + (i: number, j: number, w: number, h: number): string => '')] + grid = new Grid(this.contentWidth, this.contentHeight, + [this.indexCellWidth, ...new Array(this.startCellsOnNextPages).fill(this.startCellWidth), + ...new Array(this.cpsOnNextPages + 1).fill(1)], new Array(this.entries + 1).fill(1), config.grid) } const arr = [...headers] for (let i = 0; i < entriesToDisplay; i++) { if (params.cpPage === 1) { - arr.push(indexCell, nickNameCell, loginCell, ...new Array(cpsToDisplay).fill(cell), finishCell, ...new Array(this.cpsOnFirstPage - cpsToDisplay).fill(emptyCell)) + arr.push(indexCell, nickNameCell, loginCell, ...new Array(cpsToDisplay).fill(cell), finishCell, + ...new Array(this.cpsOnFirstPage - cpsToDisplay).fill(emptyCell)) } else { - arr.push(indexCell, nickNameCell, ...new Array(cpsToDisplay).fill(cell), finishCell, ...new Array(this.cpsOnNextPages - cpsToDisplay).fill(emptyCell)) + arr.push(indexCell, nickNameCell, ...new Array(cpsToDisplay).fill(cell), finishCell, + ...new Array(this.cpsOnNextPages - cpsToDisplay).fill(emptyCell)) } } return grid.constructXml(arr) } - protected constructFooter(login: string, params: { page: number, cpPage: number }): string { + protected constructFooter(login: string, params: { + page: number, + cpPage: number + }): string { const w = (this.cpPaginator.buttonW + this.cpPaginator.margin) * this.cpPaginator.buttonCount + config.cpPaginatorMargin return `${closeButton(this.closeId, this.windowWidth, this.headerHeight - this.margin)} ${this.paginator.constructXml(params.page)} @@ -142,8 +152,7 @@ export default class DediSectors extends PopupWindow { } private getCpIndexAndAmount(cpPage: number): [number, number] { - const cpAmount = dedimania.isUploadingLaps ? - tm.maps.current.checkpointsPerLap : tm.maps.current.checkpointsAmount + const cpAmount = dedimania.isUploadingLaps ? tm.maps.current.checkpointsPerLap : tm.maps.current.checkpointsAmount let cpsToDisplay: number = Math.min(cpAmount, this.cpsOnFirstPage) let cpIndex = 0 if (cpPage > 1) { @@ -160,7 +169,10 @@ export default class DediSectors extends PopupWindow { const page = this.paginator.getPageByLogin(login) const cpPage = this.cpPaginator.getPageByLogin(login) const pageCount: number = this.paginator.pageCount - this.displayToPlayer(login, { page, cpPage }, `${page}/${Math.max(1, pageCount)}`) + this.displayToPlayer(login, { + page, + cpPage + }, `${page}/${Math.max(1, pageCount)}`) } private reRender(): void { @@ -172,8 +184,7 @@ export default class DediSectors extends PopupWindow { private calculateCpPages(): number { let cpPages = 1 - const cpAmount = dedimania.isUploadingLaps ? - tm.maps.current.checkpointsPerLap : tm.maps.current.checkpointsAmount + const cpAmount = dedimania.isUploadingLaps ? tm.maps.current.checkpointsPerLap : tm.maps.current.checkpointsAmount for (let i = 1; i < cpAmount; i++) { if (cpPages === 1 && i >= this.cpsOnFirstPage) { cpPages++ diff --git a/plugins/dedimania/ui/DediSectors.config.js b/plugins/dedimania/ui/DediSectors.config.js index 9199269bb..22841bed5 100644 --- a/plugins/dedimania/ui/DediSectors.config.js +++ b/plugins/dedimania/ui/DediSectors.config.js @@ -1,35 +1,45 @@ -import { icons, componentIds as ids } from '../../ui/UI.js' +import { componentIds as ids, icons } from '../../ui/UI.js' + const p = tm.utils.palette export default { entries: 15, cpsOnFirstPage: 10, icon: icons.chartDedi, - title: "Dedi Sectors", + title: 'Dedi Sectors', selfColour: `${p.green}F`, cpColours: { best: `${p.green}F`, worst: `${p.red}F`, equal: `${p.yellow}F` }, - navbar: [ - { name: 'Dedi Checkpoints', actionId: ids.dediCps }, - { name: 'Local Checkpoints', actionId: ids.localCps }, - { name: 'Local Sectors', actionId: ids.localSectors }, - { name: 'Live Checkpoints', actionId: ids.liveCps }, - { name: 'Live Sectors', actionId: ids.liveSectors } - ], + navbar: [{ + name: 'Dedi Checkpoints', + actionId: ids.dediCps + }, { + name: 'Local Checkpoints', + actionId: ids.localCps + }, { + name: 'Local Sectors', + actionId: ids.localSectors + }, { + name: 'Live Checkpoints', + actionId: ids.liveCps + }, { + name: 'Live Sectors', + actionId: ids.liveSectors + }], cpPaginatorMargin: 0.25, startCellWidth: 2, indexCellWidth: 1, grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, command: { aliases: ['dsc', 'dedisectms'], help: `Display current map dedimania sectors.`, privilege: 0 } -} \ No newline at end of file +} diff --git a/plugins/donations/Config.js b/plugins/donations/Config.js index a5b9454c1..0cf4449fd 100644 --- a/plugins/donations/Config.js +++ b/plugins/donations/Config.js @@ -2,8 +2,7 @@ const p = tm.utils.palette export default { paymentFail: `${p.error}Failed to process payment.`, - paymentSuccess: `${p.highlight}#{nickname}${p.donation} ` - + `donated ${p.highlight}#{amount}C${p.donation} to the server.`, + paymentSuccess: `${p.highlight}#{nickname}${p.donation} ` + `donated ${p.highlight}#{amount}C${p.donation} to the server.`, minimalAmount: 50, amountTooLow: `${p.error}Amount of coppers too low. ${p.highlight}50${p.error} is the minimum.`, command: { @@ -11,4 +10,4 @@ export default { help: 'Donate coppers to the server.', privilege: 0 } -} \ No newline at end of file +} diff --git a/plugins/donations/Donations.ts b/plugins/donations/Donations.ts index b9fd676b3..7236564f1 100644 --- a/plugins/donations/Donations.ts +++ b/plugins/donations/Donations.ts @@ -10,7 +10,9 @@ interface DonationInfo { }[] } -const listeners: ((info: DonationInfo & { readonly amount: number }) => void)[] = [] +const listeners: ((info: DonationInfo & { + readonly amount: number +}) => void)[] = [] const nicknameChangeListeners: ((info: DonationInfo[]) => void)[] = [] let onlineDonators: DonationInfo[] = [] await tm.db.query(`CREATE TABLE IF NOT EXISTS donations( @@ -75,9 +77,13 @@ async function getFromDB(logins: string | string[]): Promise acc += cur.amount, 0), - history: res.map(a => ({ amount: a.amount, date: a.date })) + history: res.map(a => ({ + amount: a.amount, + date: a.date + })) } } const ids = await tm.db.getPlayerId(logins) @@ -95,9 +101,13 @@ async function getFromDB(logins: string | string[]): Promise a.login === login) if (arr.length === 0) { continue } ret.push({ - login, nickname: arr[0]?.nickname, + login, + nickname: arr[0]?.nickname, sum: arr.reduce((acc, cur) => acc += cur.amount, 0), - history: arr.map(a => ({ amount: a.amount, date: a.date })) + history: arr.map(a => ({ + amount: a.amount, + date: a.date + })) }) } return ret @@ -115,7 +125,10 @@ const addToDB = async (login: string, amount: number, date: Date): Promise tm.commands.add({ aliases: ['donate'], help: 'Donate coppers to the server.', - params: [{ name: 'amount', type: 'int' }], + params: [{ + name: 'amount', + type: 'int' + }], callback(info, amount: number) { donate(info.login, info.nickname, amount) }, @@ -124,7 +137,7 @@ tm.commands.add({ /** * Donate coppers to server - * @param payerLogin Login of the player + * @param payerLogin Login of the player * @param payerNickname Nickname of the player * @param amount Amount of coppers to donate * @returns True if successful, false if player refuses payment, Error if dedicated server call fails @@ -151,17 +164,26 @@ const donate = async (payerLogin: string, payerNickname: string, amount: number) if (info === undefined) { if (isOnline) { info = { - login: payerLogin, nickname: payerNickname, sum: 0, history: [] + login: payerLogin, + nickname: payerNickname, + sum: 0, + history: [] } onlineDonators.push(info) } else { return true } } - info.history.push({ amount, date }) + info.history.push({ + amount, + date + }) info.sum += amount for (const e of listeners) { - e({ ...info, amount }) + e({ + ...info, + amount + }) } return true } @@ -205,7 +227,9 @@ export const donations = { * Add a callback function to execute on donation * @param callback Function to execute on event. It takes donation object as a parameter */ - onDonation(callback: (info: DonationInfo & { readonly amount: number }) => void) { + onDonation(callback: (info: DonationInfo & { + readonly amount: number + }) => void) { listeners.push(callback) }, diff --git a/plugins/dynamic_time_limit/Config.js b/plugins/dynamic_time_limit/Config.js new file mode 100644 index 000000000..73e7d4cf0 --- /dev/null +++ b/plugins/dynamic_time_limit/Config.js @@ -0,0 +1,35 @@ +const p = tm.utils.palette + +export default { + // If false plugin cant be activated + isEnabled: true, + // This can be changed using an ingame command + isActive: false, + // Time calculation mode. For now it has only one option: "author" + defaultMode: 'author', + // Time multiplier eg. if mode = "author" and multilpier = 10, then the time will be + // set to `authorTime * 10` + defaultMultiplier: 10, + // Message gets sent at round start + sendTimeLimitMessage: true, + // Each mode can have its own message + messages: { + author: `${p.admin}The current map's ${p.highlight}AT ${p.admin}is ${p.highlight}#{authorTime}${p.admin}, time limit set to ${p.highlight}#{limit}${p.admin}.`, + }, + enableCommand: { + privilege: 3, + aliases: ['edtl', 'enabledynamictimelimit'], + help: `Enable dynamic time limit for the next map.`, + public: true, + success: `${p.admin}#{title} ${p.highlight}#{name} ${p.admin}has ${p.highlight}enabled ${p.admin}the dynamic time limit for the next map.`, + alreadyActive: `${p.error}Dynamic time limit is already enabled for the next map.` + }, + disableCommand: { + privilege: 3, + aliases: ['ddtl', 'disabledynamictimelimit'], + help: `Disable dynamic time limit for the next map.`, + public: true, + success: `${p.admin}#{title} ${p.highlight}#{name} ${p.admin}has ${p.highlight}disabled ${p.admin}the dynamic time limit for the next map.`, + alreadyDisabled: `${p.error}Dynamic time limit is already disabled for the next map.` + }, +} \ No newline at end of file diff --git a/plugins/dynamic_time_limit/DynamicTimeLimit.ts b/plugins/dynamic_time_limit/DynamicTimeLimit.ts new file mode 100644 index 000000000..ea86cd87a --- /dev/null +++ b/plugins/dynamic_time_limit/DynamicTimeLimit.ts @@ -0,0 +1,155 @@ +import config from './Config.js' +import 'dotenv/config' +import type { DynamicTimeLimitMode } from './Types.js' + +let isActive = config.isActive && config.isEnabled +let dynamicTimeLimitMode: DynamicTimeLimitMode = config.defaultMode as DynamicTimeLimitMode +let dynamicTimeLimitMultiplier = config.defaultMultiplier + +const modeFunctions: { [mode in DynamicTimeLimitMode]: (map: tm.Map) => number } = { + author: map => map.authorTime * dynamicTimeLimitMultiplier +} + +function setStaticTimeLimitForNextMap() { + const nextMap = tm.jukebox.queue[0] + const timeLimit = modeFunctions[dynamicTimeLimitMode](nextMap) + tm.timer.setTimeLimit(timeLimit) +} + +function setDynamicTimeLimitForCurrentMap() { + const timeLimit = modeFunctions[dynamicTimeLimitMode](tm.maps.current) + tm.timer.setTimeLimit(timeLimit) +} + +tm.addListener("ServerStateChanged", state => { + if (!isActive) { return } + if (state === "race" && tm.timer.isDynamic) { + setDynamicTimeLimitForCurrentMap() + } + else if (state === "result" && !tm.timer.isDynamicOnNextRound) { + setStaticTimeLimitForNextMap() + } +}) + +tm.addListener("JukeboxChanged", () => { + if (!isActive) { return } + setStaticTimeLimitForNextMap() +}) + +tm.addListener("DynamicTimerStateChanged", state => { + if (!isActive || state !== 'disabled') { return } + setStaticTimeLimitForNextMap() +}) + +tm.addListener("BeginMap", () => { + if (!isActive || !config.sendTimeLimitMessage) { return } + const timeLimit = modeFunctions[dynamicTimeLimitMode](tm.maps.current) + tm.sendMessage(tm.utils.strVar(config.messages[dynamicTimeLimitMode], { + limit: tm.utils.getVerboseTime(timeLimit), + authorTime: tm.utils.getTimeString(tm.maps.current.authorTime) + })) +}) + +tm.commands.add({ + aliases: config.enableCommand.aliases, + help: config.enableCommand.help, + callback: info => { + if (isActive) { + tm.sendMessage(config.enableCommand.alreadyActive, info.login) + return + } + isActive = true + if (!tm.timer.isDynamic) { + setStaticTimeLimitForNextMap() + } + tm.sendMessage(tm.utils.strVar(config.enableCommand.success, { + title: info.title, + name: tm.utils.strip(info.nickname) + }), config.enableCommand.public ? undefined : info.login) + }, + privilege: config.enableCommand.privilege +}) + +tm.commands.add({ + aliases: config.disableCommand.aliases, + help: config.disableCommand.help, + callback: info => { + if (!isActive) { + tm.sendMessage(config.disableCommand.alreadyDisabled, info.login) + return + } + isActive = false + tm.sendMessage(tm.utils.strVar(config.disableCommand.success, { + title: info.title, + name: tm.utils.strip(info.nickname) + }), config.disableCommand.public ? undefined : info.login) + }, + privilege: config.disableCommand.privilege +}) + +/** + * Sets dynamic time limits (eg. based on author time) + * @author lythx + * @since 1.16.2 + */ +export const dynamicTimeLimit = { + /** + * Activates the plugin on next map. + * @returns Boolean indicating whether the plugin is enabled. + */ + activate(): boolean { + if (!config.isEnabled) { return false } + isActive = true + return true + }, + /** + * Deactivates the plugin on next map. + * @returns Boolean indicating whether the plugin is enabled. + */ + deactivate(): boolean { + if (!config.isEnabled) { return false } + isActive = false + return true + }, + + /** + * Gets or sets the mode used to calculate the time limit. + */ + set mode(mode: DynamicTimeLimitMode) { + dynamicTimeLimitMode = mode + }, + + /** + * Gets or sets the mode used to calculate the time limit. + */ + get mode(): DynamicTimeLimitMode { + return dynamicTimeLimitMode + }, + + /** + * Gets or sets the multiplier used to calculate the time limit. + */ + set multiplier(multiplier: number) { + dynamicTimeLimitMultiplier = multiplier + }, + + /** + * Gets or sets the multiplier used to calculate the time limit. + */ + get multiplier(): number { + return dynamicTimeLimitMultiplier + }, + + /** + * Boolean indicating whether the plugin is currently active. + */ + get isActive(): boolean { + return isActive + }, + + /** + * Plugin status + */ + isEnabled: config.isEnabled + +} diff --git a/plugins/dynamic_time_limit/Types.ts b/plugins/dynamic_time_limit/Types.ts new file mode 100644 index 000000000..ad9def087 --- /dev/null +++ b/plugins/dynamic_time_limit/Types.ts @@ -0,0 +1 @@ +export type DynamicTimeLimitMode = "author" \ No newline at end of file diff --git a/plugins/freezone/Config.js b/plugins/freezone/Config.js index 9a2030e41..b118b3152 100644 --- a/plugins/freezone/Config.js +++ b/plugins/freezone/Config.js @@ -1,9 +1,6 @@ export default { - isEnabled: false, - // Manialive hash, static value that never changes - manialiveHash: '6f116833b419fe7cb9c912fdaefb774845f60e79', - // Last Manialive (for TMF at least) version release - manialiveVersion: 239, - // Freezone Manialive URL (aka Trackmania Webservices) + isEnabled: false, // Manialive hash, static value that never changes + manialiveHash: '6f116833b419fe7cb9c912fdaefb774845f60e79', // Last Manialive (for TMF at least) version release + manialiveVersion: 239, // Freezone Manialive URL (aka Trackmania Webservices) manialiveUrl: 'ws.trackmania.com' -} \ No newline at end of file +} diff --git a/plugins/freezone/Freezone.ts b/plugins/freezone/Freezone.ts index 52fa8292f..dfc52736f 100644 --- a/plugins/freezone/Freezone.ts +++ b/plugins/freezone/Freezone.ts @@ -11,14 +11,11 @@ const sendLive = async (): Promise => { const cfg: tm.ServerInfo = tm.config.server // Data object in any because TS coping language const data = { - serverLogin: cfg.login, - // Remove colours here so that we won't have to later + serverLogin: cfg.login, // Remove colours here so that we won't have to later serverName: tm.utils.strip(cfg.name, true), serverVersion: [cfg.game, cfg.version, cfg.build].join(), - manialiveVersion: config.manialiveVersion, - // Check if > 40 and set to 40 in that case - maxPlayers: cfg.currentMaxPlayers > 40 ? 40 : cfg.currentMaxPlayers, - // Public is 1, private is 0 + manialiveVersion: config.manialiveVersion, // Check if > 40 and set to 40 in that case + maxPlayers: cfg.currentMaxPlayers > 40 ? 40 : cfg.currentMaxPlayers, // Public is 1, private is 0 visibility: cfg.password.length === 0 ? 1 : 0, classHash: config.manialiveHash } @@ -34,7 +31,7 @@ const sendLive = async (): Promise => { path: `/freezone/live/`, method: 'POST', headers: { - 'Authorization': auth, + 'Authorization': auth } } return new Promise((resolve, reject): void => { @@ -79,8 +76,7 @@ if (config.isEnabled) { tm.addListener('Startup', async (): Promise => { tm.log.trace('Connecting to ManiaLive...') const status: true | Error = await initialize() - if (status instanceof Error) { tm.log.error(status.message) } - else { tm.log.trace('Connected to ManiaLive') } + if (status instanceof Error) { tm.log.error(status.message) } else { tm.log.trace('Connected to ManiaLive') } }) } diff --git a/plugins/info_messages/Config.js b/plugins/info_messages/Config.js index 41f2868dc..86da6f100 100644 --- a/plugins/info_messages/Config.js +++ b/plugins/info_messages/Config.js @@ -1,17 +1,12 @@ const p = tm.utils.palette export default { - isEnabled: true, - // All the info messages will be prefixed with this text - messagePrefix: `${p.record}[${p.highlight}INFO${p.record}]`, - // Default formatting for the info messages + isEnabled: true, // All the info messages will be prefixed with this text + messagePrefix: `${p.record}[${p.highlight}INFO${p.record}]`, // Default formatting for the info messages // That is put inbetween the prefix and the message - defaultFormatting: `$z$s `, - // Controls whether the default chat prefix is shown - chatPrefixEnabled: true, - // Messages will be chosen randomly from this array - messages: [ - `${p.highlight}This server is running ${p.rank}$L[${tm.config.controller.repo}]Trakman v${tm.config.controller.version}$L${p.highlight}.`, + defaultFormatting: `$z$s `, // Controls whether the default chat prefix is shown + chatPrefixEnabled: true, // Messages will be chosen randomly from this array + messages: [`${p.highlight}This server is running ${p.rank}$L[${tm.config.controller.repo}]Trakman v${tm.config.controller.version}$L${p.highlight}.`, `${p.highlight}Bugreports and suggestions are welcome on the ${p.rank}$L[${tm.config.controller.repo}/issues]Trakman bugtracker$L${p.highlight}.`, `${p.highlight}Use ${p.rank}/help ${p.highlight}to get you started with the chat commands.`, `${p.highlight}Make sure to ${p.rank}star our repo ${p.highlight}$L[${tm.config.controller.repo}]on GitHub$L if you like the controller.`, @@ -26,17 +21,10 @@ export default { `${p.highlight}Use ${p.rank}/pm ${p.highlight}to send private messages to other players.`, `${p.highlight}Use ${p.rank}/playtime ${p.highlight}to see for how long the current map has been played.`, `${p.highlight}Use ${p.rank}/autojuke ${p.highlight}to automatically queue a map. See ${p.rank}/man aj ${p.highlight}for options.`, - `${p.highlight}Use ${p.rank}/man ${p.highlight}with any command to see how to utilise it.`, - ], - // List of events to send a random message on, independently of the interval + `${p.highlight}Use ${p.rank}/man ${p.highlight}with any command to see how to utilise it.`], // List of events to send a random message on, independently of the interval // For the list of events, peek the wiki // Alternatively, see the Events interface in /src/Namespace.ts/ - events: [ - `EndMap`, - `adfadsfsdf`, - ], - // Whether to send a random message on a specific interval - sendOnInterval: false, - // Message sending interval - messageInterval: 120, // Seconds -} \ No newline at end of file + events: [`EndMap`, `adfadsfsdf`], // Whether to send a random message on a specific interval + sendOnInterval: false, // Message sending interval + messageInterval: 120 // Seconds +} diff --git a/plugins/info_messages/InfoMessages.ts b/plugins/info_messages/InfoMessages.ts index 1f68b9cc2..e71b55dd8 100644 --- a/plugins/info_messages/InfoMessages.ts +++ b/plugins/info_messages/InfoMessages.ts @@ -13,9 +13,8 @@ const getRandomMessage = (): string => { } const sendInfoMessage = (): void => { - tm.sendMessage(config.messagePrefix - + `${config.defaultFormatting}` - + getRandomMessage(), undefined, config.chatPrefixEnabled) + tm.sendMessage(config.messagePrefix + `${config.defaultFormatting}` + getRandomMessage(), undefined, + config.chatPrefixEnabled) } if (config.isEnabled) { diff --git a/plugins/live_checkpoints/Config.js b/plugins/live_checkpoints/Config.js index 6a25e7589..26c373366 100644 --- a/plugins/live_checkpoints/Config.js +++ b/plugins/live_checkpoints/Config.js @@ -1,3 +1,3 @@ export default { - isEnabled: false, -} \ No newline at end of file + isEnabled: false +} diff --git a/plugins/live_checkpoints/ui/LiveCpsRanking.component.ts b/plugins/live_checkpoints/ui/LiveCpsRanking.component.ts index c497ccdf3..2fd807e5c 100644 --- a/plugins/live_checkpoints/ui/LiveCpsRanking.component.ts +++ b/plugins/live_checkpoints/ui/LiveCpsRanking.component.ts @@ -1,4 +1,4 @@ -import { RecordList, componentIds, StaticHeader, StaticComponent } from '../../ui/UI.js' +import { componentIds, RecordList, StaticComponent, StaticHeader } from '../../ui/UI.js' import config from './LiveCpsRanking.config.js' interface PlayerCheckpointData { @@ -107,19 +107,19 @@ export default class LiveCpsRanking extends StaticComponent { return this.hideToPlayer(login) } const sorted = Array.from(this.playerData.values()) - .sort((a, b) => { - if (a.finished !== b.finished) { // finishers always first - return a.finished ? -1 : 1 - } - if (a.finished && b.finished) { // 2 finishers sorted by time - return (a.finishTime ?? 0) - (b.finishTime ?? 0) - } - if (a.currentCp !== b.currentCp) { // others sorted by cp index - return b.currentCp - a.currentCp - } - return a.time - b.time // otherwise just use the time - }) - .slice(0, this.getEntries()) + .sort((a, b) => { + if (a.finished !== b.finished) { // finishers always first + return a.finished ? -1 : 1 + } + if (a.finished && b.finished) { // 2 finishers sorted by time + return (a.finishTime ?? 0) - (b.finishTime ?? 0) + } + if (a.currentCp !== b.currentCp) { // others sorted by cp index + return b.currentCp - a.currentCp + } + return a.time - b.time // otherwise just use the time + }) + .slice(0, this.getEntries()) // infinity is used for finished runs, recordlist will display "F" as the index in this case // otherwise, the actual cp index is used instead of the default 1, 2, 3.. const indices = sorted.map(a => a.finished ? Infinity : a.currentCp >= 0 ? a.currentCp : -1) @@ -134,7 +134,11 @@ export default class LiveCpsRanking extends StaticComponent { this.title = config.title // actual cp times could be added but i dont think recordlist can handle the refresh in that case content = this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, records - .map(a => ({ name: a.nickname, time: a.time, login: a.login })), undefined, indices) + .map(a => ({ + name: a.nickname, + time: a.time, + login: a.login + })), undefined, indices) return { xml: ` @@ -149,7 +153,13 @@ export default class LiveCpsRanking extends StaticComponent { } } - private initPlayer({ login, nickname }: { login: string, nickname: string }): void { + private initPlayer({ + login, + nickname + }: { + login: string, + nickname: string + }): void { // using login instead of playerId as that can be overwritten with >=250 players this.playerData.set(login, { login: login, @@ -166,9 +176,12 @@ export default class LiveCpsRanking extends StaticComponent { let dontParseTime = false let noRecordEntryText: string | undefined this.recordList?.destroy?.() - this.recordList = new RecordList('race', this.id, config.width, height - (this.header.options.height + config.margin), - entries, this.side, this.getTopCount(), tm.records.maxLocalsAmount, config.displayNoRecordEntry, - { dontParseTime, noRecordEntryText }) + this.recordList = new RecordList('race', this.id, config.width, + height - (this.header.options.height + config.margin), entries, this.side, this.getTopCount(), + tm.records.maxLocalsAmount, config.displayNoRecordEntry, { + dontParseTime, + noRecordEntryText + }) this.recordList.onClick((info: tm.ManialinkClickInfo): void => { if (this.reduxModeEnabled) { return } const obj = this.displayToPlayer(info.login) diff --git a/plugins/live_checkpoints/ui/LiveCpsRanking.config.js b/plugins/live_checkpoints/ui/LiveCpsRanking.config.js index 5a3902fee..1e5cbf42a 100644 --- a/plugins/live_checkpoints/ui/LiveCpsRanking.config.js +++ b/plugins/live_checkpoints/ui/LiveCpsRanking.config.js @@ -5,7 +5,7 @@ export default { entries: 6, entryHeight: 2.15, width: cfg.width, - title: "Live Checkpoints", + title: 'Live Checkpoints', icon: icons.bestClock, margin: cfg.margin, topCount: 1, @@ -13,4 +13,4 @@ export default { cpsCollectedColour: tm.utils.palette.green, hidePanel: true, maxRecordsAmount: 3000 // If more records than this get driven in one round the click listener will break -} \ No newline at end of file +} diff --git a/plugins/maniakarma/Countries.js b/plugins/maniakarma/Countries.js index 7eb080c76..84eb26640 100644 --- a/plugins/maniakarma/Countries.js +++ b/plugins/maniakarma/Countries.js @@ -1,374 +1,281 @@ -export default [ - { - "code": "DZA", - "name": "Algeria" - }, - { - "code": "AGO", - "name": "Angola" - }, - { - "code": "ARG", - "name": "Argentina" - }, - { - "code": "AUS", - "name": "Australia" - }, - { - "code": "AUT", - "name": "Austria" - }, - { - "code": "BGD", - "name": "Bangladesh" - }, - { - "code": "BEL", - "name": "Belgium" - }, - { - "code": "BIH", - "name": "Bosnia&Herzegovina" - }, - { - "code": "BLR", - "name": "Belarus" - }, - { - "code": "BOL", - "name": "Bolivia" - }, - { - "code": "BRA", - "name": "Brazil" - }, - { - "code": "BHR", - "name": "Bahrain" - }, - { - "code": "BGR", - "name": "Bulgaria" - }, - { - "code": "CAN", - "name": "Canada" - }, - { - "code": "COG", - "name": "Congo" - }, - { - "code": "CHL", - "name": "Chile" - }, - { - "code": "CHN", - "name": "China" - }, - { - "code": "CIV", - "name": "Ivory Coast" - }, - { - "code": "CMR", - "name": "Cameroon" - }, - { - "code": "COL", - "name": "Colombia" - }, - { - "code": "HRV", - "name": "Croatia" - }, - { - "code": "CYP", - "name": "Cyprus" - }, - { - "code": "CZE", - "name": "Czech republic" - }, - { - "code": "DNK", - "name": "Denmark" - }, - { - "code": "ECU", - "name": "Ecuador" - }, - { - "code": "EGY", - "name": "Egypt" - }, - { - "code": "ESP", - "name": "Spain" - }, - { - "code": "EST", - "name": "Estonia" - }, - { - "code": "FIN", - "name": "Finland" - }, - { - "code": "FRA", - "name": "France" - }, - { - "code": "GBR", - "name": "United Kingdom" - }, - { - "code": "DEU", - "name": "Germany" - }, - { - "code": "GRC", - "name": "Greece" - }, - { - "code": "GTM", - "name": "Guatemala" - }, - { - "code": "HUN", - "name": "Hungary" - }, - { - "code": "IDN", - "name": "Indonesia" - }, - { - "code": "IND", - "name": "India" - }, - { - "code": "IRN", - "name": "Iran" - }, - { - "code": "IRL", - "name": "Ireland" - }, - { - "code": "IRQ", - "name": "Iraq" - }, - { - "code": "ISR", - "name": "Israel" - }, - { - "code": "ITA", - "name": "Italy" - }, - { - "code": "JPN", - "name": "Japan" - }, - { - "code": "KAZ", - "name": "Kazakhstan" - }, - { - "code": "KOR", - "name": "Korea" - }, - { - "code": "SAU", - "name": "Saudi Arabia" - }, - { - "code": "KWT", - "name": "Kuwait" - }, - { - "code": "LAO", - "name": "Laos" - }, - { - "code": "LVA", - "name": "Latvia" - }, - { - "code": "LBY", - "name": "Libya" - }, - { - "code": "LBR", - "name": "Lebanon" - }, - { - "code": "LTU", - "name": "Lithuania" - }, - { - "code": "LUX", - "name": "Luxembourg" - }, - { - "code": "MAR", - "name": "Morocco" - }, - { - "code": "MYS", - "name": "Malaysia" - }, - { - "code": "MEX", - "name": "Mexico" - }, - { - "code": "MNG", - "name": "Mongolia" - }, - { - "code": "MKD", - "name": "Macedonia" - }, - { - "code": "NLD", - "name": "Netherlands" - }, - { - "code": "NGA", - "name": "Nigeria" - }, - { - "code": "NOR", - "name": "Norway" - }, - { - "code": "NZL", - "name": "New Zealand" - }, - { - "code": "OMN", - "name": "Oman" - }, - { - // YOU MIGHT BE WONDERING WHY IS OTHER COUNTRIES SUDDENLY POLAND? - // WELL, IT TURNS OUT THERE'S NO WAY TO SEND OTHER COUNTRIES - // TO THE MANIAKARMA API, THEREFORE WE DEFAULT TO POLAND - // DIRECT ALL YOUR QUESTIONS TO THE GENIUS BEHIND THE API! - "code": "POL", - "name": "Other Countries" - }, - { - "code": "PAK", - "name": "Pakistan" - }, - { - "code": "PAN", - "name": "Panama" - }, - { - "code": "PER", - "name": "Peru" - }, - { - "code": "PHL", - "name": "Philippines" - }, - { - "code": "POL", - "name": "Poland" - }, - { - "code": "PRT", - "name": "Portugal" - }, - { - "code": "QAT", - "name": "Qatar" - }, - { - "code": "ROU", - "name": "Romania" - }, - { - "code": "ZAF", - "name": "South Africa" - }, - { - "code": "RUS", - "name": "Russia" - }, - { - "code": "SEN", - "name": "Senegal" - }, - { - "code": "SGP", - "name": "Singapore" - }, - { - "code": "SVN", - "name": "Slovenia" - }, - { - "code": "SRB", - "name": "Serbia" - }, - { - "code": "CHE", - "name": "Switzerland" - }, - { - "code": "SVK", - "name": "Slovakia" - }, - { - "code": "SWE", - "name": "Sweden" - }, - { - "code": "SYR", - "name": "Syria" - }, - { - "code": "THA", - "name": "Thailand" - }, - { - "code": "TUN", - "name": "Tunisia" - }, - { - "code": "TUR", - "name": "Turkey" - }, - { - "code": "ARE", - "name": "United Arab Emirates" - }, - { - "code": "UKR", - "name": "Ukraine" - }, - { - "code": "URY", - "name": "Uruguay" - }, - { - "code": "USA", - "name": "United States of America" - }, - { - "code": "UZB", - "name": "Uzbekistan" - }, - { - "code": "VEN", - "name": "Venezuela" - }, - { - "code": "VNM", - "name": "Vietnam" - } -] \ No newline at end of file +export default [{ + 'code': 'DZA', + 'name': 'Algeria' +}, { + 'code': 'AGO', + 'name': 'Angola' +}, { + 'code': 'ARG', + 'name': 'Argentina' +}, { + 'code': 'AUS', + 'name': 'Australia' +}, { + 'code': 'AUT', + 'name': 'Austria' +}, { + 'code': 'BGD', + 'name': 'Bangladesh' +}, { + 'code': 'BEL', + 'name': 'Belgium' +}, { + 'code': 'BIH', + 'name': 'Bosnia&Herzegovina' +}, { + 'code': 'BLR', + 'name': 'Belarus' +}, { + 'code': 'BOL', + 'name': 'Bolivia' +}, { + 'code': 'BRA', + 'name': 'Brazil' +}, { + 'code': 'BHR', + 'name': 'Bahrain' +}, { + 'code': 'BGR', + 'name': 'Bulgaria' +}, { + 'code': 'CAN', + 'name': 'Canada' +}, { + 'code': 'COG', + 'name': 'Congo' +}, { + 'code': 'CHL', + 'name': 'Chile' +}, { + 'code': 'CHN', + 'name': 'China' +}, { + 'code': 'CIV', + 'name': 'Ivory Coast' +}, { + 'code': 'CMR', + 'name': 'Cameroon' +}, { + 'code': 'COL', + 'name': 'Colombia' +}, { + 'code': 'HRV', + 'name': 'Croatia' +}, { + 'code': 'CYP', + 'name': 'Cyprus' +}, { + 'code': 'CZE', + 'name': 'Czech republic' +}, { + 'code': 'DNK', + 'name': 'Denmark' +}, { + 'code': 'ECU', + 'name': 'Ecuador' +}, { + 'code': 'EGY', + 'name': 'Egypt' +}, { + 'code': 'ESP', + 'name': 'Spain' +}, { + 'code': 'EST', + 'name': 'Estonia' +}, { + 'code': 'FIN', + 'name': 'Finland' +}, { + 'code': 'FRA', + 'name': 'France' +}, { + 'code': 'GBR', + 'name': 'United Kingdom' +}, { + 'code': 'DEU', + 'name': 'Germany' +}, { + 'code': 'GRC', + 'name': 'Greece' +}, { + 'code': 'GTM', + 'name': 'Guatemala' +}, { + 'code': 'HUN', + 'name': 'Hungary' +}, { + 'code': 'IDN', + 'name': 'Indonesia' +}, { + 'code': 'IND', + 'name': 'India' +}, { + 'code': 'IRN', + 'name': 'Iran' +}, { + 'code': 'IRL', + 'name': 'Ireland' +}, { + 'code': 'IRQ', + 'name': 'Iraq' +}, { + 'code': 'ISR', + 'name': 'Israel' +}, { + 'code': 'ITA', + 'name': 'Italy' +}, { + 'code': 'JPN', + 'name': 'Japan' +}, { + 'code': 'KAZ', + 'name': 'Kazakhstan' +}, { + 'code': 'KOR', + 'name': 'Korea' +}, { + 'code': 'SAU', + 'name': 'Saudi Arabia' +}, { + 'code': 'KWT', + 'name': 'Kuwait' +}, { + 'code': 'LAO', + 'name': 'Laos' +}, { + 'code': 'LVA', + 'name': 'Latvia' +}, { + 'code': 'LBY', + 'name': 'Libya' +}, { + 'code': 'LBR', + 'name': 'Lebanon' +}, { + 'code': 'LTU', + 'name': 'Lithuania' +}, { + 'code': 'LUX', + 'name': 'Luxembourg' +}, { + 'code': 'MAR', + 'name': 'Morocco' +}, { + 'code': 'MYS', + 'name': 'Malaysia' +}, { + 'code': 'MEX', + 'name': 'Mexico' +}, { + 'code': 'MNG', + 'name': 'Mongolia' +}, { + 'code': 'MKD', + 'name': 'Macedonia' +}, { + 'code': 'NLD', + 'name': 'Netherlands' +}, { + 'code': 'NGA', + 'name': 'Nigeria' +}, { + 'code': 'NOR', + 'name': 'Norway' +}, { + 'code': 'NZL', + 'name': 'New Zealand' +}, { + 'code': 'OMN', + 'name': 'Oman' +}, { + // YOU MIGHT BE WONDERING WHY IS OTHER COUNTRIES SUDDENLY POLAND? + // WELL, IT TURNS OUT THERE'S NO WAY TO SEND OTHER COUNTRIES + // TO THE MANIAKARMA API, THEREFORE WE DEFAULT TO POLAND + // DIRECT ALL YOUR QUESTIONS TO THE GENIUS BEHIND THE API! + 'code': 'POL', + 'name': 'Other Countries' +}, { + 'code': 'PAK', + 'name': 'Pakistan' +}, { + 'code': 'PAN', + 'name': 'Panama' +}, { + 'code': 'PER', + 'name': 'Peru' +}, { + 'code': 'PHL', + 'name': 'Philippines' +}, { + 'code': 'POL', + 'name': 'Poland' +}, { + 'code': 'PRT', + 'name': 'Portugal' +}, { + 'code': 'QAT', + 'name': 'Qatar' +}, { + 'code': 'ROU', + 'name': 'Romania' +}, { + 'code': 'ZAF', + 'name': 'South Africa' +}, { + 'code': 'RUS', + 'name': 'Russia' +}, { + 'code': 'SEN', + 'name': 'Senegal' +}, { + 'code': 'SGP', + 'name': 'Singapore' +}, { + 'code': 'SVN', + 'name': 'Slovenia' +}, { + 'code': 'SRB', + 'name': 'Serbia' +}, { + 'code': 'CHE', + 'name': 'Switzerland' +}, { + 'code': 'SVK', + 'name': 'Slovakia' +}, { + 'code': 'SWE', + 'name': 'Sweden' +}, { + 'code': 'SYR', + 'name': 'Syria' +}, { + 'code': 'THA', + 'name': 'Thailand' +}, { + 'code': 'TUN', + 'name': 'Tunisia' +}, { + 'code': 'TUR', + 'name': 'Turkey' +}, { + 'code': 'ARE', + 'name': 'United Arab Emirates' +}, { + 'code': 'UKR', + 'name': 'Ukraine' +}, { + 'code': 'URY', + 'name': 'Uruguay' +}, { + 'code': 'USA', + 'name': 'United States of America' +}, { + 'code': 'UZB', + 'name': 'Uzbekistan' +}, { + 'code': 'VEN', + 'name': 'Venezuela' +}, { + 'code': 'VNM', + 'name': 'Vietnam' +}] diff --git a/plugins/maniakarma/Maniakarma.ts b/plugins/maniakarma/Maniakarma.ts index c2b65da51..ed814a066 100644 --- a/plugins/maniakarma/Maniakarma.ts +++ b/plugins/maniakarma/Maniakarma.ts @@ -8,12 +8,23 @@ let authCode: string let isConnected = false let apiUrl: string let mapKarmaValue = 0 -let mapKarma: MKMapVotes = { fantastic: 0, beautiful: 0, good: 0, bad: 0, poor: 0, waste: 0 } +let mapKarma: MKMapVotes = { + fantastic: 0, + beautiful: 0, + good: 0, + bad: 0, + poor: 0, + waste: 0 +} const playerVotes: MKVote[] = [] const newVotes: MKVote[] = [] let lastMap: Readonly -const mapFetchListeners: ((info: { votes: MKVote[], ratio: number, karma: MKMapVotes }) => void)[] = [] +const mapFetchListeners: ((info: { + votes: MKVote[], + ratio: number, + karma: MKMapVotes +}) => void)[] = [] const voteListeners: ((votes: MKVote[]) => void)[] = [] const playerFetchListeners: ((vote: MKVote) => void)[] = [] const defaultKarmaObject: Readonly = { @@ -26,7 +37,13 @@ const defaultKarmaObject: Readonly = { } const emitMapFetch = (votes: MKVote[], ratio: number, karma: MKMapVotes): void => { - for (const e of mapFetchListeners) { e({ votes, ratio, karma }) } + for (const e of mapFetchListeners) { + e({ + votes, + ratio, + karma + }) + } } const emitVote = (...votes: MKVote[]): void => { @@ -116,8 +133,7 @@ const fetchVotes = async (...logins: string[]): Promise => { } const json: any = getJson(await res.text()) if (checkResError(json)) { - const err = new Error(`Failed to fetch maniakarma votes, received` + - ` response:\n${JSON.stringify(json, null, 2)}`) + const err = new Error(`Failed to fetch maniakarma votes, received` + ` response:\n${JSON.stringify(json, null, 2)}`) tm.log.error(err.message) return err } @@ -132,7 +148,11 @@ const fetchVotes = async (...logins: string[]): Promise => { const arr: [-3, -2, -1, 1, 2, 3] = [-3, -2, -1, 1, 2, 3] const v = arr.find(a => a === vote) if (v !== undefined) { - ret.push({ mapId: tm.maps.current.id, vote: v, login }) + ret.push({ + mapId: tm.maps.current.id, + vote: v, + login + }) storePlayerVotes(login, v) } } @@ -163,13 +183,13 @@ const sendVotes = async (newVotes: MKVote[]): Promise => { return } if (!res.ok) { - tm.log.error(`Failed to send maniakarma votes for map ${lastMap.id}.`, - `Code: ${res.status} Text: ${res.status}`) + tm.log.error(`Failed to send maniakarma votes for map ${lastMap.id}.`, `Code: ${res.status} Text: ${res.status}`) return } const json = getJson(await res.text()) if (checkResError(json)) { - tm.log.error(`Failed to send maniakarma votes for map ${lastMap.id}, received response:`, JSON.stringify(json, null, 2)) + tm.log.error(`Failed to send maniakarma votes for map ${lastMap.id}, received response:`, + JSON.stringify(json, null, 2)) } } @@ -206,9 +226,14 @@ const getJson = (data: string): any => { } function addVote(mapId: string, login: string, vote: -3 | -2 | -1 | 1 | 2 | 3): void -function addVote(mapId: string, votes: readonly { login: string, vote: -3 | -2 | -1 | 1 | 2 | 3 }[]): void -function addVote(mapId: string, arg: string | readonly { login: string, vote: -3 | -2 | -1 | 1 | 2 | 3 }[] - , vote?: -3 | -2 | -1 | 1 | 2 | 3): void { +function addVote(mapId: string, votes: readonly { + login: string, + vote: -3 | -2 | -1 | 1 | 2 | 3 +}[]): void +function addVote(mapId: string, arg: string | readonly { + login: string, + vote: -3 | -2 | -1 | 1 | 2 | 3 +}[], vote?: -3 | -2 | -1 | 1 | 2 | 3): void { const voteNames: string[] = ['waste', 'poor', 'bad', 'good', 'beautiful', 'fantastic'] if (Array.isArray(arg)) { const updated: MKVote[] = [] @@ -217,21 +242,37 @@ function addVote(mapId: string, arg: string | readonly { login: string, vote: -3 if (v?.login === e.login && v?.vote === e.vote) { continue } mapKarma[voteNames[e.vote > 0 ? e.vote + 2 : e.vote + 3] as keyof typeof mapKarma]++ if (v === undefined) { - const obj = { mapId, login: e.login, vote: e.vote } + const obj = { + mapId, + login: e.login, + vote: e.vote + } playerVotes.push(obj) updated.push(obj) - } - else { + } else { mapKarma[voteNames[v.vote > 0 ? v.vote + 2 : v.vote + 3] as keyof typeof mapKarma]-- v.vote = e.vote updated.push(v) } const newVote: MKVote | undefined = newVotes.find(a => a.login === e.login) - if (newVote === undefined) { newVotes.push({ mapId, login: e.login, vote: e.vote }) } - else { newVote.vote = e.vote } - const voteValues = { waste: 0, poor: 20, bad: 40, good: 60, beautiful: 80, fantastic: 100 } + if (newVote === undefined) { + newVotes.push({ + mapId, + login: e.login, + vote: e.vote + }) + } else { newVote.vote = e.vote } + const voteValues = { + waste: 0, + poor: 20, + bad: 40, + good: 60, + beautiful: 80, + fantastic: 100 + } const count = Object.values(mapKarma).reduce((acc, cur) => acc + cur, 0) - mapKarmaValue = Object.entries(mapKarma).map(a => (voteValues as any)[a[0]] * a[1]).reduce((acc, cur): number => acc + cur, 0) / count + mapKarmaValue = Object.entries(mapKarma).map(a => (voteValues as any)[a[0]] * a[1]) + .reduce((acc, cur): number => acc + cur, 0) / count } emitVote(...updated) return @@ -241,18 +282,40 @@ function addVote(mapId: string, arg: string | readonly { login: string, vote: -3 mapKarma[voteNames[vote > 0 ? vote + 2 : vote + 3] as keyof typeof mapKarma]++ const v: MKVote | undefined = playerVotes.find(a => a.login === login) if (v?.login === login && v.vote === vote) { return } - if (v === undefined) { playerVotes.push({ mapId, login, vote }) } - else { + if (v === undefined) { + playerVotes.push({ + mapId, + login, + vote + }) + } else { mapKarma[voteNames[v.vote > 0 ? v.vote + 2 : v.vote + 3] as keyof typeof mapKarma]-- v.vote = vote } const newVote: MKVote | undefined = newVotes.find(a => a.login === login) - if (newVote === undefined) { newVotes.push({ mapId, login, vote }) } - else { newVote.vote = vote } - const voteValues = { waste: 0, poor: 20, bad: 40, good: 60, beautiful: 80, fantastic: 100 } + if (newVote === undefined) { + newVotes.push({ + mapId, + login, + vote + }) + } else { newVote.vote = vote } + const voteValues = { + waste: 0, + poor: 20, + bad: 40, + good: 60, + beautiful: 80, + fantastic: 100 + } const count = Object.values(mapKarma).reduce((acc, cur) => acc + cur, 0) - mapKarmaValue = Object.entries(mapKarma).map(a => (voteValues as any)[a[0]] * a[1]).reduce((acc, cur): number => acc + cur, 0) / count - emitVote({ mapId, login, vote }) + mapKarmaValue = Object.entries(mapKarma).map(a => (voteValues as any)[a[0]] * a[1]) + .reduce((acc, cur): number => acc + cur, 0) / count + emitVote({ + mapId, + login, + vote + }) } const fixCoherence = async (): Promise => { @@ -262,7 +325,10 @@ const fixCoherence = async (): Promise => { const v: tm.Vote | undefined = localVotes.find(a => a.login === e.login && a.vote === e.vote) if (v === undefined) { const nickname: string | undefined = tm.players.get(e.login)?.nickname - tm.karma.add({ login: e.login, nickname: nickname ?? e.login }, e.vote) + tm.karma.add({ + login: e.login, + nickname: nickname ?? e.login + }, e.vote) } } addVote(tm.maps.current.id, localVotes) @@ -369,7 +435,11 @@ export const maniakarma = { * Adds a callback function to execute when maniakarma votes for new map get fetched * @param callback Function to execute on event. It takes object of votes array and map vote ratio */ - onMapFetch(callback: ((data: { votes: MKVote[], ratio: number, karma: MKMapVotes }) => void)) { + onMapFetch(callback: ((data: { + votes: MKVote[], + ratio: number, + karma: MKMapVotes + }) => void)) { mapFetchListeners.push(callback) }, diff --git a/plugins/maplist/Autojuke.ts b/plugins/maplist/Autojuke.ts index eef573fd4..7cd070b96 100644 --- a/plugins/maplist/Autojuke.ts +++ b/plugins/maplist/Autojuke.ts @@ -7,11 +7,13 @@ import { maplist } from './Maplist.js' import config from './Config.js' -const chooseAndAddMap = (maps: tm.Map[], info: { nickname: string, login: string }, errorMsg: string): void => { - const eligibleMaps: tm.Map[] = maps.filter(a => - !tm.jukebox.juked.some(b => b.map.id === a.id) && - !tm.jukebox.history.some(b => b.id === a.id) && - tm.maps.current.id !== a.id) +const chooseAndAddMap = (maps: tm.Map[], info: { + nickname: string, + login: string +}, errorMsg: string): void => { + const eligibleMaps: tm.Map[] = maps.filter( + a => !tm.jukebox.juked.some(b => b.map.id === a.id) && !tm.jukebox.history.some( + b => b.id === a.id) && tm.maps.current.id !== a.id) if (eligibleMaps.length === 0) { tm.sendMessage(errorMsg, info.login) return @@ -24,14 +26,18 @@ const chooseAndAddMap = (maps: tm.Map[], info: { nickname: string, login: string })) } -const autojuke = async (info: { privilege: number, login: string, nickname: string }, - option?: string): Promise => { +const autojuke = async (info: { + privilege: number, + login: string, + nickname: string +}, option?: string): Promise => { if (info.privilege <= 0 && tm.jukebox.juked.some(a => a.callerLogin === info.login)) { tm.sendMessage(config.noPermission, info.login) return } - switch (option) { - case 'nofinish': case 'nofin': { + switch(option) { + case 'nofinish': + case 'nofin': { const mapsNoRec: Readonly[] = await maplist.filterNoFinish(info.login) chooseAndAddMap(mapsNoRec, info, config.noFinishError) break @@ -55,7 +61,10 @@ const autojuke = async (info: { privilege: number, login: string, nickname: stri tm.commands.add({ aliases: config.autojuke.aliases, help: config.autojuke.help, - params: [{ name: 'option', optional: true }], + params: [{ + name: 'option', + optional: true + }], callback: autojuke, privilege: config.autojuke.privilege }) diff --git a/plugins/maplist/Config.js b/plugins/maplist/Config.js index 75b82d8e0..cb40e4c6f 100644 --- a/plugins/maplist/Config.js +++ b/plugins/maplist/Config.js @@ -12,5 +12,5 @@ export default { aliases: ['aj', 'autojuke'], help: `Juke a random map. Options: nofinish(nofin), norank, noauthor`, privilege: 0 - }, -} \ No newline at end of file + } +} diff --git a/plugins/maplist/Maplist.ts b/plugins/maplist/Maplist.ts index 8adb0cd5b..2056db2e4 100644 --- a/plugins/maplist/Maplist.ts +++ b/plugins/maplist/Maplist.ts @@ -12,17 +12,17 @@ let oldestSort: tm.Map[] = [] const jukebox: tm.Map[] = [] let cache: { type: 'best' | 'worst' | 'name' | 'author' | 'nofin' | 'norank' | 'noauthor' | 'newest' | 'oldest', - query: string, list: tm.Map[] + query: string, + list: tm.Map[] }[] = [] const updateListeners: ((action: 'add' | 'remove', addedOrRemovedMap: Readonly) => void)[] = [] const jukeboxUpdateListeners: ((jukebox: readonly Readonly[]) => void)[] = [] function initialize(): void { - const arr: tm.Map[] = tm.maps.list.sort((a, b): number => a.name.localeCompare(b.name)) - authorSort = arr.sort((a, b): number => a.author.localeCompare(b.author)) + authorSort = tm.maps.list.sort((a, b): number => a.author.localeCompare(b.author)) nameSort = [...authorSort].sort((a, b): number => a.name.localeCompare(b.name)) karmaSort = [...authorSort].sort((a, b): number => { - return a.voteRatio ?? 0 - b.voteRatio ?? 0 + return a.voteRatio ?? 0 - (b.voteRatio ?? 0) }) worstKarmaSort = [...karmaSort].reverse() atSort = [...authorSort].sort((a, b): number => a.authorTime - b.authorTime) @@ -31,35 +31,29 @@ function initialize(): void { newestSort = [...oldestSort].reverse() } -function insertMap(map: tm.MapAddedInfo): void { - authorSort.splice(authorSort.findIndex(a => map.author.localeCompare(a.author) - && map.name.localeCompare(a.name)), 0, map) - nameSort.splice(nameSort.findIndex(a => map.author.localeCompare(a.author) - && map.name.localeCompare(a.name)), 0, map) +async function insertMap(map: tm.MapAddedInfo): Promise { + authorSort.splice(authorSort.findIndex(a => map.author.localeCompare(a.author) && map.name.localeCompare(a.name)), 0, + map) + nameSort.splice(nameSort.findIndex(a => map.author.localeCompare(a.author) && map.name.localeCompare(a.name)), 0, map) const ratio: number = map.voteRatio - karmaSort.splice(karmaSort.findIndex(a => map.author.localeCompare(a.author) - && map.name.localeCompare(a.name) - && ratio > map.voteRatio), 0, map) - worstKarmaSort.splice(worstKarmaSort.findIndex(a => !map.author.localeCompare(a.author) - && !map.name.localeCompare(a.name) - && ratio <= map.voteRatio), 0, map) - atSort.splice(atSort.findIndex(a => map.author.localeCompare(a.author) - && map.name.localeCompare(a.name) - && map.authorTime < a.authorTime), 0, map) - worstAtSort.splice(worstAtSort.findIndex(a => !map.author.localeCompare(a.author) - && !map.name.localeCompare(a.name) - && map.authorTime >= a.authorTime), 0, map) - newestSort.splice(newestSort.findIndex(a => !map.author.localeCompare(a.author) - && map.name.localeCompare(a.name) - && map.addDate.getTime() < a.addDate.getTime()), 0, map) - oldestSort.splice(oldestSort.findIndex(a => !map.author.localeCompare(a.author) - && !map.name.localeCompare(a.name) - && map.addDate.getTime() >= a.addDate.getTime()), 0, map) + karmaSort.splice(karmaSort.findIndex( + a => map.author.localeCompare(a.author) && map.name.localeCompare(a.name) && ratio > map.voteRatio), 0, map) + worstKarmaSort.splice(worstKarmaSort.findIndex( + a => !map.author.localeCompare(a.author) && !map.name.localeCompare(a.name) && ratio <= map.voteRatio), 0, map) + atSort.splice(atSort.findIndex( + a => map.author.localeCompare(a.author) && map.name.localeCompare(a.name) && map.authorTime < a.authorTime), 0, map) + worstAtSort.splice(worstAtSort.findIndex( + a => !map.author.localeCompare(a.author) && !map.name.localeCompare(a.name) && map.authorTime >= a.authorTime), 0, + map) + newestSort.splice(newestSort.findIndex(a => !map.author.localeCompare(a.author) && map.name.localeCompare( + a.name) && map.addDate.getTime() < a.addDate.getTime()), 0, map) + oldestSort.splice(oldestSort.findIndex(a => !map.author.localeCompare(a.author) && !map.name.localeCompare( + a.name) && map.addDate.getTime() >= a.addDate.getTime()), 0, map) cache.length = 0 for (const e of updateListeners) { e('add', map) } } -function removeMap(map: tm.MapRemovedInfo): void { +async function removeMap(map: tm.MapRemovedInfo): Promise { authorSort.splice(authorSort.findIndex(a => a.id === map.id), 1) nameSort.splice(nameSort.findIndex(a => a.id === map.id), 1) karmaSort.splice(karmaSort.findIndex(a => a.id === map.id), 1) @@ -74,7 +68,7 @@ function removeMap(map: tm.MapRemovedInfo): void { function addOrRemove(map: tm.MapAddedInfo | tm.MapAddedInfo[], fun: Function): void { if (!Array.isArray(map)) { - fun(map) + void fun(map) return } // If the list of added or removed maps is small enough, use insertion/deletion; @@ -92,7 +86,6 @@ tm.addListener('JukeboxChanged', (list): void => { for (const e of jukeboxUpdateListeners) { e(list) } }) - tm.addListener('Startup', initialize) tm.addListener('MapAdded', (map: tm.MapAddedInfo | tm.MapAddedInfo[]): void => { @@ -124,7 +117,7 @@ export const maplist = { /** * Add a callback function to execute on list update - * @param callback Functon to execute on event, it takes action type and added od removed map as parameters + * @param callback Function to execute on event, it takes action type and added od removed map as parameters */ onListUpdate(callback: (action: 'add' | 'remove', addedOrRemovedMap: Readonly) => void) { updateListeners.push(callback) @@ -132,7 +125,7 @@ export const maplist = { /** * Add a callback function to execute on jukebox update - * @param callback Functon to execute on event, it takes updated jukebox as parameter + * @param callback Function to execute on event, it takes updated jukebox as parameter */ onJukeboxUpdate(callback: (jukebox: readonly Readonly[]) => void) { jukeboxUpdateListeners.push(callback) @@ -144,7 +137,7 @@ export const maplist = { * @returns Array of map objects */ get: (sort?: 'jukebox' | 'name' | 'karma' | 'long' | 'short' | 'worstkarma' | 'author' | 'oldest' | 'newest'): readonly Readonly[] => { - switch (sort) { + switch(sort) { case 'jukebox': return jukebox case 'name': @@ -161,7 +154,8 @@ export const maplist = { return oldestSort case 'newest': return newestSort - case 'author': default: + case 'author': + default: return authorSort } }, @@ -172,39 +166,35 @@ export const maplist = { * @param sort Sorting criteria * @returns Array of map objects */ - getByPosition: (login: string, sort: 'best' | 'worst'): Readonly[] => { - if (sort === 'best') { - let list: tm.Map[] | undefined = cache.find(a => a.query === login && a.type === 'best')?.list - if (list === undefined) { - list = [] - const ranks: { mapId: string, rank: number }[] = - tm.records.getRank(login, tm.maps.list.map(a => a.id)) - .sort((a, b): number => a.rank - b.rank) - for (let i: number = 0; i < ranks.length; i++) { - const entry: tm.Map | undefined = authorSort.find(a => a.id === ranks[i].mapId) - if (entry !== undefined) { list.push(entry) } - } - cache.unshift({ query: login, list, type: 'best' }) - cache.length = Math.min(config.cacheSize, cache.length) - } - return list - } - else { - let list: tm.Map[] | undefined = cache.find(a => a.query === login && a.type === 'worst')?.list - if (list === undefined) { - list = [] - const ranks: { mapId: string, rank: number }[] = - tm.records.getRank(login, tm.maps.list.map(a => a.id)) - .sort((a, b): number => b.rank - a.rank) - for (let i: number = 0; i < ranks.length; i++) { - const entry: tm.Map | undefined = authorSort.find(a => a.id === ranks[i].mapId) - if (entry !== undefined) { list.push(entry) } + getByPosition: async (login: string, sort: 'best' | 'worst'): Promise[]> => { + let list: tm.Map[] | undefined = cache.find(a => a.query === login && a.type === sort)?.list + if (list === undefined) { + const order = sort === 'best' ? 1 : -1 + const ranks: { + mapId: string, + rank: number + }[] = (await tm.records.getRank(login, tm.maps.list.map(a => a.id))) + .sort((a, b): number => (a.rank - b.rank) * order) + const rankMap = new Map(ranks.map((e, i) => [e.mapId, i])) + list = Array(rankMap.size) + // theoretically, the list cannot contain empty spots after this loop + // practically, im not sure, because a map might magically get deleted out of the authorsort array, TODO: see if it works + for (const map of authorSort) { + const i = rankMap.get(map.id) + if (i !== undefined) { + list[i] = map } - cache.unshift({ query: login, list, type: 'worst' }) - cache.length = Math.min(config.cacheSize, cache.length) } - return list + // in case it doesn't work just uncomment this, it's not much overhead but its overhead nonetheless + // list.filter(a => a) // delete empty slots from the list + cache.unshift({ + query: login, + list, + type: sort + }) + cache.length = Math.min(config.cacheSize, cache.length) } + return list }, /** @@ -212,13 +202,17 @@ export const maplist = { * @param query Search query * @returns Array of map objects */ - searchByName: (query: string): Readonly[] => { + searchByName: async (query: string): Promise[]> => { let list: tm.Map[] | undefined = cache.find(a => a.query === query && a.type === 'name')?.list let indices: number[] if (list === undefined) { - indices = tm.utils.matchString(query, authorSort.map(a => a.name)) + indices = tm.utils.matchString(query, authorSort.map(a => a.name), true) list = indices.map(a => authorSort[a]) - cache.unshift({ query, list, type: 'name' }) + cache.unshift({ + query, + list, + type: 'name' + }) cache.length = Math.min(config.cacheSize, cache.length) } return list @@ -229,13 +223,17 @@ export const maplist = { * @param query Search query * @returns Array of map objects */ - searchByAuthor: (query: string): Readonly[] => { + searchByAuthor: async (query: string): Promise[]> => { let list: tm.Map[] | undefined = cache.find(a => a.query === query && a.type === 'author')?.list let indices: number[] if (list === undefined) { - indices = tm.utils.matchString(query, authorSort.map(a => a.author)) + indices = tm.utils.matchString(query, authorSort.map(a => a.author), true) list = indices.map(a => authorSort[a]) - cache.unshift({ query, list, type: 'author' }) + cache.unshift({ + query, + list, + type: 'author' + }) cache.length = Math.min(config.cacheSize, cache.length) } return list @@ -267,7 +265,11 @@ export const maplist = { if (list === undefined) { const mapsWithRec: string[] = (await tm.records.fetchByLogin(login)).map(a => a.map) list = authorSort.filter(a => !mapsWithRec.includes(a.id)) - cache.unshift({ query: login, list, type: 'nofin' }) + cache.unshift({ + query: login, + list, + type: 'nofin' + }) cache.length = Math.min(config.cacheSize, cache.length) } return list @@ -282,10 +284,14 @@ export const maplist = { let list: tm.Map[] | undefined = cache.find(a => a.query === login && a.type === 'norank')?.list if (list === undefined) { const mapsWithAuthor: string[] = (await tm.records.fetchByLogin(login)) - .filter(a => authorSort.find(b => b.id === a.map)?.authorTime ?? Infinity < a.time) - .map(a => a.map) + .filter(a => authorSort.find(b => b.id === a.map)?.authorTime ?? Infinity < a.time) + .map(a => a.map) list = authorSort.filter(a => !mapsWithAuthor.includes(a.id)) - cache.unshift({ query: login, list, type: 'norank' }) + cache.unshift({ + query: login, + list, + type: 'norank' + }) cache.length = Math.min(config.cacheSize, cache.length) } return list @@ -299,10 +305,14 @@ export const maplist = { filterNoAuthor: async (login: string): Promise[]> => { let list: tm.Map[] | undefined = cache.find(a => a.query === login && a.type === 'noauthor')?.list if (list === undefined) { - const ranks = (tm.records.getRank(login, authorSort.map(a => a.id))) - .filter(a => a.rank <= tm.records.maxLocalsAmount) + const ranks = (await (tm.records.getRank(login, authorSort.map(a => a.id)))) + .filter(a => a.rank <= tm.records.maxLocalsAmount) list = authorSort.filter(a => !ranks.some(b => a.id === b.mapId)) - cache.unshift({ query: login, list, type: 'noauthor' }) + cache.unshift({ + query: login, + list, + type: 'noauthor' + }) cache.length = Math.min(config.cacheSize, cache.length) } return list diff --git a/plugins/maplist/ui/Maplist.component.ts b/plugins/maplist/ui/Maplist.component.ts index 08a0aae4c..2d687eb35 100644 --- a/plugins/maplist/ui/Maplist.component.ts +++ b/plugins/maplist/ui/Maplist.component.ts @@ -3,22 +3,32 @@ * @since 0.1 */ -import { centeredText, closeButton, Grid, componentIds, leftAlignedText, addManialinkListener, PopupWindow, Paginator } from '../../ui/UI.js' +import { addManialinkListener, centeredText, closeButton, componentIds, Grid, leftAlignedText, Paginator, PopupWindow } from '../../ui/UI.js' import { actions } from '../../actions/Actions.js' import { maplist } from '../Maplist.js' import config from './Maplist.config.js' -export default class MapList extends PopupWindow<{ page: number, paginator: Paginator, list?: readonly tm.Map[] }> { +export default class MapList extends PopupWindow<{ + page: number, + paginator: Paginator, + list?: readonly tm.Map[] +}> { private readonly paginator: Paginator private readonly mapAddId: number = 1000 private readonly maxMapCount = 1_000_000 private readonly grid: Grid private readonly mapActionIds: string[] = [] - private readonly playerQueries: { paginator: Paginator, list: readonly tm.Map[], login: string, query?: string }[] = [] + private readonly playerQueries: { + paginator: Paginator, + list: readonly tm.Map[], + login: string, + query?: string + }[] = [] private readonly paginatorIdOffset: number = this.mapAddId + this.maxMapCount private readonly mapDeleteId: number = this.mapAddId + this.maxMapCount * 2 - private readonly displayEnvironment: boolean = config.displayEnvironment !== undefined ? config.displayEnvironment : process.env.SERVER_PACKMASK !== "nations" + private readonly displayEnvironment: boolean = config.displayEnvironment !== undefined ? config.displayEnvironment : + process.env.SERVER_PACKMASK !== "nations" private nextPaginatorId = 0 constructor() { @@ -29,7 +39,10 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi let pageCount: number = Math.ceil(tm.maps.count / (config.rows * config.columns)) if (pageCount === 0) { pageCount++ } this.paginator.setPageCount(pageCount) - this.displayToPlayer(login, { page, paginator: this.paginator }, `${page}/${pageCount}`) + this.displayToPlayer(login, { + page, + paginator: this.paginator + }, `${page}/${pageCount}`) } this.grid = new Grid(this.contentWidth, this.contentHeight, new Array(config.columns).fill(1), new Array(config.rows).fill(1), config.grid) @@ -58,7 +71,11 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi tm.commands.add({ aliases: config.commands.list.aliases, help: config.commands.list.help, - params: [{ name: 'query', optional: true, type: 'multiword' }], + params: [{ + name: 'query', + optional: true, + type: 'multiword' + }], callback: (info: tm.MessageInfo, query?: string): void => { if (query === undefined) { tm.openManialink(this.openId, info.login) @@ -84,7 +101,8 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi } if (query === 'jb') { query = 'jukebox' } const option: string = query.split(' ').filter(a => a !== '')[0] - const arr = ['jukebox', 'name', 'karma', 'short', 'long', 'best', 'worst', 'worstkarma', 'oldest', 'newest'] as const + const arr = ['jukebox', 'name', 'karma', 'short', 'long', 'best', 'worst', 'worstkarma', 'oldest', + 'newest'] as const const o = arr.find(a => a === option) if (o !== undefined) { void this.openWithOption(info.login, o, page) @@ -103,45 +121,53 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi } }, privilege: config.commands.list.privilege - }, - { - aliases: config.commands.best.aliases, - help: config.commands.best.help, - params: [{ name: 'page', type: 'int', optional: true }], - callback: (info: tm.MessageInfo, page?: number): void => { - this.openWithOption(info.login, 'best', page ?? 1) - }, - privilege: config.commands.best.privilege + }, { + aliases: config.commands.best.aliases, + help: config.commands.best.help, + params: [{ + name: 'page', + type: 'int', + optional: true + }], + callback: (info: tm.MessageInfo, page?: number): void => { + this.openWithOption(info.login, 'best', page ?? 1) }, - { - aliases: config.commands.worst.aliases, - help: config.commands.worst.help, - params: [{ name: 'page', type: 'int', optional: true }], - callback: (info: tm.MessageInfo, page?: number): void => { - this.openWithOption(info.login, 'worst', page ?? 1) - }, - privilege: config.commands.worst.privilege + privilege: config.commands.best.privilege + }, { + aliases: config.commands.worst.aliases, + help: config.commands.worst.help, + params: [{ + name: 'page', + type: 'int', + optional: true + }], + callback: (info: tm.MessageInfo, page?: number): void => { + this.openWithOption(info.login, 'worst', page ?? 1) }, - { - aliases: config.commands.jukebox.aliases, - help: config.commands.jukebox.help, - params: [{ name: 'page', type: 'int', optional: true }], - callback: (info: tm.MessageInfo, page?: number): void => { - this.openWithOption(info.login, 'jukebox', page ?? 1) - }, - privilege: config.commands.jukebox.privilege - } - ) + privilege: config.commands.worst.privilege + }, { + aliases: config.commands.jukebox.aliases, + help: config.commands.jukebox.help, + params: [{ + name: 'page', + type: 'int', + optional: true + }], + callback: (info: tm.MessageInfo, page?: number): void => { + this.openWithOption(info.login, 'jukebox', page ?? 1) + }, + privilege: config.commands.jukebox.privilege + }) maplist.onListUpdate((): void => this.reRender()) maplist.onJukeboxUpdate((): void => this.reRender()) } - async openWithOption(login: string, option: 'jukebox' | 'name' | 'karma' - | 'short' | 'long' | 'best' | 'worst' | 'worstkarma' | 'nofinish' - | 'norank' | 'noauthor' | 'oldest' | 'newest', page: number): Promise { - let list: readonly Readonly[] = [] + async openWithOption(login: string, + option: 'jukebox' | 'name' | 'karma' | 'short' | 'long' | 'best' | 'worst' | 'worstkarma' | 'nofinish' | 'norank' | 'noauthor' | 'oldest' | 'newest', + page: number): Promise { + let list: readonly Readonly[] if (option === 'best' || option === 'worst') { - list = maplist.getByPosition(login, option) + list = await maplist.getByPosition(login, option) } else if (option === 'nofinish' || option === 'norank' || option === 'noauthor') { list = await maplist.getFiltered(login, option) } else { @@ -150,16 +176,24 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi const paginator = this.getPaginator(login, list, option) page = Math.max(1, Math.min(Math.ceil(list.length / (config.rows * config.columns)), page)) paginator.setPageForLogin(login, page) - this.displayToPlayer(login, { page, paginator, list }, `${page}/${paginator.pageCount}`, - undefined, config.optionTitles[option as keyof typeof config.optionTitles]) + this.displayToPlayer(login, { + page, + paginator, + list + }, `${page}/${paginator.pageCount}`, undefined, config.optionTitles[option as keyof typeof config.optionTitles]) } - openWithQuery(login: string, query: string, page: number, searchByAuthor?: true): void { - const list: Readonly[] = searchByAuthor === true ? maplist.searchByAuthor(query) : maplist.searchByName(query) + async openWithQuery(login: string, query: string, page: number, searchByAuthor?: true): Promise { + const list: Readonly[] = searchByAuthor === true ? await maplist.searchByAuthor(query) : + await maplist.searchByName(query) const paginator = this.getPaginator(login, list, query) page = Math.max(1, Math.min(Math.ceil(list.length / (config.rows * config.columns)), page)) paginator.setPageForLogin(login, page) - this.displayToPlayer(login, { page, paginator, list }, `${page}/${paginator.pageCount}`) + this.displayToPlayer(login, { + page, + paginator, + list + }, `${page}/${paginator.pageCount}`) } openOnPage(login: string, page: number): void { @@ -167,7 +201,11 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi const paginator = this.paginator page = Math.min(Math.ceil(list.length / (config.rows * config.columns)), page) paginator.setPageForLogin(login, page) - this.displayToPlayer(login, { page, paginator, list }, `${page}/${paginator.pageCount}`) + this.displayToPlayer(login, { + page, + paginator, + list + }, `${page}/${paginator.pageCount}`) } private getPaginator(login: string, list: readonly tm.Map[], option: string): Paginator { @@ -178,19 +216,28 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi playerQuery.list = list paginator = playerQuery.paginator paginator.setPageForLogin(login, 1) - paginator.onPageChange = (login: string, page: number): Promise => this.displayToPlayer(login, - { page, paginator, list }, `${page}/${pageCount}`, undefined, - config.optionTitles[option as keyof typeof config.optionTitles]) + paginator.onPageChange = (login: string, page: number): Promise => this.displayToPlayer(login, { + page, + paginator, + list + }, `${page}/${pageCount}`, undefined, config.optionTitles[option as keyof typeof config.optionTitles]) paginator.setPageCount(pageCount) } else { - paginator = new Paginator(this.openId + this.paginatorIdOffset + this.nextPaginatorId, - this.windowWidth, this.footerHeight, pageCount) + paginator = new Paginator(this.openId + this.paginatorIdOffset + this.nextPaginatorId, this.windowWidth, + this.footerHeight, pageCount) this.nextPaginatorId += 10 this.nextPaginatorId = this.nextPaginatorId % 3000 - this.playerQueries.push({ paginator, login, list, query: option }) - paginator.onPageChange = (login: string, page: number): Promise => this.displayToPlayer(login, - { page, paginator, list }, `${page}/${pageCount}`, undefined, - config.optionTitles[option as keyof typeof config.optionTitles]) + this.playerQueries.push({ + paginator, + login, + list, + query: option + }) + paginator.onPageChange = (login: string, page: number): Promise => this.displayToPlayer(login, { + page, + paginator, + list + }, `${page}/${pageCount}`, undefined, config.optionTitles[option as keyof typeof config.optionTitles]) } return paginator } @@ -211,10 +258,18 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi let pageCount: number = Math.ceil(list.length / (config.rows * config.columns)) if (pageCount === 0) { pageCount++ } if (page === undefined) { - this.displayToPlayer(login, { page: 1, paginator, list }, `1/${pageCount}`, undefined, query) + this.displayToPlayer(login, { + page: 1, + paginator, + list + }, `1/${pageCount}`, undefined, query) return } - this.displayToPlayer(login, { page, paginator, list }, `${page}/${pageCount}`, undefined, query) + this.displayToPlayer(login, { + page, + paginator, + list + }, `${page}/${pageCount}`, undefined, query) } } @@ -223,7 +278,10 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi let pageCount: number = Math.ceil(tm.maps.count / (config.rows * config.columns)) if (pageCount === 0) { pageCount++ } if (page === undefined || page > pageCount) { - this.displayToPlayer(info.login, { page: 1, paginator: this.paginator }, `1/${pageCount}`) + this.displayToPlayer(info.login, { + page: 1, + paginator: this.paginator + }, `1/${pageCount}`) return } this.paginator.setPageCount(pageCount) @@ -232,7 +290,10 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi this.playerQueries[index].paginator.destroy() this.playerQueries.splice(index, 1) } - this.displayToPlayer(info.login, { page, paginator: this.paginator }, `${page}/${pageCount}`) + this.displayToPlayer(info.login, { + page, + paginator: this.paginator + }, `${page}/${pageCount}`) } protected onClose(info: tm.ManialinkClickInfo): void { @@ -244,17 +305,23 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi this.hideToPlayer(info.login) } - protected async constructContent(login: string, params?: { page: number, list?: readonly tm.Map[] }): Promise { + protected async constructContent(login: string, params?: { + page: number, + list?: readonly tm.Map[] + }): Promise { const maps: readonly Readonly[] = params?.list ?? maplist.get() const startIndex: number = (config.rows * config.columns) * ((params?.page ?? 1) - 1) - const recordIndexStrings: string[] = await this.getRecordIndexStrings(login, ...maps.slice(startIndex, - (config.rows * config.columns) + startIndex).map(a => a.id)) + const recordIndexStrings: string[] = await this.getRecordIndexStrings(login, + ...maps.slice(startIndex, (config.rows * config.columns) + startIndex).map(a => a.id)) const mapsToDisplay: number = Math.min(maps.length - startIndex, config.rows * config.columns) const cell = (i: number, j: number, w: number, h: number): string => { const gridIndex: number = (i * config.columns) + j const recordIndexString: string = recordIndexStrings[gridIndex] const index: number = startIndex + gridIndex - const { actionId, deleteId } = this.getActionAndDeleteId(maps[index].id) + const { + actionId, + deleteId + } = this.getActionAndDeleteId(maps[index].id) const header: string = this.getHeader(login, index, maps[index].id, actionId, deleteId, w, h) const rowH: number = (h - this.margin) / 4 const width: number = (w - this.margin * 3) - config.iconWidth @@ -279,7 +346,8 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi sizen="${config.iconWidth - this.margin * 2} ${rowH - this.margin * 3}" image="${config.icons[2]}"/> - ${leftAlignedText(tm.utils.safeString(maps[index].author), width, rowH - this.margin, { textScale: config.textScale })} + ${leftAlignedText(tm.utils.safeString(maps[index].author), width, rowH - this.margin, + { textScale: config.textScale })} @@ -288,8 +356,10 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi image="${config.icons[3]}"/> - ${centeredText(tm.utils.getTimeString(maps[index].authorTime), config.timeWidth, rowH - this.margin, - { textScale: config.textScale, padding: config.padding })} + ${centeredText(tm.utils.getTimeString(maps[index].authorTime), config.timeWidth, rowH - this.margin, { + textScale: config.textScale, + padding: config.padding + })} @@ -298,7 +368,10 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi image="${config.icons[4]}"/> - ${centeredText(` ${recordIndexString} `, config.positionWidth, rowH - this.margin, { textScale: config.textScale, padding: config.padding })} + ${centeredText(` ${recordIndexString} `, config.positionWidth, rowH - this.margin, { + textScale: config.textScale, + padding: config.padding + })} @@ -307,8 +380,13 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi image="${config.icons[this.displayEnvironment ? 8 : 5]}"/> - ${centeredText(this.displayEnvironment ? (maps[index].environment === 'Stadium' ? 'Stad' : maps[index].environment) : maps[index].voteRatio === -1 ? config.defaultText : maps[index].voteRatio.toFixed(0), karmaW, - rowH - this.margin, { textScale: config.textScale, padding: config.padding })} + ${centeredText( + this.displayEnvironment ? (maps[index].environment === 'Stadium' ? 'Stad' : maps[index].environment) : + maps[index].voteRatio === -1 ? config.defaultText : maps[index].voteRatio.toFixed(0), karmaW, + rowH - this.margin, { + textScale: config.textScale, + padding: config.padding + })} ` @@ -316,8 +394,11 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi return this.grid.constructXml(new Array(mapsToDisplay).fill(cell)) } - protected constructFooter(login: string, params?: { paginator: Paginator }): string { - return closeButton(this.closeId, this.windowWidth, this.footerHeight) + (params?.paginator ?? this.paginator).constructXml(login) + protected constructFooter(login: string, params?: { + paginator: Paginator + }): string { + return closeButton(this.closeId, this.windowWidth, + this.footerHeight) + (params?.paginator ?? this.paginator).constructXml(login) } private handleMapClick(mapId: string, login: string, nickname: string, privilege: number): boolean { @@ -328,59 +409,73 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi return false } if (tm.jukebox.juked.some(a => a.map.id === mapId)) { - tm.jukebox.remove(mapId, { login, nickname }) - tm.sendMessage(tm.utils.strVar(config.messages.remove, - { player: tm.utils.strip(nickname, true), map: tm.utils.strip(tm.utils.decodeURI(map.name), true) }), config.public ? undefined : login) - } - else { - if ((privilege < config.multijukePrivilege && tm.jukebox.juked.some(a => a.callerLogin === login)) - || (config.multijukeMaxMaps - 1 < tm.jukebox.juked.filter(a => a.callerLogin === login).length && privilege < config.unlimitedJukePrivilege)) { + tm.jukebox.remove(mapId, { + login, + nickname + }) + tm.sendMessage(tm.utils.strVar(config.messages.remove, { + player: tm.utils.strip(nickname, true), + map: tm.utils.strip(tm.utils.decodeURI(map.name), true) + }), config.public ? undefined : login) + } else { + if ((privilege < config.multijukePrivilege && tm.jukebox.juked.some( + a => a.callerLogin === login)) || (config.multijukeMaxMaps - 1 < tm.jukebox.juked.filter( + a => a.callerLogin === login).length && privilege < config.unlimitedJukePrivilege)) { tm.sendMessage(config.messages.noPermission, login) return false } - tm.jukebox.add(mapId, { login, nickname }) - tm.sendMessage(tm.utils.strVar(config.messages.add, - { player: tm.utils.strip(nickname, true), map: tm.utils.strip(tm.utils.decodeURI(map.name), true) }), config.public ? undefined : login) + tm.jukebox.add(mapId, { + login, + nickname + }) + tm.sendMessage(tm.utils.strVar(config.messages.add, { + player: tm.utils.strip(nickname, true), + map: tm.utils.strip(tm.utils.decodeURI(map.name), true) + }), config.public ? undefined : login) } return true } private async getRecordIndexStrings(login: string, ...mapIds: string[]): Promise { - const ranks = tm.records.getRank(login, mapIds) + const ranks = await tm.records.getRank(login, mapIds) const ret: string[] = [] for (let i = 0; i < mapIds.length; i++) { const r = ranks.find(a => a.mapId === mapIds[i]) - if (r === undefined) { ret.push(config.texts.noRank) } - else { ret.push(tm.utils.getOrdinalSuffix(r.rank)) } + if (r === undefined) { ret.push(config.texts.noRank) } else { ret.push(tm.utils.getOrdinalSuffix(r.rank)) } } return ret } - private getActionAndDeleteId(mapId: string): { actionId: number, deleteId: number } { + private getActionAndDeleteId(mapId: string): { + actionId: number, + deleteId: number + } { let actionId = -1 let deleteId = -1 const mapActionId: number = this.mapActionIds.indexOf(mapId) if (mapActionId !== -1) { actionId = mapActionId + this.openId + this.mapAddId deleteId = mapActionId + this.openId + this.mapDeleteId - } - else { + } else { this.mapActionIds.push(mapId) actionId = this.mapActionIds.length + this.openId + this.mapAddId - 1 deleteId = this.mapActionIds.length + this.openId + this.mapDeleteId - 1 } - return { actionId, deleteId } + return { + actionId, + deleteId + } } - private getHeader(login: string, mapIndex: number, mapId: string, actionId: number, deleteId: number, w: number, h: number): string { + private getHeader(login: string, mapIndex: number, mapId: string, actionId: number, deleteId: number, w: number, + h: number): string { const height: number = h - this.margin const index: number = tm.jukebox.juked.findIndex(a => a.map.id === mapId) const prevIndex: number = [tm.maps.current, ...tm.jukebox.history].findIndex(a => a.id === mapId) const player = tm.players.get(login) if (player === undefined) { return '' } let overlay: string | undefined - if (player?.privilege < 1 - && (prevIndex !== -1 || (tm.jukebox.juked[index]?.callerLogin !== undefined && tm.jukebox.juked[index].callerLogin !== login))) { + if (player?.privilege < 1 && (prevIndex !== -1 || (tm.jukebox.juked[index]?.callerLogin !== undefined && tm.jukebox.juked[index].callerLogin !== login))) { overlay = ` ` } @@ -393,14 +488,23 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi - ${leftAlignedText(`${config.texts.map}${mapIndex + 1}`, width - (this.margin * 2 + config.queueWidth) + config.queueNumberWidth, height / 4 - this.margin, { textScale: config.textScale })} + ${leftAlignedText(`${config.texts.map}${mapIndex + 1}`, + width - (this.margin * 2 + config.queueWidth) + config.queueNumberWidth, height / 4 - this.margin, + { textScale: config.textScale })} - ${centeredText(`$${config.colour}${config.texts.queued}`, config.queueWidth, height / 4 - this.margin, { padding: config.padding, textScale: config.textScale })} + ${centeredText(`$${config.colour}${config.texts.queued}`, config.queueWidth, height / 4 - this.margin, { + padding: config.padding, + textScale: config.textScale + })} - ${centeredText(`$${config.colour}${tm.utils.getOrdinalSuffix(index + 1)}`, config.queueNumberWidth, height / 4 - this.margin, { padding: config.padding, textScale: config.textScale })} + ${centeredText(`$${config.colour}${tm.utils.getOrdinalSuffix(index + 1)}`, config.queueNumberWidth, + height / 4 - this.margin, { + padding: config.padding, + textScale: config.textScale + })} ` } @@ -408,8 +512,7 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi if (deletePrivilege) { width = (w - this.margin * 4) - config.iconWidth * 2 } - const deleteButton = deletePrivilege ? - ` + const deleteButton = deletePrivilege ? ` @@ -422,7 +525,8 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi image="${config.icons[0]}"/> - ${leftAlignedText(`${config.texts.map}${mapIndex + 1}`, width, height / 4 - this.margin, { textScale: config.textScale })} + ${leftAlignedText(`${config.texts.map}${mapIndex + 1}`, width, height / 4 - this.margin, + { textScale: config.textScale })} ${deleteButton}` } diff --git a/plugins/maplist/ui/Maplist.config.js b/plugins/maplist/ui/Maplist.config.js index 11fed3c78..5f4fff539 100644 --- a/plugins/maplist/ui/Maplist.config.js +++ b/plugins/maplist/ui/Maplist.config.js @@ -1,8 +1,9 @@ -import { icons, componentIds as ids } from '../../ui/UI.js' +import { componentIds as ids, icons } from '../../ui/UI.js' + const p = tm.utils.palette export default { - title: "Map List", + title: 'Map List', icon: icons.mapList, textScale: 1, padding: 0.1, @@ -19,7 +20,7 @@ export default { texts: { map: 'Map #', queued: 'Queued', - noRank: "--." + noRank: '--.' }, rows: 5, columns: 5, @@ -27,11 +28,11 @@ export default { background: 'FFFA', margin: 0.15 }, - navbar: [ - { name: 'Map Info', actionId: ids.TMXWindow } - ], - icons: [ - icons.ongoingMap, // Map header icon + navbar: [{ + name: 'Map Info', + actionId: ids.TMXWindow + }], + icons: [icons.ongoingMap, // Map header icon icons.tag, // Map name icon icons.personBuilder, // Author name icon icons.clockAuthor, // Author time icon @@ -47,8 +48,8 @@ export default { queueNumberWidth: 2.9, timeWidth: 5, positionWidth: 3.1, - iconBackground: "222C", - contentBackground: "555C", + iconBackground: '222C', + contentBackground: '555C', plusImage: icons.addMap, minusImage: icons.removeMap, blankImage: icons.blank, @@ -83,4 +84,4 @@ export default { privilege: 0 } } -} \ No newline at end of file +} diff --git a/plugins/messages/Config.js b/plugins/messages/Config.js index b504afe13..520d4ce71 100644 --- a/plugins/messages/Config.js +++ b/plugins/messages/Config.js @@ -5,7 +5,7 @@ export default { specialWin: 50, startup: { message: `${p.highlight}$L[${tm.config.controller.repo}]Trakman v#{version}$L${p.servermsg} startup sequence successful.`, - public: true, + public: true }, changelog: { message: `${p.error}You can see the recent changes with the ${p.highlight}/changes ${p.error}command.`, @@ -56,17 +56,17 @@ export default { public: true }, recordDifference: { - message: ` $n${p.record}(${p.rank}#{position} ${p.highlight}#{time}${p.record})`, + message: ` $n${p.record}(${p.rank}#{position} ${p.highlight}#{time}${p.record})` }, dediDifference: { - message: ` $n${p.dedirecord}(${p.rank}#{position} ${p.highlight}-#{time}${p.dedirecord})`, + message: ` $n${p.dedirecord}(${p.rank}#{position} ${p.highlight}-#{time}${p.dedirecord})` }, dediRecord: { message: `${p.highlight}#{nickname}${p.dedirecord} has #{status} the ${p.rank}#{position}${p.dedirecord} dedimania record. Time${p.highlight}: #{time}#{difference}`, public: true }, ultiDifference: { - message: ` $n${p.dedirecord}(${p.rank}#{position} ${p.highlight}+#{score}${p.dedirecord})`, + message: ` $n${p.dedirecord}(${p.rank}#{position} ${p.highlight}+#{score}${p.dedirecord})` }, ultiRecord: { message: `${p.highlight}#{nickname}${p.dedirecord} has #{status} the ${p.rank}#{position}${p.dedirecord} ultimania record. Score${p.highlight}: #{score}#{difference}`, @@ -79,5 +79,5 @@ export default { jukeSkipped: { message: `${p.vote}Map ${p.highlight}#{map} ${p.vote}will be dropped from the queue, as ${p.highlight}#{nickname} ${p.vote}has left the server.`, public: true - }, -} \ No newline at end of file + } +} diff --git a/plugins/messages/Messages.ts b/plugins/messages/Messages.ts index 08ff002f0..4e96abcc7 100644 --- a/plugins/messages/Messages.ts +++ b/plugins/messages/Messages.ts @@ -8,54 +8,18 @@ import c from './Config.js' * @since 0.1 */ -const events: tm.Listener[] = [ - { - event: 'Startup', - callback: (): void => { - if (c.startup.public) { tm.sendMessage(tm.utils.strVar(c.startup.message, { version: tm.config.controller.version })) } - if (c.changelog.public) { tm.sendMessage(c.changelog.message) } +const events: tm.Listener[] = [{ + event: 'Startup', + callback: (): void => { + if (c.startup.public) { + tm.sendMessage(tm.utils.strVar(c.startup.message, { version: tm.config.controller.version })) } - }, - { - event: ['BeginMap', 'Startup'], - callback: async (): Promise => { - for (const player of tm.players.list) { - const index: number = tm.records.local.findIndex(a => a.login === player.login) - if (index === -1 && c.noPb.public) { - tm.sendMessage(c.noPb.message, player.login) - } else { - if (c.pb.public) { - const rec: tm.LocalRecord = tm.records.local[index] - tm.sendMessage(tm.utils.strVar(c.pb.message, { - time: tm.utils.getTimeString(rec.time), - rank: tm.utils.getOrdinalSuffix(index + 1) - }), player.login) - } - } - const playerRank: number | undefined = player.rank - if (playerRank === undefined && c.noRank.public) { - tm.sendMessage(c.noRank.message, player.login) - } else { - if (c.rank.public) { - tm.sendMessage(tm.utils.strVar(c.rank.message, { - rank: tm.utils.getOrdinalSuffix(playerRank ?? 0), - total: tm.players.totalCount - }), player.login) - } - } - } - } - }, - { - event: 'PlayerJoin', - callback: async (player: tm.JoinInfo): Promise => { - if (c.welcome.public) { - tm.sendMessage(tm.utils.strVar(c.welcome.message, { - name: tm.utils.strip(tm.config.server.name), - version: tm.config.controller.version - }), player.login) - } - if (c.changelog.public) { tm.sendMessage(c.changelog.message, player.login) } + if (c.changelog.public) { tm.sendMessage(c.changelog.message) } + } +}, { + event: ['BeginMap', 'Startup'], + callback: async (): Promise => { + for (const player of tm.players.list) { const index: number = tm.records.local.findIndex(a => a.login === player.login) if (index === -1 && c.noPb.public) { tm.sendMessage(c.noPb.message, player.login) @@ -79,114 +43,161 @@ const events: tm.Listener[] = [ }), player.login) } } - if (c.join.public) { - tm.sendMessage(tm.utils.strVar(c.join.message, { - title: player.title, - nickname: tm.utils.strip(player.nickname, true), - country: player.country, - visits: player.visits - })) + } + } +}, { + event: 'PlayerJoin', + callback: async (player: tm.JoinInfo): Promise => { + if (c.welcome.public) { + tm.sendMessage(tm.utils.strVar(c.welcome.message, { + name: tm.utils.strip(tm.config.server.name), + version: tm.config.controller.version + }), player.login) + } + if (c.changelog.public) { tm.sendMessage(c.changelog.message, player.login) } + const index: number = tm.records.local.findIndex(a => a.login === player.login) + if (index === -1 && c.noPb.public) { + tm.sendMessage(c.noPb.message, player.login) + } else { + if (c.pb.public) { + const rec: tm.LocalRecord = tm.records.local[index] + tm.sendMessage(tm.utils.strVar(c.pb.message, { + time: tm.utils.getTimeString(rec.time), + rank: tm.utils.getOrdinalSuffix(index + 1) + }), player.login) + } + } + const playerRank: number | undefined = player.rank + if (playerRank === undefined && c.noRank.public) { + tm.sendMessage(c.noRank.message, player.login) + } else { + if (c.rank.public) { + tm.sendMessage(tm.utils.strVar(c.rank.message, { + rank: tm.utils.getOrdinalSuffix(playerRank ?? 0), + total: tm.players.totalCount + }), player.login) } } - }, - { - event: 'EndMap', - callback: async (info: tm.EndMapInfo): Promise => { - if (info.winnerLogin !== undefined && info.winnerWins !== undefined) { - const player: tm.OfflinePlayer | undefined = tm.players.get(info.winnerLogin) ?? await tm.players.fetch(info.winnerLogin) - if (info.winnerWins % c.specialWin === 0 && c.winPublic.public) { - tm.sendMessage(tm.utils.strVar(c.winPublic.message, { - nickname: tm.utils.strip(player?.nickname ?? '', true), + if (c.join.public) { + tm.sendMessage(tm.utils.strVar(c.join.message, { + title: player.title, + nickname: tm.utils.strip(player.nickname, true), + country: player.country, + visits: player.visits + })) + } + } +}, { + event: 'EndMap', + callback: async (info: tm.EndMapInfo): Promise => { + if (info.winnerLogin !== undefined && info.winnerWins !== undefined) { + const player: tm.OfflinePlayer | undefined = tm.players.get(info.winnerLogin) ?? await tm.players.fetch( + info.winnerLogin) + if (info.winnerWins % c.specialWin === 0 && c.winPublic.public) { + tm.sendMessage(tm.utils.strVar(c.winPublic.message, { + nickname: tm.utils.strip(player?.nickname ?? '', true), + wins: tm.utils.getOrdinalSuffix(info.winnerWins) + }), undefined) + } else { + if (c.win.public) { + tm.sendMessage(tm.utils.strVar(c.win.message, { wins: tm.utils.getOrdinalSuffix(info.winnerWins) - }), undefined) - } else { - if (c.win.public) { - tm.sendMessage(tm.utils.strVar(c.win.message, { - wins: tm.utils.getOrdinalSuffix(info.winnerWins) - }), info.winnerLogin) - } + }), info.winnerLogin) } } - if (info.droppedMap !== undefined && c.jukeSkipped.public) { - tm.sendMessage(tm.utils.strVar(c.jukeSkipped.message, { - map: tm.utils.strip(info.droppedMap.map.name, true), - nickname: tm.utils.strip((await tm.players.fetch(info.droppedMap.callerLogin))?.nickname ?? '') - })) - } - if (tm.jukebox.juked[0]?.callerLogin !== undefined && c.nextJuke.public) { - const player: tm.OfflinePlayer | undefined = tm.players.get(tm.jukebox.juked[0].callerLogin) ?? await tm.players.fetch(tm.jukebox.juked[0].callerLogin) - tm.sendMessage(tm.utils.strVar(c.nextJuke.message, { - map: tm.utils.strip(tm.utils.decodeURI(tm.jukebox.juked[0].map.name), true), - nickname: tm.utils.strip(player?.nickname ?? '', true) - })) - } } - }, - { - event: 'PlayerLeave', - callback: (player: tm.LeaveInfo): void => { - if (c.leave.public) { - tm.sendMessage(tm.utils.strVar(c.leave.message, { - nickname: tm.utils.strip(player.nickname, true), - time: tm.utils.getVerboseTime(player.sessionTime) - })) - } + if (info.droppedMap !== undefined && c.jukeSkipped.public) { + tm.sendMessage(tm.utils.strVar(c.jukeSkipped.message, { + map: tm.utils.strip(info.droppedMap.map.name, true), + nickname: tm.utils.strip((await tm.players.fetch(info.droppedMap.callerLogin))?.nickname ?? '') + })) } - }, - { - event: 'LocalRecord', - callback: (info: tm.RecordInfo): void => { - if (c.record.public) { - const isStunts = tm.getGameMode() === 'Stunts' - const diffSign = isStunts ? '+' : '-' - let prevObj: undefined | { time: number, position: number } = info.previous - if (info.previous !== undefined && info.previous.position > tm.records.maxLocalsAmount) { - prevObj = undefined - } - const rs = tm.utils.getRankingString({ time: info.time, position: info.position }, prevObj) - tm.sendMessage(tm.utils.strVar(c.record.message, { - nickname: tm.utils.strip(info.nickname, true), - status: rs.status, - position: tm.utils.getOrdinalSuffix(info.position), - time: tm.utils.getTimeString(info.time), - type: isStunts ? 'Score' : 'Time', - difference: rs.difference !== undefined ? tm.utils.strVar(c.recordDifference.message, { - position: info.previous?.position, - time: diffSign + rs.difference - }) : '' - })) + if (tm.jukebox.juked[0]?.callerLogin !== undefined && c.nextJuke.public) { + const player: tm.OfflinePlayer | undefined = tm.players.get( + tm.jukebox.juked[0].callerLogin) ?? await tm.players.fetch(tm.jukebox.juked[0].callerLogin) + tm.sendMessage(tm.utils.strVar(c.nextJuke.message, { + map: tm.utils.strip(tm.utils.decodeURI(tm.jukebox.juked[0].map.name), true), + nickname: tm.utils.strip(player?.nickname ?? '', true) + })) + } + } +}, { + event: 'PlayerLeave', + callback: (player: tm.LeaveInfo): void => { + if (c.leave.public) { + tm.sendMessage(tm.utils.strVar(c.leave.message, { + nickname: tm.utils.strip(player.nickname, true), + time: tm.utils.getVerboseTime(player.sessionTime) + })) + } + } +}, { + event: 'LocalRecord', + callback: (info: tm.RecordInfo): void => { + if (c.record.public) { + const isStunts = tm.getGameMode() === 'Stunts' + const diffSign = isStunts ? '+' : '-' + let prevObj: undefined | { + time: number, + position: number + } = info.previous + if (info.previous !== undefined && info.previous.position > tm.records.maxLocalsAmount) { + prevObj = undefined } + const rs = tm.utils.getRankingString({ + time: info.time, + position: info.position + }, prevObj) + tm.sendMessage(tm.utils.strVar(c.record.message, { + nickname: tm.utils.strip(info.nickname, true), + status: rs.status, + position: tm.utils.getOrdinalSuffix(info.position), + time: tm.utils.getTimeString(info.time), + type: isStunts ? 'Score' : 'Time', + difference: rs.difference !== undefined ? tm.utils.strVar(c.recordDifference.message, { + position: info.previous?.position, + time: diffSign + rs.difference + }) : '' + })) } - }, - { - event: 'LapRecord', - callback: (info: tm.LapRecordInfo): void => { - if (c.lapRecord.public) { - let prevObj: undefined | { time: number, position: number } = info.previous - if (info.previous !== undefined && info.previous.position > tm.records.maxLocalsAmount) { - prevObj = undefined - } - const rs = tm.utils.getRankingString({ time: info.time, position: info.position }, prevObj) - tm.sendMessage(tm.utils.strVar(c.lapRecord.message, { - nickname: tm.utils.strip(info.nickname, true), - status: rs.status, - position: tm.utils.getOrdinalSuffix(info.position), - time: tm.utils.getTimeString(info.time), - difference: rs.difference !== undefined ? tm.utils.strVar(c.recordDifference.message, { - position: info.previous?.position, - time: rs.difference - }) : '' - })) + } +}, { + event: 'LapRecord', + callback: (info: tm.LapRecordInfo): void => { + if (c.lapRecord.public) { + let prevObj: undefined | { + time: number, + position: number + } = info.previous + if (info.previous !== undefined && info.previous.position > tm.records.maxLocalsAmount) { + prevObj = undefined } + const rs = tm.utils.getRankingString({ + time: info.time, + position: info.position + }, prevObj) + tm.sendMessage(tm.utils.strVar(c.lapRecord.message, { + nickname: tm.utils.strip(info.nickname, true), + status: rs.status, + position: tm.utils.getOrdinalSuffix(info.position), + time: tm.utils.getTimeString(info.time), + difference: rs.difference !== undefined ? tm.utils.strVar(c.recordDifference.message, { + position: info.previous?.position, + time: rs.difference + }) : '' + })) } } -] +}] for (const event of events) { tm.addListener(event.event, event.callback, true) } dedimania.onRecord((record) => { if (c.dediRecord.public) { - const rs = tm.utils.getRankingString({ position: record.position, time: record.time }, record.previous) + const rs = tm.utils.getRankingString({ + position: record.position, + time: record.time + }, record.previous) tm.sendMessage(tm.utils.strVar(c.dediRecord.message, { nickname: tm.utils.strip(record.nickname, true), status: rs.status, @@ -202,9 +213,14 @@ dedimania.onRecord((record) => { ultimania.onRecord((record) => { if (c.ultiRecord.public) { - const prev = record.previous === undefined ? undefined : - { time: record.previous.score, position: record.previous.position } - const rs = tm.utils.getRankingString({ position: record.position, time: record.score }, prev) + const prev = record.previous === undefined ? undefined : { + time: record.previous.score, + position: record.previous.position + } + const rs = tm.utils.getRankingString({ + position: record.position, + time: record.score + }, prev) tm.sendMessage(tm.utils.strVar(c.ultiRecord.message, { nickname: tm.utils.strip(record.nickname, true), status: rs.status, diff --git a/plugins/music/Config.js b/plugins/music/Config.js index e927cdc33..06b7eb69a 100644 --- a/plugins/music/Config.js +++ b/plugins/music/Config.js @@ -3,28 +3,22 @@ const p = tm.utils.palette export default { isEnabled: false, songListPath: './plugins/music/SongList.js', // Needed for updates on song add/remove - overrideMapMusic: true, - // If song is in history it can't be requeued without force queue privileges - historySize: 3, - // Privilege required to queue recently queued songs, queue more than one song and shuffle songs + overrideMapMusic: true, // If song is in history it can't be requeued without force queue privileges + historySize: 3, // Privilege required to queue recently queued songs, queue more than one song and shuffle songs forceQueuePrivilege: 1, messages: { - addToQueue: `${p.highlight}#{nickname} ${p.vote}has added ${p.highlight}#{song}${p.vote}` + - ` by ${p.highlight}#{author}${p.vote} to the song queue.`, + addToQueue: `${p.highlight}#{nickname} ${p.vote}has added ${p.highlight}#{song}${p.vote}` + ` by ${p.highlight}#{author}${p.vote} to the song queue.`, cantAddMoreThanOne: `${p.error}You can't add more than one song to the queue.`, addToQueueError: `${p.error}Error while adding the song to the queue.`, removeFromQueue: `${p.highlight}#{nickname} ${p.vote}has removed ${p.highlight}#{song}${p.vote} from the song queue.`, removeFromQueueError: `${p.error}Error while removing the song from the queue.`, - add: `${p.highlight}#{nickname} ${p.vote}has added and queued song ${p.highlight}#{song}${p.vote}` + - ` by ${p.highlight}#{author}${p.vote}.`, + add: `${p.highlight}#{nickname} ${p.vote}has added and queued song ${p.highlight}#{song}${p.vote}` + ` by ${p.highlight}#{author}${p.vote}.`, addNameError: `${p.error}Song named ${p.highlight}#{name} ${p.error}already exists.`, - addFileExtensionError: `${p.error}Song url file extension needs to be ` + - `${p.highlight}.ogg ${p.error}or ${p.highlight}.mux${p.error}, ${p.highlight}#{url} ${p.error}is invalid.`, + addFileExtensionError: `${p.error}Song url file extension needs to be ` + `${p.highlight}.ogg ${p.error}or ${p.highlight}.mux${p.error}, ${p.highlight}#{url} ${p.error}is invalid.`, remove: `${p.highlight}#{nickname} ${p.vote}has deleted song ${p.highlight}#{song}${p.vote}.`, removeError: `${p.error}Song named ${p.highlight}#{name} ${p.error}doesn't exist.`, shuffle: `${p.highlight}#{nickname} ${p.vote}has shuffled the song queue.` - }, - // Whether add and remove from queue messages are public + }, // Whether add and remove from queue messages are public publicQueue: true, addCommand: { aliases: ['addsong'], @@ -48,4 +42,4 @@ export default { aliases: ['songs', 'songlist'], help: 'Open song list.' } -} \ No newline at end of file +} diff --git a/plugins/music/Music.ts b/plugins/music/Music.ts index b04194344..5f85ec318 100644 --- a/plugins/music/Music.ts +++ b/plugins/music/Music.ts @@ -1,6 +1,6 @@ import config from './Config.js' import songList from './SongList.js' -import type { Song, SongAddedCallback, SongRemovedCallback, QueueChangedCallback, Caller } from './Types.js' +import type { Caller, QueueChangedCallback, Song, SongAddedCallback, SongRemovedCallback } from './Types.js' import SongList from './ui/SongList.component.js' import MusicWidget from './ui/MusicWidget.component.js' import fs from 'fs/promises' @@ -24,7 +24,7 @@ export const music = { /** * Add a callback function to execute on a song queue change - * @param callback Function to execute on event. + * @param callback Function to execute on event. * It takes song queue and optional change information object as parameters. */ onQueueChanged(callback: QueueChangedCallback) { @@ -33,7 +33,7 @@ export const music = { /** * Add a callback function to execute when a new song gets added to the song list - * @param callback Function to execute on event. + * @param callback Function to execute on event. * It takes song object and caller object as parameters. (Caller is the player who added the song) */ onSongAdded(callback: SongAddedCallback) { @@ -42,7 +42,7 @@ export const music = { /** * Add a callback function to execute when a song gets removed from the song list - * @param callback Function to execute on event. + * @param callback Function to execute on event. * It takes song object and caller object as parameters. (Caller is the player who removed the song) */ onSongRemoved(callback: SongRemovedCallback) { @@ -65,13 +65,19 @@ export const music = { return 'name taken' } url = tm.utils.fixProtocol(url) - const song: Song = { name, author, url, isJuked: false } + const song: Song = { + name, + author, + url, + isJuked: false + } queue.push(song) emitEvent(songAddCallbacks, song, caller) const status = addToQueue(name, false, caller) if (typeof status !== 'string') { emitEvent(queueChangeCallbacks, queue, { - song, action: 'added' + song, + action: 'added' }) } void updateSongsConfigFile('add', song) @@ -90,13 +96,11 @@ export const music = { return false } const song = queue.splice(index, 1)[0] - if (index === 0) { - updateNextSong(queue[0]?.url) - } listUi.updateSongs(current, queue) emitEvent(songRemoveCallbacks, song, caller) emitEvent(queueChangeCallbacks, queue, { - song, action: 'removed' + song, + action: 'removed' }) void updateSongsConfigFile('remove', song) return true @@ -108,8 +112,8 @@ export const music = { * @param caller Caller player object * @returns Song object if it got added, error message if unsuccessful */ - addSongToQueue(songName: string, caller?: Caller): - Readonly | 'already queued' | 'not in songlist' | 'no privilege' { + addSongToQueue(songName: string, + caller?: Caller): Readonly | 'already queued' | 'not in songlist' | 'no privilege' { return addToQueue(songName, true, caller) }, @@ -143,7 +147,10 @@ if (config.isEnabled) { tm.log.warn(`Song name duplicates present in SongList, to fix them edit SongList.js file or use ingame commands.`) updateSongsConfigFile() } - queue.push(...songs.map(a => ({ ...a, isJuked: false }))) + queue.push(...songs.map(a => ({ + ...a, + isJuked: false + }))) listUi.updateSongs(current, queue) const msg = config.messages listUi.onSongJuked = (song, info) => { @@ -175,7 +182,7 @@ if (config.isEnabled) { emitEvent(queueChangeCallbacks, queue) }) - tm.addListener('BeginMap', () => { + tm.addListener('EndMap', () => { if (current !== undefined) { current.isJuked = false current.caller = undefined @@ -187,11 +194,11 @@ if (config.isEnabled) { } } current = queue[0] + updateNextSong(current?.url) queue.shift() listUi.updateSongs(current, queue) listUi.updatePreviousSongs(history) widgetUi.setCurrentSong(current) - updateNextSong(queue[0]?.url) emitEvent(queueChangeCallbacks, queue) }, true) const msg = config.messages @@ -206,7 +213,8 @@ if (config.isEnabled) { if (status === true) { tm.sendMessage(tm.utils.strVar(msg.add, { nickname: tm.utils.strip(info.nickname), - song: name, author + song: name, + author }), add.public ? undefined : info.login) } else if (status === 'name taken') { tm.sendMessage(tm.utils.strVar(msg.addNameError, { name }), info.login) @@ -240,7 +248,11 @@ if (config.isEnabled) { tm.commands.add({ aliases: ol.aliases, help: ol.help, - params: [{ name: 'query', type: 'multiword', optional: true }], + params: [{ + name: 'query', + type: 'multiword', + optional: true + }], callback: (info, query?: string) => { if (query === undefined || query.trim().length === 0) { listUi.open(info) @@ -260,7 +272,7 @@ if (config.isEnabled) { callback: (info) => { shuffleQueue(info) tm.sendMessage(tm.utils.strVar(msg.shuffle, { - nickname: tm.utils.strip(info.nickname), + nickname: tm.utils.strip(info.nickname) }), sh.public ? undefined : info.login) }, privilege: sh.privilege @@ -274,8 +286,8 @@ function emitEvent any)[]>(eventCallbacks: T, ...pa } } -function addToQueue(songName: string, emitEvents: boolean, caller?: Caller): - Song | "already queued" | "not in songlist" | "no privilege" { +function addToQueue(songName: string, emitEvents: boolean, + caller?: Caller): Song | "already queued" | "not in songlist" | "no privilege" { const songIndex = queue.findIndex(a => a.name === songName) if (songIndex === -1) { return 'not in songlist' @@ -293,9 +305,6 @@ function addToQueue(songName: string, emitEvents: boolean, caller?: Caller): song.caller = caller const newIndex = queue.findIndex(a => a.isJuked === false) queue.splice(newIndex, 0, song) - if (newIndex === 0) { - updateNextSong(song.url) - } listUi.updateSongs(current, queue) if (caller !== undefined) { tm.log.trace(`${tm.utils.strip(caller.nickname)} (${caller.login}) queued song ${song.name} by ${song.author}`) @@ -304,37 +313,36 @@ function addToQueue(songName: string, emitEvents: boolean, caller?: Caller): } if (emitEvents) { emitEvent(queueChangeCallbacks, queue, { - song, action: 'addedToQueue' + song, + action: 'addedToQueue' }) } return song } /** - * Removes a song from the song queue - * @param songName Song name - * @param caller Caller player object - * @returns Song object if it got removed, error message if unsuccessful - */ + * Removes a song from the song queue + * @param songName Song name + * @param caller Caller player object + * @returns Song object if it got removed, error message if unsuccessful + */ function removeFromQueue(name: string, caller?: Caller): Readonly | 'not queued' { if (!queue.filter(a => a.isJuked === true).some(a => a.name === name)) { return 'not queued' } const index: number = queue.findIndex(a => a.name === name) if (caller !== undefined) { - tm.log.trace(`${tm.utils.strip(caller.nickname)} (${caller.login}) removed song ` + - `${tm.utils.strip(queue[index].name)} by ${queue[index].author} from the queue`) + tm.log.trace(`${tm.utils.strip(caller.nickname)} (${caller.login}) removed song ` + `${tm.utils.strip( + queue[index].name)} by ${queue[index].author} from the queue`) } else { tm.log.trace(`Song ${tm.utils.strip(queue[index].name)} by ${queue[index].author} has been removed from the queue`) } const song = queue.splice(index, 1)[0] - if (index === 0) { - updateNextSong(queue[0].url) - } song.isJuked = false song.caller = undefined queue.push(song) listUi.updateSongs(current, queue) emitEvent(queueChangeCallbacks, queue, { - song, action: 'removedFromQueue' + song, + action: 'removedFromQueue' }) return song } @@ -344,11 +352,17 @@ function shuffleQueue(caller?: Caller): boolean { return false } queue = queue - .map(a => ({ song: { ...a, isJuked: false, caller: undefined }, rand: Math.random() })) - .sort((a, b): number => a.rand - b.rand) - .map(a => a.song) + .map(a => ({ + song: { + ...a, + isJuked: false, + caller: undefined + }, + rand: Math.random() + })) + .sort((a, b): number => a.rand - b.rand) + .map(a => a.song) listUi.updateSongs(current, queue) - updateNextSong(queue[0]?.url) emitEvent(queueChangeCallbacks, queue) if (caller !== undefined) { tm.log.info(`${tm.utils.strip(caller.nickname)} (${caller.login}) shuffled the song list`) @@ -360,10 +374,7 @@ function shuffleQueue(caller?: Caller): boolean { function updateNextSong(url: string) { if (url === undefined) { return } - tm.client.callNoRes('SetForcedMusic', [ - { boolean: config.overrideMapMusic }, - { string: url } - ]) + tm.client.callNoRes('SetForcedMusic', [{ boolean: config.overrideMapMusic }, { string: url }]) } function fixDuplicateNames(): boolean { @@ -387,13 +398,17 @@ function fixDuplicateNames(): boolean { async function updateSongsConfigFile(action?: 'add' | 'remove', song?: Song): Promise { if (action === 'add' && song !== undefined) { - songs.push({ name: song.name, author: song.author, url: song.url }) + songs.push({ + name: song.name, + author: song.author, + url: song.url + }) } else if (action === 'remove' && song !== undefined) { songs = songs.filter(a => a.name !== song.name) } try { await fs.writeFile(config.songListPath, 'export default ' + JSON.stringify(songs, null, 2)) - } catch (err: any) { + } catch(err: any) { tm.log.error(`Cannot update song list in config. ${err?.message}`) } } diff --git a/plugins/music/SongList.js b/plugins/music/SongList.js index 0e1534e95..bdb50988b 100644 --- a/plugins/music/SongList.js +++ b/plugins/music/SongList.js @@ -1,7 +1,5 @@ -export default [ - { - "name": "Transcendental Etudes No. 2", - "author": "Franz Liszt", - "url": "https://cdn.discordapp.com/attachments/618467373053247489/1093636918933213274/yt1s.com-Liszt-TE-2-Fusees-ANIMATED.ogg" - }, -] \ No newline at end of file +export default [{ + 'name': 'Transcendental Etudes No. 2', + 'author': 'Franz Liszt', + 'url': 'https://cdn.discordapp.com/attachments/618467373053247489/1093636918933213274/yt1s.com-Liszt-TE-2-Fusees-ANIMATED.ogg' +}] diff --git a/plugins/music/Types.ts b/plugins/music/Types.ts index a40ae0038..7c853e91f 100644 --- a/plugins/music/Types.ts +++ b/plugins/music/Types.ts @@ -3,6 +3,7 @@ interface Caller { readonly nickname: string readonly privilege: number } + interface Song { name: string author: string @@ -11,11 +12,10 @@ interface Song { caller?: Caller } -type QueueChangedCallback = - ((queue: Song[], change?: { - song: Song, - action: 'added' | 'removed' | 'addedToQueue' | 'removedFromQueue' - }) => void | Promise) +type QueueChangedCallback = ((queue: Song[], change?: { + song: Song, + action: 'added' | 'removed' | 'addedToQueue' | 'removedFromQueue' +}) => void | Promise) type SongAddedCallback = ((song: Song, caller?: Readonly) => void | Promise) type SongRemovedCallback = ((song: Song, caller?: Readonly) => void | Promise) diff --git a/plugins/music/ui/MusicWidget.component.ts b/plugins/music/ui/MusicWidget.component.ts index 26646a6f6..3bc6fb681 100644 --- a/plugins/music/ui/MusicWidget.component.ts +++ b/plugins/music/ui/MusicWidget.component.ts @@ -1,4 +1,4 @@ -import { componentIds, StaticHeader, StaticComponent, leftAlignedText } from '../../ui/UI.js' +import { componentIds, leftAlignedText, StaticComponent, StaticHeader } from '../../ui/UI.js' import type { Song } from '../Types.js' import config from './MusicWidget.config.js' @@ -23,7 +23,7 @@ export default class MusicWidget extends StaticComponent { setCurrentSong(song: Song | undefined) { this.song = song const xml = this.display() - if(xml !== undefined) { + if (xml !== undefined) { tm.sendManialink(xml) } } @@ -39,7 +39,10 @@ export default class MusicWidget extends StaticComponent { if (config.hidePanel && this.hasPanelsHidden(login)) { return this.hideToPlayer(login) } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } private updateXML(): void { @@ -73,8 +76,11 @@ export default class MusicWidget extends StaticComponent { private text(text: string, w: number, h: number, xOffset: number = 0, background?: string): string { xOffset = xOffset ?? 0 return ` - ${leftAlignedText(text, w, h, - { textScale: config.textScale, padding: config.textPadding, xOffset, })}` + ${leftAlignedText(text, w, h, { + textScale: config.textScale, + padding: config.textPadding, + xOffset + })}` } } diff --git a/plugins/music/ui/MusicWidget.config.js b/plugins/music/ui/MusicWidget.config.js index eba4d96e4..69d5950f6 100644 --- a/plugins/music/ui/MusicWidget.config.js +++ b/plugins/music/ui/MusicWidget.config.js @@ -7,8 +7,8 @@ export default { margin: cfg.margin, width: cfg.width, background: cfg.background, - noDataText: "--", - iconBackground: "000a", + noDataText: '--', + iconBackground: '000a', icons: { name: icons.disk, author: icons.personMusic @@ -19,4 +19,4 @@ export default { textPadding: 0.15, iconWidth: 1.7, hidePanel: true -} \ No newline at end of file +} diff --git a/plugins/music/ui/SongList.component.ts b/plugins/music/ui/SongList.component.ts index 679f9e0b5..f03dd5048 100644 --- a/plugins/music/ui/SongList.component.ts +++ b/plugins/music/ui/SongList.component.ts @@ -1,4 +1,4 @@ -import { closeButton, componentIds, Grid, centeredText, type GridCellFunction, Paginator, PopupWindow, addManialinkListener } from '../../ui/UI.js' +import { addManialinkListener, centeredText, closeButton, componentIds, Grid, type GridCellFunction, Paginator, PopupWindow } from '../../ui/UI.js' import pluginConfig from '../Config.js' import type { Song } from '../Types.js' import config from './SongList.config.js' @@ -9,7 +9,9 @@ interface DisplayParams { page: number isSearch: boolean paginator?: Paginator, - list?: (Song & { index: number })[] + list?: (Song & { + index: number + })[] query?: string target?: SearchTarget } @@ -17,11 +19,18 @@ interface DisplayParams { export default class SongList extends PopupWindow { private currentSong: Song | undefined - private songs: (Song & { index: number })[] = [] + private songs: (Song & { + index: number + })[] = [] private previousSongs: Song[] = [] private readonly queries: { - paginator: Paginator, list: (Song & { index: number })[], - login: string, query: string, target: SearchTarget + paginator: Paginator, + list: (Song & { + index: number + })[], + login: string, + query: string, + target: SearchTarget }[] = [] readonly grid: Grid readonly paginator: Paginator @@ -39,8 +48,10 @@ export default class SongList extends PopupWindow { new Array(config.entries).fill(1), config.grid) this.paginator = new Paginator(this.openId, this.contentWidth, this.footerHeight, 1) this.paginator.onPageChange = (login, page, info) => { - this.displayToPlayer(login, { page, isSearch: false }, - `${page}/${this.paginator.pageCount}`, info.privilege) + this.displayToPlayer(login, { + page, + isSearch: false + }, `${page}/${this.paginator.pageCount}`, info.privilege) } addManialinkListener(this.openId + this.addActionIdOffset, this.songListSizeLimit, (info, offset) => { const song = this.songs[offset] @@ -57,7 +68,10 @@ export default class SongList extends PopupWindow { updateSongs(currentSong: Song | undefined, queue: Song[]) { this.currentSong = currentSong queue = currentSong === undefined ? [...queue] : [...queue, currentSong] - this.songs = queue.map((a, i) => ({ ...a, index: i + 1 })).sort((a, b) => a.name > b.name ? 1 : -1) + this.songs = queue.map((a, i) => ({ + ...a, + index: i + 1 + })).sort((a, b) => a.name > b.name ? 1 : -1) this.paginator.setPageCount(Math.ceil(queue.length / (config.entries - 1))) this.reRender() } @@ -70,12 +84,19 @@ export default class SongList extends PopupWindow { openWithQuery(player: tm.Player, query: string, searchTarget: SearchTarget = 'name') { const list = this.getSearchResult(query, searchTarget) const paginator = this.getPaginator(player.login, list, player.privilege, query, searchTarget) - this.displayToPlayer(player.login, { page: 1, isSearch: true, paginator, list, query, target: searchTarget }, - `1/${paginator.pageCount}`, player.privilege) + this.displayToPlayer(player.login, { + page: 1, + isSearch: true, + paginator, + list, + query, + target: searchTarget + }, `1/${paginator.pageCount}`, player.privilege) } - private getPaginator(login: string, list: (Song & { index: number })[], - privilege: number, query: string, target: SearchTarget): Paginator { + private getPaginator(login: string, list: (Song & { + index: number + })[], privilege: number, query: string, target: SearchTarget): Paginator { const pageCount: number = Math.ceil(list.length / (config.entries - 1)) const playerQuery = this.queries.find(a => a.login === login) let paginator: Paginator // @@ -85,21 +106,36 @@ export default class SongList extends PopupWindow { paginator.setPageForLogin(login, 1) paginator.setPageCount(pageCount) } else { - paginator = new Paginator(this.openId + this.paginatorIdOffset + this.nextPaginatorId, - this.windowWidth, this.footerHeight, pageCount) + paginator = new Paginator(this.openId + this.paginatorIdOffset + this.nextPaginatorId, this.windowWidth, + this.footerHeight, pageCount) this.nextPaginatorId += 10 this.nextPaginatorId = (this.nextPaginatorId % this.paginatorIdLimit) + this.paginatorIdOffset - this.queries.push({ paginator, login, list, query, target }) + this.queries.push({ + paginator, + login, + list, + query, + target + }) } - paginator.onPageChange = (login: string, page: number): Promise => this.displayToPlayer(login, - { page, isSearch: true, paginator, list }, `${page}/${pageCount}`, privilege) + paginator.onPageChange = (login: string, page: number): Promise => this.displayToPlayer(login, { + page, + isSearch: true, + paginator, + list + }, `${page}/${pageCount}`, privilege) return paginator } - protected onOpen(info: { login: string, privilege: number }): void { + protected onOpen(info: { + login: string, + privilege: number + }): void { const page = this.paginator.getPageByLogin(info.login) - this.displayToPlayer(info.login, { page, isSearch: false }, - `${page}/${this.paginator.pageCount}`, info.privilege) + this.displayToPlayer(info.login, { + page, + isSearch: false + }, `${page}/${this.paginator.pageCount}`, info.privilege) } private reRender(): void { @@ -110,8 +146,8 @@ export default class SongList extends PopupWindow { if (player.params.query !== undefined) { player.params.list = this.getSearchResult(player.params.query, player.params.target ?? 'name') } - this.displayToPlayer(player.login, player.params, - `${page}/${paginator.pageCount}`, tm.players.get(player.login)?.privilege ?? 0) + this.displayToPlayer(player.login, player.params, `${page}/${paginator.pageCount}`, + tm.players.get(player.login)?.privilege ?? 0) } } @@ -124,23 +160,17 @@ export default class SongList extends PopupWindow { const page = params?.page ?? 1 const list = params?.list ?? this.songs const index = (page - 1) * (config.entries - 1) - 1 - const headers: GridCellFunction[] = [ - (i, j, w, h) => centeredText(' Index ', w, h), - (i, j, w, h) => centeredText(' Name ', w, h), - (i, j, w, h) => centeredText(' Author ', w, h), - (i, j, w, h) => centeredText('Queue position', w, h), - (i, j, w, h) => centeredText(' Queued by ', w, h), - (i, j, w, h) => centeredText(' Queue ', w, h), - ] + const headers: GridCellFunction[] = [(i, j, w, h) => centeredText(' Index ', w, h), + (i, j, w, h) => centeredText(' Name ', w, h), (i, j, w, h) => centeredText(' Author ', w, h), + (i, j, w, h) => centeredText('Queue position', w, h), (i, j, w, h) => centeredText(' Queued by ', w, h), + (i, j, w, h) => centeredText(' Queue ', w, h)] const indexCell: GridCellFunction = (i, j, w, h) => { const cover = '' return centeredText((i + index + 1).toString(), w, h) + cover } - const nameCell: GridCellFunction = (i, j, w, h) => - centeredText(list[index + i].name, w, h) - const authorCell: GridCellFunction = (i, j, w, h) => - centeredText(list[index + i].author, w, h) + const nameCell: GridCellFunction = (i, j, w, h) => centeredText(list[index + i].name, w, h) + const authorCell: GridCellFunction = (i, j, w, h) => centeredText(list[index + i].author, w, h) const queueIndex: GridCellFunction = (i, j, w, h) => { const song = list[index + i] if (this.currentSong?.name === song.name) { @@ -148,8 +178,8 @@ export default class SongList extends PopupWindow { } return centeredText(song.index.toString(), w, h) } - const queuedByCell: GridCellFunction = (i, j, w, h) => - centeredText(list[index + i].caller?.nickname ?? config.defaultText, w, h) + const queuedByCell: GridCellFunction = (i, j, w, h) => centeredText( + list[index + i].caller?.nickname ?? config.defaultText, w, h) const addToQueueCell: GridCellFunction = (i, j, w, h) => { const song = list[index + i] let actionId = this.openId + this.addActionIdOffset + index + i @@ -186,15 +216,14 @@ export default class SongList extends PopupWindow { private checkQueuePrivilege(login: string, privilege: number, song: Song): boolean { if (this.currentSong?.name === song.name) { return false } if (song.caller?.login === login) { return true } - return !((privilege < pluginConfig.forceQueuePrivilege && - (this.previousSongs.some(a => a.name === song.name) || - this.songs.some(a => a.caller?.login === login))) || - (song.isJuked && privilege < pluginConfig.forceQueuePrivilege)) + return !((privilege < pluginConfig.forceQueuePrivilege && (this.previousSongs.some( + a => a.name === song.name) || this.songs.some( + a => a.caller?.login === login))) || (song.isJuked && privilege < pluginConfig.forceQueuePrivilege)) } protected constructFooter(login: string, params?: DisplayParams): string { - return closeButton(this.closeId, this.windowWidth, this.footerHeight) + - (params?.paginator ?? this.paginator).constructXml(login) + return closeButton(this.closeId, this.windowWidth, + this.footerHeight) + (params?.paginator ?? this.paginator).constructXml(login) } } diff --git a/plugins/music/ui/SongList.config.js b/plugins/music/ui/SongList.config.js index b0912452e..1e959ae11 100644 --- a/plugins/music/ui/SongList.config.js +++ b/plugins/music/ui/SongList.config.js @@ -1,8 +1,9 @@ import { icons } from '../../ui/UI.js' + const p = tm.utils.palette export default { - title: " Song List ", + title: ' Song List ', icon: icons.musicList, iconWidth: 2, iconHeight: 2, @@ -14,19 +15,11 @@ export default { overlayColour: '7777', entries: 15, defaultText: '--', - columnProportions: [ - 1, - 3, - 3, - 1.7, - 3, - 1 - ], - navbar: [ - ], + columnProportions: [1, 3, 3, 1.7, 3, 1], + navbar: [], grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" - }, -} \ No newline at end of file + headerBackground: '333C' + } +} diff --git a/plugins/sector_records/Config.js b/plugins/sector_records/Config.js index 116900d62..5c884fc3c 100644 --- a/plugins/sector_records/Config.js +++ b/plugins/sector_records/Config.js @@ -1,18 +1,15 @@ -import { palette as p } from "../../config/PrefixesAndPalette.js" +import { palette as p } from '../../config/PrefixesAndPalette.js' export default { - isEnabled: true, - // Using DB client makes the plugin a bit faster due to high amount of database queries + isEnabled: true, // Using DB client makes the plugin a bit faster due to high amount of database queries // If too many plugins are using DB clients the process might hang useDBClient: true, noSectorRecords: `${p.error}You have no sector records on the ongoing map.`, allPlayerSectorsRemoved: `${p.servermsg}Your sectors on the ongoing map were removed.`, outOfRange: `${p.error}Sector index needs to be > 0 and <= to the ongoing map's sector count.`, playerSectorRemoved: `${p.servermsg}Your ${p.highlight}#{index}${p.servermsg} sector was removed.`, - allBestSectorsRemoved: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has removed ` - + `${p.highlight}all sector records${p.admin} on the ongoing map.`, - bestSectorRemoved: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has removed the ` - + `${p.highlight}#{index}${p.admin} sector record on the ongoing map.`, + allBestSectorsRemoved: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has removed ` + `${p.highlight}all sector records${p.admin} on the ongoing map.`, + bestSectorRemoved: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has removed the ` + `${p.highlight}#{index}${p.admin} sector record on the ongoing map.`, commands: { deletesector: { aliases: ['delsec', 'deletesector'], @@ -26,4 +23,4 @@ export default { privilege: 0 } } -} \ No newline at end of file +} diff --git a/plugins/sector_records/SectorDB.ts b/plugins/sector_records/SectorDB.ts index 2138eca6d..2f17f1c99 100644 --- a/plugins/sector_records/SectorDB.ts +++ b/plugins/sector_records/SectorDB.ts @@ -13,8 +13,7 @@ const createQueries = [`CREATE TABLE IF NOT EXISTS best_sector_records( CONSTRAINT fk_map_id FOREIGN KEY(map_id) REFERENCES map_ids(id) -);`, -`CREATE TABLE IF NOT EXISTS sector_records( +);`, `CREATE TABLE IF NOT EXISTS sector_records( map_id INT4 NOT NULL, player_id INT4 NOT NULL, sectors INT4[] NOT NULL, @@ -41,18 +40,30 @@ for (const e of createQueries) { export const allSecsDB = { - async get(mapDBId: number, ...playerLogins: string[]): Promise<{ sectors: (number | undefined)[], login: string, nickname: string }[] | Error> { + async get(mapDBId: number, ...playerLogins: string[]): Promise<{ + sectors: (number | undefined)[], + login: string, + nickname: string + }[] | Error> { if (playerLogins.length === 0) { return [] } const playerDBIds = await tm.db.getPlayerId(playerLogins) const query = `SELECT sectors, login, nickname FROM sector_records JOIN players ON players.id=sector_records.player_id WHERE map_id=$1 AND (${playerDBIds.map((_: any, i: number) => `player_id=$${i + 2} OR `).join('').slice(0, -3)});` - const res: { sectors: number[], login: string, nickname: string }[] | Error = (await queryDB(query, mapDBId, ...playerDBIds.map(a => a.id))) + const res: { + sectors: number[], + login: string, + nickname: string + }[] | Error = (await queryDB(query, mapDBId, ...playerDBIds.map(a => a.id))) if (res instanceof Error) { tm.log.error(`Error when fetching sector records of players ${playerDBIds} on map ${mapDBId}`, res.message) return [] } - return res.map(a => ({ login: a.login, sectors: a.sectors.map(b => b === -1 ? undefined : b), nickname: a.nickname })) + return res.map(a => ({ + login: a.login, + sectors: a.sectors.map(b => b === -1 ? undefined : b), + nickname: a.nickname + })) }, async add(mapId: number, login: string, sectors: number[]): Promise { @@ -73,11 +84,21 @@ export const allSecsDB = { export const bestSecsDB = { - async get(mapId: string | number): Promise<{ sector: number, date: Date, login: string, nickname: string }[] | Error> { + async get(mapId: string | number): Promise<{ + sector: number, + date: Date, + login: string, + nickname: string + }[] | Error> { const mapDBId = typeof mapId === 'number' ? mapId : await tm.db.getMapId(mapId) if (mapDBId === undefined) { return [] } - const res: { sector: number, date: Date, index: number, login: string, nickname: string }[] | Error = - (await queryDB(`SELECT sector, index, date, login, nickname FROM best_sector_records + const res: { + sector: number, + date: Date, + index: number, + login: string, + nickname: string + }[] | Error = (await queryDB(`SELECT sector, index, date, login, nickname FROM best_sector_records JOIN players ON players.id=best_sector_records.player_id WHERE map_id=$1;`, mapId)) if (res instanceof Error) { @@ -86,12 +107,18 @@ export const bestSecsDB = { } const ret: Omit[] = [] for (const e of res) { - ret[e.index] = { sector: e.sector, date: e.date, login: e.login, nickname: e.nickname } + ret[e.index] = { + sector: e.sector, + date: e.date, + login: e.login, + nickname: e.nickname + } } return ret }, - async add(mapId: string | number, playerId: string | number, index: number, sector: number, date: Date): Promise { + async add(mapId: string | number, playerId: string | number, index: number, sector: number, + date: Date): Promise { const mapDBId = typeof mapId === 'number' ? mapId : await tm.db.getMapId(mapId) const playerDBId = typeof playerId === 'number' ? playerId : await tm.db.getPlayerId(playerId) if (mapDBId === undefined || playerDBId === undefined) { return } @@ -99,7 +126,8 @@ export const bestSecsDB = { await queryDB(query, mapDBId, playerDBId, index, sector, date) }, - async update(mapId: string | number, playerId: string | number, index: number, sector: number, date: Date): Promise { + async update(mapId: string | number, playerId: string | number, index: number, sector: number, + date: Date): Promise { const mapDBId = typeof mapId === 'number' ? mapId : await tm.db.getMapId(mapId) const playerDBId = typeof playerId === 'number' ? playerId : await tm.db.getPlayerId(playerId) if (mapDBId === undefined || playerDBId === undefined) { return } diff --git a/plugins/sector_records/SectorEvents.ts b/plugins/sector_records/SectorEvents.ts index 00a64d13a..0fb6e7e43 100644 --- a/plugins/sector_records/SectorEvents.ts +++ b/plugins/sector_records/SectorEvents.ts @@ -13,7 +13,7 @@ const nicknameUpdateListeners: Function[] = [] * @param callback Callback function to execute */ const addListener = (event: T, callback: SectorEventFunctions[T]): void => { - switch (event) { + switch(event) { case 'BestSector': bestSectorListeners.push(callback as any) return @@ -34,9 +34,10 @@ const addListener = (event: T, callback: S } } -const emitEvent = async (event: T, ...params: Parameters) => { +const emitEvent = async (event: T, + ...params: Parameters) => { let listeners: any - switch (event) { + switch(event) { case 'BestSector': listeners = bestSectorListeners break diff --git a/plugins/sector_records/SectorListeners.ts b/plugins/sector_records/SectorListeners.ts index 901953ec9..01fc3472b 100644 --- a/plugins/sector_records/SectorListeners.ts +++ b/plugins/sector_records/SectorListeners.ts @@ -1,6 +1,6 @@ import type { BestSectors, PlayerSectors } from './SectorTypes.js' -import { bestSecsDB, allSecsDB } from './SectorDB.js' -import { emitEvent } from './SectorEvents.js' +import { allSecsDB, bestSecsDB } from './SectorDB.js' +import { addListener, emitEvent } from './SectorEvents.js' import config from './Config.js' let currentBestSecs: BestSectors @@ -19,7 +19,9 @@ const onMapStart = async (): Promise => { await tm.log.fatal(`Failed to fetch best sectors for map ${tm.maps.current.id}`, res.message) return } - if (res === undefined) return + if (res === undefined) { + return + } currentBestSecs = res const playerSecs = await allSecsDB.get(currentMapDBId, ...tm.players.list.map(a => a.login)) if (playerSecs instanceof Error) { @@ -28,7 +30,7 @@ const onMapStart = async (): Promise => { } currentPlayerSecs.length = 0 currentPlayerSecs.push(...playerSecs) - emitEvent('SectorsFetch', currentBestSecs, currentPlayerSecs) + void emitEvent('SectorsFetch', currentBestSecs, currentPlayerSecs) } if (config.isEnabled) { @@ -41,8 +43,20 @@ if (config.isEnabled) { await onMapStart() }, true) - tm.addListener('PlayerDataUpdated', (info) => { - const changedObjects: { login: string; nickname: string; }[] = [] + const playerDataUpdated = (info: readonly tm.PlayerDataUpdatedInfo[], fromQueue: boolean = false) => { + if (currentBestSecs === undefined) { + if (!fromQueue) { + tm.log.debug('PlayerDataUpdated event before sectors initialised, adding to queue') + addListener('SectorsFetch', () => playerDataUpdated(info, true)) + } else { + void tm.log.fatal('currentBestSecs is still undefined after sectors plugin initialisation') + } + return + } + const changedObjects: { + login: string; + nickname: string; + }[] = [] for (const e of currentBestSecs) { if (e === undefined) { continue } const newNickname = info.find(a => a.login === e.login)?.nickname @@ -60,173 +74,290 @@ if (config.isEnabled) { } } if (changedObjects.length !== 0) { - emitEvent('NicknameUpdated', changedObjects) + void emitEvent('NicknameUpdated', changedObjects) } - }) + } + tm.addListener('PlayerDataUpdated', playerDataUpdated) - tm.addListener('PlayerCheckpoint', (info: tm.CheckpointInfo) => { + const playerCheckpoint = (info: tm.CheckpointInfo, fromQueue: boolean = false) => { + if (currentBestSecs === undefined) { + if (!fromQueue) { + tm.log.debug('PlayerCheckpoint event before sectors initialised, adding to queue') + addListener('SectorsFetch', () => playerCheckpoint(info, true)) + } else { + void tm.log.fatal('currentBestSecs is still undefined after sectors plugin initialisation') + } + return + } const date = new Date() const playerSectors = currentPlayerSecs.find(a => a.login === info.player.login) const time = info.time - (info.player.currentCheckpoints[info.index - 1]?.time ?? 0) const isStunts = tm.getGameMode() === 'Stunts' if (playerSectors === undefined) { - currentPlayerSecs.push({ login: info.player.login, sectors: [time], nickname: info.player.nickname }) + currentPlayerSecs.push({ + login: info.player.login, + sectors: [time], + nickname: info.player.nickname + }) void allSecsDB.add(currentMapDBId, info.player.login, [time]) - emitEvent('PlayerSector', { login: info.player.login, nickname: info.player.nickname, index: info.index }) - } else if (((playerSectors.sectors[info.index] ?? Infinity) > time && !isStunts) || - ((playerSectors.sectors[info.index] ?? -1) < time && isStunts)) { + emitEvent('PlayerSector', { + login: info.player.login, + nickname: info.player.nickname, + index: info.index + }) + } else if (((playerSectors.sectors[info.index] ?? Infinity) > time && !isStunts) || ((playerSectors.sectors[info.index] ?? -1) < time && isStunts)) { playerSectors.sectors[info.index] = time void allSecsDB.update(currentMapDBId, info.player.login, playerSectors.sectors.map(a => a === undefined ? -1 : a)) - emitEvent('PlayerSector', { login: info.player.login, nickname: info.player.nickname, index: info.index }) + emitEvent('PlayerSector', { + login: info.player.login, + nickname: info.player.nickname, + index: info.index + }) } const sector = currentBestSecs[info.index]?.sector - if (sector === undefined || - (sector > time && !isStunts) || - (sector < time && isStunts)) { + if (sector === undefined || (sector > time && !isStunts) || (sector < time && isStunts)) { currentBestSecs[info.index] = { login: info.player.login, nickname: info.player.nickname, sector: time, date: date } - sector === undefined ? void bestSecsDB.add(currentMapDBId, info.player.login, info.index, time, date) - : void bestSecsDB.update(currentMapDBId, info.player.login, info.index, time, date) - emitEvent('BestSector', { login: info.player.login, nickname: info.player.nickname, index: info.index, date }) + sector === undefined ? void bestSecsDB.add(currentMapDBId, info.player.login, info.index, time, date) : + void bestSecsDB.update(currentMapDBId, info.player.login, info.index, time, date) + emitEvent('BestSector', { + login: info.player.login, + nickname: info.player.nickname, + index: info.index, + date + }) } - }) + } + tm.addListener('PlayerCheckpoint', playerCheckpoint) - tm.addListener('PlayerFinish', (info: tm.FinishInfo) => { + const playerFinish = (info: tm.FinishInfo, fromQueue: boolean = false) => { + if (currentBestSecs === undefined) { + if (!fromQueue) { + tm.log.debug('PlayerCheckpoint event before sectors initialised, adding to queue') + addListener('SectorsFetch', () => playerFinish(info, true)) + } else { + void tm.log.fatal('currentBestSecs is still undefined after sectors plugin initialisation') + } + return + } const date = new Date() const index = info.checkpoints.length const playerSectors = currentPlayerSecs.find(a => a.login === info.login) const time = info.time - (info.checkpoints[index - 1] ?? 0) const isStunts = tm.getGameMode() === 'Stunts' if (playerSectors === undefined) { - currentPlayerSecs.push({ login: info.login, sectors: [time], nickname: info.nickname }) + currentPlayerSecs.push({ + login: info.login, + sectors: [time], + nickname: info.nickname + }) void allSecsDB.add(currentMapDBId, info.login, [time]) - emitEvent('PlayerSector', { login: info.login, nickname: info.nickname, index: index }) - } else if (((playerSectors.sectors[index] ?? Infinity) > time && !isStunts) || - ((playerSectors.sectors[index] ?? -1) < time && isStunts)) { + emitEvent('PlayerSector', { + login: info.login, + nickname: info.nickname, + index: index + }) + } else if (((playerSectors.sectors[index] ?? Infinity) > time && !isStunts) || ((playerSectors.sectors[index] ?? -1) < time && isStunts)) { playerSectors.sectors[index] = time void allSecsDB.update(currentMapDBId, info.login, playerSectors.sectors.map(a => a === undefined ? -1 : a)) - emitEvent('PlayerSector', { login: info.login, nickname: info.nickname, index: index }) + emitEvent('PlayerSector', { + login: info.login, + nickname: info.nickname, + index: index + }) } const sector = currentBestSecs[index]?.sector - if (sector === undefined || - (sector > time && !isStunts) || - (sector < time && isStunts)) { + if (sector === undefined || (sector > time && !isStunts) || (sector < time && isStunts)) { currentBestSecs[index] = { login: info.login, nickname: info.nickname, sector: time, date: date } - sector === undefined ? void bestSecsDB.add(currentMapDBId, info.login, index, time, date) - : void bestSecsDB.update(currentMapDBId, info.login, index, time, date) - emitEvent('BestSector', { login: info.login, nickname: info.nickname, index: index, date }) + sector === undefined ? void bestSecsDB.add(currentMapDBId, info.login, index, time, date) : + void bestSecsDB.update(currentMapDBId, info.login, index, time, date) + emitEvent('BestSector', { + login: info.login, + nickname: info.nickname, + index: index, + date + }) } - }) + } + tm.addListener('PlayerFinish', playerFinish) - tm.addListener('PlayerJoin', async (info) => { + const playerJoin = async (info: Readonly>, fromQueue: boolean = false) => { + if (currentBestSecs === undefined) { + if (!fromQueue) { + tm.log.debug('PlayerCheckpoint event before sectors initialised, adding to queue') + addListener('SectorsFetch', () => playerJoin(info, true)) + } else { + void tm.log.fatal('currentBestSecs is still undefined after sectors plugin initialisation') + } + return + } const playerSecs = await allSecsDB.get(currentMapDBId, info.login) if (playerSecs instanceof Error) { - await tm.log.fatal(`Failed to fetch player ${info.login} sectors for map ${tm.maps.current.id}`, playerSecs.message) + await tm.log.fatal(`Failed to fetch player ${info.login} sectors for map ${tm.maps.current.id}`, + playerSecs.message) return } currentPlayerSecs.push(...playerSecs) - }) + } + tm.addListener('PlayerJoin', playerJoin) - tm.commands.add( - { - aliases: config.commands.deletemysector.aliases, - help: config.commands.deletemysector.help, - params: [{ name: 'sectorIndex', type: 'int', optional: true }], - callback(info, sectorIndex?: number) { - const secs = currentPlayerSecs.find(a => a.login === info.login) - if (secs === undefined) { - tm.sendMessage(config.noSectorRecords, info.login) + tm.commands.add({ + aliases: config.commands.deletemysector.aliases, + help: config.commands.deletemysector.help, + params: [{ + name: 'sectorIndex', + type: 'int', + optional: true + }], + callback(info, sectorIndex?: number) { + if (currentBestSecs === undefined) { + tm.sendMessage(config.noSectorRecords, info.login) + return + } + const secs = currentPlayerSecs.find(a => a.login === info.login) + if (secs === undefined) { + tm.sendMessage(config.noSectorRecords, info.login) + return + } + if (sectorIndex === undefined) { + const arr: number[] = secs.sectors.filter(a => a !== undefined) as any + secs.sectors.length = 0 + tm.sendMessage(config.allPlayerSectorsRemoved, info.login) + emitEvent('DeletePlayerSector', { + ...info, + deletedSectors: arr.map((a, i) => ({ + time: a, + index: i + })) + }) + void allSecsDB.update(currentMapDBId, info.login, secs.sectors.map(a => a === undefined ? -1 : a)) + } else { + if (sectorIndex < 1 || sectorIndex > tm.maps.current.checkpointsAmount) { + tm.sendMessage(config.outOfRange, info.login) return } - if (sectorIndex === undefined) { - const arr: number[] = secs.sectors.filter(a => a !== undefined) as any - secs.sectors.length = 0 - tm.sendMessage(config.allPlayerSectorsRemoved, info.login) - emitEvent('DeletePlayerSector', { ...info, deletedSectors: arr.map((a, i) => ({ time: a, index: i })) }) - void allSecsDB.update(currentMapDBId, info.login, secs.sectors.map(a => a === undefined ? -1 : a)) - } else { - if (sectorIndex < 1 || sectorIndex > tm.maps.current.checkpointsAmount) { - tm.sendMessage(config.outOfRange, info.login) - return - } - const deleted = secs.sectors[sectorIndex - 1] - secs.sectors[sectorIndex - 1] = undefined - tm.sendMessage(tm.utils.strVar(config.playerSectorRemoved, { index: tm.utils.getOrdinalSuffix(sectorIndex) }), info.login) - if (deleted !== undefined) { - emitEvent('DeletePlayerSector', { ...info, deletedSectors: [{ time: deleted, index: sectorIndex }] }) - } - void allSecsDB.update(currentMapDBId, info.login, secs.sectors.map(a => a === undefined ? -1 : a)) + const deleted = secs.sectors[sectorIndex - 1] + secs.sectors[sectorIndex - 1] = undefined + tm.sendMessage(tm.utils.strVar(config.playerSectorRemoved, { index: tm.utils.getOrdinalSuffix(sectorIndex) }), + info.login) + if (deleted !== undefined) { + emitEvent('DeletePlayerSector', { + ...info, + deletedSectors: [{ + time: deleted, + index: sectorIndex + }] + }) } - }, - privilege: config.commands.deletemysector.privilege + void allSecsDB.update(currentMapDBId, info.login, secs.sectors.map(a => a === undefined ? -1 : a)) + } }, - { - aliases: config.commands.deletesector.aliases, - help: config.commands.deletesector.help, - params: [{ name: 'sectorIndex', type: 'int', optional: true }], - callback(info, sectorIndex?: number) { - if (sectorIndex === undefined) { - const arr: { - login: string, - nickname: string, - sector: number, - date: Date - }[] = currentBestSecs.filter(a => a !== undefined) as any - currentBestSecs.length = 0 - tm.sendMessage(tm.utils.strVar(config.allBestSectorsRemoved, - { title: info.title, nickname: tm.utils.strip(info.nickname, true) })) - emitEvent('DeleteBestSector', arr.map((a, i) => ({ ...a, index: i }))) - void bestSecsDB.delete(currentMapDBId) - } else { - if (sectorIndex < 1 || sectorIndex > tm.maps.current.checkpointsAmount + 1) { - tm.sendMessage(config.outOfRange, info.login) - return - } - const deleted = currentBestSecs[sectorIndex - 1] - currentBestSecs[sectorIndex - 1] = undefined - tm.sendMessage(tm.utils.strVar(config.bestSectorRemoved, { - title: info.title, - nickname: tm.utils.strip(info.nickname, true), - index: tm.utils.getOrdinalSuffix(sectorIndex) - }), config.commands.deletesector.public ? undefined : info.login) - if (deleted !== undefined) { - emitEvent('DeleteBestSector', [{ ...deleted, index: sectorIndex }]) - } - void bestSecsDB.delete(currentMapDBId, sectorIndex - 1) + privilege: config.commands.deletemysector.privilege + }, { + aliases: config.commands.deletesector.aliases, + help: config.commands.deletesector.help, + params: [{ + name: 'sectorIndex', + type: 'int', + optional: true + }], + callback(info, sectorIndex?: number) { + if (currentBestSecs === undefined) { + tm.sendMessage(config.noSectorRecords, info.login) + return + } + if (sectorIndex === undefined) { + const arr: { + login: string, + nickname: string, + sector: number, + date: Date + }[] = currentBestSecs.filter(a => a !== undefined) as any + currentBestSecs.length = 0 + tm.sendMessage(tm.utils.strVar(config.allBestSectorsRemoved, { + title: info.title, + nickname: tm.utils.strip(info.nickname, true) + })) + emitEvent('DeleteBestSector', arr.map((a, i) => ({ + ...a, + index: i + }))) + void bestSecsDB.delete(currentMapDBId) + } else { + if (sectorIndex < 1 || sectorIndex > tm.maps.current.checkpointsAmount + 1) { + tm.sendMessage(config.outOfRange, info.login) + return } - }, - privilege: config.commands.deletesector.privilege - } - ) + const deleted = currentBestSecs[sectorIndex - 1] + currentBestSecs[sectorIndex - 1] = undefined + tm.sendMessage(tm.utils.strVar(config.bestSectorRemoved, { + title: info.title, + nickname: tm.utils.strip(info.nickname, true), + index: tm.utils.getOrdinalSuffix(sectorIndex) + }), config.commands.deletesector.public ? undefined : info.login) + if (deleted !== undefined) { + emitEvent('DeleteBestSector', [{ + ...deleted, + index: sectorIndex + }]) + } + void bestSecsDB.delete(currentMapDBId, sectorIndex - 1) + } + }, + privilege: config.commands.deletesector.privilege + }) } -const getMapSectors = (): ({ login: string, nickname: string, sector: number, date: Date } | null)[] => { - const arr: ({ login: string, nickname: string, sector: number, date: Date } | null)[] = new Array(tm.maps.current.checkpointsAmount).fill(null) +const getMapSectors = (): ({ + login: string, + nickname: string, + sector: number, + date: Date +} | null)[] => { + if (currentBestSecs === undefined) { + tm.log.debug('getMapSectors called before initialisation, aborting') + return [] + } + const arr: ({ + login: string, + nickname: string, + sector: number, + date: Date + } | null)[] = new Array(tm.maps.current.checkpointsAmount).fill(null) for (const [i, e] of currentBestSecs.entries()) { arr[i] = e ?? null } return arr } -const getPlayerSectors = (): ({ login: string, nickname: string, sectors: (number | null)[] })[] => { - const arr: ({ login: string, nickname: string, sectors: (number | null)[] })[] = [] +const getPlayerSectors = (): ({ + login: string, + nickname: string, + sectors: (number | null)[] +})[] => { + const arr: ({ + login: string, + nickname: string, + sectors: (number | null)[] + })[] = [] for (const [i, e] of tm.players.list.entries()) { arr[i] = { login: e.login, nickname: e.nickname, - sectors: new Array(tm.maps.current.checkpointsAmount).fill(null).map((a, i) => currentPlayerSecs.find(a => a.login === e.login)?.sectors[i] ?? null) + sectors: new Array(tm.maps.current.checkpointsAmount).fill(null) + .map((a, i) => currentPlayerSecs.find(a => a.login === e.login)?.sectors[i] ?? null) } } return arr } -export { getMapSectors, getPlayerSectors } \ No newline at end of file +export { getMapSectors, getPlayerSectors } diff --git a/plugins/sector_records/SectorRecords.ts b/plugins/sector_records/SectorRecords.ts index a46ed7f73..9b1082ef5 100644 --- a/plugins/sector_records/SectorRecords.ts +++ b/plugins/sector_records/SectorRecords.ts @@ -14,14 +14,23 @@ export const sectorRecords = { /** * @returns an array of current map best sector records or null if sector record doesn't exist */ - get mapSectors(): ({ login: string, nickname: string, sector: number, date: Date } | null)[] { + get mapSectors(): ({ + login: string, + nickname: string, + sector: number, + date: Date + } | null)[] { return getMapSectors() }, /** * @returns an array of current map online players and their sector records */ - get playerSectors(): ({ login: string, nickname: string, sectors: (number | null)[] })[] { + get playerSectors(): ({ + login: string, + nickname: string, + sectors: (number | null)[] + })[] { return getPlayerSectors() }, @@ -29,7 +38,7 @@ export const sectorRecords = { * Registers a callback to execute on a given event * @param event Event name * @param callback Callback function to execute - */ + */ addListener, /** diff --git a/plugins/sector_records/SectorTypes.ts b/plugins/sector_records/SectorTypes.ts index f75928dda..d546e9d62 100644 --- a/plugins/sector_records/SectorTypes.ts +++ b/plugins/sector_records/SectorTypes.ts @@ -12,13 +12,34 @@ export interface PlayerSectors { } export interface SectorEventFunctions { - 'BestSector': ((bestSector: Readonly<{ login: string, nickname: string, index: number, date: Date }>) => void) + 'BestSector': ((bestSector: Readonly<{ + login: string, + nickname: string, + index: number, + date: Date + }>) => void) 'SectorsFetch': ((bestSectors: Readonly, playerSectors: readonly Readonly[]) => void) - 'DeleteBestSector': ((deletedSectors: readonly Readonly<{ index: number, login: string, sector: number, date: Date }>[]) => void) + 'DeleteBestSector': ((deletedSectors: readonly Readonly<{ + index: number, + login: string, + sector: number, + date: Date + }>[]) => void) 'DeletePlayerSector': ((player: Readonly<{ - login: string, nickname: string, - deletedSectors: readonly Readonly<{ index: number, time: number }>[] + login: string, + nickname: string, + deletedSectors: readonly Readonly<{ + index: number, + time: number + }>[] + }>) => void) + 'PlayerSector': ((playerSector: Readonly<{ + login: string, + nickname: string, + index: number }>) => void) - 'PlayerSector': ((playerSector: Readonly<{ login: string, nickname: string, index: number }>) => void) - 'NicknameUpdated': ((players: Readonly[]>) => void) + 'NicknameUpdated': ((players: Readonly[]>) => void) } diff --git a/plugins/sector_records/ui/SectorRecords.component.ts b/plugins/sector_records/ui/SectorRecords.component.ts index faafc6133..a2ea6c2ae 100644 --- a/plugins/sector_records/ui/SectorRecords.component.ts +++ b/plugins/sector_records/ui/SectorRecords.component.ts @@ -3,7 +3,7 @@ * @since 0.3 */ -import { closeButton, componentIds, Grid, centeredText, Paginator, type GridCellFunction, type GridCellObject, PopupWindow } from '../../ui//UI.js' +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, Paginator, PopupWindow } from '../../ui//UI.js' import { sectorRecords } from '../../sector_records/SectorRecords.js' import config from './SectorRecords.config.js' @@ -13,7 +13,8 @@ class SectorRecords extends PopupWindow { private readonly paginator: Paginator constructor() { - super(componentIds.sectorRecords, config.icon, config.title, (tm.getGameMode() === 'Stunts' ? config.stuntsNavbar : config.navbar)) + super(componentIds.sectorRecords, config.icon, config.title, + (tm.getGameMode() === 'Stunts' ? config.stuntsNavbar : config.navbar)) this.grid = new Grid(this.contentWidth, this.contentHeight, config.columnProportions, new Array(config.entries + 2).fill(1), config.grid) this.paginator = new Paginator(this.openId, this.contentWidth, this.footerHeight, @@ -58,15 +59,13 @@ class SectorRecords extends PopupWindow { this.displayToPlayer(login, { page }, `${page}/${this.paginator.pageCount}`) } - protected constructContent(login: string, params: { page: number }): string { - const headers: GridCellFunction[] = [ - (i, j, w, h) => centeredText(' Index ', w, h), - (i, j, w, h) => centeredText(' Nickname ', w, h), - (i, j, w, h) => centeredText(' Login ', w, h), - (i, j, w, h) => centeredText(' Date ', w, h), - (i, j, w, h) => centeredText(' Best ', w, h), - (i, j, w, h) => centeredText(' Personal ', w, h) - ] + protected constructContent(login: string, params: { + page: number + }): string { + const headers: GridCellFunction[] = [(i, j, w, h) => centeredText(' Index ', w, h), + (i, j, w, h) => centeredText(' Nickname ', w, h), (i, j, w, h) => centeredText(' Login ', w, h), + (i, j, w, h) => centeredText(' Date ', w, h), (i, j, w, h) => centeredText(' Best ', w, h), + (i, j, w, h) => centeredText(' Personal ', w, h)] const sectors = sectorRecords.mapSectors const sectorIndex = config.entries * (params.page - 1) const personalSectors = sectorRecords.playerSectors @@ -76,7 +75,8 @@ class SectorRecords extends PopupWindow { } const nicknameCell: GridCellFunction = (i, j, w, h) => { - return centeredText(tm.utils.safeString(tm.utils.strip(sectors[i + sectorIndex - 1]?.nickname ?? '-', false)), w, h) + return centeredText(tm.utils.safeString(tm.utils.strip(sectors[i + sectorIndex - 1]?.nickname ?? '-', false)), w, + h) } const loginCell: GridCellFunction = (i, j, w, h) => { @@ -87,8 +87,7 @@ class SectorRecords extends PopupWindow { const dateCell: GridCellFunction = (i, j, w, h) => { const sector = sectors[i + sectorIndex - 1] - return centeredText((sector === undefined || sector === null) ? '' : - tm.utils.formatDate(sector.date, true), w, h) + return centeredText((sector === undefined || sector === null) ? '' : tm.utils.formatDate(sector.date, true), w, h) } const bestSectorCell: GridCellFunction = (i, j, w, h) => { @@ -106,7 +105,7 @@ class SectorRecords extends PopupWindow { const bestSec = sectors?.[i + sectorIndex - 1]?.sector let betterSign = '-' let worseSign = '+' - if(isStunts) { + if (isStunts) { betterSign = '+' worseSign = '-' } @@ -128,7 +127,7 @@ class SectorRecords extends PopupWindow { const emptyCell: GridCellObject = { background: '', colspan: 3, - callback: () => '', + callback: () => '' } const totalTimeHeaderCell: GridCellObject = { @@ -154,7 +153,7 @@ class SectorRecords extends PopupWindow { } let betterSign = '-' let worseSign = '+' - if(isStunts) { + if (isStunts) { betterSign = '+' worseSign = '-' } @@ -183,7 +182,9 @@ class SectorRecords extends PopupWindow { return this.grid.constructXml(arr) } - protected constructFooter(login: string, params: { page: number }): string { + protected constructFooter(login: string, params: { + page: number + }): string { return closeButton(this.closeId, this.windowWidth, this.footerHeight) + this.paginator.constructXml(params.page) } diff --git a/plugins/sector_records/ui/SectorRecords.config.js b/plugins/sector_records/ui/SectorRecords.config.js index 8074b93fc..929a59802 100644 --- a/plugins/sector_records/ui/SectorRecords.config.js +++ b/plugins/sector_records/ui/SectorRecords.config.js @@ -1,39 +1,48 @@ -import { icons, componentIds as ids } from '../../ui/UI.js' +import { componentIds as ids, icons } from '../../ui/UI.js' export default { - title: " Sector Records ", + title: ' Sector Records ', icon: icons.clock, entries: 13, - columnProportions: [ - 0.7, - 2, - 2, - 2, - 1, - 1.5 - ], - navbar: [ - { name: 'Checkpoint Records', actionId: ids.checkpointRecords }, - { name: 'Dedi Checkpoints', actionId: ids.dediCps }, - { name: 'Dedi Sectors', actionId: ids.dediSectors }, - { name: 'Local Checkpoints', actionId: ids.localCps }, - { name: 'Local Sectors', actionId: ids.localSectors }, - ], - stuntsNavbar: [ - { name: 'Checkpoint Records', actionId: ids.checkpointRecords }, - { name: 'Ulti Records', actionId: ids.ultiRecords }, - { name: 'Local Checkpoints', actionId: ids.localCps }, - { name: 'Local Sectors', actionId: ids.localSectors }, - ], + columnProportions: [0.7, 2, 2, 2, 1, 1.5], + navbar: [{ + name: 'Checkpoint Records', + actionId: ids.checkpointRecords + }, { + name: 'Dedi Checkpoints', + actionId: ids.dediCps + }, { + name: 'Dedi Sectors', + actionId: ids.dediSectors + }, { + name: 'Local Checkpoints', + actionId: ids.localCps + }, { + name: 'Local Sectors', + actionId: ids.localSectors + }], + stuntsNavbar: [{ + name: 'Checkpoint Records', + actionId: ids.checkpointRecords + }, { + name: 'Ulti Records', + actionId: ids.ultiRecords + }, { + name: 'Local Checkpoints', + actionId: ids.localCps + }, { + name: 'Local Sectors', + actionId: ids.localSectors + }], colours: { worse: tm.utils.palette.red, better: tm.utils.palette.purple, equal: tm.utils.palette.yellow }, grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, selfColour: tm.utils.palette.green, command: { @@ -43,4 +52,4 @@ export default { }, noTimeText: '--:--.-', stuntsNoTimeText: '--' -} \ No newline at end of file +} diff --git a/plugins/server_links/Config.js b/plugins/server_links/Config.js index dc76460b0..5db69dc54 100644 --- a/plugins/server_links/Config.js +++ b/plugins/server_links/Config.js @@ -1,23 +1,18 @@ export default { - isEnabled: false, - // http server can be used to connect servers running on different computers + isEnabled: false, // http server can be used to connect servers running on different computers useHttpServer: false, httpAddress: '127.0.0.1', httpPort: 7309, refreshTimeout: 10, // In seconds // In seconds. (If more time passes since other servers last update it will be considered down) updateLimit: 30, - dataFilePath: './plugins/server_links/temp/data.txt', - // to connect a remote server set "url" instead of "path" - servers: [ - { - name: 'name1', - path: 'D:///plugins/server_links/temp/data.txt' - }, - { - name: 'name2', - url: 'http://url:port' - } - ], + dataFilePath: './plugins/server_links/temp/data.txt', // to connect a remote server set "url" instead of "path" + servers: [{ + name: 'name1', + path: 'D:///plugins/server_links/temp/data.txt' + }, { + name: 'name2', + url: 'http://url:port' + }], noDataText: '--' } diff --git a/plugins/server_links/ServerLinks.ts b/plugins/server_links/ServerLinks.ts index 39d94b716..c22af583d 100644 --- a/plugins/server_links/ServerLinks.ts +++ b/plugins/server_links/ServerLinks.ts @@ -45,8 +45,10 @@ if (config.isEnabled) { } async function updateDataFile() { - const res: { LadderServerLimitMin: number, LadderServerLimitMax: number } | Error = - await tm.client.call('GetLadderServerLimits') + const res: { + LadderServerLimitMin: number, + LadderServerLimitMax: number + } | Error = await tm.client.call('GetLadderServerLimits') if (res instanceof Error) { tm.log.error(`Error when getting server ladder limits`, res.message) return @@ -104,8 +106,7 @@ async function refreshOtherServersData() { } function constructInfoObject(info: Partial, name: string): ServerInfo | undefined { - if (info.login === undefined || info.lastUpdate === undefined || - Date.now() - info.lastUpdate > config.updateLimit * 1000) { return } + if (info.login === undefined || info.lastUpdate === undefined || Date.now() - info.lastUpdate > config.updateLimit * 1000) { return } return { login: info.login, lastUpdate: info.lastUpdate, diff --git a/plugins/server_links/ui/ServerLinks.component.ts b/plugins/server_links/ui/ServerLinks.component.ts index 351bdb457..9cf25b9b6 100644 --- a/plugins/server_links/ui/ServerLinks.component.ts +++ b/plugins/server_links/ui/ServerLinks.component.ts @@ -1,5 +1,5 @@ import config from './ServerLinks.config.js' -import { StaticComponent, StaticHeader, type StaticHeaderOptions, componentIds, Grid, type GridCellFunction, centeredText, leftAlignedText, Paginator } from '../../ui/UI.js' +import { centeredText, componentIds, Grid, type GridCellFunction, leftAlignedText, Paginator, StaticComponent, StaticHeader, type StaticHeaderOptions } from '../../ui/UI.js' import type { ServerInfo } from '../ServerLinks.js' export default class ServerLinks extends StaticComponent { @@ -23,9 +23,8 @@ export default class ServerLinks extends StaticComponent { constructor() { super(componentIds.serverLinks) this.header = new StaticHeader('race') - this.grid = new Grid(config.width, - config.height - this.header.options.height, - [1], new Array(config.entries).fill(1)) + this.grid = new Grid(config.width, config.height - this.header.options.height, [1], + new Array(config.entries).fill(1)) this.paginator = new Paginator(this.id, 0, 0, 4) this.paginator.onPageChange = (login) => { const obj = this.displayToPlayer(login) @@ -115,7 +114,8 @@ export default class ServerLinks extends StaticComponent { let buttonsXml: string = '' for (let i: number = 0; i < buttonAmount; i++) { const action: string = ids[i] === undefined ? '' : `action="${ids[i]}"` - const icon: string = icons[i] === undefined ? '' : `` buttonsXml += ` @@ -126,7 +126,11 @@ export default class ServerLinks extends StaticComponent { - ${leftAlignedText(config.title, config.width - (headerCfg.squareWidth + config.margin) * (1 + buttonAmount), headerCfg.height, { textScale: headerCfg.textScale, padding: headerCfg.horizontalPadding })} + ${leftAlignedText(config.title, config.width - (headerCfg.squareWidth + config.margin) * (1 + buttonAmount), + headerCfg.height, { + textScale: headerCfg.textScale, + padding: headerCfg.horizontalPadding + })} ${buttonsXml} ` @@ -135,29 +139,27 @@ export default class ServerLinks extends StaticComponent { private constructEntry(ii: number, jj: number, ww: number, hh: number, startIndex: number): string { const data = this.serverList[(ii + startIndex) % this.serverList.length] const m = config.margin - const arr: GridCellFunction[] = [ - (i, j, w, h): string => { - return `${this.icon(icons.name, iw, h)} + const arr: GridCellFunction[] = [(i, j, w, h): string => { + return `${this.icon(icons.name, iw, h)} ${this.text(tm.utils.strip(data.name, false), w - iw - m, h, iw + m, config.iconBackground, true)}` - }, - (i, j, w, h): string => { - const width = 7 - return `${this.icon(icons.map, iw, h)} + }, (i, j, w, h): string => { + const width = 7 + return `${this.icon(icons.map, iw, h)} ${this.text(data.currentMap, width, h, iw + m)} ${this.icon(icons.playerCount, iw, h, iw + width + 2 * m)} - ${this.text(`${data.playerCount}/${data.maxPlayerCount}`, w - (2 * iw + width + 3 * m), h, 2 * iw + width + 3 * m)}` - }, - (i, j, w, h): string => { - const width = 7 - return `${this.icon(icons.author, iw, h)} + ${this.text(`${data.playerCount}/${data.maxPlayerCount}`, w - (2 * iw + width + 3 * m), h, + 2 * iw + width + 3 * m)}` + }, (i, j, w, h): string => { + const width = 7 + return `${this.icon(icons.author, iw, h)} ${this.text(data.currentMapAuthor, width, h, iw + m)} ${this.icon(this.gameModeIcons[data.gameMode], iw, h, iw + width + 2 * m)} ${this.text(this.gameModeMap[data.gameMode], w - (2 * iw + width + 3 * m), h, 2 * iw + width + 3 * m)}` - } - ] + }] const iw = config.iconWidth const icons = config.icons - const grid = new Grid(ww + config.margin * 2, hh + config.margin, [1], new Array(3).fill(1), { margin: config.margin }) + const grid = new Grid(ww + config.margin * 2, hh + config.margin, [1], new Array(3).fill(1), + { margin: config.margin }) return `${grid.constructXml(arr)}` } @@ -167,12 +169,19 @@ export default class ServerLinks extends StaticComponent { image="${url}"/>` } - private text(text: string, w: number, h: number, xOffset: number = 0, background?: string, leftAligned?: boolean): string { + private text(text: string, w: number, h: number, xOffset: number = 0, background?: string, + leftAligned?: boolean): string { xOffset = xOffset ?? 0 return ` - ${leftAligned ? leftAlignedText(text, w, h, - { textScale: config.textScale, padding: config.textPadding, xOffset: xOffset + config.margin, }) - : centeredText(text, w, h, { textScale: config.textScale, padding: config.textPadding, xOffset })}` + ${leftAligned ? leftAlignedText(text, w, h, { + textScale: config.textScale, + padding: config.textPadding, + xOffset: xOffset + config.margin + }) : centeredText(text, w, h, { + textScale: config.textScale, + padding: config.textPadding, + xOffset + })}` } } diff --git a/plugins/server_links/ui/ServerLinks.config.js b/plugins/server_links/ui/ServerLinks.config.js index 4dd6f6c84..9e38ac7f4 100644 --- a/plugins/server_links/ui/ServerLinks.config.js +++ b/plugins/server_links/ui/ServerLinks.config.js @@ -1,9 +1,9 @@ -import { raceConfig as cfg, icons } from '../../ui/UI.js' +import { icons, raceConfig as cfg } from '../../ui/UI.js' export default { height: 33, width: cfg.width, - title: "Linked Servers", + title: 'Linked Servers', icon: icons.infoList, margin: cfg.margin, entries: 5, @@ -25,10 +25,10 @@ export default { Stunts: icons.stuntsMode } }, - iconBackground: "000a", + iconBackground: '000a', textBackground: cfg.background, textScale: 0.9, textPadding: 0.15, iconWidth: 1.7, hidePanel: true -} \ No newline at end of file +} diff --git a/plugins/skip_endscreen/Config.js b/plugins/skip_endscreen/Config.js index 17a81df5e..1cbf1613d 100644 --- a/plugins/skip_endscreen/Config.js +++ b/plugins/skip_endscreen/Config.js @@ -1,5 +1,4 @@ export default { - isEnabled: false, - // For how long to wait before forcing everybody into specmode (in seconds) + isEnabled: false, // For how long to wait before forcing everybody into specmode (in seconds) waitTime: 5 -} \ No newline at end of file +} diff --git a/plugins/skip_endscreen/SkipEndscreen.ts b/plugins/skip_endscreen/SkipEndscreen.ts index 7571562ad..374443ae6 100644 --- a/plugins/skip_endscreen/SkipEndscreen.ts +++ b/plugins/skip_endscreen/SkipEndscreen.ts @@ -14,16 +14,13 @@ if (config.isEnabled) { if (specs.find(a => a.login === p.login)) { continue } - tm.client.callNoRes('system.multicall', - [{ - method: 'ForceSpectator', - params: [{ string: p.login }, { int: 2 }] - }, - { - method: 'ForceSpectator', - params: [{ string: p.login }, { int: 0 }] - }] - ) + tm.client.callNoRes('system.multicall', [{ + method: 'ForceSpectator', + params: [{ string: p.login }, { int: 2 }] + }, { + method: 'ForceSpectator', + params: [{ string: p.login }, { int: 0 }] + }]) } specs.length = 0 }) @@ -40,16 +37,13 @@ if (config.isEnabled) { specs.push(p) continue } - tm.client.callNoRes('system.multicall', - [{ - method: 'ForceSpectator', - params: [{ string: p.login }, { int: 1 }] - }, - { - method: 'ForceSpectator', - params: [{ string: p.login }, { int: 0 }] - }] - ) + tm.client.callNoRes('system.multicall', [{ + method: 'ForceSpectator', + params: [{ string: p.login }, { int: 1 }] + }, { + method: 'ForceSpectator', + params: [{ string: p.login }, { int: 0 }] + }]) } }, config.waitTime * 1000) }) diff --git a/plugins/stats/Config.js b/plugins/stats/Config.js index 94aa66747..4a64762d1 100644 --- a/plugins/stats/Config.js +++ b/plugins/stats/Config.js @@ -6,7 +6,6 @@ export default { recordsCount: 100000, playtimesCount: 100000, votesCount: 100000, - sumsCount: 100000, - // Playtimes are updated on endmap and on this interval (default 10 minutes) + sumsCount: 100000, // Playtimes are updated on endmap and on this interval (default 10 minutes) playtimesUpdateInterval: 1000 * 60 * 10 -} \ No newline at end of file +} diff --git a/plugins/stats/TopAverages.ts b/plugins/stats/TopAverages.ts index efbb77c2d..f9bbd571c 100644 --- a/plugins/stats/TopAverages.ts +++ b/plugins/stats/TopAverages.ts @@ -1,12 +1,26 @@ import config from './Config.js' -let topList: { readonly login: string, nickname: string, average: number }[] = [] -const updateListeners: ((changes: readonly Readonly<{ login: string, nickname: string, average: number }>[]) => void)[] = [] -const nicknameChangeListeners: ((changes: readonly Readonly<{ login: string, nickname: string }>[]) => void)[] = [] +let topList: { + readonly login: string, + nickname: string, + average: number +}[] = [] +const updateListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string, + average: number +}>[]) => void)[] = [] +const nicknameChangeListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string +}>[]) => void)[] = [] const initialize = async () => { - const res: { login: string, nickname: string, average: number }[] | Error = - await tm.db.query(`SELECT login, nickname, average FROM players + const res: { + login: string, + nickname: string, + average: number + }[] | Error = await tm.db.query(`SELECT login, nickname, average FROM players ORDER BY average ASC, last_online DESC LIMIT ${config.averagesCount}`) @@ -22,7 +36,10 @@ const initialize = async () => { tm.addListener('Startup', (): void => void initialize(), true) tm.addListener('PlayerDataUpdated', (info) => { - const changedObjects: { login: string, nickname: string }[] = [] + const changedObjects: { + login: string, + nickname: string + }[] = [] for (const e of topList) { const newNickname = info.find(a => a.login === e.login)?.nickname if (newNickname !== undefined) { @@ -40,16 +57,20 @@ tm.addListener('PlayerDataUpdated', (info) => { tm.addListener('RanksAndAveragesUpdated', async (info) => { const updated: typeof topList = [] for (const e of info) { - if (topList.length !== 0 && topList.length >= config.averagesCount && - e.average >= topList[topList.length - 1].average) { return } + if (topList.length !== 0 && topList.length >= config.averagesCount && e.average >= topList[topList.length - 1].average) { return } const entry = topList.find(a => a.login === e.login) if (entry !== undefined) { entry.average = e.average updated.push(entry) topList.sort((a, b) => a.average - b.average) } else { - const nickname: string | undefined = tm.players.get(e.login)?.nickname ?? (await tm.players.fetch(e.login))?.nickname - const obj = { login: e.login, nickname: nickname ?? e.login, average: e.average } + const nickname: string | undefined = tm.players.get(e.login)?.nickname ?? (await tm.players.fetch( + e.login))?.nickname + const obj = { + login: e.login, + nickname: nickname ?? e.login, + average: e.average + } updated.push(obj) topList.push(obj) topList.sort((a, b) => a.average - b.average) @@ -74,15 +95,23 @@ export const topAverages = { /** * List of players sorted by their average server rank */ - get list(): readonly Readonly<{ login: string, nickname: string, average: number }>[] { + get list(): readonly Readonly<{ + login: string, + nickname: string, + average: number + }>[] { return topList }, /** - * Add a callback function to execute on top averages list update - * @param callback Function to execute on event. It takes an array of updated objects as a parameter - */ - onUpdate(callback: (changes: readonly Readonly<{ login: string, nickname: string, average: number }>[]) => void): void { + * Add a callback function to execute on top averages list update + * @param callback Function to execute on event. It takes an array of updated objects as a parameter + */ + onUpdate(callback: (changes: readonly Readonly<{ + login: string, + nickname: string, + average: number + }>[]) => void): void { updateListeners.push(callback) }, @@ -90,7 +119,10 @@ export const topAverages = { * Add a callback function to execute on player nickname change * @param callback Function to execute on event. It takes an array of objects containing login and nickname as a parameter */ - onNicknameChange(callback: (changes: readonly Readonly<{ login: string, nickname: string }>[]) => void): void { + onNicknameChange(callback: (changes: readonly Readonly<{ + login: string, + nickname: string + }>[]) => void): void { nicknameChangeListeners.push(callback) } diff --git a/plugins/stats/TopDonations.ts b/plugins/stats/TopDonations.ts index 76a1ed677..3233e3b79 100644 --- a/plugins/stats/TopDonations.ts +++ b/plugins/stats/TopDonations.ts @@ -1,13 +1,27 @@ import { donations } from '../donations/Donations.js' import config from './Config.js' -let topList: { readonly login: string, nickname: string, amount: number }[] = [] -const updateListeners: ((changes: readonly Readonly<{ login: string, nickname: string, amount: number }>[]) => void)[] = [] -const nicknameChangeListeners: ((changes: readonly Readonly<{ login: string, nickname: string }>[]) => void)[] = [] +let topList: { + readonly login: string, + nickname: string, + amount: number +}[] = [] +const updateListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string, + amount: number +}>[]) => void)[] = [] +const nicknameChangeListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string +}>[]) => void)[] = [] const initialize = async () => { - const res: { amount: number, login: string, nickname: string }[] | Error = - await tm.db.query(`SELECT SUM(amount) AS amount, login, nickname FROM donations + const res: { + amount: number, + login: string, + nickname: string + }[] | Error = await tm.db.query(`SELECT SUM(amount) AS amount, login, nickname FROM donations JOIN players ON players.id=donations.player_id GROUP BY (login, nickname) ORDER BY amount DESC @@ -24,7 +38,10 @@ const initialize = async () => { tm.addListener('Startup', (): void => void initialize(), true) tm.addListener('PlayerDataUpdated', (info) => { - const changedObjects: { login: string, nickname: string }[] = [] + const changedObjects: { + login: string, + nickname: string + }[] = [] for (const e of topList) { const newNickname = info.find(a => a.login === e.login)?.nickname if (newNickname !== undefined) { @@ -42,8 +59,7 @@ tm.addListener('PlayerDataUpdated', (info) => { donations.onDonation((info) => { const login = info.login const amount = info.sum - if (topList.length !== 0 && topList.length >= config.donationsCount && - amount <= topList[topList.length - 1].amount) { return } + if (topList.length !== 0 && topList.length >= config.donationsCount && amount <= topList[topList.length - 1].amount) { return } const entry = topList.find(a => a.login === login) const updated: typeof topList = [] if (entry !== undefined) { @@ -51,7 +67,11 @@ donations.onDonation((info) => { updated.push(entry) topList.sort((a, b) => b.amount - a.amount) } else { - const obj = { login, nickname: info.nickname, amount } + const obj = { + login, + nickname: info.nickname, + amount + } updated.push(obj) topList.push(obj) topList.sort((a, b) => b.amount - a.amount) @@ -70,7 +90,11 @@ export const topDonations = { /** * List of players sorted by the amount of coppers donated to the server */ - get list(): readonly Readonly<{ login: string, nickname: string, amount: number }>[] { + get list(): readonly Readonly<{ + login: string, + nickname: string, + amount: number + }>[] { return topList }, @@ -78,7 +102,11 @@ export const topDonations = { * Add a callback function to execute on top donations list update * @param callback Function to execute on event. It takes an array of updated objects as a parameter */ - onUpdate(callback: (changes: readonly Readonly<{ login: string, nickname: string, amount: number }>[]) => void): void { + onUpdate(callback: (changes: readonly Readonly<{ + login: string, + nickname: string, + amount: number + }>[]) => void): void { updateListeners.push(callback) }, @@ -86,7 +114,10 @@ export const topDonations = { * Add a callback function to execute on donator nickname change * @param callback Function to execute on event. It takes donation object as a parameter */ - onNicknameChange(callback: (changes: readonly Readonly<{ login: string, nickname: string }>[]) => void): void { + onNicknameChange(callback: (changes: readonly Readonly<{ + login: string, + nickname: string + }>[]) => void): void { nicknameChangeListeners.push(callback) } diff --git a/plugins/stats/TopPlaytimes.ts b/plugins/stats/TopPlaytimes.ts index 0f4238d3e..f77fa40a2 100644 --- a/plugins/stats/TopPlaytimes.ts +++ b/plugins/stats/TopPlaytimes.ts @@ -1,13 +1,27 @@ import config from './Config.js' -let topList: { readonly login: string, nickname: string, playtime: number }[] = [] -const updateListeners: ((changes: readonly Readonly<{ login: string, nickname: string, playtime: number }>[]) => void)[] = [] -const nicknameChangeListeners: ((changes: readonly Readonly<{ login: string, nickname: string }>[]) => void)[] = [] +let topList: { + readonly login: string, + nickname: string, + playtime: number +}[] = [] +const updateListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string, + playtime: number +}>[]) => void)[] = [] +const nicknameChangeListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string +}>[]) => void)[] = [] let interval: NodeJS.Timeout const initialize = async () => { - const res: { login: string, nickname: string, playtime: number }[] | Error = - await tm.db.query(`SELECT login, nickname, time_played AS playtime FROM players + const res: { + login: string, + nickname: string, + playtime: number + }[] | Error = await tm.db.query(`SELECT login, nickname, time_played AS playtime FROM players ORDER BY time_played DESC, last_online DESC LIMIT ${config.playtimesCount}`) @@ -28,15 +42,18 @@ const update = async () => { const updated: typeof topList = [] for (const e of players) { const pt = e.timePlayed + Date.now() - e.joinTimestamp - if (topList.length !== 0 && topList.length >= config.playtimesCount && - pt <= topList[topList.length - 1].playtime) { return } + if (topList.length !== 0 && topList.length >= config.playtimesCount && pt <= topList[topList.length - 1].playtime) { return } const entry = topList.find(a => a.login === e.login) if (entry !== undefined) { entry.playtime = pt updated.push(entry) topList.sort((a, b) => b.playtime - a.playtime) } else { - const obj = { login: e.login, nickname: e.nickname, playtime: pt } + const obj = { + login: e.login, + nickname: e.nickname, + playtime: pt + } updated.push(obj) topList.push(obj) topList.sort((a, b) => b.playtime - a.playtime) @@ -47,7 +64,10 @@ const update = async () => { } tm.addListener('PlayerDataUpdated', (info) => { - const changedObjects: { login: string, nickname: string }[] = [] + const changedObjects: { + login: string, + nickname: string + }[] = [] for (const e of topList) { const newNickname = info.find(a => a.login === e.login)?.nickname if (newNickname !== undefined) { @@ -68,7 +88,6 @@ tm.addListener('EndMap', () => { update() }) - /** * Creates and provides utilities for accessing players playtime ranking * @author lythx @@ -79,7 +98,11 @@ export const topPlaytimes = { /** * List of players sorted by their total playtime */ - get list(): readonly Readonly<{ login: string, nickname: string, playtime: number }>[] { + get list(): readonly Readonly<{ + login: string, + nickname: string, + playtime: number + }>[] { return topList }, @@ -87,7 +110,11 @@ export const topPlaytimes = { * Add a callback function to execute on top playtimes list update * @param callback Function to execute on event. It takes an array of updated objects as a parameter */ - onUpdate(callback: (changes: readonly Readonly<{ login: string, nickname: string, playtime: number }>[]) => void): void { + onUpdate(callback: (changes: readonly Readonly<{ + login: string, + nickname: string, + playtime: number + }>[]) => void): void { updateListeners.push(callback) }, @@ -95,7 +122,10 @@ export const topPlaytimes = { * Add a callback function to execute on player nickname change * @param callback Function to execute on event. It takes an array of objects containing login and nickname as a parameter */ - onNicknameChange(callback: (changes: readonly Readonly<{ login: string, nickname: string }>[]) => void): void { + onNicknameChange(callback: (changes: readonly Readonly<{ + login: string, + nickname: string + }>[]) => void): void { nicknameChangeListeners.push(callback) } diff --git a/plugins/stats/TopRecords.ts b/plugins/stats/TopRecords.ts index 2d223421d..c2d6a458b 100644 --- a/plugins/stats/TopRecords.ts +++ b/plugins/stats/TopRecords.ts @@ -1,17 +1,34 @@ import config from './Config.js' -let topList: { readonly login: string, nickname: string, amount: number }[] = [] -let onlineList: { readonly login: string, nickname: string, amount: number }[] = [] -const updateListeners: ((changes: readonly Readonly<{ login: string, nickname: string, amount: number }>[]) => void)[] = [] -const nicknameChangeListeners: ((changes: readonly Readonly<{ login: string, nickname: string }>[]) => void)[] = [] +let topList: { + readonly login: string, + nickname: string, + amount: number +}[] = [] +let onlineList: { + readonly login: string, + nickname: string, + amount: number +}[] = [] +const updateListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string, + amount: number +}>[]) => void)[] = [] +const nicknameChangeListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string +}>[]) => void)[] = [] const initialize = async () => { const mapIds = await tm.db.getMapId(tm.maps.list.map(a => a.id)) - const res: { readonly login: string, nickname: string, amount: number }[] | Error = - await tm.db.query(`WITH r(player_id, map_id) AS - (SELECT player_id, map_id FROM records WHERE map_id IN(${ - // Use only map ids in the current maplist - mapIds.map(a => `${a.id},`).join('').slice(0, -1)})) + const res: { + readonly login: string, + nickname: string, + amount: number + }[] | Error = await tm.db.query(`WITH r(player_id, map_id) AS + (SELECT player_id, map_id FROM records WHERE map_id IN(${// Use only map ids in the current maplist + mapIds.map(a => `${a.id},`).join('').slice(0, -1)})) SELECT count(*)::int as amount, nickname, login FROM r JOIN players ON players.id=r.player_id GROUP BY (nickname, login, last_online) @@ -29,7 +46,10 @@ const initialize = async () => { } tm.addListener('PlayerDataUpdated', (info) => { - const changedObjects: { login: string, nickname: string }[] = [] + const changedObjects: { + login: string, + nickname: string + }[] = [] for (const e of topList) { const newNickname = info.find(a => a.login === e.login)?.nickname if (newNickname !== undefined) { @@ -51,11 +71,25 @@ tm.addListener('PlayerDataUpdated', (info) => { } }) -async function getFromDB(login: string): Promise<{ login: string, nickname: string, amount: number } | undefined> -async function getFromDB(logins: string[]): Promise<{ login: string, nickname: string, amount: number }[]> -async function getFromDB(logins: string | string[]): - Promise<{ login: string, nickname: string, amount: number } | undefined | - { login: string, nickname: string, amount: number }[]> { +async function getFromDB(login: string): Promise<{ + login: string, + nickname: string, + amount: number +} | undefined> +async function getFromDB(logins: string[]): Promise<{ + login: string, + nickname: string, + amount: number +}[]> +async function getFromDB(logins: string | string[]): Promise<{ + login: string, + nickname: string, + amount: number +} | undefined | { + login: string, + nickname: string, + amount: number +}[]> { if (typeof logins === 'string') { const id = await tm.db.getPlayerId(logins) if (id === undefined) { return } @@ -73,9 +107,17 @@ async function getFromDB(logins: string | string[]): if (res[0] === undefined) { const player = await tm.players.fetch(logins) if (player === undefined) { return } - return { login: player.login, nickname: player.nickname, amount: 0 } + return { + login: player.login, + nickname: player.nickname, + amount: 0 + } + } + return { + login: logins, + nickname: res[0].nickname, + amount: res[0].amount } - return { login: logins, nickname: res[0].nickname, amount: res[0].amount } } const ids = await tm.db.getPlayerId(logins) if (ids.length === 0) { return [] } @@ -91,11 +133,21 @@ async function getFromDB(logins: string | string[]): tm.log.error(`Failed to get record count info for players ${logins.join(',')}`, res.message, res.stack) return [] } - const ret = res.map(a => ({ login: a.login, nickname: a.nickname, amount: a.amount })) + const ret = res.map(a => ({ + login: a.login, + nickname: a.nickname, + amount: a.amount + })) for (const e of logins) { if (!ret.some(a => a.login === e)) { const player = await tm.players.fetch(e) - if (player !== undefined) { ret.push({ login: e, nickname: player.nickname, amount: 0 }) } + if (player !== undefined) { + ret.push({ + login: e, + nickname: player.nickname, + amount: 0 + }) + } } } return ret @@ -119,8 +171,7 @@ tm.addListener('LocalRecord', info => { const obj = onlineList.find(a => a.login === info.login) if (obj === undefined) { return } obj.amount++ - if (topList.length !== 0 && topList.length >= config.recordsCount && - obj.amount <= topList[topList.length - 1].amount) { return } + if (topList.length !== 0 && topList.length >= config.recordsCount && obj.amount <= topList[topList.length - 1].amount) { return } const entry = topList.find(a => a.login === info.login) if (entry !== undefined) { entry.amount = obj.amount @@ -152,14 +203,22 @@ export const topRecords = { /** * List of players sorted by their record count */ - get list(): readonly Readonly<{ login: string, nickname: string, amount: number }>[] { + get list(): readonly Readonly<{ + login: string, + nickname: string, + amount: number + }>[] { return topList }, /** * List of currently online players sorted by their record count */ - get onlineList(): readonly Readonly<{ login: string, nickname: string, amount: number }>[] { + get onlineList(): readonly Readonly<{ + login: string, + nickname: string, + amount: number + }>[] { return onlineList }, @@ -167,7 +226,11 @@ export const topRecords = { * Add a callback function to execute on top records list update * @param callback Function to execute on event. It takes an array of updated objects as a parameter */ - onUpdate(callback: (changes: readonly Readonly<{ login: string, nickname: string, amount: number }>[]) => void): void { + onUpdate(callback: (changes: readonly Readonly<{ + login: string, + nickname: string, + amount: number + }>[]) => void): void { updateListeners.push(callback) }, @@ -175,7 +238,10 @@ export const topRecords = { * Add a callback function to execute on player nickname change * @param callback Function to execute on event. It takes an array of objects containing login and nickname as a parameter */ - onNicknameChange(callback: (changes: readonly Readonly<{ login: string, nickname: string }>[]) => void): void { + onNicknameChange(callback: (changes: readonly Readonly<{ + login: string, + nickname: string + }>[]) => void): void { nicknameChangeListeners.push(callback) } diff --git a/plugins/stats/TopSums.ts b/plugins/stats/TopSums.ts index 7595b6198..18bb87f00 100644 --- a/plugins/stats/TopSums.ts +++ b/plugins/stats/TopSums.ts @@ -1,69 +1,68 @@ import config from './Config.js' -const topList: { readonly login: string, nickname: string, sums: [number, number, number, number] }[] = [] +const topList: { + readonly login: string, + nickname: string, + sums: [number, number, number, number] +}[] = [] const updateListeners: ((changes: readonly Readonly<{ - login: string, nickname: string, + login: string, + nickname: string, sums: [number, number, number, number] }>[]) => void)[] = [] -const nicknameChangeListeners: ((changes: readonly Readonly<{ login: string, nickname: string }>[]) => void)[] = [] +const nicknameChangeListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string +}>[]) => void)[] = [] let initialLocals: tm.LocalRecord[] = [] let refreshNeeded = false const initialize = async () => { refreshNeeded = false - const res: { uid: string, login: string, nickname: string }[] | Error = - await tm.db.query(`SELECT uid, login, nickname FROM records - JOIN map_ids ON map_ids.id=records.map_id - JOIN players ON players.id=records.player_id - ORDER BY uid ASC, - time ASC, - date ASC;`) + topList.length = 0 + + const res: { + login: string, + nickname: string, + firsts: number, + seconds: number, + thirds: number, + others: number + }[] | Error = await tm.db.query(` + WITH ranked AS (SELECT p.login, + p.nickname, + RANK() OVER ( + PARTITION BY m.id + ORDER BY CAST(r.time AS NUMERIC) ASC, r.date ASC, r.player_id ASC + ) AS rk + FROM maps m + JOIN records r ON r.map_id = m.id + JOIN players p ON p.id = r.player_id) + SELECT login, + MAX(nickname) AS nickname, + SUM(CASE WHEN rk = 1 THEN 1 ELSE 0 END) AS firsts, + SUM(CASE WHEN rk = 2 THEN 1 ELSE 0 END) AS seconds, + SUM(CASE WHEN rk = 3 THEN 1 ELSE 0 END) AS thirds, + SUM(CASE WHEN rk > 3 THEN 1 ELSE 0 END) AS others + FROM ranked + GROUP BY login + ORDER BY firsts DESC, seconds DESC, thirds DESC, others DESC; + `) + if (res instanceof Error) { await tm.log.fatal(`Failed to fetch topsums`, res.message) return } if (res.length === 0) { return } - const presentMaps = tm.maps.list.map(a => a.id).sort((a, b) => a.localeCompare(b)) - let i = 0 - let j = 0 - while (i < res.length && j < presentMaps.length) { - const cmp = res[i].uid.localeCompare(presentMaps[j]) - if (cmp > 0) { - j++ - continue - } - if (cmp < 0) { - i++ - continue - } - const uid = res[i].uid - let rank = 1 - while (i < res.length && res[i].uid === uid) { - const login = res[i].login - const find = topList.find(a => a.login === login) - if (find !== undefined) { - if (rank <= 3) { - find.sums[rank - 1]++ - } else { - find.sums[3]++ - } - } else { - const arr: [number, number, number, number] = [0, 0, 0, 0] - if (rank <= 3) { - arr[rank - 1]++ - } else { - arr[3]++ - } - topList.push({ - login: res[i].login, - nickname: res[i].nickname, - sums: arr - }) - } - rank += 1 - i += 1 - } + + for (const row of res) { + topList.push({ + login: row.login, + nickname: row.nickname, + sums: [row.firsts, row.seconds, row.thirds, row.others] + }) } + sortToplist() topList.length = Math.min(config.sumsCount, topList.length) for (const e of updateListeners) { e(topList) } @@ -81,7 +80,10 @@ tm.addListener('BeginMap', (): void => { }) tm.addListener('PlayerDataUpdated', (info) => { - const changedObjects: { login: string, nickname: string }[] = [] + const changedObjects: { + login: string, + nickname: string + }[] = [] for (const e of topList) { const newNickname = info.find(a => a.login === e.login)?.nickname if (newNickname !== undefined) { @@ -101,7 +103,7 @@ tm.addListener('LocalRecord', (info) => { let oldArrPos = prevRecordIndex === -1 ? undefined : prevRecordIndex if (oldArrPos !== undefined && oldArrPos > 2) { oldArrPos = 3 - } // [1,2,3,4] 7000 + } // [1,2,3,4] 7000 let newArrPos = info.position - 1 if (info.position > 2) { newArrPos = 3 @@ -146,7 +148,8 @@ export const topSums = { * List of players sorted by their records amount */ get list(): readonly Readonly<{ - login: string, nickname: string, + login: string, + nickname: string, sums: Readonly<[number, number, number, number]> }>[] { return topList @@ -157,7 +160,8 @@ export const topSums = { * @param callback Function to execute on event. It takes an array of updated objects as a parameter */ onUpdate(callback: (changes: readonly Readonly<{ - login: string, nickname: string, + login: string, + nickname: string, sums: Readonly<[number, number, number, number]> }>[]) => void) { updateListeners.push(callback) @@ -167,7 +171,10 @@ export const topSums = { * Add a callback function to execute on player nickname change * @param callback Function to execute on event. It takes an array of objects containing login and nickname as a parameter */ - onNicknameChange(callback: (changes: readonly Readonly<{ login: string, nickname: string }>[]) => void): void { + onNicknameChange(callback: (changes: readonly Readonly<{ + login: string, + nickname: string + }>[]) => void): void { nicknameChangeListeners.push(callback) } @@ -175,14 +182,10 @@ export const topSums = { function sortToplist() { topList.sort((a, b) => { - if (a.sums[0] < b.sums[0]) return 1 - if (a.sums[0] > b.sums[0]) return -1 - if (a.sums[1] < b.sums[1]) return 1 - if (a.sums[1] > b.sums[1]) return -1 - if (a.sums[2] < b.sums[2]) return 1 - if (a.sums[2] > b.sums[2]) return -1 - if (a.sums[3] < b.sums[3]) return 1 - if (a.sums[3] > b.sums[3]) return -1 + if (a.sums[0] !== b.sums[0]) return b.sums[0] - a.sums[0] + if (a.sums[1] !== b.sums[1]) return b.sums[1] - a.sums[1] + if (a.sums[2] !== b.sums[2]) return b.sums[2] - a.sums[2] + if (a.sums[3] !== b.sums[3]) return b.sums[3] - a.sums[3] return 0 }) -} \ No newline at end of file +} diff --git a/plugins/stats/TopVisits.ts b/plugins/stats/TopVisits.ts index aca91ace5..6e230af80 100644 --- a/plugins/stats/TopVisits.ts +++ b/plugins/stats/TopVisits.ts @@ -1,12 +1,26 @@ import config from './Config.js' -let topList: { readonly login: string, nickname: string, visits: number }[] = [] -const updateListeners: ((changes: readonly Readonly<{ login: string, nickname: string, visits: number }>[]) => void)[] = [] -const nicknameChangeListeners: ((changes: readonly Readonly<{ login: string, nickname: string }>[]) => void)[] = [] +let topList: { + readonly login: string, + nickname: string, + visits: number +}[] = [] +const updateListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string, + visits: number +}>[]) => void)[] = [] +const nicknameChangeListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string +}>[]) => void)[] = [] const initialize = async () => { - const res: { login: string, nickname: string, visits: number }[] | Error = - await tm.db.query(`SELECT login, nickname, visits FROM players + const res: { + login: string, + nickname: string, + visits: number + }[] | Error = await tm.db.query(`SELECT login, nickname, visits FROM players ORDER BY visits DESC, last_online DESC LIMIT ${config.visitsCount}`) @@ -22,7 +36,10 @@ const initialize = async () => { tm.addListener('Startup', (): void => void initialize()) tm.addListener('PlayerDataUpdated', (info) => { - const changedObjects: { login: string, nickname: string }[] = [] + const changedObjects: { + login: string, + nickname: string + }[] = [] for (const e of topList) { const newNickname = info.find(a => a.login === e.login)?.nickname if (newNickname !== undefined) { @@ -40,8 +57,7 @@ tm.addListener('PlayerDataUpdated', (info) => { tm.addListener('PlayerJoin', (info) => { const login = info.login const visits = info.visits - if (topList.length !== 0 && topList.length >= config.visitsCount && - visits <= topList[topList.length - 1].visits) { return } + if (topList.length !== 0 && topList.length >= config.visitsCount && visits <= topList[topList.length - 1].visits) { return } const entry = topList.find(a => a.login === login) let obj: typeof topList[number] if (entry !== undefined) { @@ -49,7 +65,11 @@ tm.addListener('PlayerJoin', (info) => { obj = entry topList.sort((a, b) => b.visits - a.visits) } else { - obj = { login, visits, nickname: info.nickname } + obj = { + login, + visits, + nickname: info.nickname + } topList.push(obj) topList.sort((a, b) => b.visits - a.visits) topList.length = Math.min(config.visitsCount, topList.length) @@ -69,7 +89,11 @@ export const topVisits = { /** * List of players sorted by their visit count */ - get list(): readonly Readonly<{ login: string, nickname: string, visits: number }>[] { + get list(): readonly Readonly<{ + login: string, + nickname: string, + visits: number + }>[] { return topList }, @@ -77,7 +101,11 @@ export const topVisits = { * Add a callback function to execute on top visits list update * @param callback Function to execute on event. It takes an array of updated objects as a parameter */ - onUpdate(callback: (changes: readonly Readonly<{ login: string, nickname: string, visits: number }>[]) => void): void { + onUpdate(callback: (changes: readonly Readonly<{ + login: string, + nickname: string, + visits: number + }>[]) => void): void { updateListeners.push(callback) }, @@ -85,7 +113,10 @@ export const topVisits = { * Add a callback function to execute on player nickname change * @param callback Function to execute on event. It takes an array of objects containing login and nickname as a parameter */ - onNicknameChange(callback: (changes: readonly Readonly<{ login: string, nickname: string }>[]) => void): void { + onNicknameChange(callback: (changes: readonly Readonly<{ + login: string, + nickname: string + }>[]) => void): void { nicknameChangeListeners.push(callback) } diff --git a/plugins/stats/TopVotes.ts b/plugins/stats/TopVotes.ts index 9f9549a79..58924f6c8 100644 --- a/plugins/stats/TopVotes.ts +++ b/plugins/stats/TopVotes.ts @@ -1,20 +1,41 @@ import config from './Config.js' -let onlineList: { readonly login: string, nickname: string, count: number }[] = [] +let onlineList: { + readonly login: string, + nickname: string, + count: number +}[] = [] let initialVotes: tm.Vote[] = [] -let topList: { readonly login: string, nickname: string, count: number }[] = [] -const updateListeners: ((changes: readonly Readonly<{ login: string, nickname: string, count: number }>[]) => void)[] = [] -const nicknameChangeListeners: ((changes: readonly Readonly<{ login: string, nickname: string }>[]) => void)[] = [] +let topList: { + readonly login: string, + nickname: string, + count: number +}[] = [] +const updateListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string, + count: number +}>[]) => void)[] = [] +const nicknameChangeListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string +}>[]) => void)[] = [] const addToOnlineList = async (login: string, nickname: string) => { const id: number | undefined = await tm.db.getPlayerId(login) - const res: { count: number }[] | Error = await tm.db.query(`SELECT count(*)::int FROM votes + const res: { + count: number + }[] | Error = await tm.db.query(`SELECT count(*)::int FROM votes WHERE player_id=$1`, id) if (res instanceof Error) { tm.log.error(`Failed to fetch vote count for player ${login}`, res.message, res.stack) return } - onlineList.push({ login, nickname, count: res[0].count }) + onlineList.push({ + login, + nickname, + count: res[0].count + }) } const initialize = async () => { @@ -37,7 +58,10 @@ const initialize = async () => { } tm.addListener('PlayerDataUpdated', (info) => { - const changedObjects: { login: string, nickname: string }[] = [] + const changedObjects: { + login: string, + nickname: string + }[] = [] for (const e of topList) { const newNickname = info.find(a => a.login === e.login)?.nickname if (newNickname !== undefined) { @@ -70,8 +94,7 @@ const handleVote = (vote: tm.Vote) => { const obj = onlineList.find(a => a.login === vote.login) if (obj === undefined) { return } obj.count++ - if (topList.length !== 0 && topList.length >= config.votesCount && - obj.count <= topList[topList.length - 1].count) { return } + if (topList.length !== 0 && topList.length >= config.votesCount && obj.count <= topList[topList.length - 1].count) { return } const entry = topList.find(a => a.login === obj.login) if (entry !== undefined) { entry.count = obj.count @@ -120,7 +143,11 @@ export const topVotes = { /** * List of players sorted by their votes count */ - get list(): readonly Readonly<{ login: string, nickname: string, count: number }>[] { + get list(): readonly Readonly<{ + login: string, + nickname: string, + count: number + }>[] { return topList }, @@ -128,7 +155,11 @@ export const topVotes = { * Add a callback function to execute on top votes list update * @param callback Function to execute on event. It takes an array of updated objects as a parameter */ - onUpdate(callback: (changes: readonly Readonly<{ login: string, nickname: string, count: number }>[]) => void): void { + onUpdate(callback: (changes: readonly Readonly<{ + login: string, + nickname: string, + count: number + }>[]) => void): void { updateListeners.push(callback) }, @@ -136,7 +167,10 @@ export const topVotes = { * Add a callback function to execute on player nickname change * @param callback Function to execute on event. It takes an array of objects containing login and nickname as a parameter */ - onNicknameChange(callback: (changes: readonly Readonly<{ login: string, nickname: string }>[]) => void): void { + onNicknameChange(callback: (changes: readonly Readonly<{ + login: string, + nickname: string + }>[]) => void): void { nicknameChangeListeners.push(callback) } diff --git a/plugins/stats/TopWins.ts b/plugins/stats/TopWins.ts index 493411968..5dbdb84c9 100644 --- a/plugins/stats/TopWins.ts +++ b/plugins/stats/TopWins.ts @@ -1,8 +1,19 @@ import config from './Config.js' -let topList: { readonly login: string, nickname: string, wins: number }[] = [] -const updateListeners: ((changes: readonly Readonly<{ login: string, nickname: string, wins: number }>[]) => void)[] = [] -const nicknameChangeListeners: ((changes: readonly Readonly<{ login: string, nickname: string }>[]) => void)[] = [] +let topList: { + readonly login: string, + nickname: string, + wins: number +}[] = [] +const updateListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string, + wins: number +}>[]) => void)[] = [] +const nicknameChangeListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string +}>[]) => void)[] = [] const initialize = async () => { const res: any[] | Error = await tm.db.query(`SELECT login, nickname, wins FROM players @@ -21,7 +32,10 @@ const initialize = async () => { tm.addListener('Startup', (): void => void initialize()) tm.addListener('PlayerDataUpdated', (info) => { - const changedObjects: { login: string, nickname: string }[] = [] + const changedObjects: { + login: string, + nickname: string + }[] = [] for (const e of topList) { const newNickname = info.find(a => a.login === e.login)?.nickname if (newNickname !== undefined) { @@ -39,9 +53,7 @@ tm.addListener('PlayerDataUpdated', (info) => { tm.addListener('EndMap', async (info) => { const login = info.winnerLogin const wins = info.winnerWins - if (wins === undefined || login === undefined - || (topList.length !== 0 && topList.length >= config.winsCount - && wins <= topList[topList.length - 1].wins)) { return } + if (wins === undefined || login === undefined || (topList.length !== 0 && topList.length >= config.winsCount && wins <= topList[topList.length - 1].wins)) { return } const entry = topList.find(a => a.login === login) let obj: typeof topList[number] if (entry !== undefined) { @@ -50,7 +62,11 @@ tm.addListener('EndMap', async (info) => { topList.sort((a, b) => b.wins - a.wins) } else { const nickname: string | undefined = tm.players.get(login)?.nickname ?? (await tm.players.fetch(login))?.nickname - obj = { login, wins, nickname: nickname ?? login } + obj = { + login, + wins, + nickname: nickname ?? login + } topList.push(obj) topList.sort((a, b) => b.wins - a.wins) topList.length = Math.min(config.winsCount, topList.length) @@ -68,7 +84,11 @@ export const topWins = { /** * List of players sorted by their wins count */ - get list(): readonly Readonly<{ login: string, nickname: string, wins: number }>[] { + get list(): readonly Readonly<{ + login: string, + nickname: string, + wins: number + }>[] { return topList }, @@ -76,7 +96,11 @@ export const topWins = { * Add a callback function to execute on top wins list update * @param callback Function to execute on event. It takes an array of updated objects as a parameter */ - onUpdate(callback: (changes: readonly Readonly<{ login: string, nickname: string, wins: number }>[]) => void): void { + onUpdate(callback: (changes: readonly Readonly<{ + login: string, + nickname: string, + wins: number + }>[]) => void): void { updateListeners.push(callback) }, @@ -84,7 +108,10 @@ export const topWins = { * Add a callback function to execute on player nickname change * @param callback Function to execute on event. It takes an array of objects containing login and nickname as a parameter */ - onNicknameChange(callback: (changes: readonly Readonly<{ login: string, nickname: string }>[]) => void): void { + onNicknameChange(callback: (changes: readonly Readonly<{ + login: string, + nickname: string + }>[]) => void): void { nicknameChangeListeners.push(callback) } diff --git a/plugins/stats/ui/StatsUi.ts b/plugins/stats/ui/StatsUi.ts index 32eababfa..c24ff4df3 100644 --- a/plugins/stats/ui/StatsUi.ts +++ b/plugins/stats/ui/StatsUi.ts @@ -7,7 +7,7 @@ import TopVisits from './TopVisits.component.js' import TopVotes from './TopVotes.component.js' import TopWins from './TopWins.component.js' -tm.addListener('Startup', ()=> { +tm.addListener('Startup', () => { new TopDonations() new TopPlaytimes() new TopRanks() diff --git a/plugins/stats/ui/TopDonations.component.ts b/plugins/stats/ui/TopDonations.component.ts index 1d11f4876..18ab7a006 100644 --- a/plugins/stats/ui/TopDonations.component.ts +++ b/plugins/stats/ui/TopDonations.component.ts @@ -4,15 +4,18 @@ */ import { stats } from "../Stats.js" -import { componentIds, centeredText } from '../../ui/UI.js' -import { Paginator, Grid, type GridCellFunction, closeButton, type GridCellObject, PopupWindow } from "../../ui/UI.js" +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, Paginator, PopupWindow } from '../../ui/UI.js' import config from './TopDonations.config.js' export default class TopDonations extends PopupWindow { private readonly paginator: Paginator private readonly grid: Grid - private ranks: readonly { login: string, nickname: string, amount: number }[] + private ranks: readonly { + login: string, + nickname: string, + amount: number + }[] constructor() { super(componentIds.topDonations, config.icon, config.title, config.navbar) @@ -29,7 +32,8 @@ export default class TopDonations extends PopupWindow { this.paginator.setPageCount(Math.ceil(this.ranks.length / config.entries)) this.reRender() }) - this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, Math.ceil(this.ranks.length / config.entries)) + this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, + Math.ceil(this.ranks.length / config.entries)) this.paginator.onPageChange = (login, page) => { this.displayToPlayer(login, page, `${page}/${this.paginator.pageCount}`) } @@ -61,17 +65,17 @@ export default class TopDonations extends PopupWindow { const leftColumns = 3 const offset = (((page ?? 1) - 1) * config.entries) - 1 const getIndex = (i: number, j: number) => i + offset + (j > leftColumns ? (config.entries / columns) : 0) - const arr: (GridCellFunction | GridCellObject)[] = config.headers.map((a) => - (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) + const arr: (GridCellFunction | GridCellObject)[] = config.headers.map( + (a) => (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) const indexCell: GridCellFunction = (i, j, w, h) => centeredText((getIndex(i, j) + 1).toString(), w, h) - const nicknameCell: GridCellFunction = (i, j, w, h) => - centeredText(tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) + const nicknameCell: GridCellFunction = (i, j, w, h) => centeredText( + tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) const loginCell: GridCellFunction = (i, j, w, h) => { const colour = this.ranks[getIndex(i, j)].login === login ? `$${config.selfColour} ` : '' return centeredText(colour + this.ranks[getIndex(i, j)].login, w, h) } - const averageCell: GridCellFunction = (i, j, w, h) => - centeredText(this.ranks[getIndex(i, j)].amount.toString(), w, h) + const averageCell: GridCellFunction = (i, j, w, h) => centeredText(this.ranks[getIndex(i, j)].amount.toString(), w, + h) const emptyCell: GridCellObject = { callback: (i, j, w, h) => '', background: undefined diff --git a/plugins/stats/ui/TopDonations.config.js b/plugins/stats/ui/TopDonations.config.js index 73000471b..865cceb08 100644 --- a/plugins/stats/ui/TopDonations.config.js +++ b/plugins/stats/ui/TopDonations.config.js @@ -1,24 +1,37 @@ -import { icons, componentIds as ids } from '../../ui/UI.js' +import { componentIds as ids, icons } from '../../ui/UI.js' export default { - title: "Top Donations", + title: 'Top Donations', icon: icons.cash, gridColumns: [0.8, 4, 3, 3, 0.8, 4, 3, 3], headers: ['Lp.', 'Nickname', 'Login', 'Donations', 'Lp.', 'Nickname', 'Login', 'Donations'], entries: 30, // Has to be even number - navbar: [ - { name: "Top Playtimes", actionId: ids.topPlaytimes }, - { name: "Top Ranks", actionId: ids.topRanks }, - { name: "Top Records", actionId: ids.topRecords }, - { name: "Top Sums", actionId: ids.topSums }, - { name: "Top Visits", actionId: ids.topVisits }, - { name: "Top Votes", actionId: ids.topVotes }, - { name: "Top Wins", actionId: ids.topWins } - ], + navbar: [{ + name: 'Top Playtimes', + actionId: ids.topPlaytimes + }, { + name: 'Top Ranks', + actionId: ids.topRanks + }, { + name: 'Top Records', + actionId: ids.topRecords + }, { + name: 'Top Sums', + actionId: ids.topSums + }, { + name: 'Top Visits', + actionId: ids.topVisits + }, { + name: 'Top Votes', + actionId: ids.topVotes + }, { + name: 'Top Wins', + actionId: ids.topWins + }], grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, selfColour: tm.utils.palette.green, command: { @@ -26,4 +39,4 @@ export default { help: `Display top donations.`, privilege: 0 } -} \ No newline at end of file +} diff --git a/plugins/stats/ui/TopPlaytimes.component.ts b/plugins/stats/ui/TopPlaytimes.component.ts index f8add324e..36e8d07da 100644 --- a/plugins/stats/ui/TopPlaytimes.component.ts +++ b/plugins/stats/ui/TopPlaytimes.component.ts @@ -4,14 +4,18 @@ */ import { stats } from "../../stats/Stats.js" -import { Paginator, Grid, type GridCellFunction, closeButton, type GridCellObject, PopupWindow, componentIds, centeredText } from "../../ui/UI.js" +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, Paginator, PopupWindow } from "../../ui/UI.js" import config from './TopPlaytimes.config.js' export default class TopPlaytimes extends PopupWindow { private readonly paginator: Paginator private readonly grid: Grid - private ranks: readonly { login: string, nickname: string, playtime: number }[] + private ranks: readonly { + login: string, + nickname: string, + playtime: number + }[] constructor() { super(componentIds.topPlaytimes, config.icon, config.title, config.navbar) @@ -28,7 +32,8 @@ export default class TopPlaytimes extends PopupWindow { this.paginator.setPageCount(Math.ceil(this.ranks.length / config.entries)) this.reRender() }) - this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, Math.ceil(this.ranks.length / config.entries)) + this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, + Math.ceil(this.ranks.length / config.entries)) this.paginator.onPageChange = (login, page) => { this.displayToPlayer(login, page, `${page}/${this.paginator.pageCount}`) } @@ -60,17 +65,17 @@ export default class TopPlaytimes extends PopupWindow { const leftColumns = 3 const offset = (((page ?? 1) - 1) * config.entries) - 1 const getIndex = (i: number, j: number) => i + offset + (j > leftColumns ? (config.entries / columns) : 0) - const arr: (GridCellFunction | GridCellObject)[] = config.headers.map((a) => - (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) + const arr: (GridCellFunction | GridCellObject)[] = config.headers.map( + (a) => (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) const indexCell: GridCellFunction = (i, j, w, h) => centeredText((getIndex(i, j) + 1).toString(), w, h) - const nicknameCell: GridCellFunction = (i, j, w, h) => - centeredText(tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) + const nicknameCell: GridCellFunction = (i, j, w, h) => centeredText( + tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) const loginCell: GridCellFunction = (i, j, w, h) => { const colour = this.ranks[getIndex(i, j)].login === login ? `$${config.selfColour} ` : '' return centeredText(colour + this.ranks[getIndex(i, j)].login, w, h) } - const averageCell: GridCellFunction = (i, j, w, h) => - centeredText((this.ranks[getIndex(i, j)].playtime / (60 * 60 * 1000)).toFixed(0), w, h) + const averageCell: GridCellFunction = (i, j, w, h) => centeredText( + (this.ranks[getIndex(i, j)].playtime / (60 * 60 * 1000)).toFixed(0), w, h) const emptyCell: GridCellObject = { callback: (i, j, w, h) => '', background: undefined diff --git a/plugins/stats/ui/TopPlaytimes.config.js b/plugins/stats/ui/TopPlaytimes.config.js index 00b6b9433..aea9af1b4 100644 --- a/plugins/stats/ui/TopPlaytimes.config.js +++ b/plugins/stats/ui/TopPlaytimes.config.js @@ -1,24 +1,37 @@ -import { icons, componentIds as ids } from '../../ui/UI.js' +import { componentIds as ids, icons } from '../../ui/UI.js' export default { - title: "Top Playtimes", + title: 'Top Playtimes', icon: icons.clockList, gridColumns: [0.8, 4, 3, 3, 0.8, 4, 3, 3], headers: ['Lp.', 'Nickname', 'Login', 'Hours Played', 'Lp.', 'Nickname', 'Login', 'Hours Played'], entries: 30, // Has to be even number - navbar: [ - { name: "Top Donations", actionId: ids.topDonations }, - { name: "Top Ranks", actionId: ids.topRanks }, - { name: "Top Records", actionId: ids.topRecords }, - { name: "Top Sums", actionId: ids.topSums }, - { name: "Top Visits", actionId: ids.topVisits }, - { name: "Top Votes", actionId: ids.topVotes }, - { name: "Top Wins", actionId: ids.topWins } - ], + navbar: [{ + name: 'Top Donations', + actionId: ids.topDonations + }, { + name: 'Top Ranks', + actionId: ids.topRanks + }, { + name: 'Top Records', + actionId: ids.topRecords + }, { + name: 'Top Sums', + actionId: ids.topSums + }, { + name: 'Top Visits', + actionId: ids.topVisits + }, { + name: 'Top Votes', + actionId: ids.topVotes + }, { + name: 'Top Wins', + actionId: ids.topWins + }], grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, selfColour: tm.utils.palette.green, command: { @@ -26,4 +39,4 @@ export default { help: `Display top playtimes.`, privilege: 0 } -} \ No newline at end of file +} diff --git a/plugins/stats/ui/TopRanks.component.ts b/plugins/stats/ui/TopRanks.component.ts index 67a1f7bcf..07a2cc2f1 100644 --- a/plugins/stats/ui/TopRanks.component.ts +++ b/plugins/stats/ui/TopRanks.component.ts @@ -4,14 +4,18 @@ */ import { stats } from "../../stats/Stats.js" -import { Paginator, Grid, type GridCellFunction, closeButton, type GridCellObject, PopupWindow, componentIds, centeredText } from "../../ui/UI.js" +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, Paginator, PopupWindow } from "../../ui/UI.js" import config from './TopRanks.config.js' export default class TopRanks extends PopupWindow { private readonly paginator: Paginator private readonly grid: Grid - private ranks: readonly { login: string, nickname: string, average: number }[] + private ranks: readonly { + login: string, + nickname: string, + average: number + }[] constructor() { super(componentIds.topRanks, config.icon, config.title, config.navbar) @@ -28,7 +32,8 @@ export default class TopRanks extends PopupWindow { this.paginator.setPageCount(Math.ceil(this.ranks.length / config.entries)) this.reRender() }) - this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, Math.ceil(this.ranks.length / config.entries)) + this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, + Math.ceil(this.ranks.length / config.entries)) this.paginator.onPageChange = (login, page) => { this.displayToPlayer(login, page, `${page}/${this.paginator.pageCount}`) } @@ -60,17 +65,17 @@ export default class TopRanks extends PopupWindow { const leftColumns = 3 const offset = (((page ?? 1) - 1) * config.entries) - 1 const getIndex = (i: number, j: number) => i + offset + (j > leftColumns ? (config.entries / columns) : 0) - const arr: (GridCellFunction | GridCellObject)[] = config.headers.map((a) => - (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) + const arr: (GridCellFunction | GridCellObject)[] = config.headers.map( + (a) => (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) const indexCell: GridCellFunction = (i, j, w, h) => centeredText((getIndex(i, j) + 1).toString(), w, h) - const nicknameCell: GridCellFunction = (i, j, w, h) => - centeredText(tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) + const nicknameCell: GridCellFunction = (i, j, w, h) => centeredText( + tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) const loginCell: GridCellFunction = (i, j, w, h) => { const colour = this.ranks[getIndex(i, j)].login === login ? `$${config.selfColour} ` : '' return centeredText(colour + this.ranks[getIndex(i, j)].login, w, h) } - const averageCell: GridCellFunction = (i, j, w, h) => - centeredText(this.ranks[getIndex(i, j)].average.toFixed(config.precision), w, h) + const averageCell: GridCellFunction = (i, j, w, h) => centeredText( + this.ranks[getIndex(i, j)].average.toFixed(config.precision), w, h) const emptyCell: GridCellObject = { callback: (i, j, w, h) => '', background: undefined diff --git a/plugins/stats/ui/TopRanks.config.js b/plugins/stats/ui/TopRanks.config.js index b5266b254..6d36b7c69 100644 --- a/plugins/stats/ui/TopRanks.config.js +++ b/plugins/stats/ui/TopRanks.config.js @@ -1,25 +1,38 @@ -import { icons, componentIds as ids } from '../../ui/UI.js' +import { componentIds as ids, icons } from '../../ui/UI.js' export default { - title: "Top Ranks", + title: 'Top Ranks', icon: icons.chartLocal, gridColumns: [0.8, 4, 3, 3, 0.8, 4, 3, 3], headers: ['Lp.', 'Nickname', 'Login', 'Average Rank', 'Lp.', 'Nickname', 'Login', 'Average Rank'], entries: 30, // Has to be even number precision: 4, - navbar: [ - { name: "Top Donations", actionId: ids.topDonations }, - { name: "Top Playtimes", actionId: ids.topPlaytimes }, - { name: "Top Records", actionId: ids.topRecords }, - { name: "Top Sums", actionId: ids.topSums }, - { name: "Top Visits", actionId: ids.topVisits }, - { name: "Top Votes", actionId: ids.topVotes }, - { name: "Top Wins", actionId: ids.topWins } - ], + navbar: [{ + name: 'Top Donations', + actionId: ids.topDonations + }, { + name: 'Top Playtimes', + actionId: ids.topPlaytimes + }, { + name: 'Top Records', + actionId: ids.topRecords + }, { + name: 'Top Sums', + actionId: ids.topSums + }, { + name: 'Top Visits', + actionId: ids.topVisits + }, { + name: 'Top Votes', + actionId: ids.topVotes + }, { + name: 'Top Wins', + actionId: ids.topWins + }], grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, selfColour: tm.utils.palette.green, command: { @@ -27,4 +40,4 @@ export default { help: `Display top average ranks.`, privilege: 0 } -} \ No newline at end of file +} diff --git a/plugins/stats/ui/TopRecords.component.ts b/plugins/stats/ui/TopRecords.component.ts index 44826fb14..520ec12eb 100644 --- a/plugins/stats/ui/TopRecords.component.ts +++ b/plugins/stats/ui/TopRecords.component.ts @@ -4,14 +4,18 @@ */ import { stats } from "../../stats/Stats.js" -import { Paginator, Grid, type GridCellFunction, closeButton, type GridCellObject, PopupWindow, componentIds, centeredText } from "../../ui/UI.js" +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, Paginator, PopupWindow } from "../../ui/UI.js" import config from './TopRecords.config.js' export default class TopRecords extends PopupWindow { private readonly paginator: Paginator private readonly grid: Grid - private ranks: readonly { login: string, nickname: string, amount: number }[] + private ranks: readonly { + login: string, + nickname: string, + amount: number + }[] constructor() { super(componentIds.topRecords, config.icon, config.title, config.navbar) @@ -28,7 +32,8 @@ export default class TopRecords extends PopupWindow { this.paginator.setPageCount(Math.ceil(this.ranks.length / config.entries)) this.reRender() }) - this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, Math.ceil(this.ranks.length / config.entries)) + this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, + Math.ceil(this.ranks.length / config.entries)) this.paginator.onPageChange = (login, page) => { this.displayToPlayer(login, page, `${page}/${this.paginator.pageCount}`) } @@ -60,17 +65,17 @@ export default class TopRecords extends PopupWindow { const leftColumns = 3 const offset = (((page ?? 1) - 1) * config.entries) - 1 const getIndex = (i: number, j: number) => i + offset + (j > leftColumns ? (config.entries / columns) : 0) - const arr: (GridCellFunction | GridCellObject)[] = config.headers.map((a) => - (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) + const arr: (GridCellFunction | GridCellObject)[] = config.headers.map( + (a) => (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) const indexCell: GridCellFunction = (i, j, w, h) => centeredText((getIndex(i, j) + 1).toString(), w, h) - const nicknameCell: GridCellFunction = (i, j, w, h) => - centeredText(tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) + const nicknameCell: GridCellFunction = (i, j, w, h) => centeredText( + tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) const loginCell: GridCellFunction = (i, j, w, h) => { const colour = this.ranks[getIndex(i, j)].login === login ? `$${config.selfColour} ` : '' return centeredText(colour + this.ranks[getIndex(i, j)].login, w, h) } - const averageCell: GridCellFunction = (i, j, w, h) => - centeredText(this.ranks[getIndex(i, j)].amount.toString(), w, h) + const averageCell: GridCellFunction = (i, j, w, h) => centeredText(this.ranks[getIndex(i, j)].amount.toString(), w, + h) const emptyCell: GridCellObject = { callback: (i, j, w, h) => '', background: undefined diff --git a/plugins/stats/ui/TopRecords.config.js b/plugins/stats/ui/TopRecords.config.js index 4e41060d2..1ed615125 100644 --- a/plugins/stats/ui/TopRecords.config.js +++ b/plugins/stats/ui/TopRecords.config.js @@ -1,23 +1,37 @@ -import { icons, componentIds as ids } from '../../ui/UI.js' +import { componentIds as ids, icons } from '../../ui/UI.js' + export default { - title: "Top Records", + title: 'Top Records', icon: icons.stats, gridColumns: [0.8, 4, 3, 3, 0.8, 4, 3, 3], headers: ['Lp.', 'Nickname', 'Login', 'Records', 'Lp.', 'Nickname', 'Login', 'Records'], entries: 30, // Has to be even number - navbar: [ - { name: "Top Donations", actionId: ids.topDonations }, - { name: "Top Playtimes", actionId: ids.topPlaytimes }, - { name: "Top Ranks", actionId: ids.topRanks }, - { name: "Top Sums", actionId: ids.topSums }, - { name: "Top Visits", actionId: ids.topVisits }, - { name: "Top Votes", actionId: ids.topVotes }, - { name: "Top Wins", actionId: ids.topWins } - ], + navbar: [{ + name: 'Top Donations', + actionId: ids.topDonations + }, { + name: 'Top Playtimes', + actionId: ids.topPlaytimes + }, { + name: 'Top Ranks', + actionId: ids.topRanks + }, { + name: 'Top Sums', + actionId: ids.topSums + }, { + name: 'Top Visits', + actionId: ids.topVisits + }, { + name: 'Top Votes', + actionId: ids.topVotes + }, { + name: 'Top Wins', + actionId: ids.topWins + }], grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, selfColour: tm.utils.palette.green, command: { @@ -25,4 +39,4 @@ export default { help: `Display top record amounts.`, privilege: 0 } -} \ No newline at end of file +} diff --git a/plugins/stats/ui/TopSums.component.ts b/plugins/stats/ui/TopSums.component.ts index 8690ef8ba..e1d3dc943 100644 --- a/plugins/stats/ui/TopSums.component.ts +++ b/plugins/stats/ui/TopSums.component.ts @@ -4,14 +4,18 @@ */ import { stats } from "../../stats/Stats.js" -import { Paginator, Grid, type GridCellFunction, closeButton, type GridCellObject, PopupWindow, componentIds, centeredText } from "../../ui/UI.js" +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, Paginator, PopupWindow } from "../../ui/UI.js" import config from './TopSums.config.js' export default class TopSums extends PopupWindow { private readonly paginator: Paginator private readonly grid: Grid - private ranks: readonly { login: string, nickname: string, sums: Readonly<[number, number, number, number]> }[] + private ranks: readonly { + login: string, + nickname: string, + sums: Readonly<[number, number, number, number]> + }[] constructor() { super(componentIds.topSums, config.icon, config.title, config.navbar) @@ -28,7 +32,8 @@ export default class TopSums extends PopupWindow { this.paginator.setPageCount(Math.ceil(this.ranks.length / config.entries)) this.reRender() }) - this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, Math.ceil(this.ranks.length / config.entries)) + this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, + Math.ceil(this.ranks.length / config.entries)) this.paginator.onPageChange = (login, page) => { this.displayToPlayer(login, page, `${page}/${this.paginator.pageCount}`) } @@ -60,23 +65,23 @@ export default class TopSums extends PopupWindow { const leftColumns = 6 const offset = (((page ?? 1) - 1) * config.entries) - 1 const getIndex = (i: number, j: number) => i + offset + (j > leftColumns ? (config.entries / columns) : 0) - const arr: (GridCellFunction | GridCellObject)[] = config.headers.map((a) => - (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) + const arr: (GridCellFunction | GridCellObject)[] = config.headers.map( + (a) => (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) const indexCell: GridCellFunction = (i, j, w, h) => centeredText((getIndex(i, j) + 1).toString(), w, h) - const nicknameCell: GridCellFunction = (i, j, w, h) => - centeredText(tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) + const nicknameCell: GridCellFunction = (i, j, w, h) => centeredText( + tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) const loginCell: GridCellFunction = (i, j, w, h) => { const colour = this.ranks[getIndex(i, j)].login === login ? `$${config.selfColour} ` : '' return centeredText(colour + this.ranks[getIndex(i, j)].login, w, h) } - const firstCell: GridCellFunction = (i, j, w, h) => - centeredText('$' + config.colours.gold + this.ranks[getIndex(i, j)].sums[0].toString(), w, h) - const secondCell: GridCellFunction = (i, j, w, h) => - centeredText('$' + config.colours.silver + this.ranks[getIndex(i, j)].sums[1].toString(), w, h) - const thirdCell: GridCellFunction = (i, j, w, h) => - centeredText('$' + config.colours.bronze + this.ranks[getIndex(i, j)].sums[2].toString(), w, h) - const otherCell: GridCellFunction = (i, j, w, h) => - centeredText(this.ranks[getIndex(i, j)].sums[3].toString(), w, h) + const firstCell: GridCellFunction = (i, j, w, h) => centeredText( + '$' + config.colours.gold + this.ranks[getIndex(i, j)].sums[0].toString(), w, h) + const secondCell: GridCellFunction = (i, j, w, h) => centeredText( + '$' + config.colours.silver + this.ranks[getIndex(i, j)].sums[1].toString(), w, h) + const thirdCell: GridCellFunction = (i, j, w, h) => centeredText( + '$' + config.colours.bronze + this.ranks[getIndex(i, j)].sums[2].toString(), w, h) + const otherCell: GridCellFunction = (i, j, w, h) => centeredText(this.ranks[getIndex(i, j)].sums[3].toString(), w, + h) const emptyCell: GridCellObject = { callback: (i, j, w, h) => '', background: undefined diff --git a/plugins/stats/ui/TopSums.config.js b/plugins/stats/ui/TopSums.config.js index 7744669c6..95142e8c9 100644 --- a/plugins/stats/ui/TopSums.config.js +++ b/plugins/stats/ui/TopSums.config.js @@ -1,25 +1,38 @@ -import { icons, componentIds as ids } from '../../ui/UI.js' +import { componentIds as ids, icons } from '../../ui/UI.js' export default { - title: "Top Sums", + title: 'Top Sums', icon: icons.stats, gridColumns: [0.8, 4, 3, 1, 1, 1, 1.3, 0.8, 4, 3, 1, 1, 1, 1.3], - headers: ['Lp.', 'Nickname', 'Login', '1st', '2nd', '3rd', 'Other', - 'Lp.', 'Nickname', 'Login', '1st', '2nd', '3rd', 'Other',], + headers: ['Lp.', 'Nickname', 'Login', '1st', '2nd', '3rd', 'Other', 'Lp.', 'Nickname', 'Login', '1st', '2nd', '3rd', + 'Other'], entries: 30, // Has to be even number - navbar: [ - { name: "Top Donations", actionId: ids.topDonations }, - { name: "Top Playtimes", actionId: ids.topPlaytimes }, - { name: "Top Ranks", actionId: ids.topRanks }, - { name: "Top Records", actionId: ids.topRecords }, - { name: "Top Visits", actionId: ids.topVisits }, - { name: "Top Votes", actionId: ids.topVotes }, - { name: "Top Wins", actionId: ids.topWins } - ], + navbar: [{ + name: 'Top Donations', + actionId: ids.topDonations + }, { + name: 'Top Playtimes', + actionId: ids.topPlaytimes + }, { + name: 'Top Ranks', + actionId: ids.topRanks + }, { + name: 'Top Records', + actionId: ids.topRecords + }, { + name: 'Top Visits', + actionId: ids.topVisits + }, { + name: 'Top Votes', + actionId: ids.topVotes + }, { + name: 'Top Wins', + actionId: ids.topWins + }], grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, selfColour: tm.utils.palette.green, colours: { @@ -32,4 +45,4 @@ export default { help: `Display top record summaries.`, privilege: 0 } -} \ No newline at end of file +} diff --git a/plugins/stats/ui/TopVisits.component.ts b/plugins/stats/ui/TopVisits.component.ts index eb8239df7..64992916f 100644 --- a/plugins/stats/ui/TopVisits.component.ts +++ b/plugins/stats/ui/TopVisits.component.ts @@ -4,14 +4,18 @@ */ import { stats } from "../../stats/Stats.js" -import { Paginator, Grid, type GridCellFunction, closeButton, type GridCellObject, PopupWindow, componentIds, centeredText } from "../../ui/UI.js" +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, Paginator, PopupWindow } from "../../ui/UI.js" import config from './TopVisits.config.js' export default class TopVisits extends PopupWindow { private readonly paginator: Paginator private readonly grid: Grid - private ranks: readonly { login: string, nickname: string, visits: number }[] + private ranks: readonly { + login: string, + nickname: string, + visits: number + }[] constructor() { super(componentIds.topVisits, config.icon, config.title, config.navbar) @@ -28,7 +32,8 @@ export default class TopVisits extends PopupWindow { this.paginator.setPageCount(Math.ceil(this.ranks.length / config.entries)) this.reRender() }) - this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, Math.ceil(this.ranks.length / config.entries)) + this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, + Math.ceil(this.ranks.length / config.entries)) this.paginator.onPageChange = (login, page) => { this.displayToPlayer(login, page, `${page}/${this.paginator.pageCount}`) } @@ -60,17 +65,17 @@ export default class TopVisits extends PopupWindow { const leftColumns = 3 const offset = (((page ?? 1) - 1) * config.entries) - 1 const getIndex = (i: number, j: number) => i + offset + (j > leftColumns ? (config.entries / columns) : 0) - const arr: (GridCellFunction | GridCellObject)[] = config.headers.map((a) => - (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) + const arr: (GridCellFunction | GridCellObject)[] = config.headers.map( + (a) => (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) const indexCell: GridCellFunction = (i, j, w, h) => centeredText((getIndex(i, j) + 1).toString(), w, h) - const nicknameCell: GridCellFunction = (i, j, w, h) => - centeredText(tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) + const nicknameCell: GridCellFunction = (i, j, w, h) => centeredText( + tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) const loginCell: GridCellFunction = (i, j, w, h) => { const colour = this.ranks[getIndex(i, j)].login === login ? `$${config.selfColour} ` : '' return centeredText(colour + this.ranks[getIndex(i, j)].login, w, h) } - const averageCell: GridCellFunction = (i, j, w, h) => - centeredText(this.ranks[getIndex(i, j)].visits.toString(), w, h) + const averageCell: GridCellFunction = (i, j, w, h) => centeredText(this.ranks[getIndex(i, j)].visits.toString(), w, + h) const emptyCell: GridCellObject = { callback: (i, j, w, h) => '', background: undefined diff --git a/plugins/stats/ui/TopVisits.config.js b/plugins/stats/ui/TopVisits.config.js index c10cac303..fc065f3fd 100644 --- a/plugins/stats/ui/TopVisits.config.js +++ b/plugins/stats/ui/TopVisits.config.js @@ -1,24 +1,37 @@ -import { icons, componentIds as ids } from '../../ui/UI.js' +import { componentIds as ids, icons } from '../../ui/UI.js' export default { - title: "Top Visits", + title: 'Top Visits', icon: icons.stats, gridColumns: [0.8, 4, 3, 3, 0.8, 4, 3, 3], headers: ['Lp.', 'Nickname', 'Login', 'Visits', 'Lp.', 'Nickname', 'Login', 'Visits'], entries: 30, // Has to be even number - navbar: [ - { name: "Top Donations", actionId: ids.topDonations }, - { name: "Top Playtimes", actionId: ids.topPlaytimes }, - { name: "Top Ranks", actionId: ids.topRanks }, - { name: "Top Records", actionId: ids.topRecords }, - { name: "Top Sums", actionId: ids.topSums }, - { name: "Top Votes", actionId: ids.topVotes }, - { name: "Top Wins", actionId: ids.topWins } - ], + navbar: [{ + name: 'Top Donations', + actionId: ids.topDonations + }, { + name: 'Top Playtimes', + actionId: ids.topPlaytimes + }, { + name: 'Top Ranks', + actionId: ids.topRanks + }, { + name: 'Top Records', + actionId: ids.topRecords + }, { + name: 'Top Sums', + actionId: ids.topSums + }, { + name: 'Top Votes', + actionId: ids.topVotes + }, { + name: 'Top Wins', + actionId: ids.topWins + }], grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, selfColour: tm.utils.palette.green, command: { @@ -26,4 +39,4 @@ export default { help: `Display top visitors.`, privilege: 0 } -} \ No newline at end of file +} diff --git a/plugins/stats/ui/TopVotes.component.ts b/plugins/stats/ui/TopVotes.component.ts index b5f4b34cc..6cbf2ff63 100644 --- a/plugins/stats/ui/TopVotes.component.ts +++ b/plugins/stats/ui/TopVotes.component.ts @@ -4,14 +4,18 @@ */ import { stats } from "../../stats/Stats.js" -import { Paginator, Grid, type GridCellFunction, closeButton, type GridCellObject, PopupWindow, componentIds, centeredText } from "../../ui/UI.js" +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, Paginator, PopupWindow } from "../../ui/UI.js" import config from './TopVotes.config.js' export default class TopVisits extends PopupWindow { private readonly paginator: Paginator private readonly grid: Grid - private ranks: readonly { login: string, nickname: string, count: number }[] + private ranks: readonly { + login: string, + nickname: string, + count: number + }[] constructor() { super(componentIds.topVotes, config.icon, config.title, config.navbar) @@ -28,7 +32,8 @@ export default class TopVisits extends PopupWindow { this.paginator.setPageCount(Math.ceil(this.ranks.length / config.entries)) this.reRender() }) - this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, Math.ceil(this.ranks.length / config.entries)) + this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, + Math.ceil(this.ranks.length / config.entries)) this.paginator.onPageChange = (login, page) => { this.displayToPlayer(login, page, `${page}/${this.paginator.pageCount}`) } @@ -60,17 +65,17 @@ export default class TopVisits extends PopupWindow { const leftColumns = 3 const offset = (((page ?? 1) - 1) * config.entries) - 1 const getIndex = (i: number, j: number) => i + offset + (j > leftColumns ? (config.entries / columns) : 0) - const arr: (GridCellFunction | GridCellObject)[] = config.headers.map((a) => - (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) + const arr: (GridCellFunction | GridCellObject)[] = config.headers.map( + (a) => (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) const indexCell: GridCellFunction = (i, j, w, h) => centeredText((getIndex(i, j) + 1).toString(), w, h) - const nicknameCell: GridCellFunction = (i, j, w, h) => - centeredText(tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) + const nicknameCell: GridCellFunction = (i, j, w, h) => centeredText( + tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) const loginCell: GridCellFunction = (i, j, w, h) => { const colour = this.ranks[getIndex(i, j)].login === login ? `$${config.selfColour} ` : '' return centeredText(colour + this.ranks[getIndex(i, j)].login, w, h) } - const averageCell: GridCellFunction = (i, j, w, h) => - centeredText(this.ranks[getIndex(i, j)].count.toString(), w, h) + const averageCell: GridCellFunction = (i, j, w, h) => centeredText(this.ranks[getIndex(i, j)].count.toString(), w, + h) const emptyCell: GridCellObject = { callback: (i, j, w, h) => '', background: undefined diff --git a/plugins/stats/ui/TopVotes.config.js b/plugins/stats/ui/TopVotes.config.js index bb4bc2155..1a4e7194a 100644 --- a/plugins/stats/ui/TopVotes.config.js +++ b/plugins/stats/ui/TopVotes.config.js @@ -1,24 +1,37 @@ -import { icons, componentIds as ids } from '../../ui/UI.js' +import { componentIds as ids, icons } from '../../ui/UI.js' export default { - title: "Top Votes", + title: 'Top Votes', icon: icons.karmaStats, gridColumns: [0.8, 4, 3, 3, 0.8, 4, 3, 3], headers: ['Lp.', 'Nickname', 'Login', 'Votes', 'Lp.', 'Nickname', 'Login', 'Votes'], entries: 30, // Has to be even number - navbar: [ - { name: "Top Donations", actionId: ids.topDonations }, - { name: "Top Playtimes", actionId: ids.topPlaytimes }, - { name: "Top Ranks", actionId: ids.topRanks }, - { name: "Top Records", actionId: ids.topRecords }, - { name: "Top Sums", actionId: ids.topSums }, - { name: "Top Visits", actionId: ids.topVisits }, - { name: "Top Wins", actionId: ids.topWins } - ], + navbar: [{ + name: 'Top Donations', + actionId: ids.topDonations + }, { + name: 'Top Playtimes', + actionId: ids.topPlaytimes + }, { + name: 'Top Ranks', + actionId: ids.topRanks + }, { + name: 'Top Records', + actionId: ids.topRecords + }, { + name: 'Top Sums', + actionId: ids.topSums + }, { + name: 'Top Visits', + actionId: ids.topVisits + }, { + name: 'Top Wins', + actionId: ids.topWins + }], grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, selfColour: tm.utils.palette.green, command: { @@ -26,4 +39,4 @@ export default { help: `Display top vote amounts.`, privilege: 0 } -} \ No newline at end of file +} diff --git a/plugins/stats/ui/TopWins.component.ts b/plugins/stats/ui/TopWins.component.ts index aeec78b8e..d0ec9b5d7 100644 --- a/plugins/stats/ui/TopWins.component.ts +++ b/plugins/stats/ui/TopWins.component.ts @@ -4,14 +4,18 @@ */ import { stats } from "../../stats/Stats.js" -import { Paginator, Grid, type GridCellFunction, closeButton, type GridCellObject, PopupWindow, componentIds, centeredText } from "../../ui/UI.js" +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, Paginator, PopupWindow } from "../../ui/UI.js" import config from './TopWins.config.js' export default class TopWins extends PopupWindow { private readonly paginator: Paginator private readonly grid: Grid - private ranks: readonly { login: string, nickname: string, wins: number }[] + private ranks: readonly { + login: string, + nickname: string, + wins: number + }[] constructor() { super(componentIds.topWins, config.icon, config.title, config.navbar) @@ -28,7 +32,8 @@ export default class TopWins extends PopupWindow { this.paginator.setPageCount(Math.ceil(this.ranks.length / config.entries)) this.reRender() }) - this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, Math.ceil(this.ranks.length / config.entries)) + this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, + Math.ceil(this.ranks.length / config.entries)) this.paginator.onPageChange = (login, page) => { this.displayToPlayer(login, page, `${page}/${this.paginator.pageCount}`) } @@ -60,17 +65,16 @@ export default class TopWins extends PopupWindow { const leftColumns = 3 const offset = (((page ?? 1) - 1) * config.entries) - 1 const getIndex = (i: number, j: number) => i + offset + (j > leftColumns ? (config.entries / columns) : 0) - const arr: (GridCellFunction | GridCellObject)[] = config.headers.map((a) => - (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) + const arr: (GridCellFunction | GridCellObject)[] = config.headers.map( + (a) => (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) const indexCell: GridCellFunction = (i, j, w, h) => centeredText((getIndex(i, j) + 1).toString(), w, h) - const nicknameCell: GridCellFunction = (i, j, w, h) => - centeredText(tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) + const nicknameCell: GridCellFunction = (i, j, w, h) => centeredText( + tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) const loginCell: GridCellFunction = (i, j, w, h) => { const colour = this.ranks[getIndex(i, j)].login === login ? `$${config.selfColour} ` : '' return centeredText(colour + this.ranks[getIndex(i, j)].login, w, h) } - const averageCell: GridCellFunction = (i, j, w, h) => - centeredText(this.ranks[getIndex(i, j)].wins.toString(), w, h) + const averageCell: GridCellFunction = (i, j, w, h) => centeredText(this.ranks[getIndex(i, j)].wins.toString(), w, h) const emptyCell: GridCellObject = { callback: (i, j, w, h) => '', background: undefined diff --git a/plugins/stats/ui/TopWins.config.js b/plugins/stats/ui/TopWins.config.js index 45a50fcc2..3f76fc325 100644 --- a/plugins/stats/ui/TopWins.config.js +++ b/plugins/stats/ui/TopWins.config.js @@ -1,24 +1,37 @@ -import { icons, componentIds as ids } from '../../ui/UI.js' +import { componentIds as ids, icons } from '../../ui/UI.js' export default { - title: "Top Wins", + title: 'Top Wins', icon: icons.stats, gridColumns: [0.8, 4, 3, 3, 0.8, 4, 3, 3], headers: ['Lp.', 'Nickname', 'Login', 'Wins', 'Lp.', 'Nickname', 'Login', 'Wins'], entries: 30, // Has to be even number - navbar: [ - { name: "Top Donations", actionId: ids.topDonations }, - { name: "Top Playtimes", actionId: ids.topPlaytimes }, - { name: "Top Ranks", actionId: ids.topRanks }, - { name: "Top Records", actionId: ids.topRecords }, - { name: "Top Sums", actionId: ids.topSums }, - { name: "Top Visits", actionId: ids.topVisits }, - { name: "Top Votes", actionId: ids.topVotes } - ], + navbar: [{ + name: 'Top Donations', + actionId: ids.topDonations + }, { + name: 'Top Playtimes', + actionId: ids.topPlaytimes + }, { + name: 'Top Ranks', + actionId: ids.topRanks + }, { + name: 'Top Records', + actionId: ids.topRecords + }, { + name: 'Top Sums', + actionId: ids.topSums + }, { + name: 'Top Visits', + actionId: ids.topVisits + }, { + name: 'Top Votes', + actionId: ids.topVotes + }], grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, selfColour: tm.utils.palette.green, command: { @@ -26,4 +39,4 @@ export default { help: `Display top winners.`, privilege: 0 } -} \ No newline at end of file +} diff --git a/plugins/tmx/Tmx.ts b/plugins/tmx/Tmx.ts index 0e49c88ad..fdd5bf88d 100644 --- a/plugins/tmx/Tmx.ts +++ b/plugins/tmx/Tmx.ts @@ -30,7 +30,8 @@ const getUid = (map: tm.TMXMap | string): string => typeof map === 'string' ? ma const initialize = async (): Promise => { if (queueSize > tm.jukebox.queueCount) { - await tm.log.fatal(`jukeboxQueueSize (${tm.jukebox.queueCount}) can't be lower than tmx queueSize (${queueSize}). Change your tmx config`) + await tm.log.fatal( + `jukeboxQueueSize (${tm.jukebox.queueCount}) can't be lower than tmx queueSize (${queueSize}). Change your tmx config`) } const res: tm.TMXMap | Error = await tm.tmx.fetchMapInfo(tm.maps.current.id) current = res instanceof Error ? tm.maps.current.id : res @@ -56,8 +57,7 @@ const update = async (updateCurrent?: true): Promise => { const curId = tm.maps.current.id if (updateCurrent) { const entry = fetchedMaps.find(a => getUid(a) === curId) - if (entry !== undefined) { current = entry } - else { + if (entry !== undefined) { current = entry } else { const res = await tm.tmx.fetchMapInfo(curId) current = res instanceof Error ? curId : res } @@ -127,7 +127,7 @@ function getFromQueue(uids: string | string[]): Readonly | undefined } /** - * Fetches TMX data for current map and maps in the queue. + * Fetches TMX data for current map and maps in the queue. * Provides utilities for accessing TMX related data. * @author lythx * @since 0.1 @@ -155,7 +155,7 @@ export const tmx = { getFromQueue, /** - * Current map TMX info or null if map is not on tmx + * Current map TMX info or null if map is not on tmx */ get current(): Readonly | null { return typeof current === 'string' ? null : current diff --git a/plugins/tmx/ui/TMXWindow.component.ts b/plugins/tmx/ui/TMXWindow.component.ts index 23faeb999..98b2cfb70 100644 --- a/plugins/tmx/ui/TMXWindow.component.ts +++ b/plugins/tmx/ui/TMXWindow.component.ts @@ -3,7 +3,7 @@ * @since 0.5 */ -import { componentIds, Paginator, Grid, type GridCellFunction, centeredText, closeButton, leftAlignedText, type GridCellObject, PopupWindow } from '../../ui/UI.js' +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, leftAlignedText, Paginator, PopupWindow } from '../../ui/UI.js' import config from './TMXWindow.config.js' import { tmx } from "../../tmx/Tmx.js" @@ -15,12 +15,15 @@ class TMXWindow extends PopupWindow { constructor() { super(componentIds.TMXWindow, config.icon, config.title, config.navbar) - this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, Math.ceil(config.queueCount / config.itemsPerPage)) + this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, + Math.ceil(config.queueCount / config.itemsPerPage)) this.paginator.onPageChange = (login, page) => { this.displayToPlayer(login, page, `${page}/${this.paginator.pageCount}`) } - this.grid = new Grid(this.contentWidth, this.contentHeight, new Array(config.itemsPerPage).fill(1), [1], - { background: config.gridBackground, margin: config.margin }) + this.grid = new Grid(this.contentWidth, this.contentHeight, new Array(config.itemsPerPage).fill(1), [1], { + background: config.gridBackground, + margin: config.margin + }) tmx.onMapChange(() => this.reRender()) tmx.onQueueChange(() => this.reRender()) tm.addListener('BeginMap', () => { @@ -66,22 +69,27 @@ class TMXWindow extends PopupWindow { const index = Math.ceil((currentPage - (page + 1)) * config.itemsPerPage) + 1 maps = [tm.jukebox.history?.[index + 2], tm.jukebox.history?.[index + 1], tm.jukebox.history?.[index]] TMXMaps = [tmx.history?.[index + 2], tmx.history?.[index + 1], tmx.history?.[index]] - titles = [`${config.titles.previous} #${index + 3} `, `${config.titles.previous} #${index + 2} `, `${config.titles.previous} #${index + 1} `] + titles = [`${config.titles.previous} #${index + 3} `, `${config.titles.previous} #${index + 2} `, + `${config.titles.previous} #${index + 1} `] } else { const index = Math.ceil((page - (currentPage + 1)) * config.itemsPerPage) + 1 maps = [tm.jukebox.queue?.[index], tm.jukebox.queue?.[index + 1], tm.jukebox.queue?.[index + 2]] TMXMaps = [tmx.queue?.[index], tmx.queue?.[index + 1], tmx.queue?.[index + 2]] - titles = [`${config.titles.next} #${index + 1} `, `${config.titles.next} #${index + 2} `, `${config.titles.next} #${index + 3} `] + titles = [`${config.titles.next} #${index + 1} `, `${config.titles.next} #${index + 2} `, + `${config.titles.next} #${index + 3} `] } const m = maps.filter(a => a !== undefined).map(a => (a as any).id) - const allRecords: Readonly = [...tm.records.getFromHistory(...m), ...tm.records.local, ...tm.records.getFromQueue(...m)] + const allRecords: Readonly = [...tm.records.getFromHistory(...m), ...tm.records.local, + ...tm.records.getFromQueue(...m)] .sort((a, b) => a.time - b.time) .filter((a, i, arr) => arr.findIndex(b => b.login === a.login && a.map === b.map) === i) const cell: GridCellFunction = (i, j, w, h) => { const map = maps[j] if (map === undefined) { return '' } - const grid = new Grid(w, h, [1], config.gridRows, - { background: config.info.background, margin: config.margin }) + const grid = new Grid(w, h, [1], config.gridRows, { + background: config.info.background, + margin: config.margin + }) let mapName = map.name if (tm.utils.isMultibyte(mapName)) { mapName = '' @@ -89,16 +97,19 @@ class TMXWindow extends PopupWindow { const TMXMap = TMXMaps[j] ?? undefined const mapRecords = allRecords.filter(a => a.map === map.id) const header: GridCellFunction = (ii, jj, ww, hh) => this.constructHeader(ww, hh, titles[j], map, TMXMap) - const screenshot: GridCellFunction = (ii, jj, ww, hh) => this.constructScreenshot(login, ww, hh, mapRecords, map.environment, TMXMap) - const name: GridCellFunction = (ii, jj, ww, hh) => - this.constructEntry(tm.utils.safeString(tm.utils.strip(tm.utils.decodeURI(mapName), false)), config.icons.name, ww, hh, config.iconWidth) + const screenshot: GridCellFunction = (ii, jj, ww, hh) => this.constructScreenshot(login, ww, hh, mapRecords, + map.environment, TMXMap) + const name: GridCellFunction = (ii, jj, ww, hh) => this.constructEntry( + tm.utils.safeString(tm.utils.strip(tm.utils.decodeURI(mapName), false)), config.icons.name, ww, hh, + config.iconWidth) const author: GridCellFunction = (ii, jj, ww, hh) => this.constructAuthor(ww, hh, map) - const infos: GridCellFunction = (ii, jj, ww, hh) => - this.constructInfoXml(ww, hh, map, TMXMap) - const tmxRecords: GridCellFunction = (ii, jj, ww, hh) => this.counstructTmxRecordsXml(ww, hh, TMXMap?.validReplays) + const infos: GridCellFunction = (ii, jj, ww, hh) => this.constructInfoXml(ww, hh, map, TMXMap) + const tmxRecords: GridCellFunction = (ii, jj, ww, hh) => this.counstructTmxRecordsXml(ww, hh, + TMXMap?.validReplays) return grid.constructXml([header, screenshot, name, author, infos, tmxRecords]) } - return ` ` + this.grid.constructXml(new Array(config.itemsPerPage).fill(null).map(() => cell)) + return ` ` + this.grid.constructXml( + new Array(config.itemsPerPage).fill(null).map(() => cell)) } protected constructFooter(login: string, page: number): string { @@ -114,34 +125,46 @@ sizen="${config.iconWidth - config.margin * 2} ${height - config.margin * 4}" image="${image}" url="${url}" /> ` } if (TMXMap === undefined) { - return `${this.constructEntry(title, config.icons.header, width - (config.iconWidth + config.margin), height, config.iconWidth)} + return `${this.constructEntry(title, config.icons.header, width - (config.iconWidth + config.margin), height, + config.iconWidth)} - ${icon(0, 0, config.icons.dedimania, tm.utils.safeString(`dedimania.net/tmstats/?do=stat&Uid=${map.id}&Show=RECORDS`))} + ${icon(0, 0, config.icons.dedimania, + tm.utils.safeString(`dedimania.net/tmstats/?do=stat&Uid=${map.id}&Show=RECORDS`))} ` } - return `${this.constructEntry(title, config.icons.header, width - (config.iconWidth + config.margin) * 3, height, config.iconWidth)} + return `${this.constructEntry(title, config.icons.header, width - (config.iconWidth + config.margin) * 3, height, + config.iconWidth)} ${icon(0, 0, config.icons.maniaExchange, tm.utils.fixProtocol(TMXMap.pageUrl))} ${icon(config.iconWidth + config.margin, 0, config.icons.downloadGreen, tm.utils.fixProtocol(TMXMap.downloadUrl))} - ${icon((config.iconWidth + config.margin) * 2, 0, config.icons.dedimania, tm.utils.safeString(`dedimania.net/tmstats/?do=stat&Uid=${map.id}&Show=RECORDS`))} + ${icon((config.iconWidth + config.margin) * 2, 0, config.icons.dedimania, + tm.utils.safeString(`dedimania.net/tmstats/?do=stat&Uid=${map.id}&Show=RECORDS`))} ` } - private constructEntry(text: string, image: string, width: number, height: number, iconWidth: number, useCenteredText?: true): string { + private constructEntry(text: string, image: string, width: number, height: number, iconWidth: number, + useCenteredText?: true): string { return ` - ${useCenteredText === true ? centeredText(text, width - (iconWidth + config.margin * 3), height - config.margin * 2, { textScale: config.textscale }) : - leftAlignedText(text, width - (iconWidth + config.margin * 3), height - config.margin * 2, { textScale: config.textscale })} + ${useCenteredText === true ? + centeredText(text, width - (iconWidth + config.margin * 3), height - config.margin * 2, + { textScale: config.textscale }) : + leftAlignedText(text, width - (iconWidth + config.margin * 3), height - config.margin * 2, + { textScale: config.textscale })} ` } - protected constructScreenshot(login: string, width: number, height: number, records: tm.Record[], environment: tm.Environment, TMXMap?: tm.TMXMap) { + protected constructScreenshot(login: string, width: number, height: number, records: tm.Record[], + environment: tm.Environment, TMXMap?: tm.TMXMap) { const rightW = width - (config.screenshotWidth + config.margin) const count = config.localsCount - const grid = new Grid(rightW, height, [1, 2, 3], new Array(count + 1).fill(1), - { headerBackground: config.iconBackground, background: config.gridBackground, margin: config.margin }) + const grid = new Grid(rightW, height, [1, 2, 3], new Array(count + 1).fill(1), { + headerBackground: config.iconBackground, + background: config.gridBackground, + margin: config.margin + }) const options = { textScale: config.recordTextScale } const index = records.findIndex(a => a.login === login) let personalIndex: number | undefined @@ -153,33 +176,32 @@ image="${image}" url="${url}" /> ` } const indexCell: GridCellObject = { callback: (i, j, w, h) => { - return centeredText(personalIndex === i ? (index + 1).toString() : (records[i - 1] === undefined ? config.defaultText : i.toString()), w, h, options) + return centeredText(personalIndex === i ? (index + 1).toString() : + (records[i - 1] === undefined ? config.defaultText : i.toString()), w, h, options) }, background: config.iconBackground } const nameCell: GridCellFunction = (i, j, w, h) => { let nickname: string | undefined = records[i - 1]?.nickname - if (((records[i - 1] === undefined && i === 1) || (records[i - 1] === undefined && records[i - 2] !== undefined)) && - (index > count || index === -1)) { + if (((records[i - 1] === undefined && i === 1) || (records[i - 1] === undefined && records[i - 2] !== undefined)) && (index > count || index === -1)) { nickname = tm.players.get(login)?.nickname } return leftAlignedText(tm.utils.safeString(tm.utils.strip(nickname ?? config.defaultText, false)), w, h, options) } - const timeCell: GridCellFunction = (i, j, w, h) => centeredText(records[i - 1] !== undefined ? - tm.utils.getTimeString(records[i - 1]?.time) : config.defaultTime, w, h, options) - const arr: (GridCellFunction | GridCellObject)[] = [ - (i, j, w, h) => centeredText('Lp.', w, h, options), - (i, j, w, h) => centeredText('Time', w, h, options), - (i, j, w, h) => centeredText('Name', w, h, options) - ] + const timeCell: GridCellFunction = (i, j, w, h) => centeredText( + records[i - 1] !== undefined ? tm.utils.getTimeString(records[i - 1]?.time) : config.defaultTime, w, h, options) + const arr: (GridCellFunction | GridCellObject)[] = [(i, j, w, h) => centeredText('Lp.', w, h, options), + (i, j, w, h) => centeredText('Time', w, h, options), (i, j, w, h) => centeredText('Name', w, h, options)] for (let i = 0; i < count; i++) { arr.push(indexCell, timeCell, nameCell) } - const image = TMXMap === undefined - ? config.noScreenshot[environment] - : tm.utils.safeString(TMXMap.thumbnailUrl + `&.jpeg`) + const image = TMXMap === undefined ? (config.noScreenshot[environment as keyof typeof config.noScreenshot] ?? config.noScreenshot.Stadium) : + tm.utils.safeString(TMXMap.thumbnailUrl + `&.jpeg`) return ` - ${centeredText(config.notLoaded, config.screenshotWidth, height, { textScale: config.notLoadedTextscale, yOffset: -1 })} + ${centeredText(config.notLoaded, config.screenshotWidth, height, { + textScale: config.notLoadedTextscale, + yOffset: -1 + })} ${grid.constructXml(arr)} ` @@ -190,9 +212,11 @@ image="${image}" url="${url}" /> ` if (tm.utils.isMultibyte(author)) { author = '' } - return `${this.constructEntry(tm.utils.safeString(author), config.icons.author, width - config.authorTimeWidth, height, config.iconWidth)} + return `${this.constructEntry(tm.utils.safeString(author), config.icons.author, width - config.authorTimeWidth, + height, config.iconWidth)} - ${this.constructEntry(tm.utils.getTimeString(map.authorTime), config.icons.authorTime, config.authorTimeWidth + config.margin, height, config.iconWidth, true)} + ${this.constructEntry(tm.utils.getTimeString(map.authorTime), config.icons.authorTime, + config.authorTimeWidth + config.margin, height, config.iconWidth, true)} ` } @@ -205,33 +229,26 @@ image="${image}" url="${url}" /> ` if (TMXMap?.isNadeo ?? map?.leaderboardRating === 50000) { lbRating = 'Nadeo' lbIcon = ic.leaderboardRating.nadeo - } - else if (map.isClassic) { + } else if (map.isClassic) { lbRating = 'Classic' lbIcon = ic.leaderboardRating.classic } let awardsIcon = ic.awards.normal - if (lbRating === 'Nadeo') { awardsIcon = ic.awards.nadeo } - else if (lbRating === 'Classic') { awardsIcon = ic.awards.classic } - const infos: [string, string][] = [ - [map.mood, ic.mood[map.mood.toLowerCase() as keyof typeof ic.mood]], - [tm.utils.formatDate(map.addDate, true), ic.addDate], - [map.voteRatio === -1 ? config.defaultText : map.voteRatio.toFixed(0), ic.voteRatio], - [map.copperPrice.toString(), ic.copperPrice], - [map.environment, ic.environment], - [map?.checkpointsPerLap !== undefined ? `${map.checkpointsPerLap - 1} CPs` : config.defaultText, ic.checkpointsAmount], - [map.voteCount.toString(), ic.voteCount], - [(TMXMap?.awards?.toString() ?? map?.awards?.toString()) ?? config.defaultText, awardsIcon], - [TMXMap?.author === undefined ? config.defaultText : - (tm.utils.isMultibyte(TMXMap.author) ? '' : tm.utils.safeString(TMXMap.author)), ic.tmxAuthor], - [TMXMap !== undefined ? tm.utils.formatDate(TMXMap.lastUpdateDate, true) : config.defaultText, ic.buildDate], - [TMXMap?.style ?? config.defaultText, ic.style], - [lbRating, lbIcon], - [TMXMap?.difficulty ?? config.defaultText, ic.difficulty[(TMXMap?.difficulty?.toLowerCase() as keyof typeof ic.difficulty) ?? 'beginner']], - [TMXMap?.routes ?? config.defaultText, ic.routes], - [TMXMap?.type ?? config.defaultText, ic.type], - [TMXMap?.game ?? config.defaultText, ic.game] - ] + if (lbRating === 'Nadeo') { awardsIcon = ic.awards.nadeo } else if (lbRating === 'Classic') { awardsIcon = ic.awards.classic } + const infos: [string, string][] = [[map.mood, ic.mood[map.mood.toLowerCase() as keyof typeof ic.mood]], + [tm.utils.formatDate(map.addDate, true), ic.addDate], + [map.voteRatio === -1 ? config.defaultText : map.voteRatio.toFixed(0), ic.voteRatio], + [map.copperPrice.toString(), ic.copperPrice], [map.environment, ic.environment], + [map?.checkpointsPerLap !== undefined ? `${map.checkpointsPerLap - 1} CPs` : config.defaultText, + ic.checkpointsAmount], [map.voteCount.toString(), ic.voteCount], + [(TMXMap?.awards?.toString() ?? map?.awards?.toString()) ?? config.defaultText, awardsIcon], + [TMXMap?.author === undefined ? config.defaultText : + (tm.utils.isMultibyte(TMXMap.author) ? '' : tm.utils.safeString(TMXMap.author)), ic.tmxAuthor], + [TMXMap !== undefined ? tm.utils.formatDate(TMXMap.lastUpdateDate, true) : config.defaultText, ic.buildDate], + [TMXMap?.style ?? config.defaultText, ic.style], [lbRating, lbIcon], [TMXMap?.difficulty ?? config.defaultText, + ic.difficulty[(TMXMap?.difficulty?.toLowerCase() as keyof typeof ic.difficulty) ?? 'beginner']], + [TMXMap?.routes ?? config.defaultText, ic.routes], [TMXMap?.type ?? config.defaultText, ic.type], + [TMXMap?.game ?? config.defaultText, ic.game]] const cell: GridCellFunction = (i, j, w, h) => { const index = (i * grid.columns) + j return ` @@ -239,33 +256,35 @@ image="${image}" url="${url}" /> ` - ${centeredText(infos?.[index]?.[0] ?? '', w - (config.info.iconWidth + config.margin), h, { textScale: config.info.textscale })} + ${centeredText(infos?.[index]?.[0] ?? '', w - (config.info.iconWidth + config.margin), h, + { textScale: config.info.textscale })} ` } - return grid.constructXml(new Array(config.info.columnsProportions.length * config.info.rows).fill(null).map(() => cell)) + return grid.constructXml( + new Array(config.info.columnsProportions.length * config.info.rows).fill(null).map(() => cell)) } private counstructTmxRecordsXml(width: number, height: number, replays: tm.TMXReplay[] = []): string { - const grid = new Grid(width, height, config.tmxColumns, new Array(config.tmxRecordCount + 1).fill(1), - { margin: config.margin, background: config.gridBackground, headerBackground: config.iconBackground }) + const grid = new Grid(width, height, config.tmxColumns, new Array(config.tmxRecordCount + 1).fill(1), { + margin: config.margin, + background: config.gridBackground, + headerBackground: config.iconBackground + }) const options = { textScale: config.recordTextScale } - const arr: (GridCellFunction | GridCellObject)[] = [ - (i, j, w, h) => centeredText('Lp.', w, h, options), - (i, j, w, h) => centeredText('Time', w, h, options), - (i, j, w, h) => centeredText('Name', w, h, options), - (i, j, w, h) => centeredText('Date', w, h, options), - (i, j, w, h) => centeredText('Dl.', w, h, options), - ] + const arr: (GridCellFunction | GridCellObject)[] = [(i, j, w, h) => centeredText('Lp.', w, h, options), + (i, j, w, h) => centeredText('Time', w, h, options), (i, j, w, h) => centeredText('Name', w, h, options), + (i, j, w, h) => centeredText('Date', w, h, options), (i, j, w, h) => centeredText('Dl.', w, h, options)] const indexCell: GridCellObject = { callback: (i, j, w, h) => centeredText(i.toString(), w, h, options), background: config.iconBackground } - const timeCell: GridCellFunction = (i, j, w, h) => centeredText(replays[i - 1] !== undefined ? - tm.utils.getTimeString(replays[i - 1]?.time) : config.defaultTime, w, h, options) - const nameCell: GridCellFunction = (i, j, w, h) => - leftAlignedText(tm.utils.safeString(replays[i - 1]?.name ?? config.defaultText), w, h, options) - const dateCell: GridCellFunction = (i, j, w, h) => centeredText(replays[i - 1] !== undefined ? - tm.utils.formatDate(replays[i - 1]?.recordDate, true) : config.defaultText, w, h, options) + const timeCell: GridCellFunction = (i, j, w, h) => centeredText( + replays[i - 1] !== undefined ? tm.utils.getTimeString(replays[i - 1]?.time) : config.defaultTime, w, h, options) + const nameCell: GridCellFunction = (i, j, w, h) => leftAlignedText( + tm.utils.safeString(replays[i - 1]?.name ?? config.defaultText), w, h, options) + const dateCell: GridCellFunction = (i, j, w, h) => centeredText( + replays[i - 1] !== undefined ? tm.utils.formatDate(replays[i - 1]?.recordDate, true) : config.defaultText, w, h, + options) const downloadCell: GridCellObject = { callback: (i, j, w, h) => replays[i - 1] !== undefined ? ` + tm.sendManialink(` @@ -17,13 +16,11 @@ export default class CustomUi { - ` - ) + `) } displayToPlayer(login: string): void { - tm.sendManialink( - ` + tm.sendManialink(` @@ -32,9 +29,7 @@ export default class CustomUi { - `, - login - ) + `, login) } } diff --git a/plugins/ui/Imports.ts b/plugins/ui/Imports.ts index cb6d0fbca..69dad69a8 100644 --- a/plugins/ui/Imports.ts +++ b/plugins/ui/Imports.ts @@ -1,8 +1,8 @@ /* ------------------ -STATIC COMPONENTS ------------------ -*/ + ----------------- + STATIC COMPONENTS + ----------------- + */ import RankWidget from './static_components/race/RankWidget.component.js' import DediRanking from './static_components/race/DediRanking.component.js' @@ -43,11 +43,10 @@ import BannerWidget from './static_components/result/BannerWidget.component.js' import UltiRanking from './static_components/race/UltiRanking.component.js' /* ------------------- -DYNAMIC COMPONENTS ------------------- -*/ - + ------------------ + DYNAMIC COMPONENTS + ------------------ + */ import CommandList from './dynamic_components/Commandlist.component.js' import LocalCps from './dynamic_components/checkpoint_windows/LocalCps.component.js' import LiveSectors from './dynamic_components/checkpoint_windows/LiveSectors.component.js' diff --git a/plugins/ui/UI.ts b/plugins/ui/UI.ts index e9693887e..94e44f909 100644 --- a/plugins/ui/UI.ts +++ b/plugins/ui/UI.ts @@ -5,7 +5,7 @@ */ import icons from './config/Icons.js' -import { initialize as initalizeKeyListeners } from './utils/KeyListener.js' +import { addKeyListener, initialize as initalizeKeyListeners, removeKeyListener } from './utils/KeyListener.js' import modConfig from './config/Mod.js' import TestWindow from './test_widgets/TestWindow.js' import StaticComponent from './utils/StaticComponent.js' @@ -20,7 +20,6 @@ import componentIds from './config/ComponentIds.js' import { centeredText, horizontallyCenteredText, leftAlignedText, rightAlignedText } from './utils/TextUtils.js' import { getCpTypes } from './utils/GetCpTypes.js' import { closeButton } from './utils/CloseButton.js' -import { addKeyListener, removeKeyListener } from './utils/KeyListener.js' import { List } from './utils/List.js' import StaticHeader, { type StaticHeaderOptions } from './utils/StaticHeader.js' import staticButton from './utils/StaticButton.js' @@ -32,37 +31,54 @@ import { fullScreenListener } from './utils/FullScreenListener.js' import flagIcons from './config/FlagIcons.js' import utilIds from './config/UtilIds.js' import Buttons from './Buttons.js' +// Has to be like that due to circular dependencies +import './Imports.js' let customUi: CustomUi -const currentModIndex = { 'Stadium': 0, 'Desert': 0, 'Snow': 0, 'Bay': 0, 'Coast': 0, 'Island': 0, 'Rally': 0 } +const currentModIndex = { + 'Stadium': 0, + 'Desert': 0, + 'Snow': 0, + 'Bay': 0, + 'Coast': 0, + 'Island': 0, + 'Rally': 0, + ...tm.config.controller.customEnvironments.reduce((acc, env) => ({ ...acc, [env]: 0 }), {}) +} const loadMod = (): void => { - const mods: { struct: { Env: { string: string }, Url: { string: string } } }[] = [] + const mods: { + struct: { + Env: { + string: string + }, + Url: { + string: string + } + } + }[] = [] for (const obj of modConfig) { if (obj.modLinks.length === 0) { continue } mods.push({ struct: { Env: { string: (tm.utils.environmentToNadeoEnvironment(obj.environment as tm.Environment) as string) }, Url: { - string: obj.randomOrder - ? tm.utils.fixProtocol(obj.modLinks[~~(Math.random() * obj.modLinks.length)]) - : tm.utils.fixProtocol(obj.modLinks[currentModIndex[obj.environment as keyof typeof currentModIndex] % obj.modLinks.length]) + string: obj.randomOrder ? tm.utils.fixProtocol(obj.modLinks[~~(Math.random() * obj.modLinks.length)]) : + tm.utils.fixProtocol( + obj.modLinks[currentModIndex[obj.environment as keyof typeof currentModIndex] % obj.modLinks.length]) } } }) } - tm.client.callNoRes('SetForcedMods', - [{ - boolean: true - }, - { - array: mods - }]) + tm.client.callNoRes('SetForcedMods', [{ + boolean: true + }, { + array: mods + }]) } -const iconArr = Object.values(icons.preloadedIcons).map(a => - ``) +const iconArr = Object.values(icons.preloadedIcons).map(a => ``) const preloadIcons = (login?: string): void => { tm.sendManialink(` @@ -81,55 +97,50 @@ let staticUpdateNeeded = false tm.client.addProxy(['SetGameMode'], () => { staticUpdateNeeded = true }) -const events: tm.Listener[] = [ - { - event: 'Startup', - callback: async (): Promise => { - await tm.client.call('SendHideManialinkPage') - preloadIcons() - loadMod() - initalizeKeyListeners() - customUi = new CustomUi() - customUi.display() - StaticComponent.refreshStaticLayouts() - for (const c of Object.values(staticComponents)) { - c.updateIsDisplayed() - c.updatePosition() - const ret = c.display() - StaticComponent.sendMultipleManialinks(ret) - } - StaticComponent.refreshStaticLayouts() - new Buttons() - new TestWindow() - for (const e of loadListeners) { e() } - StaticComponent.onComponentCreated(() => { - StaticComponent.refreshStaticLayouts() - }) - } - }, - { - event: 'BeginMap', - callback: async (): Promise => { - if (staticUpdateNeeded) { - staticUpdateNeeded = false - for (const e of staticComponents) { e.updatePosition() } - } - } - }, - { - event: 'EndMap', - callback: async (): Promise => { - currentModIndex[tm.maps.current.environment]++ - loadMod() +const events: tm.Listener[] = [{ + event: 'Startup', + callback: async (): Promise => { + await tm.client.call('SendHideManialinkPage') + preloadIcons() + loadMod() + initalizeKeyListeners() + customUi = new CustomUi() + customUi.display() + StaticComponent.refreshStaticLayouts() + for (const c of Object.values(staticComponents)) { + c.updateIsDisplayed() + c.updatePosition() + const ret = c.display() + StaticComponent.sendMultipleManialinks(ret) } - }, - { - event: 'PlayerJoin', - callback: (info: tm.JoinInfo): void => { - preloadIcons(info.login) + StaticComponent.refreshStaticLayouts() + new Buttons() + new TestWindow() + for (const e of loadListeners) { e() } + StaticComponent.onComponentCreated(() => { + StaticComponent.refreshStaticLayouts() + }) + } +}, { + event: 'BeginMap', + callback: async (): Promise => { + if (staticUpdateNeeded) { + staticUpdateNeeded = false + for (const e of staticComponents) { e.updatePosition() } } } -] +}, { + event: 'EndMap', + callback: async (): Promise => { + currentModIndex[tm.maps.current.environment as keyof typeof currentModIndex]++ + loadMod() + } +}, { + event: 'PlayerJoin', + callback: (info: tm.JoinInfo): void => { + preloadIcons(info.login) + } +}] for (const event of events) { tm.addListener(event.event, event.callback, true) } @@ -140,16 +151,16 @@ for (const event of events) { tm.addListener(event.event, event.callback, true) */ const components = { - /** - * Finds a static component based on given class name + /** + * Finds a static component based on given class name * @param className Component class name */ findStatic(className: string): StaticComponent | undefined { return staticComponents.find(a => a.constructor.name === className) }, - /** - * Finds a dynamic component based on given class name + /** + * Finds a dynamic component based on given class name * @param className Component class name */ findDynamic(className: string): DynamicComponent | undefined { @@ -180,21 +191,14 @@ const components = { } /** -* Register a callback function to execute on UI load -* @param callback Function to execute on event -*/ + * Register a callback function to execute on UI load + * @param callback Function to execute on event + */ const addLoadListener = (callback: Function): void => { loadListeners.push(callback) } export { - Paginator, Grid, Navbar, VoteWindow, RecordList, type GridCellFunction, type GridCellObject, List, StaticHeader, - PopupWindow, StaticComponent, DynamicComponent, type StaticHeaderOptions, type RLImage, type RLRecord, - components, componentIds, icons, raceConfig, resultConfig, flagIcons, utilIds, - addKeyListener, removeKeyListener, rightAlignedText, getCpTypes, closeButton, horizontallyCenteredText, - staticButton, fullScreenListener, centeredText, addLoadListener, - leftAlignedText, addManialinkListener, removeManialinkListener, + Paginator, Grid, Navbar, VoteWindow, RecordList, type GridCellFunction, type GridCellObject, List, StaticHeader, PopupWindow, StaticComponent, DynamicComponent, type StaticHeaderOptions, type RLImage, type RLRecord, components, componentIds, icons, raceConfig, resultConfig, flagIcons, utilIds, addKeyListener, removeKeyListener, rightAlignedText, getCpTypes, closeButton, horizontallyCenteredText, staticButton, fullScreenListener, centeredText, addLoadListener, leftAlignedText, addManialinkListener, removeManialinkListener } -// Has to be like that due to circular dependencies -import './Imports.js' diff --git a/plugins/ui/config/Buttons.js b/plugins/ui/config/Buttons.js index e432a91e3..f20445ccb 100644 --- a/plugins/ui/config/Buttons.js +++ b/plugins/ui/config/Buttons.js @@ -1,13 +1,14 @@ import icons from './Icons.js' import raceUi from './RaceUi.js' import resultUi from './ResultUi.js' + const p = tm.utils.palette /** * In this config you can easily add static buttons to your UI layout. - * + * * REQUIRED PROPS: - * + * * - displayed (bool) - Whether to display the button * - icon (str) - Icon URL * - posX (num) - X position of the button (0 is the middle of the screen) @@ -17,8 +18,8 @@ const p = tm.utils.palette * - zIndex (num) - Z "position" of the button (determines stacking order of elements) * - text1 (str) - Upper text * - text2 (str) - Lower text (leave as empty string if you need only one text line) - * OPTIONAL PROPS: - * + * OPTIONAL PROPS: + * * - iconWidth (num) - Icon width * - iconHeight (num) - Icon height * - topPadding (num) - Gap between the top border of the background and the top border of the icon @@ -36,55 +37,47 @@ export default { /** * RaceUi icons */ - race: [ - { - displayed: false, - icon: icons.placeholder, - posX: 44.9, - posY: 24.2, - width: 4, - height: 6.45, - zIndex: 1, - text1: `$${p.purple}DISCORD`, - text2: 'SERVER', - iconWidth: 3, - iconHeight: 3, - topPadding: 0.3, - equalTexts: false, - // actionId: , - background: raceUi.background, - // link: '', - // text1Scale: , - // text2Scale: , - text1PositionOffset: 1.75, - text2PositionOffset: 1.75, - } - ], + race: [{ + displayed: false, + icon: icons.placeholder, + posX: 44.9, + posY: 24.2, + width: 4, + height: 6.45, + zIndex: 1, + text1: `$${p.purple}DISCORD`, + text2: 'SERVER', + iconWidth: 3, + iconHeight: 3, + topPadding: 0.3, + equalTexts: false, // actionId: , + background: raceUi.background, // link: '', + // text1Scale: , + // text2Scale: , + text1PositionOffset: 1.75, + text2PositionOffset: 1.75 + }], /** * ResultUi icons */ - result: [ - { - displayed: false, - icon: icons.placeholder, - posX: 44.9, - posY: 22.85, - width: 4, - height: 6.1, - zIndex: 1, - text1: `$${p.purple}DISCORD`, - text2: 'SERVER', - iconWidth: 3, - iconHeight: 3, - topPadding: 0.3, - equalTexts: false, - // actionId: , - background: resultUi.background, - link: '', - // text1Scale: , - // text2Scale: , - text1PositionOffset: 1.75, - text2PositionOffset: 1.75, - } - ] + result: [{ + displayed: false, + icon: icons.placeholder, + posX: 44.9, + posY: 22.85, + width: 4, + height: 6.1, + zIndex: 1, + text1: `$${p.purple}DISCORD`, + text2: 'SERVER', + iconWidth: 3, + iconHeight: 3, + topPadding: 0.3, + equalTexts: false, // actionId: , + background: resultUi.background, + link: '', // text1Scale: , + // text2Scale: , + text1PositionOffset: 1.75, + text2PositionOffset: 1.75 + }] } diff --git a/plugins/ui/config/ComponentIds.js b/plugins/ui/config/ComponentIds.js index f0b07e067..877ab0397 100644 --- a/plugins/ui/config/ComponentIds.js +++ b/plugins/ui/config/ComponentIds.js @@ -82,7 +82,7 @@ export default { localSectors: 520000, ultiRanking: 530000, ultiRecords: 540000, - liveCpsRanking: 550000, - // needs 3.1 mil ids + favouriteButton: 560000, + liveCpsRanking: 550000, // needs 3.1 mil ids mapList: 10000000 } diff --git a/plugins/ui/config/FlagIcons.js b/plugins/ui/config/FlagIcons.js index f728ae5e0..47aae909f 100644 --- a/plugins/ui/config/FlagIcons.js +++ b/plugins/ui/config/FlagIcons.js @@ -1,94 +1,94 @@ export default { - "ALG": "https://trakman.ptrk.eu/icons/ALG.png", - "ANG": "https://trakman.ptrk.eu/icons/ANG.png", - "ARG": "https://trakman.ptrk.eu/icons/ARG.png", - "AUS": "https://trakman.ptrk.eu/icons/AUS.png", - "AUT": "https://trakman.ptrk.eu/icons/AUT.png", - "BAN": "https://trakman.ptrk.eu/icons/BAN.png", - "BEL": "https://trakman.ptrk.eu/icons/BEL.png", - "BIH": "https://trakman.ptrk.eu/icons/BIH.png", - "BLR": "https://trakman.ptrk.eu/icons/BLR.png", - "BOL": "https://trakman.ptrk.eu/icons/BOL.png", - "BRA": "https://trakman.ptrk.eu/icons/BRA.png", - "BRN": "https://trakman.ptrk.eu/icons/BRN.png", - "BUL": "https://trakman.ptrk.eu/icons/BUL.png", - "CAN": "https://trakman.ptrk.eu/icons/CAN.png", - "CGO": "https://trakman.ptrk.eu/icons/CGO.png", - "CHI": "https://trakman.ptrk.eu/icons/CHI.png", - "CHN": "https://trakman.ptrk.eu/icons/CHN.png", - "CIV": "https://trakman.ptrk.eu/icons/CIV.png", - "CMR": "https://trakman.ptrk.eu/icons/CMR.png", - "COL": "https://trakman.ptrk.eu/icons/COL.png", - "CRO": "https://trakman.ptrk.eu/icons/CRO.png", - "CYP": "https://trakman.ptrk.eu/icons/CYP.png", - "CZE": "https://trakman.ptrk.eu/icons/CZE.png", - "DEN": "https://trakman.ptrk.eu/icons/DEN.png", - "ECU": "https://trakman.ptrk.eu/icons/ECU.png", - "EGY": "https://trakman.ptrk.eu/icons/EGY.png", - "ESP": "https://trakman.ptrk.eu/icons/ESP.png", - "EST": "https://trakman.ptrk.eu/icons/EST.png", - "FIN": "https://trakman.ptrk.eu/icons/FIN.png", - "FRA": "https://trakman.ptrk.eu/icons/FRA.png", - "GBR": "https://trakman.ptrk.eu/icons/GBR.png", - "GER": "https://trakman.ptrk.eu/icons/GER.png", - "GRE": "https://trakman.ptrk.eu/icons/GRE.png", - "GUA": "https://trakman.ptrk.eu/icons/GUA.png", - "HUN": "https://trakman.ptrk.eu/icons/HUN.png", - "INA": "https://trakman.ptrk.eu/icons/INA.png", - "IND": "https://trakman.ptrk.eu/icons/IND.png", - "IRI": "https://trakman.ptrk.eu/icons/IRI.png", - "IRL": "https://trakman.ptrk.eu/icons/IRL.png", - "IRQ": "https://trakman.ptrk.eu/icons/IRQ.png", - "ISR": "https://trakman.ptrk.eu/icons/ISR.png", - "ITA": "https://trakman.ptrk.eu/icons/ITA.png", - "JPN": "https://trakman.ptrk.eu/icons/JPN.png", - "KAZ": "https://trakman.ptrk.eu/icons/KAZ.png", - "KOR": "https://trakman.ptrk.eu/icons/KOR.png", - "KSA": "https://trakman.ptrk.eu/icons/KSA.png", - "KUW": "https://trakman.ptrk.eu/icons/KUW.png", - "LAO": "https://trakman.ptrk.eu/icons/LAO.png", - "LAT": "https://trakman.ptrk.eu/icons/LAT.png", - "LBA": "https://trakman.ptrk.eu/icons/LBA.png", - "LIB": "https://trakman.ptrk.eu/icons/LIB.png", - "LTU": "https://trakman.ptrk.eu/icons/LTU.png", - "LUX": "https://trakman.ptrk.eu/icons/LUX.png", - "MAR": "https://trakman.ptrk.eu/icons/MAR.png", - "MAS": "https://trakman.ptrk.eu/icons/MAS.png", - "MEX": "https://trakman.ptrk.eu/icons/MEX.png", - "MGL": "https://trakman.ptrk.eu/icons/MGL.png", - "MKD": "https://trakman.ptrk.eu/icons/MKD.png", - "NED": "https://trakman.ptrk.eu/icons/NED.png", - "NGR": "https://trakman.ptrk.eu/icons/NGR.png", - "NOR": "https://trakman.ptrk.eu/icons/NOR.png", - "NZL": "https://trakman.ptrk.eu/icons/NZL.png", - "OMA": "https://trakman.ptrk.eu/icons/OMA.png", - "OTH": "https://trakman.ptrk.eu/icons/OTH.png", - "PAK": "https://trakman.ptrk.eu/icons/PAK.png", - "PAN": "https://trakman.ptrk.eu/icons/PAN.png", - "PER": "https://trakman.ptrk.eu/icons/PER.png", - "PHI": "https://trakman.ptrk.eu/icons/PHI.png", - "POL": "https://trakman.ptrk.eu/icons/POL.png", - "POR": "https://trakman.ptrk.eu/icons/POR.png", - "QAT": "https://trakman.ptrk.eu/icons/QAT.png", - "ROU": "https://trakman.ptrk.eu/icons/ROU.png", - "RSA": "https://trakman.ptrk.eu/icons/RSA.png", - "RUS": "https://trakman.ptrk.eu/icons/RUS.png", - "SEN": "https://trakman.ptrk.eu/icons/SEN.png", - "SIN": "https://trakman.ptrk.eu/icons/SIN.png", - "SLO": "https://trakman.ptrk.eu/icons/SLO.png", - "SRB": "https://trakman.ptrk.eu/icons/SRB.png", - "SUI": "https://trakman.ptrk.eu/icons/SUI.png", - "SVK": "https://trakman.ptrk.eu/icons/SVK.png", - "SWE": "https://trakman.ptrk.eu/icons/SWE.png", - "SYR": "https://trakman.ptrk.eu/icons/SYR.png", - "THA": "https://trakman.ptrk.eu/icons/THA.png", - "TUN": "https://trakman.ptrk.eu/icons/TUN.png", - "TUR": "https://trakman.ptrk.eu/icons/TUR.png", - "UAE": "https://trakman.ptrk.eu/icons/UAE.png", - "UKR": "https://trakman.ptrk.eu/icons/UKR.png", - "URU": "https://trakman.ptrk.eu/icons/URU.png", - "USA": "https://trakman.ptrk.eu/icons/USA.png", - "UZB": "https://trakman.ptrk.eu/icons/UZB.png", - "VEN": "https://trakman.ptrk.eu/icons/VEN.png", - "VIE": "https://trakman.ptrk.eu/icons/VIE.png" + 'ALG': 'https://trakman.ptrk.eu/icons/ALG.png', + 'ANG': 'https://trakman.ptrk.eu/icons/ANG.png', + 'ARG': 'https://trakman.ptrk.eu/icons/ARG.png', + 'AUS': 'https://trakman.ptrk.eu/icons/AUS.png', + 'AUT': 'https://trakman.ptrk.eu/icons/AUT.png', + 'BAN': 'https://trakman.ptrk.eu/icons/BAN.png', + 'BEL': 'https://trakman.ptrk.eu/icons/BEL.png', + 'BIH': 'https://trakman.ptrk.eu/icons/BIH.png', + 'BLR': 'https://trakman.ptrk.eu/icons/BLR.png', + 'BOL': 'https://trakman.ptrk.eu/icons/BOL.png', + 'BRA': 'https://trakman.ptrk.eu/icons/BRA.png', + 'BRN': 'https://trakman.ptrk.eu/icons/BRN.png', + 'BUL': 'https://trakman.ptrk.eu/icons/BUL.png', + 'CAN': 'https://trakman.ptrk.eu/icons/CAN.png', + 'CGO': 'https://trakman.ptrk.eu/icons/CGO.png', + 'CHI': 'https://trakman.ptrk.eu/icons/CHI.png', + 'CHN': 'https://trakman.ptrk.eu/icons/CHN.png', + 'CIV': 'https://trakman.ptrk.eu/icons/CIV.png', + 'CMR': 'https://trakman.ptrk.eu/icons/CMR.png', + 'COL': 'https://trakman.ptrk.eu/icons/COL.png', + 'CRO': 'https://trakman.ptrk.eu/icons/CRO.png', + 'CYP': 'https://trakman.ptrk.eu/icons/CYP.png', + 'CZE': 'https://trakman.ptrk.eu/icons/CZE.png', + 'DEN': 'https://trakman.ptrk.eu/icons/DEN.png', + 'ECU': 'https://trakman.ptrk.eu/icons/ECU.png', + 'EGY': 'https://trakman.ptrk.eu/icons/EGY.png', + 'ESP': 'https://trakman.ptrk.eu/icons/ESP.png', + 'EST': 'https://trakman.ptrk.eu/icons/EST.png', + 'FIN': 'https://trakman.ptrk.eu/icons/FIN.png', + 'FRA': 'https://trakman.ptrk.eu/icons/FRA.png', + 'GBR': 'https://trakman.ptrk.eu/icons/GBR.png', + 'GER': 'https://trakman.ptrk.eu/icons/GER.png', + 'GRE': 'https://trakman.ptrk.eu/icons/GRE.png', + 'GUA': 'https://trakman.ptrk.eu/icons/GUA.png', + 'HUN': 'https://trakman.ptrk.eu/icons/HUN.png', + 'INA': 'https://trakman.ptrk.eu/icons/INA.png', + 'IND': 'https://trakman.ptrk.eu/icons/IND.png', + 'IRI': 'https://trakman.ptrk.eu/icons/IRI.png', + 'IRL': 'https://trakman.ptrk.eu/icons/IRL.png', + 'IRQ': 'https://trakman.ptrk.eu/icons/IRQ.png', + 'ISR': 'https://trakman.ptrk.eu/icons/ISR.png', + 'ITA': 'https://trakman.ptrk.eu/icons/ITA.png', + 'JPN': 'https://trakman.ptrk.eu/icons/JPN.png', + 'KAZ': 'https://trakman.ptrk.eu/icons/KAZ.png', + 'KOR': 'https://trakman.ptrk.eu/icons/KOR.png', + 'KSA': 'https://trakman.ptrk.eu/icons/KSA.png', + 'KUW': 'https://trakman.ptrk.eu/icons/KUW.png', + 'LAO': 'https://trakman.ptrk.eu/icons/LAO.png', + 'LAT': 'https://trakman.ptrk.eu/icons/LAT.png', + 'LBA': 'https://trakman.ptrk.eu/icons/LBA.png', + 'LIB': 'https://trakman.ptrk.eu/icons/LIB.png', + 'LTU': 'https://trakman.ptrk.eu/icons/LTU.png', + 'LUX': 'https://trakman.ptrk.eu/icons/LUX.png', + 'MAR': 'https://trakman.ptrk.eu/icons/MAR.png', + 'MAS': 'https://trakman.ptrk.eu/icons/MAS.png', + 'MEX': 'https://trakman.ptrk.eu/icons/MEX.png', + 'MGL': 'https://trakman.ptrk.eu/icons/MGL.png', + 'MKD': 'https://trakman.ptrk.eu/icons/MKD.png', + 'NED': 'https://trakman.ptrk.eu/icons/NED.png', + 'NGR': 'https://trakman.ptrk.eu/icons/NGR.png', + 'NOR': 'https://trakman.ptrk.eu/icons/NOR.png', + 'NZL': 'https://trakman.ptrk.eu/icons/NZL.png', + 'OMA': 'https://trakman.ptrk.eu/icons/OMA.png', + 'OTH': 'https://trakman.ptrk.eu/icons/OTH.png', + 'PAK': 'https://trakman.ptrk.eu/icons/PAK.png', + 'PAN': 'https://trakman.ptrk.eu/icons/PAN.png', + 'PER': 'https://trakman.ptrk.eu/icons/PER.png', + 'PHI': 'https://trakman.ptrk.eu/icons/PHI.png', + 'POL': 'https://trakman.ptrk.eu/icons/POL.png', + 'POR': 'https://trakman.ptrk.eu/icons/POR.png', + 'QAT': 'https://trakman.ptrk.eu/icons/QAT.png', + 'ROU': 'https://trakman.ptrk.eu/icons/ROU.png', + 'RSA': 'https://trakman.ptrk.eu/icons/RSA.png', + 'RUS': 'https://trakman.ptrk.eu/icons/RUS.png', + 'SEN': 'https://trakman.ptrk.eu/icons/SEN.png', + 'SIN': 'https://trakman.ptrk.eu/icons/SIN.png', + 'SLO': 'https://trakman.ptrk.eu/icons/SLO.png', + 'SRB': 'https://trakman.ptrk.eu/icons/SRB.png', + 'SUI': 'https://trakman.ptrk.eu/icons/SUI.png', + 'SVK': 'https://trakman.ptrk.eu/icons/SVK.png', + 'SWE': 'https://trakman.ptrk.eu/icons/SWE.png', + 'SYR': 'https://trakman.ptrk.eu/icons/SYR.png', + 'THA': 'https://trakman.ptrk.eu/icons/THA.png', + 'TUN': 'https://trakman.ptrk.eu/icons/TUN.png', + 'TUR': 'https://trakman.ptrk.eu/icons/TUR.png', + 'UAE': 'https://trakman.ptrk.eu/icons/UAE.png', + 'UKR': 'https://trakman.ptrk.eu/icons/UKR.png', + 'URU': 'https://trakman.ptrk.eu/icons/URU.png', + 'USA': 'https://trakman.ptrk.eu/icons/USA.png', + 'UZB': 'https://trakman.ptrk.eu/icons/UZB.png', + 'VEN': 'https://trakman.ptrk.eu/icons/VEN.png', + 'VIE': 'https://trakman.ptrk.eu/icons/VIE.png' } diff --git a/plugins/ui/config/Icons.js b/plugins/ui/config/Icons.js index 45f70edd0..d3d013f14 100644 --- a/plugins/ui/config/Icons.js +++ b/plugins/ui/config/Icons.js @@ -1,192 +1,179 @@ export default { - clock: "https://trakman.ptrk.eu/icons/Clock.png", - close: "https://trakman.ptrk.eu/icons/Close.png", - closeHover: "https://trakman.ptrk.eu/icons/CloseHover.png", - code: "https://trakman.ptrk.eu/icons/Code.png", - codeBranch: "https://trakman.ptrk.eu/icons/CodeBranch.png", - download: "https://trakman.ptrk.eu/icons/Download.png", - downloadHover: "https://trakman.ptrk.eu/icons/DownloadHover.png", - pageDoubleLeft: "https://trakman.ptrk.eu/icons/PageDoubleLeft.png", - pageDoubleLeftHover: "https://trakman.ptrk.eu/icons/PageDoubleLeftHover.png", - pageDoubleRight: "https://trakman.ptrk.eu/icons/PageDoubleRight.png", - pageDoubleRightHover: "https://trakman.ptrk.eu/icons/PageDoubleRightHover.png", - pageFirst: "https://trakman.ptrk.eu/icons/PageFirst.png", - pageFirstHover: "https://trakman.ptrk.eu/icons/PageFirstHover.png", - pageLast: "https://trakman.ptrk.eu/icons/PageLast.png", - pageLastHover: "https://trakman.ptrk.eu/icons/PageLastHover.png", - pageLeft: "https://trakman.ptrk.eu/icons/PageLeft.png", - pageLeftHover: "https://trakman.ptrk.eu/icons/PageLeftHover.png", - pageRight: "https://trakman.ptrk.eu/icons/PageRight.png", - pageRightHover: "https://trakman.ptrk.eu/icons/PageRightHover.png", - people: "https://trakman.ptrk.eu/icons/People.png", - peopleGaming: "https://trakman.ptrk.eu/icons/PeopleGaming.png", - tag: "https://trakman.ptrk.eu/icons/Tag.png", - tagGreen: "https://trakman.ptrk.eu/icons/TagGreen.png", - tagPinkCyan: "https://trakman.ptrk.eu/icons/TagPinkCyan.png", - tagRed: "https://trakman.ptrk.eu/icons/TagRed.png", - tagYellow: "https://trakman.ptrk.eu/icons/TagYellow.png", - numberedList: "https://trakman.ptrk.eu/icons/NumberedList.png", - stats: "https://trakman.ptrk.eu/icons/Stats.png", - clockList: "https://trakman.ptrk.eu/icons/ClockList.png", - voteReplay: "https://trakman.ptrk.eu/icons/VoteReplay.png", - voteSkip: "https://trakman.ptrk.eu/icons/VoteSkip.png", - payReplay: "https://trakman.ptrk.eu/icons/PayReplay.png", - paySkip: "https://trakman.ptrk.eu/icons/PaySkip.png", - karmaStats: "https://trakman.ptrk.eu/icons/KarmaStats.png", - karmaPulse: "https://trakman.ptrk.eu/icons/KarmaPulse.png", - peopleKarma: "https://trakman.ptrk.eu/icons/PeopleKarma.png", - karmaGlobal: "https://trakman.ptrk.eu/icons/KarmaGlobal.png", - karmaLocal: "https://trakman.ptrk.eu/icons/KarmaLocal.png", - banlist: "https://trakman.ptrk.eu/icons/BanList.png", - blacklist: "https://trakman.ptrk.eu/icons/BlacklistList.png", - mutelist: "https://trakman.ptrk.eu/icons/MuteList.png", - guestlist: "https://trakman.ptrk.eu/icons/GuestlistList.png", - infoCircle: "https://trakman.ptrk.eu/icons/InfoCircle.png", - tools: "https://trakman.ptrk.eu/icons/Tools.png", - ban: "https://trakman.ptrk.eu/icons/BanHammer.png", - banHover: "https://trakman.ptrk.eu/icons/BanHover.png", - addToBlacklist: "https://trakman.ptrk.eu/icons/BanHammer.png", - addToBlacklistHover: "https://trakman.ptrk.eu/icons/BlacklistHover.png", - unban: "https://trakman.ptrk.eu/icons/Unban.png", - unbanHover: "https://trakman.ptrk.eu/icons/UnbanHover.png", - unblacklist: "https://trakman.ptrk.eu/icons/Unban.png", - unblacklistHover: "https://trakman.ptrk.eu/icons/UnblacklistHover.png", - addGuest: "https://trakman.ptrk.eu/icons/AddGuest.png", - addGuestHover: "https://trakman.ptrk.eu/icons/AddGuestHover.png", - removeGuest: "https://trakman.ptrk.eu/icons/RemoveGuest.png", - removeGuestHover: "https://trakman.ptrk.eu/icons/RemoveGuestHover.png", - mute: "https://trakman.ptrk.eu/icons/Mute.png", - muteHover: "https://trakman.ptrk.eu/icons/MuteHover.png", - unmute: "https://trakman.ptrk.eu/icons/Unmute.png", - unmuteHover: "https://trakman.ptrk.eu/icons/UnmuteHover.png", - forceSpectator: "https://trakman.ptrk.eu/icons/ForceSpectator.png", - forceGaming: "https://trakman.ptrk.eu/icons/ForceGaming.png", - chartDedi: "https://trakman.ptrk.eu/icons/ChartDedi.png", - chartLive: "https://trakman.ptrk.eu/icons/ChartLive.png", - chartLocal: "https://trakman.ptrk.eu/icons/ChartLocal.png", - bestFinishes: "https://trakman.ptrk.eu/icons/BestFinishes.png", - bestClock: "https://trakman.ptrk.eu/icons/BestClock.png", - ongoingMap: "https://trakman.ptrk.eu/icons/OngoingMap.png", - personBuilder: "https://trakman.ptrk.eu/icons/PersonBuilder.png", - person: "https://trakman.ptrk.eu/icons/Person.png", - personWorse: "https://trakman.ptrk.eu/icons/PersonWorse.png", - personBetter: "https://trakman.ptrk.eu/icons/PersonBetter.png", - clockAuthor: "https://trakman.ptrk.eu/icons/ClockAuthor.png", - maniaExchange: "https://trakman.ptrk.eu/icons/ManiaExchange.png", - cash: "https://trakman.ptrk.eu/icons/Cash.png", - info: "https://trakman.ptrk.eu/icons/Info.png", - gears: "https://trakman.ptrk.eu/icons/Gears.png", - playerList: "https://trakman.ptrk.eu/icons/PlayerList.png", - mapList: "https://trakman.ptrk.eu/icons/MapList.png", - infoList: "https://trakman.ptrk.eu/icons/InfoList.png", - placeholder: "https://trakman.ptrk.eu/icons/Placeholder.png", - checkmark: "https://trakman.ptrk.eu/icons/Checkmark.png", - cross: "https://trakman.ptrk.eu/icons/Cross.png", - addMap: "https://trakman.ptrk.eu/icons/AddMap.png", - removeMap: "https://trakman.ptrk.eu/icons/RemoveMap.png", - thumbsUpF5: "https://trakman.ptrk.eu/icons/ThumbsUpF5.png", - thumbsDownF6: "https://trakman.ptrk.eu/icons/ThumbsDownF6.png", - F5: "https://trakman.ptrk.eu/icons/F5.png", - F6: "https://trakman.ptrk.eu/icons/F6.png", - blank: "http://maniacdn.net/undef.de/uaseco/blank.png", - trackmania: "https://trakman.ptrk.eu/icons/Trackmania.png", - calendarPlus: "https://trakman.ptrk.eu/icons/CalendarPlus.png", - carExplode: "https://trakman.ptrk.eu/icons/CarExplode.png", - moodNight: "https://trakman.ptrk.eu/icons/MoodNight.png", - moodSunrise: "https://trakman.ptrk.eu/icons/MoodSunrise.png", - moodSunset: "https://trakman.ptrk.eu/icons/MoodSunset.png", - moodDay: "https://trakman.ptrk.eu/icons/MoodDay.png", - mapStyle: "https://trakman.ptrk.eu/icons/MapStyle.png", - mapRoutes: "https://trakman.ptrk.eu/icons/MapRoutes.png", - difficultyEasy: "https://trakman.ptrk.eu/icons/DifficultyOne.png", - difficultyMedium: "https://trakman.ptrk.eu/icons/DifficultyTwo.png", - difficultyHard: "https://trakman.ptrk.eu/icons/DifficultyThree.png", - difficultyExtreme: "https://trakman.ptrk.eu/icons/DifficultyFour.png", - puzzle: "https://trakman.ptrk.eu/icons/Puzzle.png", - leaderboardRating: "https://trakman.ptrk.eu/icons/LeaderboardRating.png", - leaderboardRatingClassic: "https://trakman.ptrk.eu/icons/LeaderboardRatingClassic.png", - leaderboardRatingNadeo: "https://trakman.ptrk.eu/icons/LeaderboardRatingNadeo.png", - trophy: "https://trakman.ptrk.eu/icons/Trophy.png", - trophyClassic: "https://trakman.ptrk.eu/icons/TrophyClassic.png", - trophyNadeo: "https://trakman.ptrk.eu/icons/TrophyNadeo.png", - kick: "https://trakman.ptrk.eu/icons/Kick.png", - kickHover: "https://trakman.ptrk.eu/icons/KickHover.png", - forceSpectatorHover: "https://trakman.ptrk.eu/icons/ForceSpectatorHover.png", - forceGamingHover: "https://trakman.ptrk.eu/icons/ForceGamingHover.png", - playerListHover: "https://trakman.ptrk.eu/icons/PlayerListHover.png", - restartHover: "https://trakman.ptrk.eu/icons/RestartHover.png", - restart: "https://trakman.ptrk.eu/icons/Restart.png", - requeueHover: "https://trakman.ptrk.eu/icons/RequeueHover.png", - requeue: "https://trakman.ptrk.eu/icons/Requeue.png", - mapListHover: "https://trakman.ptrk.eu/icons/MapListHover.png", - tagKacky: "https://trakman.ptrk.eu/icons/TagKacky.png", + clock: 'https://trakman.ptrk.eu/icons/Clock.png', + close: 'https://trakman.ptrk.eu/icons/Close.png', + closeHover: 'https://trakman.ptrk.eu/icons/CloseHover.png', + code: 'https://trakman.ptrk.eu/icons/Code.png', + codeBranch: 'https://trakman.ptrk.eu/icons/CodeBranch.png', + download: 'https://trakman.ptrk.eu/icons/Download.png', + downloadHover: 'https://trakman.ptrk.eu/icons/DownloadHover.png', + pageDoubleLeft: 'https://trakman.ptrk.eu/icons/PageDoubleLeft.png', + pageDoubleLeftHover: 'https://trakman.ptrk.eu/icons/PageDoubleLeftHover.png', + pageDoubleRight: 'https://trakman.ptrk.eu/icons/PageDoubleRight.png', + pageDoubleRightHover: 'https://trakman.ptrk.eu/icons/PageDoubleRightHover.png', + pageFirst: 'https://trakman.ptrk.eu/icons/PageFirst.png', + pageFirstHover: 'https://trakman.ptrk.eu/icons/PageFirstHover.png', + pageLast: 'https://trakman.ptrk.eu/icons/PageLast.png', + pageLastHover: 'https://trakman.ptrk.eu/icons/PageLastHover.png', + pageLeft: 'https://trakman.ptrk.eu/icons/PageLeft.png', + pageLeftHover: 'https://trakman.ptrk.eu/icons/PageLeftHover.png', + pageRight: 'https://trakman.ptrk.eu/icons/PageRight.png', + pageRightHover: 'https://trakman.ptrk.eu/icons/PageRightHover.png', + people: 'https://trakman.ptrk.eu/icons/People.png', + peopleGaming: 'https://trakman.ptrk.eu/icons/PeopleGaming.png', + tag: 'https://trakman.ptrk.eu/icons/Tag.png', + tagGreen: 'https://trakman.ptrk.eu/icons/TagGreen.png', + tagPinkCyan: 'https://trakman.ptrk.eu/icons/TagPinkCyan.png', + tagRed: 'https://trakman.ptrk.eu/icons/TagRed.png', + tagYellow: 'https://trakman.ptrk.eu/icons/TagYellow.png', + numberedList: 'https://trakman.ptrk.eu/icons/NumberedList.png', + stats: 'https://trakman.ptrk.eu/icons/Stats.png', + clockList: 'https://trakman.ptrk.eu/icons/ClockList.png', + voteReplay: 'https://trakman.ptrk.eu/icons/VoteReplay.png', + voteSkip: 'https://trakman.ptrk.eu/icons/VoteSkip.png', + payReplay: 'https://trakman.ptrk.eu/icons/PayReplay.png', + paySkip: 'https://trakman.ptrk.eu/icons/PaySkip.png', + karmaStats: 'https://trakman.ptrk.eu/icons/KarmaStats.png', + karmaPulse: 'https://trakman.ptrk.eu/icons/KarmaPulse.png', + peopleKarma: 'https://trakman.ptrk.eu/icons/PeopleKarma.png', + karmaGlobal: 'https://trakman.ptrk.eu/icons/KarmaGlobal.png', + karmaLocal: 'https://trakman.ptrk.eu/icons/KarmaLocal.png', + banlist: 'https://trakman.ptrk.eu/icons/BanList.png', + blacklist: 'https://trakman.ptrk.eu/icons/BlacklistList.png', + mutelist: 'https://trakman.ptrk.eu/icons/MuteList.png', + guestlist: 'https://trakman.ptrk.eu/icons/GuestlistList.png', + infoCircle: 'https://trakman.ptrk.eu/icons/InfoCircle.png', + tools: 'https://trakman.ptrk.eu/icons/Tools.png', + ban: 'https://trakman.ptrk.eu/icons/BanHammer.png', + banHover: 'https://trakman.ptrk.eu/icons/BanHover.png', + addToBlacklist: 'https://trakman.ptrk.eu/icons/BanHammer.png', + addToBlacklistHover: 'https://trakman.ptrk.eu/icons/BlacklistHover.png', + unban: 'https://trakman.ptrk.eu/icons/Unban.png', + unbanHover: 'https://trakman.ptrk.eu/icons/UnbanHover.png', + unblacklist: 'https://trakman.ptrk.eu/icons/Unban.png', + unblacklistHover: 'https://trakman.ptrk.eu/icons/UnblacklistHover.png', + addGuest: 'https://trakman.ptrk.eu/icons/AddGuest.png', + addGuestHover: 'https://trakman.ptrk.eu/icons/AddGuestHover.png', + removeGuest: 'https://trakman.ptrk.eu/icons/RemoveGuest.png', + removeGuestHover: 'https://trakman.ptrk.eu/icons/RemoveGuestHover.png', + mute: 'https://trakman.ptrk.eu/icons/Mute.png', + muteHover: 'https://trakman.ptrk.eu/icons/MuteHover.png', + unmute: 'https://trakman.ptrk.eu/icons/Unmute.png', + unmuteHover: 'https://trakman.ptrk.eu/icons/UnmuteHover.png', + forceSpectator: 'https://trakman.ptrk.eu/icons/ForceSpectator.png', + forceGaming: 'https://trakman.ptrk.eu/icons/ForceGaming.png', + chartDedi: 'https://trakman.ptrk.eu/icons/ChartDedi.png', + chartLive: 'https://trakman.ptrk.eu/icons/ChartLive.png', + chartLocal: 'https://trakman.ptrk.eu/icons/ChartLocal.png', + bestFinishes: 'https://trakman.ptrk.eu/icons/BestFinishes.png', + bestClock: 'https://trakman.ptrk.eu/icons/BestClock.png', + ongoingMap: 'https://trakman.ptrk.eu/icons/OngoingMap.png', + personBuilder: 'https://trakman.ptrk.eu/icons/PersonBuilder.png', + person: 'https://trakman.ptrk.eu/icons/Person.png', + personWorse: 'https://trakman.ptrk.eu/icons/PersonWorse.png', + personBetter: 'https://trakman.ptrk.eu/icons/PersonBetter.png', + clockAuthor: 'https://trakman.ptrk.eu/icons/ClockAuthor.png', + maniaExchange: 'https://trakman.ptrk.eu/icons/ManiaExchange.png', + cash: 'https://trakman.ptrk.eu/icons/Cash.png', + info: 'https://trakman.ptrk.eu/icons/Info.png', + gears: 'https://trakman.ptrk.eu/icons/Gears.png', + playerList: 'https://trakman.ptrk.eu/icons/PlayerList.png', + mapList: 'https://trakman.ptrk.eu/icons/MapList.png', + infoList: 'https://trakman.ptrk.eu/icons/InfoList.png', + placeholder: 'https://trakman.ptrk.eu/icons/Placeholder.png', + checkmark: 'https://trakman.ptrk.eu/icons/Checkmark.png', + cross: 'https://trakman.ptrk.eu/icons/Cross.png', + addMap: 'https://trakman.ptrk.eu/icons/AddMap.png', + removeMap: 'https://trakman.ptrk.eu/icons/RemoveMap.png', + thumbsUpF5: 'https://trakman.ptrk.eu/icons/ThumbsUpF5.png', + thumbsDownF6: 'https://trakman.ptrk.eu/icons/ThumbsDownF6.png', + F5: 'https://trakman.ptrk.eu/icons/F5.png', + F6: 'https://trakman.ptrk.eu/icons/F6.png', + blank: 'http://maniacdn.net/undef.de/uaseco/blank.png', + trackmania: 'https://trakman.ptrk.eu/icons/Trackmania.png', + calendarPlus: 'https://trakman.ptrk.eu/icons/CalendarPlus.png', + carExplode: 'https://trakman.ptrk.eu/icons/CarExplode.png', + moodNight: 'https://trakman.ptrk.eu/icons/MoodNight.png', + moodSunrise: 'https://trakman.ptrk.eu/icons/MoodSunrise.png', + moodSunset: 'https://trakman.ptrk.eu/icons/MoodSunset.png', + moodDay: 'https://trakman.ptrk.eu/icons/MoodDay.png', + mapStyle: 'https://trakman.ptrk.eu/icons/MapStyle.png', + mapRoutes: 'https://trakman.ptrk.eu/icons/MapRoutes.png', + difficultyEasy: 'https://trakman.ptrk.eu/icons/DifficultyOne.png', + difficultyMedium: 'https://trakman.ptrk.eu/icons/DifficultyTwo.png', + difficultyHard: 'https://trakman.ptrk.eu/icons/DifficultyThree.png', + difficultyExtreme: 'https://trakman.ptrk.eu/icons/DifficultyFour.png', + puzzle: 'https://trakman.ptrk.eu/icons/Puzzle.png', + leaderboardRating: 'https://trakman.ptrk.eu/icons/LeaderboardRating.png', + leaderboardRatingClassic: 'https://trakman.ptrk.eu/icons/LeaderboardRatingClassic.png', + leaderboardRatingNadeo: 'https://trakman.ptrk.eu/icons/LeaderboardRatingNadeo.png', + trophy: 'https://trakman.ptrk.eu/icons/Trophy.png', + trophyClassic: 'https://trakman.ptrk.eu/icons/TrophyClassic.png', + trophyNadeo: 'https://trakman.ptrk.eu/icons/TrophyNadeo.png', + kick: 'https://trakman.ptrk.eu/icons/Kick.png', + kickHover: 'https://trakman.ptrk.eu/icons/KickHover.png', + forceSpectatorHover: 'https://trakman.ptrk.eu/icons/ForceSpectatorHover.png', + forceGamingHover: 'https://trakman.ptrk.eu/icons/ForceGamingHover.png', + playerListHover: 'https://trakman.ptrk.eu/icons/PlayerListHover.png', + restartHover: 'https://trakman.ptrk.eu/icons/RestartHover.png', + restart: 'https://trakman.ptrk.eu/icons/Restart.png', + requeueHover: 'https://trakman.ptrk.eu/icons/RequeueHover.png', + requeue: 'https://trakman.ptrk.eu/icons/Requeue.png', + mapListHover: 'https://trakman.ptrk.eu/icons/MapListHover.png', + tagKacky: 'https://trakman.ptrk.eu/icons/TagKacky.png', noScreenshot: { - Bay: "https://trakman.ptrk.eu/icons/ScreenshotBay.png", - Coast: "https://trakman.ptrk.eu/icons/ScreenshotCoast.png", - Desert: "https://trakman.ptrk.eu/icons/ScreenshotDesert.png", - Island: "https://trakman.ptrk.eu/icons/ScreenshotIsland.png", - Rally: "https://trakman.ptrk.eu/icons/ScreenshotRally.png", - Snow: "https://trakman.ptrk.eu/icons/ScreenshotSnow.png", - Stadium: "https://trakman.ptrk.eu/icons/ScreenshotStadium.png" + Bay: 'https://trakman.ptrk.eu/icons/ScreenshotBay.png', + Coast: 'https://trakman.ptrk.eu/icons/ScreenshotCoast.png', + Desert: 'https://trakman.ptrk.eu/icons/ScreenshotDesert.png', + Island: 'https://trakman.ptrk.eu/icons/ScreenshotIsland.png', + Rally: 'https://trakman.ptrk.eu/icons/ScreenshotRally.png', + Snow: 'https://trakman.ptrk.eu/icons/ScreenshotSnow.png', + Stadium: 'https://trakman.ptrk.eu/icons/ScreenshotStadium.png' }, - waypoint: "https://trakman.ptrk.eu/icons/Waypoint.png", - bgGreyOpaque50: "https://trakman.ptrk.eu/icons/GreyOpaque50.png", - shuffleHover: "https://trakman.ptrk.eu/icons/ShuffleHover.png", - shuffle: "https://trakman.ptrk.eu/icons/Shuffle.png", - tagArcade: "https://trakman.ptrk.eu/icons/TagArcade.png", - pause: "https://trakman.ptrk.eu/icons/Pause.png", - pauseGreen: "https://trakman.ptrk.eu/icons/PauseHover.png", - minus: "https://trakman.ptrk.eu/icons/Minus.png", - minusGreen: "https://trakman.ptrk.eu/icons/MinusHover.png", - plus: "https://trakman.ptrk.eu/icons/Plus.png", - plusGreen: "https://trakman.ptrk.eu/icons/PlusHover.png", - play: "https://trakman.ptrk.eu/icons/Play.png", - playGreen: "https://trakman.ptrk.eu/icons/PlayHover.png", - clockLap: "https://trakman.ptrk.eu/icons/ClockLap.png", - lap: "https://trakman.ptrk.eu/icons/Laps.png", - trakman: "https://trakman.ptrk.eu/icons/Trakman.png", - flagFinish: "https://trakman.ptrk.eu/icons/FlagFinish.png", - trophySilver: "https://trakman.ptrk.eu/icons/TrophySilver.png", - trophyBronze: "https://trakman.ptrk.eu/icons/TrophyBronze.png", - clockFire: "https://trakman.ptrk.eu/icons/ClockFire.png", - lapsMode: "https://trakman.ptrk.eu/icons/LapsMode.png", - teamsMode: "https://trakman.ptrk.eu/icons/TeamsMode.png", - roundsMode: "https://trakman.ptrk.eu/icons/RoundsMode.png", - stuntsMode: "https://trakman.ptrk.eu/icons/StuntsMode.png", - servers: "https://trakman.ptrk.eu/icons/Servers.png", - musicRemove: "https://trakman.ptrk.eu/icons/MusicRemove.png", - musicRemoveHover: "https://trakman.ptrk.eu/icons/MusicRemoveHover.png", - musicAdd: "https://trakman.ptrk.eu/icons/MusicAdd.png", - musicAddHover: "https://trakman.ptrk.eu/icons/MusicAddHover.png", - musicList: "https://trakman.ptrk.eu/icons/MusicList.png", - disk: "https://trakman.ptrk.eu/icons/Disk.png", - music: "https://trakman.ptrk.eu/icons/Music.png", - exclamation: "https://trakman.ptrk.eu/icons/Exclamation.png", - warn: "https://trakman.ptrk.eu/icons/Warn.png", - personMusic: "https://trakman.ptrk.eu/icons/PersonMusic.png", - promotePlayer: "https://trakman.ptrk.eu/icons/PromotePlayer.png", - promotePlayerHover: "https://trakman.ptrk.eu/icons/PromotePlayerHover.png", - demotePlayer: "https://trakman.ptrk.eu/icons/DemotePlayer.png", - demotePlayerHover: "https://trakman.ptrk.eu/icons/DemotePlayerHover.png", - trashcanDelete: "https://trakman.ptrk.eu/icons/TrashDelete.png", - trashcanDeleteHover: "https://trakman.ptrk.eu/icons/TrashDeleteHover.png", + waypoint: 'https://trakman.ptrk.eu/icons/Waypoint.png', + bgGreyOpaque50: 'https://trakman.ptrk.eu/icons/GreyOpaque50.png', + shuffleHover: 'https://trakman.ptrk.eu/icons/ShuffleHover.png', + shuffle: 'https://trakman.ptrk.eu/icons/Shuffle.png', + tagArcade: 'https://trakman.ptrk.eu/icons/TagArcade.png', + pause: 'https://trakman.ptrk.eu/icons/Pause.png', + pauseGreen: 'https://trakman.ptrk.eu/icons/PauseHover.png', + minus: 'https://trakman.ptrk.eu/icons/Minus.png', + minusGreen: 'https://trakman.ptrk.eu/icons/MinusHover.png', + plus: 'https://trakman.ptrk.eu/icons/Plus.png', + plusGreen: 'https://trakman.ptrk.eu/icons/PlusHover.png', + play: 'https://trakman.ptrk.eu/icons/Play.png', + playGreen: 'https://trakman.ptrk.eu/icons/PlayHover.png', + clockLap: 'https://trakman.ptrk.eu/icons/ClockLap.png', + lap: 'https://trakman.ptrk.eu/icons/Laps.png', + trakman: 'https://trakman.ptrk.eu/icons/Trakman.png', + flagFinish: 'https://trakman.ptrk.eu/icons/FlagFinish.png', + trophySilver: 'https://trakman.ptrk.eu/icons/TrophySilver.png', + trophyBronze: 'https://trakman.ptrk.eu/icons/TrophyBronze.png', + clockFire: 'https://trakman.ptrk.eu/icons/ClockFire.png', + lapsMode: 'https://trakman.ptrk.eu/icons/LapsMode.png', + teamsMode: 'https://trakman.ptrk.eu/icons/TeamsMode.png', + roundsMode: 'https://trakman.ptrk.eu/icons/RoundsMode.png', + stuntsMode: 'https://trakman.ptrk.eu/icons/StuntsMode.png', + servers: 'https://trakman.ptrk.eu/icons/Servers.png', + musicRemove: 'https://trakman.ptrk.eu/icons/MusicRemove.png', + musicRemoveHover: 'https://trakman.ptrk.eu/icons/MusicRemoveHover.png', + musicAdd: 'https://trakman.ptrk.eu/icons/MusicAdd.png', + musicAddHover: 'https://trakman.ptrk.eu/icons/MusicAddHover.png', + musicList: 'https://trakman.ptrk.eu/icons/MusicList.png', + disk: 'https://trakman.ptrk.eu/icons/Disk.png', + music: 'https://trakman.ptrk.eu/icons/Music.png', + exclamation: 'https://trakman.ptrk.eu/icons/Exclamation.png', + warn: 'https://trakman.ptrk.eu/icons/Warn.png', + personMusic: 'https://trakman.ptrk.eu/icons/PersonMusic.png', + promotePlayer: 'https://trakman.ptrk.eu/icons/PromotePlayer.png', + promotePlayerHover: 'https://trakman.ptrk.eu/icons/PromotePlayerHover.png', + demotePlayer: 'https://trakman.ptrk.eu/icons/DemotePlayer.png', + demotePlayerHover: 'https://trakman.ptrk.eu/icons/DemotePlayerHover.png', + trashcanDelete: 'https://trakman.ptrk.eu/icons/TrashDelete.png', + trashcanDeleteHover: 'https://trakman.ptrk.eu/icons/TrashDeleteHover.png', get preloadedIcons() { - return [ - this.close, - this.closeHover, - this.pageDoubleLeft, - this.pageDoubleLeftHover, - this.pageDoubleRight, - this.pageDoubleRightHover, - this.pageFirst, - this.pageFirstHover, - this.pageLast, - this.pageLastHover, - this.pageLeft, - this.pageLeftHover, - this.pageRight, - this.pageRightHover - ] + return [this.close, this.closeHover, this.pageDoubleLeft, this.pageDoubleLeftHover, this.pageDoubleRight, + this.pageDoubleRightHover, this.pageFirst, this.pageFirstHover, this.pageLast, this.pageLastHover, this.pageLeft, + this.pageLeftHover, this.pageRight, this.pageRightHover] } } diff --git a/plugins/ui/config/Mod.js b/plugins/ui/config/Mod.js index b0859ff52..6857cfb1c 100644 --- a/plugins/ui/config/Mod.js +++ b/plugins/ui/config/Mod.js @@ -1,40 +1,31 @@ -export default -// Environments are Stadium, Desert, Snow, Bay, Coast, Island, Rally +export default // Environments are Stadium, Desert, Snow, Bay, Coast, Island, Rally // If random order is disabled, mods array will be applied in order -[ - { - environment: 'Stadium', - modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], - randomOrder: true, - }, - { - environment: 'Desert', - modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], - randomOrder: true, - }, - { - environment: 'Snow', - modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], - randomOrder: true, - }, - { - environment: 'Bay', - modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], - randomOrder: true, - }, - { - environment: 'Coast', - modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], - randomOrder: true, - }, - { - environment: 'Island', - modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], - randomOrder: true, - }, - { - environment: 'Rally', - modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], - randomOrder: true, - }, -] +[{ + environment: 'Stadium', + modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], + randomOrder: true +}, { + environment: 'Desert', + modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], + randomOrder: true +}, { + environment: 'Snow', + modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], + randomOrder: true +}, { + environment: 'Bay', + modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], + randomOrder: true +}, { + environment: 'Coast', + modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], + randomOrder: true +}, { + environment: 'Island', + modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], + randomOrder: true +}, { + environment: 'Rally', + modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], + randomOrder: true +}] diff --git a/plugins/ui/config/PopupWindow.js b/plugins/ui/config/PopupWindow.js index d2e4b6723..191c2e75c 100644 --- a/plugins/ui/config/PopupWindow.js +++ b/plugins/ui/config/PopupWindow.js @@ -1,6 +1,6 @@ export default { - headerBg: "000C", - bg: "000C", + headerBg: '000C', + bg: '000C', margin: 0.15, titleHeight: 5, windowHeight: 60, @@ -9,4 +9,4 @@ export default { footerHeight: 4, headerPageWidth: 10, textScale: 1 -} \ No newline at end of file +} diff --git a/plugins/ui/config/RaceUi.js b/plugins/ui/config/RaceUi.js index a64f1d946..db98bfce8 100644 --- a/plugins/ui/config/RaceUi.js +++ b/plugins/ui/config/RaceUi.js @@ -3,122 +3,38 @@ export default { reduxModeDisablePlayerAmount: 18, margin: 0.15, marginBig: 0.27, - format: "$s", + format: '$s', textScale: 0.7, padding: 0.2, yOffset: -0.1, - background: "0006", + background: '0006', width: 14.65, topBorder: 47.85, rightPosition: 49.13, - leftPosition: -63.75, - // Put component class names here, if you want a margin between + leftPosition: -63.75, // Put component class names here, if you want a margin between // components then put a number - rightSideOrder: [ - "MapWidget", - "PreviousAndBest", - // 3.5, <- example margin - "TMXRanking", - "TimerWidget", - "LocalRanking", - "LiveRanking", - "DonationPanel" - ], - leftSideOrder: [ - "ButtonsWidget", - "RankWidget", - "KarmaWidget", - "DediRanking", - "AdminPanel" - ], + rightSideOrder: ['MapWidget', 'PreviousAndBest', // 3.5, <- example margin + 'TMXRanking', 'TimerWidget', 'LocalRanking', 'LiveRanking', 'DonationPanel'], + leftSideOrder: ['ButtonsWidget', 'RankWidget', 'KarmaWidget', 'DediRanking', 'AdminPanel'], otherComponents: ['BestCps', 'BestFinishes', 'CpCounter'], - roundsRightSideOrder: [ - "MapWidget", - "PreviousAndBest", - "TMXRanking", - "TimerWidget", - "RoundScore", - "RoundsPointsRanking", - "DonationPanel" - ], - roundsLeftSideOrder: [ - "ButtonsWidget", - "RankWidget", - "KarmaWidget", - "DediRanking", - "LocalRanking", - "AdminPanel" - ], + roundsRightSideOrder: ['MapWidget', 'PreviousAndBest', 'TMXRanking', 'TimerWidget', 'RoundScore', + 'RoundsPointsRanking', 'DonationPanel'], + roundsLeftSideOrder: ['ButtonsWidget', 'RankWidget', 'KarmaWidget', 'DediRanking', 'LocalRanking', 'AdminPanel'], roundsOtherComponents: ['BestCps', 'BestFinishes', 'CpCounter'], - cupRightSideOrder: [ - "MapWidget", - "PreviousAndBest", - "TMXRanking", - "TimerWidget", - "RoundScore", - "RoundsPointsRanking", - "DonationPanel" - ], - cupLeftSideOrder: [ - "ButtonsWidget", - "RankWidget", - "KarmaWidget", - "DediRanking", - "LocalRanking", - "AdminPanel" - ], + cupRightSideOrder: ['MapWidget', 'PreviousAndBest', 'TMXRanking', 'TimerWidget', 'RoundScore', 'RoundsPointsRanking', + 'DonationPanel'], + cupLeftSideOrder: ['ButtonsWidget', 'RankWidget', 'KarmaWidget', 'DediRanking', 'LocalRanking', 'AdminPanel'], cupOtherComponents: ['BestCps', 'BestFinishes', 'CpCounter'], - teamsRightSideOrder: [ - "MapWidget", - "PreviousAndBest", - "TMXRanking", - "TimerWidget", - "RoundScore", - "LocalRanking", - "TeamScore", - "DonationPanel" - ], - teamsLeftSideOrder: [ - "ButtonsWidget", - "RankWidget", - "KarmaWidget", - "DediRanking", - "AdminPanel" - ], + teamsRightSideOrder: ['MapWidget', 'PreviousAndBest', 'TMXRanking', 'TimerWidget', 'RoundScore', 'LocalRanking', + 'TeamScore', 'DonationPanel'], + teamsLeftSideOrder: ['ButtonsWidget', 'RankWidget', 'KarmaWidget', 'DediRanking', 'AdminPanel'], teamsOtherComponents: ['BestCps', 'BestFinishes', 'CpCounter'], - lapsRightSideOrder: [ - "MapWidget", - "PreviousAndBest", - "TMXRanking", - "TimerWidget", - "LiveRanking", - "LocalRanking", - "DonationPanel" - ], - lapsLeftSideOrder: [ - "ButtonsWidget", - "RankWidget", - "KarmaWidget", - "DediRanking", - "LapRanking", - "AdminPanel" - ], + lapsRightSideOrder: ['MapWidget', 'PreviousAndBest', 'TMXRanking', 'TimerWidget', 'LiveRanking', 'LocalRanking', + 'DonationPanel'], + lapsLeftSideOrder: ['ButtonsWidget', 'RankWidget', 'KarmaWidget', 'DediRanking', 'LapRanking', 'AdminPanel'], lapsOtherComponents: ['BestCps', 'BestFinishes', 'CpCounter'], - stuntsRightSideOrder: [ - "MapWidget", - "PreviousAndBest", - "DonationPanel", - "TimerWidget", - "LocalRanking", - "LiveRanking", - ], - stuntsLeftSideOrder: [ - "ButtonsWidget", - "RankWidget", - "KarmaWidget", - "TMXRanking", - "UltiRanking", - "AdminPanel" - ], + stuntsRightSideOrder: ['MapWidget', 'PreviousAndBest', 'DonationPanel', 'TimerWidget', 'LocalRanking', + 'LiveRanking'], + stuntsLeftSideOrder: ['ButtonsWidget', 'RankWidget', 'KarmaWidget', 'TMXRanking', 'UltiRanking', 'AdminPanel'], stuntsOtherComponents: ['BestCps', 'BestFinishes', 'CpCounter'] -} \ No newline at end of file +} diff --git a/plugins/ui/config/ResultUi.js b/plugins/ui/config/ResultUi.js index 5229f4f5d..67964ecd8 100644 --- a/plugins/ui/config/ResultUi.js +++ b/plugins/ui/config/ResultUi.js @@ -1,32 +1,18 @@ export default { margin: 0.15, marginBig: 0.27, - format: "$s", + format: '$s', textScale: 0.7, padding: 0.2, yOffset: -0.1, - background: "CCC6", + background: 'CCC6', width: 14.65, topBorder: 47.85, rightPosition: 49.13, leftPosition: -63.75, - rightSideOrder: [ - "MapWidgetResult", - "NextMapRecords", - "TimerWidgetResult", - "KarmaRanking", - "VotersRanking", - "VisitorsRanking", - "DonationPanelResult" - ], - leftSideOrder: [ - "AveragesRanking", - "RankWidgetResult", - "KarmaWidgetResult", - "LocalRankingResult", - "DediRankingResult", - "RoundAveragesRanking", - "AdminPanelResult" - ], - otherComponents: ["DonatorsRanking", "MostRecordsRanking", "PlaytimeRanking", "WinnersRanking", "BannerWidget"] -} \ No newline at end of file + rightSideOrder: ['MapWidgetResult', 'NextMapRecords', 'TimerWidgetResult', 'KarmaRanking', 'VotersRanking', + 'VisitorsRanking', 'DonationPanelResult'], + leftSideOrder: ['AveragesRanking', 'RankWidgetResult', 'KarmaWidgetResult', 'LocalRankingResult', 'DediRankingResult', + 'RoundAveragesRanking', 'AdminPanelResult'], + otherComponents: ['DonatorsRanking', 'MostRecordsRanking', 'PlaytimeRanking', 'WinnersRanking', 'BannerWidget'] +} diff --git a/plugins/ui/dynamic_components/Changelog.component.ts b/plugins/ui/dynamic_components/Changelog.component.ts index 64ccf372c..5e811735e 100644 --- a/plugins/ui/dynamic_components/Changelog.component.ts +++ b/plugins/ui/dynamic_components/Changelog.component.ts @@ -3,11 +3,13 @@ * @since 0.3 */ -import { Grid, componentIds, type GridCellFunction, centeredText, closeButton, PopupWindow, Paginator } from '../UI.js' +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, Paginator, PopupWindow } from '../UI.js' import config from './Changelog.config.js' import fs from 'fs/promises' -export default class Changelog extends PopupWindow<{ page: number }> { +export default class Changelog extends PopupWindow<{ + page: number +}> { private readonly versions: { version: string @@ -31,7 +33,8 @@ export default class Changelog extends PopupWindow<{ page: number }> { this.paginator.onPageChange = (login, page) => { this.displayToPlayer(login, { page }, `${page}/${this.paginator.pageCount}`) } - this.grid = new Grid(this.contentWidth, this.contentHeight, new Array(config.entries).fill(1), [1], { margin: config.marginBig }) + this.grid = new Grid(this.contentWidth, this.contentHeight, new Array(config.entries).fill(1), [1], + { margin: config.marginBig }) this.readChangelog() } @@ -55,7 +58,11 @@ export default class Changelog extends PopupWindow<{ page: number }> { } }).slice(2, 2 + config.lineCount).join('\n') if (![version, date, content].includes(undefined as any)) { - this.versions.push({ version, date, content }) + this.versions.push({ + version, + date, + content + }) } } this.paginator.setPageCount(Math.ceil(this.versions.length / config.entries)) @@ -67,7 +74,9 @@ export default class Changelog extends PopupWindow<{ page: number }> { this.displayToPlayer(info.login, { page }, `${page}/${this.paginator.pageCount}`) } - protected constructContent(login: string, { page }: { page: number }): string { + protected constructContent(login: string, { page }: { + page: number + }): string { const index = config.entries * (page - 1) const cells: GridCellFunction[] = [] const cell: GridCellFunction = (i, j, w, h) => { @@ -86,10 +95,16 @@ export default class Changelog extends PopupWindow<{ page: number }> { const dateW = w - (versionWidth + this.margin) return ` - ${centeredText(`$s$${tm.utils.palette.green}${title}`, versionWidth, headerH, { padding: this.margin, textScale: config.textScale })} + ${centeredText(`$s$${tm.utils.palette.green}${title}`, versionWidth, headerH, { + padding: this.margin, + textScale: config.textScale + })} - ${centeredText(`$s${date}`, dateW, headerH, { padding: this.margin, textScale: config.textScale })} + ${centeredText(`$s${date}`, dateW, headerH, { + padding: this.margin, + textScale: config.textScale + })} @@ -99,7 +114,9 @@ export default class Changelog extends PopupWindow<{ page: number }> { ` } - protected constructFooter(login: string, { page }: { page: number }): string { + protected constructFooter(login: string, { page }: { + page: number + }): string { return closeButton(this.closeId, this.windowWidth, this.footerHeight) + this.paginator.constructXml(page) } diff --git a/plugins/ui/dynamic_components/Changelog.config.js b/plugins/ui/dynamic_components/Changelog.config.js index c019c5589..ebed89a7d 100644 --- a/plugins/ui/dynamic_components/Changelog.config.js +++ b/plugins/ui/dynamic_components/Changelog.config.js @@ -2,13 +2,14 @@ import ids from '../config/ComponentIds.js' import icons from '../config/Icons.js' export default { - title: "Changelog", + title: 'Changelog', icon: icons.codeBranch, - navbar: [ - { name: 'Command List', actionId: ids.commandList } - ], + navbar: [{ + name: 'Command List', + actionId: ids.commandList + }], textScale: 1.4, - tileBackground: "9996", + tileBackground: '9996', marginBig: 1, entries: 2, versionWidth: 6, @@ -20,4 +21,4 @@ export default { help: `Display the controller changelog.`, privilege: 0 } -} \ No newline at end of file +} diff --git a/plugins/ui/dynamic_components/Chatlog.component.ts b/plugins/ui/dynamic_components/Chatlog.component.ts index 5bb38446b..e4eb6a3e9 100644 --- a/plugins/ui/dynamic_components/Chatlog.component.ts +++ b/plugins/ui/dynamic_components/Chatlog.component.ts @@ -3,10 +3,12 @@ * @since 1.0 */ -import { componentIds, Grid, leftAlignedText, centeredText, closeButton, Paginator, type GridCellFunction, PopupWindow } from '../UI.js' +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, leftAlignedText, Paginator, PopupWindow } from '../UI.js' import config from './Chatlog.config.js' -export default class ChatLog extends PopupWindow<{ page: number }> { +export default class ChatLog extends PopupWindow<{ + page: number +}> { readonly grid: Grid readonly paginator: Paginator @@ -46,21 +48,21 @@ export default class ChatLog extends PopupWindow<{ page: number }> { } } - protected constructContent(login: string, params: { page: number }): string { + protected constructContent(login: string, params: { + page: number + }): string { const page = params.page const index = (page - 1) * (config.entries - 1) - 1 const messages = tm.chat.messages.reverse() - const headers: GridCellFunction[] = [ - (i, j, w, h) => centeredText(' Date ', w, h), - (i, j, w, h) => centeredText(' Nickname ', w, h), - (i, j, w, h) => centeredText(' Login ', w, h), - (i, j, w, h) => centeredText(' Message ', w, h), - ] + const headers: GridCellFunction[] = [(i, j, w, h) => centeredText(' Date ', w, h), + (i, j, w, h) => centeredText(' Nickname ', w, h), (i, j, w, h) => centeredText(' Login ', w, h), + (i, j, w, h) => centeredText(' Message ', w, h)] const dateCell: GridCellFunction = (i, j, w, h) => { return centeredText(tm.utils.formatDate(messages[index + i].date, true), w, h) } const nicknameCell: GridCellFunction = (i, j, w, h) => { - return centeredText(tm.utils.safeString(tm.utils.safeString(tm.utils.strip(messages[index + i].nickname, false))), w, h) + return centeredText(tm.utils.safeString(tm.utils.safeString(tm.utils.strip(messages[index + i].nickname, false))), + w, h) } const loginCell: GridCellFunction = (i, j, w, h) => { const playerLogin = messages[index + i].login diff --git a/plugins/ui/dynamic_components/Chatlog.config.js b/plugins/ui/dynamic_components/Chatlog.config.js index d5a41d9cd..e0e91de36 100644 --- a/plugins/ui/dynamic_components/Chatlog.config.js +++ b/plugins/ui/dynamic_components/Chatlog.config.js @@ -1,8 +1,9 @@ import icons from '../config/Icons.js' + const p = tm.utils.palette export default { - title: " Chat Log ", + title: ' Chat Log ', kickPrivilege: 1, forceSpecPrivilege: 1, privilege: 0, // Privilege required to open the window @@ -11,19 +12,13 @@ export default { iconHeight: 2, public: true, entries: 16, - columnProportions: [ - 2, - 3, - 2.5, - 7, - ], - navbar: [ - ], + columnProportions: [2, 3, 2.5, 7], + navbar: [], selfColour: p.green, grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, command: { aliases: ['chatlog', 'cl'], diff --git a/plugins/ui/dynamic_components/Commandlist.component.ts b/plugins/ui/dynamic_components/Commandlist.component.ts index fc50a0261..87c07e8dc 100644 --- a/plugins/ui/dynamic_components/Commandlist.component.ts +++ b/plugins/ui/dynamic_components/Commandlist.component.ts @@ -3,7 +3,7 @@ * @since 0.1 */ -import { Paginator, Grid, centeredText, componentIds, closeButton, Navbar, type GridCellFunction, addManialinkListener, PopupWindow } from "../UI.js" +import { addManialinkListener, centeredText, closeButton, componentIds, Grid, type GridCellFunction, Paginator, PopupWindow } from "../UI.js" import config from './Commandlist.config.js' interface DisplayParams { @@ -29,7 +29,12 @@ export default class CommandList extends PopupWindow { private adminCommands: tm.Command[] = [] private masteradminCommands: tm.Command[] = [] private ownerCommands: tm.Command[] = [] - private searchQueries: { login: string, privilege: number, list: tm.Command[], paginator: Paginator }[] = [] + private searchQueries: { + login: string, + privilege: number, + list: tm.Command[], + paginator: Paginator + }[] = [] private paginatorIdOffset = 0 private readonly paginatorIdRange = 400 // Starts at 600 @@ -51,13 +56,19 @@ export default class CommandList extends PopupWindow { if (entry !== undefined) { const page = entry[0].getPageByLogin(info.login) this.displayToPlayer(info.login, { - paginator: entry[0], commands: entry[1], - page, privilege: info.privilege, singleType: true + paginator: entry[0], + commands: entry[1], + page, + privilege: info.privilege, + singleType: true }, `${page}/${entry[0].pageCount}`, info.privilege, entry[2]) } }) tm.addListener("PrivilegeChanged", (info) => { - const p: { login: string, params: DisplayParams } | undefined = this.getPlayersWithWindowOpen(true).find(a => a.login === info.login) + const p: { + login: string, + params: DisplayParams + } | undefined = this.getPlayersWithWindowOpen(true).find(a => a.login === info.login) if (p !== undefined) { // Handling list change on search query would be quite complex and its // very unlikely to occur so I just leave the window as it is in that case @@ -68,16 +79,27 @@ export default class CommandList extends PopupWindow { const paginator: Paginator = this.paginators[info.newPrivilege] const commands: tm.Command[] = this.commandLists[info.newPrivilege] const page = paginator.getPageByLogin(info.login) - this.displayToPlayer(info.login, { page, commands, paginator, privilege: info.newPrivilege }, `${page}/${paginator.pageCount}`, info.newPrivilege) + this.displayToPlayer(info.login, { + page, + commands, + paginator, + privilege: info.newPrivilege + }, `${page}/${paginator.pageCount}`, info.newPrivilege) } else { - this.displayToPlayer(info.login, { ...p.params, privilege: info.newPrivilege }, `0/${p.params.paginator.pageCount}`, info.newPrivilege) + this.displayToPlayer(info.login, { + ...p.params, + privilege: info.newPrivilege + }, `0/${p.params.paginator.pageCount}`, info.newPrivilege) } } }) tm.commands.add({ aliases: config.command.aliases, help: config.command.help, - params: [{ name: 'query', optional: true }], + params: [{ + name: 'query', + optional: true + }], callback: (info: tm.MessageInfo, query?: string): void => { if (query !== undefined) { this.openWithQuery(info.login, info.privilege, query) @@ -105,38 +127,58 @@ export default class CommandList extends PopupWindow { this.adminCommands = commandList.filter(a => a.help !== undefined && a.privilege === 2) this.masteradminCommands = commandList.filter(a => a.help !== undefined && a.privilege === 3) this.ownerCommands = commandList.filter(a => a.help !== undefined && a.privilege === 4) - this.userPaginator = new Paginator(this.openId + 100, this.contentWidth, this.footerHeight, Math.ceil(this.userCommands.length / config.entries)) - this.opPaginator = new Paginator(this.openId + 200, this.contentWidth, this.footerHeight, Math.ceil(this.opCommands.length / config.entries)) - this.adminPaginator = new Paginator(this.openId + 300, this.contentWidth, this.footerHeight, Math.ceil(this.adminCommands.length / config.entries)) - this.masteradminPaginator = new Paginator(this.openId + 400, this.contentWidth, this.footerHeight, Math.ceil(this.masteradminCommands.length / config.entries)) - this.ownerPaginator = new Paginator(this.openId + 500, this.contentWidth, this.footerHeight, Math.ceil(this.ownerCommands.length / config.entries)) - const paginators = [this.userPaginator, this.opPaginator, this.adminPaginator, this.masteradminPaginator, this.ownerPaginator] - const commands = [this.userCommands, this.opCommands, this.adminCommands, this.masteradminCommands, this.ownerCommands] + this.userPaginator = new Paginator(this.openId + 100, this.contentWidth, this.footerHeight, + Math.ceil(this.userCommands.length / config.entries)) + this.opPaginator = new Paginator(this.openId + 200, this.contentWidth, this.footerHeight, + Math.ceil(this.opCommands.length / config.entries)) + this.adminPaginator = new Paginator(this.openId + 300, this.contentWidth, this.footerHeight, + Math.ceil(this.adminCommands.length / config.entries)) + this.masteradminPaginator = new Paginator(this.openId + 400, this.contentWidth, this.footerHeight, + Math.ceil(this.masteradminCommands.length / config.entries)) + this.ownerPaginator = new Paginator(this.openId + 500, this.contentWidth, this.footerHeight, + Math.ceil(this.ownerCommands.length / config.entries)) + const paginators = [this.userPaginator, this.opPaginator, this.adminPaginator, this.masteradminPaginator, + this.ownerPaginator] + const commands = [this.userCommands, this.opCommands, this.adminCommands, this.masteradminCommands, + this.ownerCommands] for (const e of paginators) { e.onPageChange = (login: string, page: number): void => { const privilege = tm.players.get(login)?.privilege ?? 0 this.displayToPlayer(login, { - page, paginator: e, commands: commands[paginators.indexOf(e)], privilege, singleType: true + page, + paginator: e, + commands: commands[paginators.indexOf(e)], + privilege, + singleType: true }, `${page}/${e.pageCount}`, privilege) } } this.commandLists.length = 0 for (let i = 0; i <= 4; i++) { - const arr = [this.userCommands, this.opCommands, this.adminCommands, this.masteradminCommands, this.ownerCommands].slice(0, i + 1) + const arr = [this.userCommands, this.opCommands, this.adminCommands, this.masteradminCommands, + this.ownerCommands].slice(0, i + 1) const commands: tm.Command[] = arr.flat(1) this.commandLists.push(commands) const pageCount: number = Math.ceil(commands.length / config.entries) - const paginator: Paginator = new Paginator(this.openId + (i * 10), this.contentWidth, this.footerHeight, pageCount) + const paginator: Paginator = new Paginator(this.openId + (i * 10), this.contentWidth, this.footerHeight, + pageCount) paginator.onPageChange = (login: string, page: number, info): void => { - this.displayToPlayer(login, { page, paginator, commands, privilege: i }, `${page}/${paginator.pageCount}`, info.privilege) + this.displayToPlayer(login, { + page, + paginator, + commands, + privilege: i + }, `${page}/${paginator.pageCount}`, info.privilege) } this.paginators.push(paginator) } - this.table = new Grid(this.contentWidth, this.contentHeight, config.columnProportions, new Array(config.entries).fill(1), config.grid) + this.table = new Grid(this.contentWidth, this.contentHeight, config.columnProportions, + new Array(config.entries).fill(1), config.grid) } openWithQuery(login: string, privilege: number, query: string) { - const commands = [this.userCommands, this.opCommands, this.adminCommands, this.masteradminCommands, this.ownerCommands].slice(0, privilege + 1).flat(1) + const commands = [this.userCommands, this.opCommands, this.adminCommands, this.masteradminCommands, + this.ownerCommands].slice(0, privilege + 1).flat(1) const aliases = commands.map(a => a.aliases).flat(1) const matchedAliases = [] let aliasIndices: number[] @@ -150,18 +192,23 @@ export default class CommandList extends PopupWindow { } let indices: number[] indices = tm.utils.matchString(query, commands.map(a => a.help ?? '')) - const list: tm.Command[] = [...indices.map(a => commands[a]), ...matchedAliases].filter((a, i, arr) => arr.indexOf(a) === i) + const list: tm.Command[] = [...indices.map(a => commands[a]), ...matchedAliases].filter( + (a, i, arr) => arr.indexOf(a) === i) if (list.length === 0) { tm.sendMessage(tm.utils.strVar(config.noMatchesMessage, { query }), login) return } const paginatorId = (this.openId + 600 + this.paginatorIdOffset) % this.paginatorIdRange this.paginatorIdOffset++ - const paginator = new Paginator(paginatorId, this.contentWidth, - this.footerHeight, Math.ceil(list.length / config.entries)) + const paginator = new Paginator(paginatorId, this.contentWidth, this.footerHeight, + Math.ceil(list.length / config.entries)) paginator.onPageChange = (login: string, page: number, info): void => { - this.displayToPlayer(login, { page, paginator, commands: list, privilege }, - `${page}/${paginator.pageCount}`, info.privilege) + this.displayToPlayer(login, { + page, + paginator, + commands: list, + privilege + }, `${page}/${paginator.pageCount}`, info.privilege) } this.searchQueries.push({ login, @@ -169,8 +216,12 @@ export default class CommandList extends PopupWindow { list, paginator }) - this.displayToPlayer(login, { page: 1, commands: list, paginator, privilege }, - `1/${paginator.pageCount}`, privilege) + this.displayToPlayer(login, { + page: 1, + commands: list, + paginator, + privilege + }, `1/${paginator.pageCount}`, privilege) } protected onOpen(info: tm.ManialinkClickInfo): void { @@ -184,7 +235,12 @@ export default class CommandList extends PopupWindow { this.searchQueries[index].paginator.destroy() this.searchQueries.splice(index, 1) } - this.displayToPlayer(info.login, { page, commands, paginator, privilege: player.privilege }, `${page}/${paginator.pageCount}`, info.privilege) + this.displayToPlayer(info.login, { + page, + commands, + paginator, + privilege: player.privilege + }, `${page}/${paginator.pageCount}`, info.privilege) } protected onClose(info: tm.ManialinkClickInfo): void { @@ -198,16 +254,15 @@ export default class CommandList extends PopupWindow { protected constructContent(login: string, params: DisplayParams): string { const n = ((params.page - 1) * config.entries) - 1 - const headers: GridCellFunction[] = [ - (i, j, w, h) => centeredText(' P ', w, h), - (i, j, w, h) => centeredText(' Aliases ', w, h), - (i, j, w, h) => centeredText(' Arguments ', w, h), - (i, j, w, h) => centeredText(' Comment ', w, h), // Space to prevent translation + const headers: GridCellFunction[] = [(i, j, w, h) => centeredText(' P ', w, h), + (i, j, w, h) => centeredText(' Aliases ', w, h), (i, j, w, h) => centeredText(' Arguments ', w, h), + (i, j, w, h) => centeredText(' Comment ', w, h) // Space to prevent translation ] const privCell: GridCellFunction = (i, j, w, h) => { const command: tm.Command = params.commands[i + n] if (command === undefined) { return '' } - const text: string = config.slashesForPrivilege ? (command.privilege > 0 ? '//' : '/') : command.privilege.toString() + const text: string = config.slashesForPrivilege ? (command.privilege > 0 ? '//' : '/') : + command.privilege.toString() return centeredText(tm.utils.safeString(tm.utils.strip(text, true)), w, h) } const nameCell: GridCellFunction = (i, j, w, h) => { @@ -236,7 +291,8 @@ export default class CommandList extends PopupWindow { } protected constructFooter(login: string, params: DisplayParams): string { - return closeButton(this.closeId, this.windowWidth, this.headerHeight - this.margin) + params.paginator.constructXml(params.page) + return closeButton(this.closeId, this.windowWidth, this.headerHeight - this.margin) + params.paginator.constructXml( + params.page) } } \ No newline at end of file diff --git a/plugins/ui/dynamic_components/Commandlist.config.js b/plugins/ui/dynamic_components/Commandlist.config.js index a4735a294..2129839cb 100644 --- a/plugins/ui/dynamic_components/Commandlist.config.js +++ b/plugins/ui/dynamic_components/Commandlist.config.js @@ -4,54 +4,42 @@ import icons from '../config/Icons.js' const p = tm.utils.palette export default { - title: "Command List", + title: 'Command List', entries: 15, icon: icons.infoList, textScale: 0.7, - navbar: [ - { - name: "All Commands", - actionId: ids.commandList, - privilege: 1 - }, - { - name: "User Commands", - actionId: ids.commandList + 100, - privilege: 1 - }, - { - name: "Operator Commands", - actionId: ids.commandList + 200, - privilege: 1 - }, - { - name: "Admin Commands", - actionId: ids.commandList + 300, - privilege: 2 - }, - { - name: "Masteradmin Commands", - actionId: ids.commandList + 400, - privilege: 3 - }, - { - name: "Server Owner Commands", - actionId: ids.commandList + 500, - privilege: 4 - } - ], + navbar: [{ + name: 'All Commands', + actionId: ids.commandList, + privilege: 1 + }, { + name: 'User Commands', + actionId: ids.commandList + 100, + privilege: 1 + }, { + name: 'Operator Commands', + actionId: ids.commandList + 200, + privilege: 1 + }, { + name: 'Admin Commands', + actionId: ids.commandList + 300, + privilege: 2 + }, { + name: 'Masteradmin Commands', + actionId: ids.commandList + 400, + privilege: 3 + }, { + name: 'Server Owner Commands', + actionId: ids.commandList + 500, + privilege: 4 + }], grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, slashesForPrivilege: true, // Whether to display slashes or privilege level in the window - columnProportions: [ - 0.2, - 1, - 2, - 2 - ], + columnProportions: [0.2, 1, 2, 2], command: { aliases: ['h', 'help', 'helpall'], help: `Display the list of controller commands. Can be used with optional search query.`, @@ -59,4 +47,4 @@ export default { }, noMatchesMessage: `${p.error}Nothing found for ${p.highlight}#{query}${p.error}.`, aliasSearch: true // Whether to search for alias on query -} \ No newline at end of file +} diff --git a/plugins/ui/dynamic_components/PlayerStats.component.ts b/plugins/ui/dynamic_components/PlayerStats.component.ts index 9b6a4c69b..b3d370a56 100644 --- a/plugins/ui/dynamic_components/PlayerStats.component.ts +++ b/plugins/ui/dynamic_components/PlayerStats.component.ts @@ -3,7 +3,7 @@ * @since 1.3.3 */ -import { Grid, componentIds, type GridCellFunction, type GridCellObject, centeredText, closeButton, PopupWindow } from '../UI.js' +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, PopupWindow } from '../UI.js' import { titles } from '../../../config/Titles.js' import { stats } from '../../stats/Stats.js' import config from './PlayerStats.config.js' @@ -17,9 +17,13 @@ export default class PlayerStatsWindow extends PopupWindow { tm.commands.add({ aliases: config.command.aliases, help: config.command.help, - params: [{ name: 'login', optional: true }], + params: [{ + name: 'login', + optional: true + }], callback: async (info: tm.MessageInfo, login?: string): Promise => { - const player: tm.OfflinePlayer | undefined = tm.players.get(login ?? info.login) ?? await tm.players.fetch(login ?? info.login) + const player: tm.OfflinePlayer | undefined = tm.players.get(login ?? info.login) ?? await tm.players.fetch( + login ?? info.login) if (player === undefined) { tm.sendMessage(config.command.error, info.login) return @@ -50,34 +54,28 @@ export default class PlayerStatsWindow extends PopupWindow { } } - protected constructContent(login: string, params?: { player: tm.OfflinePlayer }): string { + protected constructContent(login: string, params?: { + player: tm.OfflinePlayer + }): string { if (params === undefined) { return '' } - const data: string[] = [ - params.player.login, - tm.utils.safeString(tm.utils.strip(params.player.nickname, false)), + const data: string[] = [params.player.login, tm.utils.safeString(tm.utils.strip(params.player.nickname, false)), `${params.player.country} / ${params.player.countryCode}`, titles.privileges[params.player.privilege as keyof typeof titles.privileges], (stats.averages.list.findIndex(a => a.login === params.player.login) + 1).toString(), - params.player.average.toString(), - tm.utils.getVerboseTime(params.player.timePlayed), + params.player.average.toString(), tm.utils.getVerboseTime(params.player.timePlayed), stats.records.list.find(a => a.login === params.player.login)?.amount.toString() ?? `0`, stats.votes.list.find(a => a.login === params.player.login)?.count.toString() ?? `0`, stats.sums.list.find(a => a.login === params.player.login)?.sums.slice(0, 4) // TODO: Find out how does the index go out of bounds here - .map((a, i) => `$${config.sumsColours[i]}${a}$FFF`).join(' / ') ?? `None`, - params.player.visits.toString(), + .map((a, i) => `$${config.sumsColours[i]}${a}$FFF`).join(' / ') ?? `None`, params.player.visits.toString(), stats.donations.list.find(a => a.login === params.player.login)?.amount.toString() ?? `0`, - params.player.wins.toString(), - params.player.isUnited ? `United` : `Nations`, - tm.utils.formatDate(params.player.lastOnline ?? new Date(), true) - ] - const header: GridCellObject[] = [ - { - background: config.grid.headerBackground, - colspan: 2, - callback: (i, j, w, h): string => centeredText(config.cellHeader, w, h), - } - ] + params.player.wins.toString(), params.player.isUnited ? `United` : `Nations`, + tm.utils.formatDate(params.player.lastOnline ?? new Date(), true)] + const header: GridCellObject[] = [{ + background: config.grid.headerBackground, + colspan: 2, + callback: (i, j, w, h): string => centeredText(config.cellHeader, w, h) + }] const nameCell: GridCellObject = { background: config.nameColumnBackground, callback: (i, j, w, h): string => { diff --git a/plugins/ui/dynamic_components/PlayerStats.config.js b/plugins/ui/dynamic_components/PlayerStats.config.js index b526a2d80..c55d015ec 100644 --- a/plugins/ui/dynamic_components/PlayerStats.config.js +++ b/plugins/ui/dynamic_components/PlayerStats.config.js @@ -7,42 +7,26 @@ export default { width: 60, title: `Personal Stats`, icon: icons.person, - navbar: [ - { name: `All Stats`, actionId: ids.topRanks } - ], + navbar: [{ + name: `All Stats`, + actionId: ids.topRanks + }], grid: { background: '9996', margin: 0.15, headerBackground: '333C' }, - sumsColours: ['FD0','CCC', 'C73', 'FFF'], + sumsColours: ['FD0', 'CCC', 'C73', 'FFF'], nameColumnBackground: '5559', - columnProportions: [ - 2, - 2, - ], + columnProportions: [2, 2], cellHeader: ` Player Info `, - cells: [ - ` Player Login `, - ` Player Nickname `, - ` Player Nation `, - ` Player Privilege `, - ` Server Rank `, - ` Server Average `, - ` Total Playtime `, - ` Total Records `, - ` Total Map Votes `, - ` Top Sums `, - ` Visits Amount `, - ` Donations Amount `, - ` Wins Amount `, - ` Account Type `, - ` Last Seen ` - ], + cells: [` Player Login `, ` Player Nickname `, ` Player Nation `, ` Player Privilege `, ` Server Rank `, + ` Server Average `, ` Total Playtime `, ` Total Records `, ` Total Map Votes `, ` Top Sums `, ` Visits Amount `, + ` Donations Amount `, ` Wins Amount `, ` Account Type `, ` Last Seen `], command: { aliases: ['ps', 'stats', 'playerstats', 'pls'], help: `Display the detailed stats page for a player.`, privilege: 0, error: `${p.error}There's no information about this player in the database.` } -} \ No newline at end of file +} diff --git a/plugins/ui/dynamic_components/ServerInfo.component.ts b/plugins/ui/dynamic_components/ServerInfo.component.ts index 95dc8c00c..1c389fa70 100644 --- a/plugins/ui/dynamic_components/ServerInfo.component.ts +++ b/plugins/ui/dynamic_components/ServerInfo.component.ts @@ -3,10 +3,10 @@ * @since 1.1 */ -import { Grid, componentIds, type GridCellFunction, type GridCellObject, centeredText, closeButton, PopupWindow } from '../UI.js' +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, PopupWindow } from '../UI.js' import config from './ServerInfo.config.js' -import { arch, cpus, loadavg, totalmem, freemem, platform } from 'node:os' -import { uptime, version, memoryUsage } from 'node:process' +import { arch, cpus, freemem, loadavg, platform, totalmem } from 'node:os' +import { memoryUsage, uptime, version } from 'node:process' import 'dotenv/config' export default class ServerInfoWindow extends PopupWindow { @@ -43,9 +43,8 @@ export default class ServerInfoWindow extends PopupWindow { const serverMaxRecs: string = tm.config.controller.localRecordsLimit.toString() const serverMapCount: string = tm.maps.count.toString() const serverVisitorsCount: string = tm.players.totalCount.toString() - return [dedicatedUptime, dedicatedVersion, dedicatedBuild, serverName, serverLogin, - serverZone, serverRights, serverMaxPlayers, serverMaxSpecs, serverMaxRecs, - serverMapCount, serverVisitorsCount] + return [dedicatedUptime, dedicatedVersion, dedicatedBuild, serverName, serverLogin, serverZone, serverRights, + serverMaxPlayers, serverMaxSpecs, serverMaxRecs, serverMapCount, serverVisitorsCount] } private getHostInfo(): string[] { @@ -62,9 +61,8 @@ export default class ServerInfoWindow extends PopupWindow { // Node information const nodeVersion: string = version const nodeRAMUsage = String(~~(memoryUsage().heapTotal / (1024 ** 2)) + ` MB`) - return [osUptime, osArch, osCPU, osCPULoad, osRAM, osKernel, - trakmanVersion, trakmanUptime, nodeVersion, nodeRAMUsage, - tm.db.dbVersion, tm.db.dbSize] + return [osUptime, osArch, osCPU, osCPULoad, osRAM, osKernel, trakmanVersion, trakmanUptime, nodeVersion, + nodeRAMUsage, tm.db.dbVersion, tm.db.dbSize] } protected onOpen(info: tm.ManialinkClickInfo): void { @@ -90,18 +88,15 @@ export default class ServerInfoWindow extends PopupWindow { protected async constructContent(): Promise { const serverInfo: string[] = await this.getServerInfo() const hostInfo: string[] = await this.getHostInfo() - const headers: GridCellObject[] = [ - { - background: config.grid.headerBackground, - colspan: 2, - callback: (i, j, w, h): string => centeredText(config.serverCellHeader, w, h), - }, - { - background: config.grid.headerBackground, - colspan: 2, - callback: (i, j, w, h): string => centeredText(config.hostCellHeader, w, h), - }, - ] + const headers: GridCellObject[] = [{ + background: config.grid.headerBackground, + colspan: 2, + callback: (i, j, w, h): string => centeredText(config.serverCellHeader, w, h) + }, { + background: config.grid.headerBackground, + colspan: 2, + callback: (i, j, w, h): string => centeredText(config.hostCellHeader, w, h) + }] const nameCell: GridCellObject = { background: config.nameColumnBackground, callback: (i, j, w, h): string => { diff --git a/plugins/ui/dynamic_components/ServerInfo.config.js b/plugins/ui/dynamic_components/ServerInfo.config.js index f358c9775..141f92d7f 100644 --- a/plugins/ui/dynamic_components/ServerInfo.config.js +++ b/plugins/ui/dynamic_components/ServerInfo.config.js @@ -4,54 +4,28 @@ import icons from '../config/Icons.js' export default { title: `Server Overview`, icon: icons.trakman, - navbar: [ - { name: `Changelog`, actionId: ids.changelog } - ], + navbar: [{ + name: `Changelog`, + actionId: ids.changelog + }], grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, nameColumnBackground: '5559', - columnProportions: [ - 2, - 2, - 2, - 2 - ], + columnProportions: [2, 2, 2, 2], serverCellHeader: ` Server Information `, - serverCells: [ - ` Dedicated Server Uptime `, - ` Dedicated Server Version `, - ` Dedicated Server Build `, - ` Server Name `, - ` Server Login `, - ` Server Zone `, - ` Server Rights `, - ` Server Max Players `, - ` Server Max Spectators `, - ` Server Max Records `, - ` Server Map Count `, - ` Server Visitor Count`, - ], + serverCells: [` Dedicated Server Uptime `, ` Dedicated Server Version `, ` Dedicated Server Build `, ` Server Name `, + ` Server Login `, ` Server Zone `, ` Server Rights `, ` Server Max Players `, ` Server Max Spectators `, + ` Server Max Records `, ` Server Map Count `, ` Server Visitor Count`], hostCellHeader: ` Host Information `, - hostCells: [ - ` System Uptime `, - ` System Architecture `, - ` System CPU `, - ` System CPU Load `, - ` System Free RAM `, - ` System Kernel `, - ` Trakman Version `, - ` Trakman Uptime `, - ` NodeJS Version `, - ` NodeJS RAM Usage `, - ` PostgreSQL Version `, - ` PostgreSQL DB Size ` - ], + hostCells: [` System Uptime `, ` System Architecture `, ` System CPU `, ` System CPU Load `, ` System Free RAM `, + ` System Kernel `, ` Trakman Version `, ` Trakman Uptime `, ` NodeJS Version `, ` NodeJS RAM Usage `, + ` PostgreSQL Version `, ` PostgreSQL DB Size `], command: { aliases: ['server', 'serverinfo', 'sinfo', 'si'], help: `Display the detailed server information.`, privilege: 0 } -} \ No newline at end of file +} diff --git a/plugins/ui/dynamic_components/TMXDetailsWindow.component.ts b/plugins/ui/dynamic_components/TMXDetailsWindow.component.ts index bcd3065a6..eda0cda3b 100644 --- a/plugins/ui/dynamic_components/TMXDetailsWindow.component.ts +++ b/plugins/ui/dynamic_components/TMXDetailsWindow.component.ts @@ -1,9 +1,9 @@ -/** +/** * @author wiseraven * @since 1.3.3 */ -import { componentIds, Grid, type GridCellFunction, centeredText, closeButton, leftAlignedText, type GridCellObject, PopupWindow } from '../../ui/UI.js' +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, leftAlignedText, PopupWindow } from '../../ui/UI.js' import config from './TMXDetailsWindow.config.js' import { tmx } from '../../tmx/Tmx.js' @@ -24,10 +24,10 @@ export default class TMXDetailsWindow extends PopupWindow { tmx.onMapChange((): void => { this.reRender() }) - this.grid = new Grid(this.contentWidth, this.contentHeight, config.columnProportions, config.rowProportions, config.grid) + this.grid = new Grid(this.contentWidth, this.contentHeight, config.columnProportions, config.rowProportions, + config.grid) } - private reRender(): void { const logins: string[] = this.getPlayersWithWindowOpen() for (const login of logins) { @@ -36,36 +36,37 @@ export default class TMXDetailsWindow extends PopupWindow { } private constructScreenshot(width: number, height: number, map: tm.TMXMap): string { - const image: string = map === undefined - ? config.noScreenshot["Stadium"] // this shouldn't ever be used anyway cause the window isn't constructed if the map is undefined + const image: string = map === undefined ? config.noScreenshot["Stadium"] // this shouldn't ever be used anyway cause the window isn't constructed if the map is undefined : tm.utils.safeString(map.thumbnailUrl + `&.jpeg`) return ` - ${centeredText(config.notLoaded, width, height, { textScale: config.notLoadedTextscale, yOffset: -1 })}` + ${centeredText(config.notLoaded, width, height, { + textScale: config.notLoadedTextscale, + yOffset: -1 + })}` } private constructTmxRecordsXml(width: number, height: number, replays: tm.TMXReplay[] = []): string { - const grid = new Grid(width, height, config.tmxRecordsProportions, new Array(config.tmxRecordsAmount + 1).fill(1), - { margin: config.margin, background: config.gridBackground, headerBackground: config.iconBackground }) + const grid = new Grid(width, height, config.tmxRecordsProportions, new Array(config.tmxRecordsAmount + 1).fill(1), { + margin: config.margin, + background: config.gridBackground, + headerBackground: config.iconBackground + }) const options = { textScale: config.recordTextScale } - const arr: (GridCellFunction | GridCellObject)[] = [ - (i, j, w, h) => centeredText(' Lp. ', w, h, options), - (i, j, w, h) => centeredText(' Time ', w, h, options), - (i, j, w, h) => centeredText(' Name ', w, h, options), - (i, j, w, h) => centeredText(' Date ', w, h, options), - (i, j, w, h) => centeredText(' Dl. ', w, h, options), - ] + const arr: (GridCellFunction | GridCellObject)[] = [(i, j, w, h) => centeredText(' Lp. ', w, h, options), + (i, j, w, h) => centeredText(' Time ', w, h, options), (i, j, w, h) => centeredText(' Name ', w, h, options), + (i, j, w, h) => centeredText(' Date ', w, h, options), (i, j, w, h) => centeredText(' Dl. ', w, h, options)] const indexCell: GridCellObject = { callback: (i, j, w, h) => centeredText(i.toString(), w, h, options), background: config.iconBackground } - const timeCell: GridCellFunction = (i, j, w, h) => centeredText(replays[i - 1] !== undefined ? - tm.utils.getTimeString(replays[i - 1]?.time) : config.defaultTime, w, h, options) - const nameCell: GridCellFunction = (i, j, w, h) => - tm.utils.isMultibyte(replays[i - 1]?.name) - ? leftAlignedText(tm.utils.safeString(config.defaultTime), w, h, options) - : leftAlignedText(tm.utils.safeString(replays[i - 1]?.name ?? config.defaultTime), w, h, options) - const dateCell: GridCellFunction = (i, j, w, h) => centeredText(replays[i - 1] !== undefined ? - tm.utils.formatDate(replays[i - 1]?.recordDate, true) : config.defaultTime, w, h, options) + const timeCell: GridCellFunction = (i, j, w, h) => centeredText( + replays[i - 1] !== undefined ? tm.utils.getTimeString(replays[i - 1]?.time) : config.defaultTime, w, h, options) + const nameCell: GridCellFunction = (i, j, w, h) => tm.utils.isMultibyte(replays[i - 1]?.name) ? + leftAlignedText(tm.utils.safeString(config.defaultTime), w, h, options) : + leftAlignedText(tm.utils.safeString(replays[i - 1]?.name ?? config.defaultTime), w, h, options) + const dateCell: GridCellFunction = (i, j, w, h) => centeredText( + replays[i - 1] !== undefined ? tm.utils.formatDate(replays[i - 1]?.recordDate, true) : config.defaultTime, w, h, + options) const downloadCell: GridCellObject = { callback: (i, j, w, h) => replays[i - 1] !== undefined ? ` ` diff --git a/plugins/ui/static_components/race/AdminPanel.config.js b/plugins/ui/static_components/race/AdminPanel.config.js index 0d21af636..566d7ed89 100644 --- a/plugins/ui/static_components/race/AdminPanel.config.js +++ b/plugins/ui/static_components/race/AdminPanel.config.js @@ -1,5 +1,6 @@ import cfg from '../../config/RaceUi.js' import icons from '../../config/Icons.js' + const p = tm.utils.palette export default { @@ -8,7 +9,7 @@ export default { privilege: 1, margin: cfg.margin, background: cfg.background, - title: "Admin Panel", + title: 'Admin Panel', icon: icons.gears, disabledColour: '333C', public: true, @@ -37,4 +38,4 @@ export default { restart: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has restarted the ongoing map.`, endRound: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has forced the ongoing round to end.` } -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/race/BestCps.component.ts b/plugins/ui/static_components/race/BestCps.component.ts index f8309e4c8..2faa3e4f8 100644 --- a/plugins/ui/static_components/race/BestCps.component.ts +++ b/plugins/ui/static_components/race/BestCps.component.ts @@ -3,12 +3,16 @@ * @since 0.1 */ -import { componentIds, Grid, centeredText, rightAlignedText, leftAlignedText, Paginator, StaticHeader, StaticComponent, type StaticHeaderOptions } from '../../UI.js' +import { centeredText, componentIds, Grid, leftAlignedText, Paginator, rightAlignedText, StaticComponent, StaticHeader, type StaticHeaderOptions } from '../../UI.js' import config from './BestCps.config.js' export default class BestCps extends StaticComponent { - private readonly bestCps: { login: string, time: number, nickname: string }[] = [] + private readonly bestCps: { + login: string, + time: number, + nickname: string + }[] = [] private readonly header: StaticHeader private readonly headerBg: string private readonly headerHeight: number @@ -28,15 +32,18 @@ export default class BestCps extends StaticComponent { this.grid = this.getGrid() this.paginator = new Paginator(this.id, 0, 0, 0) this.renderOnEvent('PlayerCheckpoint', (info: tm.CheckpointInfo) => { - if (this.bestCps[info.index] === undefined || - (this.bestCps[info.index].time > info.time && tm.getGameMode() !== 'Stunts') || - (this.bestCps[info.index].time < info.time && tm.getGameMode() === 'Stunts')) { - this.bestCps[info.index] = { login: info.player.login, time: info.time, nickname: info.player.nickname } + if (this.bestCps[info.index] === undefined || (this.bestCps[info.index].time > info.time && tm.getGameMode() !== 'Stunts') || (this.bestCps[info.index].time < info.time && tm.getGameMode() === 'Stunts')) { + this.bestCps[info.index] = { + login: info.player.login, + time: info.time, + nickname: info.player.nickname + } this.paginator.setPageCount(Math.ceil(this.bestCps.length / config.entries)) this.newestCp = info.index return this.display() } - const page: number = this.paginator.setPageForLogin(info.player.login, Math.ceil((info.index + 1) / config.entries)) + const page: number = this.paginator.setPageForLogin(info.player.login, + Math.ceil((info.index + 1) / config.entries)) return this.displayToPlayer(info.player.login, { page }) }) this.renderOnEvent('BeginMap', () => { @@ -70,8 +77,9 @@ export default class BestCps extends StaticComponent { getGrid(): Grid { if (config.horizontal) { const rows = Math.min(config.horizontalMaxRows, Math.ceil(tm.maps.current.checkpointsAmount / config.entries)) - return new Grid(config.horizontalModeWidth, config.entryHeight * rows, new Array(config.entries).fill(config.columnProportions).flat(), - new Array(rows).fill(1), { margin: config.margin }) + return new Grid(config.horizontalModeWidth, config.entryHeight * rows, + new Array(config.entries).fill(config.columnProportions).flat(), new Array(rows).fill(1), + { margin: config.margin }) } return new Grid(config.width + config.margin * 2, this.contentHeight, config.columnProportions, new Array(config.entries).fill(1), { margin: config.margin }) @@ -91,7 +99,9 @@ export default class BestCps extends StaticComponent { return arr } - displayToPlayer(login: string, params?: { page?: number }) { + displayToPlayer(login: string, params?: { + page?: number + }) { if (!this.isDisplayed) { return } if (config.hidePanel && this.hasPanelsHidden(login)) { return this.hideToPlayer(login) @@ -107,7 +117,8 @@ export default class BestCps extends StaticComponent { ${this.constructText(login, 1)} - `, login + `, + login } } return { @@ -121,7 +132,8 @@ export default class BestCps extends StaticComponent { ${this.constructText(login, page)} - `, login + `, + login } } @@ -149,7 +161,8 @@ export default class BestCps extends StaticComponent { let buttonsXml: string = '' for (let i: number = 0; i < buttonAmount; i++) { const action: string = ids[i] === undefined ? '' : `action="${ids[i]}"` - const icon: string = icons[i] === undefined ? '' : `` buttonsXml += ` @@ -157,7 +170,11 @@ export default class BestCps extends StaticComponent { ` } return ` - ${rightAlignedText(config.title, config.width - (headerCfg.squareWidth + config.margin) * (1 + buttonAmount), this.headerHeight, { textScale: config.textScale, yOffset: -0.1 })} + ${rightAlignedText(config.title, config.width - (headerCfg.squareWidth + config.margin) * (1 + buttonAmount), + this.headerHeight, { + textScale: config.textScale, + yOffset: -0.1 + })} @@ -175,7 +192,10 @@ export default class BestCps extends StaticComponent { const indexCell = (i: number, j: number, w: number, h: number): string => { const index = config.horizontal ? i * config.entries + j / 3 : i + cpIndex const bg = `` - return this.bestCps[index] === undefined ? '' : bg + (centeredText((index + 1).toString(), w, h, { textScale: config.textScale, padding: config.textPadding })) + return this.bestCps[index] === undefined ? '' : bg + (centeredText((index + 1).toString(), w, h, { + textScale: config.textScale, + padding: config.textPadding + })) } const timeCell = (i: number, j: number, w: number, h: number): string => { @@ -185,17 +205,20 @@ export default class BestCps extends StaticComponent { if (cp === undefined) { return '' } let format: string = cp.login === login ? `` : '' if (index === this.newestCp) { format = `` } - return bg + format + centeredText(tm.utils.getTimeString(cp.time), w, h, { textScale: config.textScale, padding: config.textPadding }) + return bg + format + centeredText(tm.utils.getTimeString(cp.time), w, h, { + textScale: config.textScale, + padding: config.textPadding + }) } const nicknameCell = (i: number, j: number, w: number, h: number): string => { const index = config.horizontal ? i * config.entries + (j - 2) / 3 : i + cpIndex const bg = `` - return this.bestCps[index] === undefined ? '' : - bg + (this.bestCps[index] === undefined ? '' : - leftAlignedText(tm.utils.safeString(tm.utils.strip( - this.bestCps[index].nickname, false)), w, h, - { textScale: config.textScale, padding: config.textPadding })) + return this.bestCps[index] === undefined ? '' : bg + (this.bestCps[index] === undefined ? '' : + leftAlignedText(tm.utils.safeString(tm.utils.strip(this.bestCps[index].nickname, false)), w, h, { + textScale: config.textScale, + padding: config.textPadding + })) } const cpsToDisplay: number = this.cpAmount - cpIndex diff --git a/plugins/ui/static_components/race/BestCps.config.js b/plugins/ui/static_components/race/BestCps.config.js index ce0764444..442bef52e 100644 --- a/plugins/ui/static_components/race/BestCps.config.js +++ b/plugins/ui/static_components/race/BestCps.config.js @@ -10,15 +10,11 @@ export default { horizontalModePosX: cfg.leftPosition + cfg.width + cfg.margin, posY: cfg.topBorder, side: false, - title: "Best Cps", + title: 'Best Cps', icon: icons.bestClock, margin: cfg.margin, background: cfg.background, - columnProportions: [ - 1, - 2.8, - 4 - ], + columnProportions: [1, 2.8, 4], selfColour: `${tm.utils.palette.green}F`, newestColour: `${tm.utils.palette.yellow}F`, textScale: 0.87, @@ -29,5 +25,5 @@ export default { downIconHover: icons.pageRightHover, horizontal: false, horizontalMaxRows: 5, - hidePanel: true, + hidePanel: true } diff --git a/plugins/ui/static_components/race/BestFinishes.component.ts b/plugins/ui/static_components/race/BestFinishes.component.ts index 532c48db5..53867e634 100644 --- a/plugins/ui/static_components/race/BestFinishes.component.ts +++ b/plugins/ui/static_components/race/BestFinishes.component.ts @@ -3,12 +3,16 @@ * @since 0.1 */ -import { componentIds, Grid, centeredText, leftAlignedText, StaticHeader, StaticComponent } from '../../UI.js' +import { centeredText, componentIds, Grid, leftAlignedText, StaticComponent, StaticHeader } from '../../UI.js' import config from './BestFinishes.config.js' export default class BestFinishes extends StaticComponent { - private readonly bestFinishes: { login: string, time: number, nickname: string }[] = [] + private readonly bestFinishes: { + login: string, + time: number, + nickname: string + }[] = [] private readonly header: StaticHeader private readonly headerBg: string private readonly headerHeight: number @@ -21,8 +25,8 @@ export default class BestFinishes extends StaticComponent { this.headerBg = this.header.options.textBackground this.headerHeight = this.header.options.height if (config.horizontal) { - this.grid = new Grid(config.horizontalModeWidth, config.entryHeight, new Array(config.entries).fill(config.columnProportions).flat(), - [1], { margin: config.margin }) + this.grid = new Grid(config.horizontalModeWidth, config.entryHeight, + new Array(config.entries).fill(config.columnProportions).flat(), [1], { margin: config.margin }) } else { const contentHeight = ((config.entryHeight + config.margin * 2) * config.entries) - (this.headerHeight + config.margin) this.grid = new Grid(config.width + config.margin * 2, contentHeight, config.columnProportions, @@ -38,7 +42,11 @@ export default class BestFinishes extends StaticComponent { } if (index === -1) { index = this.bestFinishes.length } if (index < config.entries) { - this.bestFinishes.splice(index, 0, { login: info.login, time: info.time, nickname: info.nickname }) + this.bestFinishes.splice(index, 0, { + login: info.login, + time: info.time, + nickname: info.nickname + }) this.bestFinishes.length = Math.min(config.entries, this.bestFinishes.length) this.newestFinish = index return this.display() @@ -106,7 +114,8 @@ export default class BestFinishes extends StaticComponent { ${this.constructGrid(login)} - `, login + `, + login } } @@ -125,8 +134,10 @@ export default class BestFinishes extends StaticComponent { index = i + 1 } const bg = `` - return this.bestFinishes[i] === undefined ? '' : bg + (centeredText((index).toString(), w, h, - { textScale: config.textScale, padding: config.textPadding })) + return this.bestFinishes[i] === undefined ? '' : bg + (centeredText((index).toString(), w, h, { + textScale: config.textScale, + padding: config.textPadding + })) } const timeCell = (i: number, j: number, w: number, h: number): string => { @@ -136,15 +147,20 @@ export default class BestFinishes extends StaticComponent { if (fin === undefined) { return '' } let format: string = fin.login === login ? `` : '' if (index === this.newestFinish) { format = `` } - return bg + format + centeredText(tm.utils.getTimeString(fin.time), w, h, { textScale: config.textScale, padding: config.textPadding }) + return bg + format + centeredText(tm.utils.getTimeString(fin.time), w, h, { + textScale: config.textScale, + padding: config.textPadding + }) } const nicknameCell = (i: number, j: number, w: number, h: number): string => { const index = config.horizontal ? (j - 2) / 3 : i const bg = `` - return this.bestFinishes[index] === undefined ? '' : bg + - (leftAlignedText(tm.utils.safeString(tm.utils.strip(this.bestFinishes[index].nickname, false)), w, h, - { textScale: config.textScale, padding: config.textPadding })) + return this.bestFinishes[index] === undefined ? '' : + bg + (leftAlignedText(tm.utils.safeString(tm.utils.strip(this.bestFinishes[index].nickname, false)), w, h, { + textScale: config.textScale, + padding: config.textPadding + })) } const arr: ((i: number, j: number, w: number, h: number) => string)[] = [] diff --git a/plugins/ui/static_components/race/BestFinishes.config.js b/plugins/ui/static_components/race/BestFinishes.config.js index a228f8e3c..bb189917a 100644 --- a/plugins/ui/static_components/race/BestFinishes.config.js +++ b/plugins/ui/static_components/race/BestFinishes.config.js @@ -12,19 +12,15 @@ export default { horizontalModePosX: cfg.leftPosition + cfg.width * 2 + cfg.margin * 2, posY: cfg.topBorder, side: true, - title: "Best Finishes", + title: 'Best Finishes', icon: icons.bestFinishes, margin: cfg.margin, background: cfg.background, - columnProportions: [ - 1, - 2.8, - 4 - ], + columnProportions: [1, 2.8, 4], selfColour: `${tm.utils.palette.green}F`, newestColour: `${tm.utils.palette.yellow}F`, textScale: 0.87, textPadding: 0.2, horizontal: false, - hidePanel: true, + hidePanel: true } diff --git a/plugins/ui/static_components/race/CpCounter.component.ts b/plugins/ui/static_components/race/CpCounter.component.ts index 97bf09cac..4d42ab0da 100644 --- a/plugins/ui/static_components/race/CpCounter.component.ts +++ b/plugins/ui/static_components/race/CpCounter.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { componentIds, StaticHeader, centeredText, StaticComponent, type StaticHeaderOptions } from '../../UI.js' +import { centeredText, componentIds, StaticComponent, StaticHeader, type StaticHeaderOptions } from '../../UI.js' import config from './CpCounter.config.js' import { dedimania, type DediRecord } from '../../../dedimania/Dedimania.js' @@ -17,8 +17,18 @@ interface CheckpointData { export default class CpCounter extends StaticComponent { private readonly header: StaticHeader - private prevTimes: { login: string, best?: number, current: number, isFinish: boolean }[] = [] - private prevLapTimes: { login: string, best?: number, current: number, isFinish: boolean }[] = [] + private prevTimes: { + login: string, + best?: number, + current: number, + isFinish: boolean + }[] = [] + private prevLapTimes: { + login: string, + best?: number, + current: number, + isFinish: boolean + }[] = [] constructor() { super(componentIds.cpCounter) @@ -29,7 +39,9 @@ export default class CpCounter extends StaticComponent { const player = tm.players.get(login) if (player === undefined) { return } let best: number | undefined - let lap: undefined | CheckpointData & { cpIndex: number } + let lap: undefined | CheckpointData & { + cpIndex: number + } if (time !== 0) { const local: tm.LocalRecord | undefined = tm.records.getLocal(login) const dedi: DediRecord | undefined = dedimania.isUploadingLaps ? undefined : dedimania.getRecord(login) @@ -39,8 +51,7 @@ export default class CpCounter extends StaticComponent { } if (tm.maps.current.isInLapsMode && tm.maps.current.lapsAmount !== 1) { const local: tm.LocalRecord | undefined = tm.records.getLap(login) - const dedi: DediRecord | undefined = !dedimania.isUploadingLaps ? - undefined : dedimania.getRecord(login) + const dedi: DediRecord | undefined = !dedimania.isUploadingLaps ? undefined : dedimania.getRecord(login) let pb: number | undefined = dedi?.time ?? local?.time if (dedi !== undefined && local !== undefined) { pb = Math.min(local?.time, dedi?.time) @@ -48,15 +59,19 @@ export default class CpCounter extends StaticComponent { const startIndex = tm.maps.current.checkpointsAmount - (tm.maps.current.checkpointsPerLap + 1) lap = { cpIndex: 0, - index: 1, best: pb, + index: 1, + best: pb, current: time - (player.currentCheckpoints[startIndex]?.time ?? 0), isFinish: true } } } const obj = this.displayToPlayer(login, { - index: 0, current: time === 0 ? undefined : time, - best, isFinish: time !== 0, lap + index: 0, + current: time === 0 ? undefined : time, + best, + isFinish: time !== 0, + lap }) if (obj !== undefined) { tm.sendManialink(obj.xml, obj.login) @@ -87,7 +102,10 @@ export default class CpCounter extends StaticComponent { } const cpInfo = target.currentCheckpoints.at(-1) if (cpInfo === undefined) { return } - const ret = this.calculateData({...cpInfo, player: target}) + const ret = this.calculateData({ + ...cpInfo, + player: target + }) if (ret === undefined) { return } tm.sendManialink(ret.xml, player.login) }) @@ -117,11 +135,14 @@ export default class CpCounter extends StaticComponent { if (dedi !== undefined && local !== undefined) { pb = Math.min(local?.checkpoints?.[info.index], dedi?.checkpoints?.[info.index]) } - let lap: undefined | CheckpointData & { cpIndex: number } + let lap: undefined | CheckpointData & { + cpIndex: number + } if (tm.maps.current.isInLapsMode && tm.maps.current.lapsAmount !== 1) { let cpIndex = info.lapCheckpointIndex + 1 const local: tm.LocalRecord | undefined = tm.records.getLap(info.player.login) - const dedi: DediRecord | undefined = !dedimania.isUploadingLaps ? undefined : dedimania.getRecord(info.player.login) + const dedi: DediRecord | undefined = !dedimania.isUploadingLaps ? undefined : + dedimania.getRecord(info.player.login) let pb: number | undefined if (info.isLapFinish) { cpIndex = 0 @@ -130,27 +151,30 @@ export default class CpCounter extends StaticComponent { pb = Math.min(local?.time, dedi?.time) } } else { - pb = dedi?.checkpoints?.[info.lapCheckpointIndex] ?? - local?.checkpoints?.[info.lapCheckpointIndex] + pb = dedi?.checkpoints?.[info.lapCheckpointIndex] ?? local?.checkpoints?.[info.lapCheckpointIndex] if (dedi !== undefined && local !== undefined) { - pb = Math.min(local?.checkpoints?.[info.lapCheckpointIndex], - dedi?.checkpoints?.[info.lapCheckpointIndex]) + pb = Math.min(local?.checkpoints?.[info.lapCheckpointIndex], dedi?.checkpoints?.[info.lapCheckpointIndex]) } } lap = { - cpIndex, index: info.lap + 1, best: pb, + cpIndex, + index: info.lap + 1, + best: pb, current: info.lapCheckpointTime, isFinish: info.isLapFinish } } return this.displayToPlayer(info.player.login, { index: info.index + 1, - best: pb, current: info.time, isFinish: false, lap + best: pb, + current: info.time, + isFinish: false, + lap }) } - private constructTimeXml(login: string, isLap: boolean, icon: string, - isFinish?: boolean, currentTime?: number, bestTime?: number): string { + private constructTimeXml(login: string, isLap: boolean, icon: string, isFinish?: boolean, currentTime?: number, + bestTime?: number): string { const isStunts = tm.getGameMode() === 'Stunts' const arr = isLap ? this.prevLapTimes : this.prevTimes const prev = arr.find(a => a.login === login) @@ -160,7 +184,12 @@ export default class CpCounter extends StaticComponent { bestTime = prev?.best isFinish = prev?.isFinish } else if (prev === undefined) { - arr.push({ login, best: bestTime, current: currentTime, isFinish: isFinish === true }) + arr.push({ + login, + best: bestTime, + current: currentTime, + isFinish: isFinish === true + }) } else { prev.best = bestTime prev.current = currentTime @@ -187,15 +216,21 @@ export default class CpCounter extends StaticComponent { const h: StaticHeaderOptions = this.header.options const w: number = ((config.width - h.squareWidth) - config.margin) / 2 const timeColour: string = '$' + (isFinish === true ? config.colours.finish : config.colours.default) - return `${this.header.constructXml(timeColour + tm.utils.getTimeString(currentTime), - icon, config.side, { rectangleWidth: w, centerText: true })} - + return `${this.header.constructXml(timeColour + tm.utils.getTimeString(currentTime), icon, config.side, { + rectangleWidth: w, + centerText: true + })} + ${centeredText('$' + config.colours.default + differenceString, w, h.height, h)} ` } - displayToPlayer(login: string, params?: CheckpointData & { lap?: CheckpointData & { cpIndex: number } }) { + displayToPlayer(login: string, params?: CheckpointData & { + lap?: CheckpointData & { + cpIndex: number + } + }) { if (!this.isDisplayed) { return } if (config.hidePanel && this.hasPanelsHidden(login)) { return this.hideToPlayer(login) @@ -237,25 +272,36 @@ export default class CpCounter extends StaticComponent { const spectators = tm.players.get(login)?.spectators const logins = spectators !== undefined ? Array.from(spectators) : [] logins.unshift(login) + + let times = '' + if (config.times) { + times = ` + ${cpAmount === 0 ? '' : + this.constructTimeXml(login, false, config.iconBottom, params?.isFinish, params?.current, + params?.best)}` + } + return { xml: ` ${this.getLapsXml(login, params?.lap)} - ${this.header.constructXml('$' + config.colours.default + text, config.icon, config.side, - { rectangleWidth, centerText })} + ${this.header.constructXml('$' + config.colours.default + text, config.icon, config.side, { + rectangleWidth, + centerText + })} ${counterXml} - - ${cpAmount === 0 ? '' : this.constructTimeXml(login, false, config.iconBottom, - params?.isFinish, params?.current, params?.best)} - + ${times} - `, login: logins + `, + login: logins } } - private getLapsXml(login: string, data?: CheckpointData & { cpIndex: number }) { + private getLapsXml(login: string, data?: CheckpointData & { + cpIndex: number + }) { if (!tm.maps.current.isInLapsMode || tm.maps.current.lapsAmount === 1) { return '' } const h: StaticHeaderOptions = this.header.options const lapCounterW: number = config.lap.lapCounterWidth @@ -287,11 +333,11 @@ export default class CpCounter extends StaticComponent { ` return ` - ${cpAmount === 0 ? '' : this.constructTimeXml(login, true, config.lap.iconTop, - data?.isFinish, data?.current, data?.best)} + ${cpAmount === 0 ? '' : + this.constructTimeXml(login, true, config.lap.iconTop, data?.isFinish, data?.current, data?.best)} - ${this.header.constructXml('$' + config.colours.default + config.lap.lapText, config.lap.iconBottom, - config.side, { rectangleWidth: lapTextW })} + ${this.header.constructXml('$' + config.colours.default + config.lap.lapText, config.lap.iconBottom, config.side, + { rectangleWidth: lapTextW })} ${lapCounterXml} diff --git a/plugins/ui/static_components/race/CpCounter.config.js b/plugins/ui/static_components/race/CpCounter.config.js index 3ba30f140..6486c852a 100644 --- a/plugins/ui/static_components/race/CpCounter.config.js +++ b/plugins/ui/static_components/race/CpCounter.config.js @@ -1,22 +1,22 @@ import cfg from '../../config/RaceUi.js' import icons from '../../config/Icons.js' + const p = tm.utils.palette export default { height: 2.17, - width: cfg.width, - // Change this ONLY if you want the widget to be on the side + width: cfg.width, // Change this ONLY if you want the widget to be on the side useRelative: false, posX: 12, posY: -37, - margin: cfg.marginBig, + margin: cfg.margin, side: true, + times: true, rectangleWidth: 7.7, icon: icons.waypoint, iconBottom: icons.clock, text: 'Checkpoint', - noCpsText: 'No Checkpoints', - // 1.7 is the squarewidth + noCpsText: 'No Checkpoints', // 1.7 is the squarewidth noCpsWidth: cfg.width - cfg.margin - 1.7, defaultDifference: '$CCC-:--.--', finishText: `$${p.green}Finish`, @@ -32,12 +32,12 @@ export default { iconBottom: icons.lap, iconTop: icons.clockLap, lapText: 'Lap', - lapTextWidth: 2.8, - lapCounterWidth: 3.1, + lapTextWidth: 2.7, + lapCounterWidth: 3.2, checkpointText: 'CP', - checkpointTextWidth: 2.8, + checkpointTextWidth: 2 }, background: cfg.background, finishTextDuration: 3000, - hidePanel: false, -} \ No newline at end of file + hidePanel: false +} diff --git a/plugins/ui/static_components/race/DediRanking.component.ts b/plugins/ui/static_components/race/DediRanking.component.ts index ca70bd69c..a5aee3d50 100644 --- a/plugins/ui/static_components/race/DediRanking.component.ts +++ b/plugins/ui/static_components/race/DediRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.1 */ -import { RecordList, componentIds, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, RecordList, StaticComponent, StaticHeader } from '../../UI.js' import { dedimania } from '../../../dedimania/Dedimania.js' import config from './DediRanking.config.js' @@ -53,7 +53,10 @@ export default class DediRanking extends StaticComponent { ${this.header.constructXml(config.title, config.icon, this.side, { actionId: componentIds.dediCps })} - ${this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, dedimania.records.map(a => ({ ...a, name: a.nickname })))} + ${this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, dedimania.records.map(a => ({ + ...a, + name: a.nickname + })))} `, @@ -64,11 +67,14 @@ export default class DediRanking extends StaticComponent { getEntries(): number { if (tm.getGameMode() === 'Teams') { return config.teamsEntries - } if (tm.getGameMode() === 'Rounds') { + } + if (tm.getGameMode() === 'Rounds') { return config.roundsEntries - } if (tm.getGameMode() === 'Cup') { + } + if (tm.getGameMode() === 'Cup') { return config.cupEntries - } if (tm.getGameMode() === 'Laps') { + } + if (tm.getGameMode() === 'Laps') { return config.lapsEntries } return config.entries @@ -81,11 +87,14 @@ export default class DediRanking extends StaticComponent { getTopCount(): number { if (tm.getGameMode() === 'Teams') { return config.teamsTopCount - } if (tm.getGameMode() === 'Rounds') { + } + if (tm.getGameMode() === 'Rounds') { return config.roundsTopCount - } if (tm.getGameMode() === 'Cup') { + } + if (tm.getGameMode() === 'Cup') { return config.cupTopCount - } if (tm.getGameMode() === 'Laps') { + } + if (tm.getGameMode() === 'Laps') { return config.lapsTopCount } return config.topCount @@ -98,8 +107,9 @@ export default class DediRanking extends StaticComponent { private getRecordList(): void { this.recordList?.destroy?.() - this.recordList = new RecordList('race', this.id, config.width, this.getHeight() - (this.header.options.height + config.margin), - this.getEntries(), this.side, this.getTopCount(), this.maxDedis, config.displayNoRecordEntry) + this.recordList = new RecordList('race', this.id, config.width, + this.getHeight() - (this.header.options.height + config.margin), this.getEntries(), this.side, this.getTopCount(), + this.maxDedis, config.displayNoRecordEntry) this.recordList.onClick((info: tm.ManialinkClickInfo): void => { if (this.reduxModeEnabled) { return } const obj = this.displayToPlayer(info.login) diff --git a/plugins/ui/static_components/race/DediRanking.config.js b/plugins/ui/static_components/race/DediRanking.config.js index 72539ff69..f1e166f02 100644 --- a/plugins/ui/static_components/race/DediRanking.config.js +++ b/plugins/ui/static_components/race/DediRanking.config.js @@ -9,7 +9,7 @@ export default { lapsEntries: 7, entryHeight: 2.15, width: cfg.width, - title: "Dedi Records", + title: 'Dedi Records', icon: icons.chartDedi, margin: cfg.margin, topCount: 5, diff --git a/plugins/ui/static_components/race/DonationPanel.component.ts b/plugins/ui/static_components/race/DonationPanel.component.ts index c4a2a82d1..f692cf24d 100644 --- a/plugins/ui/static_components/race/DonationPanel.component.ts +++ b/plugins/ui/static_components/race/DonationPanel.component.ts @@ -4,7 +4,7 @@ */ import { donations } from '../../../donations/Donations.js' -import { componentIds, StaticHeader, centeredText, StaticComponent } from '../../UI.js' +import { centeredText, componentIds, StaticComponent, StaticHeader } from '../../UI.js' import config from './DonationPanel.config.js' export default class DonationPanel extends StaticComponent { @@ -33,7 +33,10 @@ export default class DonationPanel extends StaticComponent { display() { if (!this.isDisplayed) { return } - return { xml: this.xml, login: tm.players.list.filter(a => a.isUnited).map(a => a.login) } + return { + xml: this.xml, + login: tm.players.list.filter(a => a.isUnited).map(a => a.login) + } } displayToPlayer(login: string) { @@ -42,7 +45,10 @@ export default class DonationPanel extends StaticComponent { return this.hideToPlayer(login) } if (tm.players.get(login)?.isUnited) { - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } } diff --git a/plugins/ui/static_components/race/DonationPanel.config.js b/plugins/ui/static_components/race/DonationPanel.config.js index f82b653d5..30723054e 100644 --- a/plugins/ui/static_components/race/DonationPanel.config.js +++ b/plugins/ui/static_components/race/DonationPanel.config.js @@ -2,19 +2,12 @@ import cfg from '../../config/RaceUi.js' import icons from '../../config/Icons.js' export default { - title: "Donations", + title: 'Donations', height: 4.49, width: cfg.width, icon: icons.cash, margin: cfg.margin, background: cfg.background, hidePanel: true, - amounts: [ - 50, - 100, - 200, - 500, - 1000, - 5000 - ] -} \ No newline at end of file + amounts: [50, 100, 200, 500, 1000, 5000] +} diff --git a/plugins/ui/static_components/race/KarmaWidget.component.ts b/plugins/ui/static_components/race/KarmaWidget.component.ts index 5388c0265..73f097be2 100644 --- a/plugins/ui/static_components/race/KarmaWidget.component.ts +++ b/plugins/ui/static_components/race/KarmaWidget.component.ts @@ -3,7 +3,7 @@ * @since 0.1 */ -import { centeredText, Grid, type GridCellFunction, componentIds, StaticHeader, addManialinkListener, StaticComponent } from '../../UI.js' +import { addManialinkListener, centeredText, componentIds, Grid, type GridCellFunction, StaticComponent, StaticHeader } from '../../UI.js' import { maniakarma, type MKMapVotes } from '../../../maniakarma/Maniakarma.js' import config from './KarmaWidget.config.js' import { actions } from '../../../actions/Actions.js' @@ -20,8 +20,11 @@ export default class KarmaWidget extends StaticComponent { super(componentIds.karma) this.header = new StaticHeader('race') this.headerH = this.header.options.height - this.grid = new Grid((config.width + config.margin - config.buttonWidth) / 2, config.margin + config.height - this.headerH, - new Array(3).fill(1), new Array(3).fill(1), { background: config.background, margin: config.margin }) + this.grid = new Grid((config.width + config.margin - config.buttonWidth) / 2, + config.margin + config.height - this.headerH, new Array(3).fill(1), new Array(3).fill(1), { + background: config.background, + margin: config.margin + }) this.renderOnEvent('KarmaVote', () => this.display()) maniakarma.onMapFetch(() => this.sendMultipleManialinks(this.display())) maniakarma.onVote(() => this.sendMultipleManialinks(this.display())) @@ -51,7 +54,10 @@ export default class KarmaWidget extends StaticComponent { if (config.hidePanel && this.hasPanelsHidden(login)) { return this.hideToPlayer(login) } - return { xml: this.constructXml(login), login } + return { + xml: this.constructXml(login), + login + } } private constructXml(login: string): string { @@ -106,9 +112,11 @@ export default class KarmaWidget extends StaticComponent { const karmaStr: string = karma !== -1 ? (~~karma).toString() : config.defaultText const mkKarmaStr: string = mkVoteCount !== 0 ? (~~mkKarma).toString() : config.defaultText const mkCountStr: string = maniakarma.isEnabled ? mkVoteCount.toString() : config.defaultText - const options = { padding: config.textPadding, textScale: config.textScale } - const arr: GridCellFunction[] = [ - (i, j, w, h): string => ``, + const options = { + padding: config.textPadding, + textScale: config.textScale + } + const arr: GridCellFunction[] = [(i, j, w, h): string => ``, (i, j, w, h): string => ``, (i, j, w, h): string => ` centeredText(karmaStr, w, h, options), (i, j, w, h): string => centeredText(mkKarmaStr, w, h, options), - (i, j, w, h): string => ``, + (i, j, w, + h): string => ``, (i, j, w, h): string => centeredText(voteCount.toString(), w, h, options), - (i, j, w, h): string => centeredText(mkCountStr, w, h, options), - ] + (i, j, w, h): string => centeredText(mkCountStr, w, h, options)] return this.grid.constructXml(arr) } @@ -135,8 +143,12 @@ export default class KarmaWidget extends StaticComponent { const colour: string = this.options[i] === personalVote ? config.selfColour : config.mkColours[i] ret += ` - ${centeredText(e, config.buttonWidth - (config.margin * 2), h - config.margin, - { xOffset: config.margin, yOffset: config.margin + h * i + offsetFix, padding: 0, textScale })}` + ${centeredText(e, config.buttonWidth - (config.margin * 2), h - config.margin, { + xOffset: config.margin, + yOffset: config.margin + h * i + offsetFix, + padding: 0, + textScale + })}` } return ret } diff --git a/plugins/ui/static_components/race/KarmaWidget.config.js b/plugins/ui/static_components/race/KarmaWidget.config.js index e928ee667..055550f4e 100644 --- a/plugins/ui/static_components/race/KarmaWidget.config.js +++ b/plugins/ui/static_components/race/KarmaWidget.config.js @@ -4,16 +4,11 @@ import icons from '../../config/Icons.js' export default { height: 8.8, width: cfg.width, - title: "Karma", + title: 'Karma', icon: icons.karmaStats, margin: cfg.margin, hidePanel: true, - icons: [ - icons.karmaLocal, - icons.karmaGlobal, - icons.karmaPulse, - icons.peopleKarma - ], + icons: [icons.karmaLocal, icons.karmaGlobal, icons.karmaPulse, icons.peopleKarma], colours: ['0F8A', '0D6A', '0B4A', 'BO4A', 'D06A', 'F08A'], mkColours: ['0F0A', '0D0A', '0B0A', 'BOOA', 'D00A', 'F00A'], options: ['+++', '++', '+', '-', '--', '---'], @@ -31,4 +26,4 @@ export default { scale: 1, offset: -0.3 } -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/race/LapRanking.component.ts b/plugins/ui/static_components/race/LapRanking.component.ts index d48f175a3..e2eaf8d90 100644 --- a/plugins/ui/static_components/race/LapRanking.component.ts +++ b/plugins/ui/static_components/race/LapRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.1 */ -import { RecordList, componentIds, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, RecordList, StaticComponent, StaticHeader } from '../../UI.js' import config from './LapRanking.config.js' @@ -50,8 +50,9 @@ export default class LapRanking extends StaticComponent { let entries = config.entries let height = this.getHeight() this.recordList?.destroy?.() - this.recordList = new RecordList('race', this.id, config.width, height - (this.header.options.height + config.margin), - entries, this.side, config.topCount, tm.records.maxLocalsAmount, config.displayNoRecordEntry) + this.recordList = new RecordList('race', this.id, config.width, + height - (this.header.options.height + config.margin), entries, this.side, config.topCount, + tm.records.maxLocalsAmount, config.displayNoRecordEntry) this.recordList.onClick((info: tm.ManialinkClickInfo): void => { if (this.reduxModeEnabled) { return } const obj = this.displayToPlayer(info.login) @@ -75,11 +76,18 @@ export default class LapRanking extends StaticComponent { xml: ` - ${this.header.constructXml(config.title, config.icon, this.side, { actionId:/*TODO MAKE WINDOW FOR THIS */ componentIds.localCps })} + ${this.header.constructXml(config.title, config.icon, this.side, + { actionId:/*TODO MAKE WINDOW FOR THIS */ componentIds.localCps })} ${this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, tm.records.lap - .map(a => ({ name: a.nickname, time: a.time, date: a.date, checkpoints: a.checkpoints, login: a.login })) - .slice(0, tm.records.maxLocalsAmount))} + .map(a => ({ + name: a.nickname, + time: a.time, + date: a.date, + checkpoints: a.checkpoints, + login: a.login + })) + .slice(0, tm.records.maxLocalsAmount))} `, diff --git a/plugins/ui/static_components/race/LapRanking.config.js b/plugins/ui/static_components/race/LapRanking.config.js index b6d8cea82..071d38812 100644 --- a/plugins/ui/static_components/race/LapRanking.config.js +++ b/plugins/ui/static_components/race/LapRanking.config.js @@ -5,10 +5,10 @@ export default { entries: 7, entryHeight: 2.15, width: cfg.width, - title: "Lap Records", + title: 'Lap Records', icon: icons.chartLocal, margin: cfg.margin, topCount: 3, displayNoRecordEntry: true, hidePanel: true -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/race/LiveRanking.component.ts b/plugins/ui/static_components/race/LiveRanking.component.ts index 1d885aa1e..675a5d3cd 100644 --- a/plugins/ui/static_components/race/LiveRanking.component.ts +++ b/plugins/ui/static_components/race/LiveRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.1 */ -import { RecordList, componentIds, StaticHeader, StaticComponent, type RLRecord } from '../../UI.js' +import { componentIds, RecordList, type RLRecord, StaticComponent, StaticHeader } from '../../UI.js' import config from './LiveRanking.config.js' export default class LiveRanking extends StaticComponent { @@ -39,7 +39,8 @@ export default class LiveRanking extends StaticComponent { getEntries(): number { if (tm.getGameMode() === 'Laps') { return config.lapsEntries - } if (tm.getGameMode() === 'Stunts') { + } + if (tm.getGameMode() === 'Stunts') { return config.stuntsEntries } return config.entries @@ -52,7 +53,8 @@ export default class LiveRanking extends StaticComponent { getTopCount(): number { if (tm.getGameMode() === 'Laps') { return config.lapsTopCount - } if (tm.getGameMode() === 'Stunts') { + } + if (tm.getGameMode() === 'Stunts') { return config.stuntsTopCount } return config.topCount @@ -91,17 +93,26 @@ export default class LiveRanking extends StaticComponent { text = `${cpCount}/${mapCps}` } return { - cpCount, time: 0, login: a.login, name: a.nickname, + cpCount, + time: 0, + login: a.login, + name: a.nickname, checkpoints: record === undefined ? a.currentCheckpoints.map(a => a.time) : record.checkpoints, - text, finishTime: record?.time + text, + finishTime: record?.time } }).filter(a => a !== null) as any) - .sort((a: any, b: any) => b.cpCount - a.cpCount) // Sort secondary by cp amount - .sort((a: any, b: any) => a.finishTime - b.finishTime) as RLRecord[]) // Sort primary by finish time + .sort((a: any, b: any) => b.cpCount - a.cpCount) // Sort secondary by cp amount + .sort((a: any, b: any) => a.finishTime - b.finishTime) as RLRecord[]) // Sort primary by finish time content = this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, list) } else { content = this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, tm.records.live - .map(a => ({ name: a.nickname, time: a.time, checkpoints: a.checkpoints, login: a.login }))) + .map(a => ({ + name: a.nickname, + time: a.time, + checkpoints: a.checkpoints, + login: a.login + }))) } return { xml: ` @@ -129,9 +140,12 @@ export default class LiveRanking extends StaticComponent { noRecordEntryText = config.stuntsNoRecordEntry } this.recordList?.destroy?.() - this.recordList = new RecordList('race', this.id, config.width, height - (this.header.options.height + config.margin), - entries, this.side, this.getTopCount(), tm.records.maxLocalsAmount, config.displayNoRecordEntry, - { dontParseTime, noRecordEntryText }) + this.recordList = new RecordList('race', this.id, config.width, + height - (this.header.options.height + config.margin), entries, this.side, this.getTopCount(), + tm.records.maxLocalsAmount, config.displayNoRecordEntry, { + dontParseTime, + noRecordEntryText + }) this.recordList.onClick((info: tm.ManialinkClickInfo): void => { if (this.reduxModeEnabled) { return } const obj = this.displayToPlayer(info.login) diff --git a/plugins/ui/static_components/race/LiveRanking.config.js b/plugins/ui/static_components/race/LiveRanking.config.js index abaa8bd77..ef904c72b 100644 --- a/plugins/ui/static_components/race/LiveRanking.config.js +++ b/plugins/ui/static_components/race/LiveRanking.config.js @@ -7,8 +7,8 @@ export default { stuntsEntries: 8, entryHeight: 2.15, width: cfg.width, - title: "Live Records", - lapsTitle: "Live Ranking", + title: 'Live Records', + lapsTitle: 'Live Ranking', icon: icons.chartLive, margin: cfg.margin, topCount: 3, @@ -20,4 +20,4 @@ export default { cpsCollectedColour: tm.utils.palette.green, hidePanel: true, maxRecordsAmount: 3000 // If more records than this get driven in one round the click listener will break -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/race/LocalRanking.component.ts b/plugins/ui/static_components/race/LocalRanking.component.ts index ff8212da9..92cb020fe 100644 --- a/plugins/ui/static_components/race/LocalRanking.component.ts +++ b/plugins/ui/static_components/race/LocalRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.1 */ -import { RecordList, componentIds, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, RecordList, StaticComponent, StaticHeader } from '../../UI.js' import config from './LocalRanking.config.js' @@ -35,13 +35,17 @@ export default class LocalRanking extends StaticComponent { getEntries(): number { if (tm.getGameMode() === 'Teams') { return config.teamsEntries - } if (tm.getGameMode() === 'Rounds') { + } + if (tm.getGameMode() === 'Rounds') { return config.roundsEntries - } if (tm.getGameMode() === 'Cup') { + } + if (tm.getGameMode() === 'Cup') { return config.cupEntries - } if (tm.getGameMode() === 'Laps') { + } + if (tm.getGameMode() === 'Laps') { return config.lapsEntries - } if (tm.getGameMode() === 'Stunts') { + } + if (tm.getGameMode() === 'Stunts') { return config.stuntsEntries } return config.entries @@ -50,13 +54,17 @@ export default class LocalRanking extends StaticComponent { getTopCount(): number { if (tm.getGameMode() === 'Teams') { return config.teamsTopCount - } if (tm.getGameMode() === 'Rounds') { + } + if (tm.getGameMode() === 'Rounds') { return config.roundsTopCount - } if (tm.getGameMode() === 'Cup') { + } + if (tm.getGameMode() === 'Cup') { return config.cupTopCount - } if (tm.getGameMode() === 'Laps') { + } + if (tm.getGameMode() === 'Laps') { return config.lapsTopCount - } if (tm.getGameMode() === 'Stunts') { + } + if (tm.getGameMode() === 'Stunts') { return config.lapsTopCount } return config.topCount @@ -80,8 +88,9 @@ export default class LocalRanking extends StaticComponent { const height = this.getHeight() const entries = this.getEntries() this.recordList?.destroy?.() - this.recordList = new RecordList('race', this.id, config.width, height - (this.header.options.height + config.margin), - entries, this.side, this.getTopCount(), tm.records.maxLocalsAmount, config.displayNoRecordEntry, + this.recordList = new RecordList('race', this.id, config.width, + height - (this.header.options.height + config.margin), entries, this.side, this.getTopCount(), + tm.records.maxLocalsAmount, config.displayNoRecordEntry, { noRecordEntryText: tm.getGameMode() === 'Stunts' ? config.stuntsNoRecordEntry : undefined }) this.recordList.onClick((info: tm.ManialinkClickInfo): void => { if (this.reduxModeEnabled) { return } @@ -109,8 +118,14 @@ export default class LocalRanking extends StaticComponent { ${this.header.constructXml(config.title, config.icon, this.side, { actionId: componentIds.localCps })} ${this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, tm.records.local - .map(a => ({ name: a.nickname, time: a.time, date: a.date, checkpoints: a.checkpoints, login: a.login })) - .slice(0, tm.records.maxLocalsAmount))} + .map(a => ({ + name: a.nickname, + time: a.time, + date: a.date, + checkpoints: a.checkpoints, + login: a.login + })) + .slice(0, tm.records.maxLocalsAmount))} `, diff --git a/plugins/ui/static_components/race/LocalRanking.config.js b/plugins/ui/static_components/race/LocalRanking.config.js index 9727c9492..f4a84a141 100644 --- a/plugins/ui/static_components/race/LocalRanking.config.js +++ b/plugins/ui/static_components/race/LocalRanking.config.js @@ -10,7 +10,7 @@ export default { stuntsEntries: 12, entryHeight: 2.15, width: cfg.width, - title: "Local Records", + title: 'Local Records', icon: icons.chartLocal, margin: cfg.margin, topCount: 5, @@ -22,4 +22,4 @@ export default { displayNoRecordEntry: true, hidePanel: true, stuntsNoRecordEntry: '--' -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/race/MapWidget.component.ts b/plugins/ui/static_components/race/MapWidget.component.ts index 589301506..9779e9b7f 100644 --- a/plugins/ui/static_components/race/MapWidget.component.ts +++ b/plugins/ui/static_components/race/MapWidget.component.ts @@ -3,7 +3,7 @@ * @since 0.1 */ -import { componentIds, Grid, StaticHeader, StaticComponent, type StaticHeaderOptions } from '../../UI.js' +import { componentIds, Grid, StaticComponent, StaticHeader, type StaticHeaderOptions } from '../../UI.js' import flags from '../../config/FlagIcons.js' import { tmx } from '../../../tmx/Tmx.js' import { webservices } from '../../../webservices/Webservices.js' @@ -39,7 +39,10 @@ export default class MapWidget extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } private updateXML(): void { @@ -65,13 +68,10 @@ export default class MapWidget extends StaticComponent { timeOrScoreIcon = ic.goldScore } } - const infos: [string, string][] = [ - [config.title, ic.header], + const infos: [string, string][] = [[config.title, ic.header], [tm.utils.safeString(tm.utils.decodeURI(map.name)), this.getTag(map, TMXMap ?? undefined)], - [tm.utils.safeString(author), authorIcon], - [timeOrScore, timeOrScoreIcon], - [date === undefined ? config.noDateText : tm.utils.formatDate(date), ic.buildDate] - ] + [tm.utils.safeString(author), authorIcon], [timeOrScore, timeOrScoreIcon], + [date === undefined ? config.noDateText : tm.utils.formatDate(date), ic.buildDate]] const headerCfg: StaticHeaderOptions = this.header.options const cell = (i: number, j: number, w: number, h: number): string => { if (i === 3) { @@ -84,24 +84,23 @@ export default class MapWidget extends StaticComponent { textBackground: config.background })} - + ${this.header.constructXml(infos[i + 1][0], infos[i + 1][1], this.side, { - rectangleWidth: (headerCfg.rectangleWidth / 2) - (headerCfg.margin + (headerCfg.squareWidth / 2)), - textScale: config.textScale, - centerText: true, - textBackground: config.background - })} + rectangleWidth: (headerCfg.rectangleWidth / 2) - (headerCfg.margin + (headerCfg.squareWidth / 2)), + textScale: config.textScale, + centerText: true, + textBackground: config.background + })} ` } return ` ${i === 0 ? this.header.constructXml(infos[i][0], infos[i][1], this.side) : - this.header.constructXml(tm.utils.strip(infos[i][0], false), infos[i][1], this.side, { - textScale: config.textScale, - textBackground: config.background, - horizontalPadding: config.mapPadding - })} + this.header.constructXml(tm.utils.strip(infos[i][0], false), infos[i][1], this.side, { + textScale: config.textScale, + textBackground: config.background, + horizontalPadding: config.mapPadding + })} ` } const arr: any[] = new Array(this.rows).fill(cell) @@ -116,9 +115,9 @@ export default class MapWidget extends StaticComponent { private getTag(map: tm.Map, TMXMap?: tm.TMXMap): string { for (const e of config.customTags) { - if (e?.authors?.some(a => a === map.author || a === TMXMap?.author) || - e?.names?.some(a => a.test(map.name) || a.test(tm.utils.strip(map.name)) || - (TMXMap !== undefined ? (a.test(TMXMap.name) || a.test(tm.utils.strip(TMXMap.name))) : false))) { + if (e?.authors?.some(a => a === map.author || a === TMXMap?.author) || e?.names?.some( + a => a.test(map.name) || a.test(tm.utils.strip(map.name)) || (TMXMap !== undefined ? + (a.test(TMXMap.name) || a.test(tm.utils.strip(TMXMap.name))) : false))) { return e.icon } } diff --git a/plugins/ui/static_components/race/MapWidget.config.js b/plugins/ui/static_components/race/MapWidget.config.js index 610ff3f13..af168a0b7 100644 --- a/plugins/ui/static_components/race/MapWidget.config.js +++ b/plugins/ui/static_components/race/MapWidget.config.js @@ -6,21 +6,28 @@ export default { height: 9.13, margin: cfg.margin, width: cfg.width, - title: "Ongoing", + title: 'Ongoing', background: cfg.background, mapPadding: 0.3, - customTags: [ - /* Custom tags allow you to display special tag icon for certain authors or maps - If tmx name or login matches one of the authors or - map name matches one of the names regexes special tag will be displayed - map name is tested both stripped and unstripped from special characters - Tags are read from top to bottom - */ - { authors: ['Fwo Niro'], icon: icons.tagRed }, - { authors: ['tony89300'], icon: icons.tagPinkCyan }, - { names: [/{Kackiest Kacky}*/], icon: icons.tagKacky }, - { names: [/\$w\$i\$f70ARC_*/], icon: icons.tagArcade } - ], + customTags: [/* Custom tags allow you to display special tag icon for certain authors or maps + If tmx name or login matches one of the authors or + map name matches one of the names regexes special tag will be displayed + map name is tested both stripped and unstripped from special characters + Tags are read from top to bottom + */ + { + authors: ['Fwo Niro'], + icon: icons.tagRed + }, { + authors: ['tony89300'], + icon: icons.tagPinkCyan + }, { + names: [/{Kackiest Kacky}*/], + icon: icons.tagKacky + }, { + names: [/\$w\$i\$f70ARC_*/], + icon: icons.tagArcade + }], icons: { header: icons.ongoingMap, tags: { @@ -35,10 +42,10 @@ export default { authorScore: icons.clockAuthor }, textScale: 1, - noDateText: "N/A", + noDateText: 'N/A', titles: { - lastTrack: "Last Map", - currTrack: "Ongoing", - nextTrack: "Next Map" + lastTrack: 'Last Map', + currTrack: 'Ongoing', + nextTrack: 'Next Map' } -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/race/PreviousAndBest.component.ts b/plugins/ui/static_components/race/PreviousAndBest.component.ts index f9aa5ada5..2fd937190 100644 --- a/plugins/ui/static_components/race/PreviousAndBest.component.ts +++ b/plugins/ui/static_components/race/PreviousAndBest.component.ts @@ -31,7 +31,10 @@ export default class PreviousAndBest extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } private constructXml(): void { diff --git a/plugins/ui/static_components/race/PreviousAndBest.config.js b/plugins/ui/static_components/race/PreviousAndBest.config.js index 004c522ad..b256d3737 100644 --- a/plugins/ui/static_components/race/PreviousAndBest.config.js +++ b/plugins/ui/static_components/race/PreviousAndBest.config.js @@ -1,8 +1,8 @@ import cfg from '../../config/RaceUi.js' export default { - height: 4.95, - width: cfg.width, - margin: cfg.margin, - background: cfg.background -} \ No newline at end of file + height: 4.95, + width: cfg.width, + margin: cfg.margin, + background: cfg.background +} diff --git a/plugins/ui/static_components/race/RankWidget.component.ts b/plugins/ui/static_components/race/RankWidget.component.ts index 9f9e17797..6283ce412 100644 --- a/plugins/ui/static_components/race/RankWidget.component.ts +++ b/plugins/ui/static_components/race/RankWidget.component.ts @@ -32,7 +32,10 @@ export default class RankWidget extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } private constructXml(): void { diff --git a/plugins/ui/static_components/race/RankWidget.config.js b/plugins/ui/static_components/race/RankWidget.config.js index 07d38a852..5fd3439e2 100644 --- a/plugins/ui/static_components/race/RankWidget.config.js +++ b/plugins/ui/static_components/race/RankWidget.config.js @@ -1,7 +1,7 @@ import cfg from '../../config/RaceUi.js' export default { - height: 3.8, - width: cfg.width, - background: cfg.background -} \ No newline at end of file + height: 3.8, + width: cfg.width, + background: cfg.background +} diff --git a/plugins/ui/static_components/race/RoundScore.component.ts b/plugins/ui/static_components/race/RoundScore.component.ts index b8cacb789..7ff65eb19 100644 --- a/plugins/ui/static_components/race/RoundScore.component.ts +++ b/plugins/ui/static_components/race/RoundScore.component.ts @@ -3,7 +3,7 @@ * @since 1.2 */ -import { RecordList, componentIds, StaticHeader, StaticComponent, type RLImage } from '../../UI.js' +import { componentIds, RecordList, type RLImage, StaticComponent, StaticHeader } from '../../UI.js' import config from './RoundScore.config.js' @@ -29,7 +29,8 @@ export default class RoundScore extends StaticComponent { getEntries(): number { if (tm.getGameMode() === 'Teams') { return config.teamsEntries - } if (tm.getGameMode() === 'Cup') { + } + if (tm.getGameMode() === 'Cup') { return config.cupEntries } return config.roundsEntries @@ -42,7 +43,8 @@ export default class RoundScore extends StaticComponent { getTopCount(): number { if (tm.getGameMode() === 'Teams') { return config.teamsTopCount - } if (tm.getGameMode() === 'Cup') { + } + if (tm.getGameMode() === 'Cup') { return config.cupTopCount } return config.roundsTopCount @@ -70,13 +72,16 @@ export default class RoundScore extends StaticComponent { ${this.header.constructXml(config.title, config.icon, this.side, { actionId: componentIds.localCps })} ${this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, tm.records.roundRecords - .map(a => ({ - name: a.nickname, time: a.time, checkpoints: a.checkpoints, - login: a.login, points: a.roundPoints, - colour: a.team !== undefined ? config.markerBackgrounds[a.team] : undefined, - markerImage: this.getCupImage(a) - })) - .slice(0, tm.records.maxLocalsAmount))} + .map(a => ({ + name: a.nickname, + time: a.time, + checkpoints: a.checkpoints, + login: a.login, + points: a.roundPoints, + colour: a.team !== undefined ? config.markerBackgrounds[a.team] : undefined, + markerImage: this.getCupImage(a) + })) + .slice(0, tm.records.maxLocalsAmount))} `, @@ -93,8 +98,9 @@ export default class RoundScore extends StaticComponent { const height = this.getHeight() const entries = this.getEntries() this.recordList?.destroy?.() - this.recordList = new RecordList('race', this.id, config.width, height - (this.header.options.height + config.margin), - entries, this.side, this.getTopCount(), 250, config.displayNoRecordEntry) + this.recordList = new RecordList('race', this.id, config.width, + height - (this.header.options.height + config.margin), entries, this.side, this.getTopCount(), 250, + config.displayNoRecordEntry) this.recordList.onClick((info: tm.ManialinkClickInfo): void => { if (this.reduxModeEnabled) { return } const obj = this.displayToPlayer(info.login) diff --git a/plugins/ui/static_components/race/RoundScore.config.js b/plugins/ui/static_components/race/RoundScore.config.js index 97effd0cf..8a091a53c 100644 --- a/plugins/ui/static_components/race/RoundScore.config.js +++ b/plugins/ui/static_components/race/RoundScore.config.js @@ -7,7 +7,7 @@ export default { cupEntries: 12, entryHeight: 2.15, width: cfg.width, - title: "Round Score", + title: 'Round Score', icon: icons.chartLocal, margin: cfg.margin, roundsTopCount: 5, @@ -22,4 +22,4 @@ export default { blue: tm.utils.palette.purple + '6' }, hidePanel: true -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/race/RoundsPointsRanking.component.ts b/plugins/ui/static_components/race/RoundsPointsRanking.component.ts index 88202d8c0..ae7dc2cba 100644 --- a/plugins/ui/static_components/race/RoundsPointsRanking.component.ts +++ b/plugins/ui/static_components/race/RoundsPointsRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.1 */ -import { RecordList, componentIds, StaticHeader, StaticComponent, type RLImage } from '../../UI.js' +import { componentIds, RecordList, type RLImage, StaticComponent, StaticHeader } from '../../UI.js' import config from './RoundsPointsRanking.config.js' export default class RoundsPointsRanking extends StaticComponent { @@ -17,15 +17,15 @@ export default class RoundsPointsRanking extends StaticComponent { this.getRecordList() this.renderOnEvent('PlayerJoin', (info: tm.JoinInfo) => { if (tm.rounds.pointsRanking.filter(a => a.roundsPoints !== 0) - .some(a => a.login === info.login)) { return this.display() } + .some(a => a.login === info.login)) { return this.display() } }) this.renderOnEvent('PlayerLeave', (info: tm.LeaveInfo) => { if (tm.rounds.pointsRanking.filter(a => a.roundsPoints !== 0) - .some(a => a.login === info.login)) { return this.display() } + .some(a => a.login === info.login)) { return this.display() } }) this.renderOnEvent('PlayerDataUpdated', (info) => { if (info.some(a => tm.rounds.pointsRanking - .filter(b => b.roundsPoints !== 0).some(b => b.login === a.login))) { return this.display() } + .filter(b => b.roundsPoints !== 0).some(b => b.login === a.login))) { return this.display() } }) this.renderOnEvent('PlayerFinish', () => this.display()) this.onPanelHide((player) => { @@ -65,9 +65,13 @@ export default class RoundsPointsRanking extends StaticComponent { let height = this.getHeight() let entries = this.getEntries() this.recordList?.destroy?.() - this.recordList = new RecordList('race', this.id, config.width, height - (this.header.options.height + config.margin), - entries, this.side, this.getTopCount(), tm.records.maxLocalsAmount, config.displayNoRecordEntry, - { dontParseTime: true, columnProportions: config.columnProportions, noRecordEntryText: config.noRecordEntryText }) + this.recordList = new RecordList('race', this.id, config.width, + height - (this.header.options.height + config.margin), entries, this.side, this.getTopCount(), + tm.records.maxLocalsAmount, config.displayNoRecordEntry, { + dontParseTime: true, + columnProportions: config.columnProportions, + noRecordEntryText: config.noRecordEntryText + }) this.recordList.onClick((info: tm.ManialinkClickInfo): void => { if (this.reduxModeEnabled) { return } const obj = this.displayToPlayer(info.login) @@ -94,12 +98,15 @@ export default class RoundsPointsRanking extends StaticComponent { ${this.header.constructXml(config.title, config.icon, this.side)} ${this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, tm.rounds.pointsRanking - .filter(a => a.roundsPoints !== 0) - .map(a => ({ - name: a.nickname, time: a.roundsPoints, login: a.login, - checkpoints: a.roundTimes.map(a => a === -1 ? undefined : a), image: this.getCupImage(a) - })) - .slice(0, tm.records.maxLocalsAmount))} + .filter(a => a.roundsPoints !== 0) + .map(a => ({ + name: a.nickname, + time: a.roundsPoints, + login: a.login, + checkpoints: a.roundTimes.map(a => a === -1 ? undefined : a), + image: this.getCupImage(a) + })) + .slice(0, tm.records.maxLocalsAmount))} `, diff --git a/plugins/ui/static_components/race/RoundsPointsRanking.config.js b/plugins/ui/static_components/race/RoundsPointsRanking.config.js index 9411f404a..90f6aeb0b 100644 --- a/plugins/ui/static_components/race/RoundsPointsRanking.config.js +++ b/plugins/ui/static_components/race/RoundsPointsRanking.config.js @@ -6,7 +6,7 @@ export default { cupEntries: 8, entryHeight: 2.15, width: cfg.width, - title: "Rounds Points", + title: 'Rounds Points', icon: icons.chartLocal, margin: cfg.margin, topCount: 5, @@ -20,4 +20,4 @@ export default { cupImageVerticalPadding: 0.1, cupImageHorizontalPadding: 0.3, hidePanel: true -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/race/TMXRanking.component.ts b/plugins/ui/static_components/race/TMXRanking.component.ts index 3d2892ae6..c39155921 100644 --- a/plugins/ui/static_components/race/TMXRanking.component.ts +++ b/plugins/ui/static_components/race/TMXRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.1 */ -import { RecordList, componentIds, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, RecordList, StaticComponent, StaticHeader } from '../../UI.js' import { tmx } from '../../../tmx/Tmx.js' import config from './TMXRanking.config.js' @@ -34,8 +34,9 @@ export default class TMXRanking extends StaticComponent { createRecordList(): void { this.recordList?.destroy() - this.recordList = new RecordList('race', this.id, config.width, this.getHeight() - (this.header.options.height + config.margin), config.entries, - this.side, config.topCount, config.entries, config.displayNoRecordEntry, { getColoursFromPb: true }) + this.recordList = new RecordList('race', this.id, config.width, + this.getHeight() - (this.header.options.height + config.margin), config.entries, this.side, config.topCount, + config.entries, config.displayNoRecordEntry, { getColoursFromPb: true }) this.recordList.onClick((info: tm.ManialinkClickInfo): void => { if (this.reduxModeEnabled) { return } const obj = this.displayToPlayer(info.login) @@ -64,13 +65,20 @@ export default class TMXRanking extends StaticComponent { if (config.hidePanel && this.hasPanelsHidden(login)) { return this.hideToPlayer(login) } - let replays: { name: string, time: number, date: Date, login?: string }[] = [] + let replays: { + name: string, + time: number, + date: Date, + login?: string + }[] = [] const tmxInfo: tm.TMXMap | null = tmx.current const isStunts = tm.getGameMode() === 'Stunts' if (tmxInfo !== null) { replays = tmxInfo.validReplays.map(a => ({ name: a.name, - time: isStunts ? (a.score ?? 0) : a.time, date: a.recordDate, url: a.url + time: isStunts ? (a.score ?? 0) : a.time, + date: a.recordDate, + url: a.url })) } return { diff --git a/plugins/ui/static_components/race/TMXRanking.config.js b/plugins/ui/static_components/race/TMXRanking.config.js index 56407369b..4f2ebe178 100644 --- a/plugins/ui/static_components/race/TMXRanking.config.js +++ b/plugins/ui/static_components/race/TMXRanking.config.js @@ -6,15 +6,10 @@ export default { entryHeight: 2.15, width: cfg.width, margin: cfg.margin, - title: "TMX Records", + title: 'TMX Records', icon: icons.maniaExchange, - columnProportions: [ - 1, - 1, - 2.8, - 4 - ], + columnProportions: [1, 1, 2.8, 4], topCount: 3, displayNoRecordEntry: false, hidePanel: true -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/race/TeamScore.component.ts b/plugins/ui/static_components/race/TeamScore.component.ts index f7c80d4fd..632073bec 100644 --- a/plugins/ui/static_components/race/TeamScore.component.ts +++ b/plugins/ui/static_components/race/TeamScore.component.ts @@ -2,7 +2,7 @@ * @author lythx * @since 1.2 */ -import { componentIds, StaticHeader, centeredText, StaticComponent, type GridCellFunction, Grid } from '../../UI.js' +import { centeredText, componentIds, Grid, type GridCellFunction, StaticComponent, StaticHeader } from '../../UI.js' import config from './TeamScore.config.js' export default class TeamScore extends StaticComponent { @@ -42,7 +42,10 @@ export default class TeamScore extends StaticComponent { if (config.hidePanel && this.hasPanelsHidden(login)) { return this.hideToPlayer(login) } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } private constructXml() { diff --git a/plugins/ui/static_components/race/TeamScore.config.js b/plugins/ui/static_components/race/TeamScore.config.js index 93a839a3c..a96afa6c9 100644 --- a/plugins/ui/static_components/race/TeamScore.config.js +++ b/plugins/ui/static_components/race/TeamScore.config.js @@ -5,16 +5,14 @@ export default { height: 5.5, width: cfg.width, margin: cfg.margin, - title: "Team Score", + title: 'Team Score', icon: icons.teamsMode, - entries: 3, - columnProportions: [ - 1, - 1, - 2.8, - 4 - ], - text: { specialFont: true, textScale: 0.5 }, + entries: 3, + columnProportions: [1, 1, 2.8, 4], + text: { + specialFont: true, + textScale: 0.5 + }, colours: { left: tm.utils.palette.purple + '8', middle: cfg.background, @@ -24,4 +22,4 @@ export default { displayNoRecordEntry: false, noMaxScore: '-', hidePanel: true -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/race/TimerWidget.component.ts b/plugins/ui/static_components/race/TimerWidget.component.ts index f0da3847e..ade79fd17 100644 --- a/plugins/ui/static_components/race/TimerWidget.component.ts +++ b/plugins/ui/static_components/race/TimerWidget.component.ts @@ -3,7 +3,7 @@ * @since 0.1 */ -import { componentIds, StaticHeader, StaticComponent, centeredText, addManialinkListener } from '../../UI.js' +import { addManialinkListener, centeredText, componentIds, StaticComponent, StaticHeader } from '../../UI.js' import config from './TimerWidget.config.js' export default class TimerWidget extends StaticComponent { @@ -140,9 +140,15 @@ export default class TimerWidget extends StaticComponent { // } privilege ??= tm.players.get(login)?.privilege ?? 0 if (this.isOnRestart || !tm.timer.isDynamic || privilege < config.timerActionsPrivilege) { - return { xml: this.noButtonXml, login } + return { + xml: this.noButtonXml, + login + } } else { - return { xml: this.xmlWithButtons, login } + return { + xml: this.xmlWithButtons, + login + } } } @@ -154,8 +160,7 @@ export default class TimerWidget extends StaticComponent { private constructXml(isDynamic: boolean): string { const headerHeight: number = this.header.options.height - let headerXml = isDynamic ? this.getButtonsXml() : - this.header.constructXml(config.title, config.icon, this.side) + let headerXml = isDynamic ? this.getButtonsXml() : this.header.constructXml(config.title, config.icon, this.side) let timeXml = '' let bottomH = config.height - (headerHeight + config.margin) const isStunts = tm.getGameMode() === 'Stunts' @@ -164,8 +169,11 @@ export default class TimerWidget extends StaticComponent { } if (tm.timer.isDynamic && !this.isOnRestart) { if (tm.timer.isPaused) { - timeXml = centeredText(config.pausedText, config.width, bottomH, - { specialFont: true, yOffset: -0.3, xOffset: 0.2 }) + timeXml = centeredText(config.pausedText, config.width, bottomH, { + specialFont: true, + yOffset: -0.3, + xOffset: 0.2 + }) } else { const time = Math.floor(tm.timer.remainingRaceTime / 1000) let timeColour = config.timeColours[0] @@ -179,8 +187,10 @@ export default class TimerWidget extends StaticComponent { const minutes = (~~(time / 60) % 60).toString().padStart(2, '0') const seconds = (time % 60).toString().padStart(2, '0') const timeStr = hoursAmount < 100 ? `${hours}${minutes}:${seconds}` : `${hoursAmount} hours` - timeXml = centeredText('$' + timeColour + timeStr, config.width, bottomH, - { specialFont: true, yOffset: config.textYOffset }) + timeXml = centeredText('$' + timeColour + timeStr, config.width, bottomH, { + specialFont: true, + yOffset: config.textYOffset + }) } } let stuntsMargin = (isStunts && isDynamic) ? config.stuntsDynamicMarginTop : 0 @@ -237,8 +247,8 @@ export default class TimerWidget extends StaticComponent { buttonXml += ` ` } - return this.header.constructXml(config.title, config.icon, - this.side, { rectangleWidth: headerRectWidth }) + buttonXml + return this.header.constructXml(config.title, config.icon, this.side, + { rectangleWidth: headerRectWidth }) + buttonXml } } diff --git a/plugins/ui/static_components/race/TimerWidget.config.js b/plugins/ui/static_components/race/TimerWidget.config.js index f5cba7ab5..e0de51e33 100644 --- a/plugins/ui/static_components/race/TimerWidget.config.js +++ b/plugins/ui/static_components/race/TimerWidget.config.js @@ -1,5 +1,6 @@ import icons from '../../config/Icons.js' import cfg from '../../config/RaceUi.js' + const p = tm.utils.palette export default { @@ -7,7 +8,7 @@ export default { stuntsDynamicMarginTop: 5.5, stuntsHeight: 6.45 + 5.5, width: cfg.width, - title: "Timer", + title: 'Timer', icon: icons.clock, margin: cfg.margin, background: cfg.background, @@ -21,7 +22,7 @@ export default { pause: icons.pause, resume: icons.play, add: icons.plus, - subtract: icons.minus, + subtract: icons.minus }, iconsHover: { pause: icons.pauseGreen, @@ -36,5 +37,5 @@ export default { timeSubtractedOnClick: 5 * 60 * 1000, //msec pause: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has ${p.highlight}paused ${p.admin}the timer.`, resume: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has ${p.highlight}resumed ${p.admin}the timer.`, - set: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has set the remaining time to ${p.highlight}#{time}${p.admin}.`, -} \ No newline at end of file + set: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has set the remaining time to ${p.highlight}#{time}${p.admin}.` +} diff --git a/plugins/ui/static_components/race/UltiRanking.component.ts b/plugins/ui/static_components/race/UltiRanking.component.ts index 2cae34413..d63f2c6cc 100644 --- a/plugins/ui/static_components/race/UltiRanking.component.ts +++ b/plugins/ui/static_components/race/UltiRanking.component.ts @@ -3,7 +3,7 @@ * @since 1.5.0 */ -import { RecordList, componentIds, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, RecordList, StaticComponent, StaticHeader } from '../../UI.js' import { ultimania } from '../../../ultimania/Ultimania.js' import config from './UltiRanking.config.js' @@ -12,6 +12,7 @@ export default class UltiRanking extends StaticComponent { private readonly header: StaticHeader private recordList!: RecordList + constructor() { super(componentIds.ultiRanking) this.header = new StaticHeader('race') @@ -51,7 +52,11 @@ export default class UltiRanking extends StaticComponent { ${this.header.constructXml(config.title, config.icon, this.side, { actionId: componentIds.ultiRecords })} - ${this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, ultimania.records.map(a => ({ ...a, name: a.nickname, time: a.score })))} + ${this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, ultimania.records.map(a => ({ + ...a, + name: a.nickname, + time: a.score + })))} `, @@ -62,11 +67,14 @@ export default class UltiRanking extends StaticComponent { getEntries(): number { if (tm.getGameMode() === 'Teams') { return config.teamsEntries - } if (tm.getGameMode() === 'Rounds') { + } + if (tm.getGameMode() === 'Rounds') { return config.roundsEntries - } if (tm.getGameMode() === 'Cup') { + } + if (tm.getGameMode() === 'Cup') { return config.cupEntries - } if (tm.getGameMode() === 'Laps') { + } + if (tm.getGameMode() === 'Laps') { return config.lapsEntries } return config.entries @@ -79,11 +87,14 @@ export default class UltiRanking extends StaticComponent { getTopCount(): number { if (tm.getGameMode() === 'Teams') { return config.teamsTopCount - } if (tm.getGameMode() === 'Rounds') { + } + if (tm.getGameMode() === 'Rounds') { return config.roundsTopCount - } if (tm.getGameMode() === 'Cup') { + } + if (tm.getGameMode() === 'Cup') { return config.cupTopCount - } if (tm.getGameMode() === 'Laps') { + } + if (tm.getGameMode() === 'Laps') { return config.lapsTopCount } return config.topCount @@ -96,9 +107,9 @@ export default class UltiRanking extends StaticComponent { private getRecordList(): void { this.recordList?.destroy?.() - this.recordList = new RecordList('race', this.id, config.width, this.getHeight() - (this.header.options.height + config.margin), - this.getEntries(), this.side, this.getTopCount(), config.maxRecordCount, config.displayNoRecordEntry, - { noRecordEntryText: config.noRecordEntryText }) + this.recordList = new RecordList('race', this.id, config.width, + this.getHeight() - (this.header.options.height + config.margin), this.getEntries(), this.side, this.getTopCount(), + config.maxRecordCount, config.displayNoRecordEntry, { noRecordEntryText: config.noRecordEntryText }) this.recordList.onClick((info: tm.ManialinkClickInfo): void => { if (this.reduxModeEnabled) { return } const obj = this.displayToPlayer(info.login) diff --git a/plugins/ui/static_components/race/UltiRanking.config.js b/plugins/ui/static_components/race/UltiRanking.config.js index e33d05d66..9eca4c0e9 100644 --- a/plugins/ui/static_components/race/UltiRanking.config.js +++ b/plugins/ui/static_components/race/UltiRanking.config.js @@ -9,7 +9,7 @@ export default { lapsEntries: 7, entryHeight: 2.15, width: cfg.width, - title: "Ultimania Records", + title: 'Ultimania Records', icon: icons.chartDedi, margin: cfg.margin, topCount: 5, diff --git a/plugins/ui/static_components/race/buttons/ButtonData.ts b/plugins/ui/static_components/race/buttons/ButtonData.ts index 818c056c8..a07348f13 100644 --- a/plugins/ui/static_components/race/buttons/ButtonData.ts +++ b/plugins/ui/static_components/race/buttons/ButtonData.ts @@ -7,5 +7,12 @@ export interface ButtonData { padding: number, equalTexts?: boolean, actionId?: number, - link?: string + link?: string, + manialink?: string, + addPlayerId?: boolean + /** + * Enables per-player rendering. Buttons using per-player rendering should override the + * `UiButton.renderForPlayer()` function (see `MedalButton.ts`) + */ + perPlayer?: boolean } \ No newline at end of file diff --git a/plugins/ui/static_components/race/buttons/ButtonsWidget.component.ts b/plugins/ui/static_components/race/buttons/ButtonsWidget.component.ts index 751ff368b..0c9d4b057 100644 --- a/plugins/ui/static_components/race/buttons/ButtonsWidget.component.ts +++ b/plugins/ui/static_components/race/buttons/ButtonsWidget.component.ts @@ -1,4 +1,4 @@ -import { componentIds, Grid, staticButton, type GridCellFunction, StaticComponent } from '../../../UI.js' +import { componentIds, Grid, type GridCellFunction, staticButton, StaticComponent } from '../../../UI.js' import { VisitCounter } from './VisitCounter.js' import { TimeButton } from './TimeButton.js' import { PlayerCounter } from './PlayerCounter.js' @@ -12,6 +12,8 @@ import { StatsButton } from './StatsButton.js' import { CommandListButton } from './CommandListButton.js' import { SectorsButton } from './SectorsButton.js' import { UiButton } from './UiButton.js' +import { MedalButton } from "./MedalButton.js"; +import { FavouriteButton } from "./FavouriteButton.js"; import config from './ButtonsWidget.config.js' export default class ButtonsWidget extends StaticComponent { @@ -24,37 +26,49 @@ export default class ButtonsWidget extends StaticComponent { constructor() { super(componentIds.buttons) - this.grid = new Grid(this.width + config.margin, this.height + config.margin, - new Array(config.columns).fill(1), new Array(config.rows).fill(1)) - const allButtons = [ - new VisitCounter(), - new TimeButton(), - new PlayerCounter(), - new VersionButton(), - new MapsButton(), - new StatsButton(), - new CommandListButton(), - new SectorsButton(), - new PayReplay(this.id), - new PaySkip(this.id), - new VoteReplay(this.id), - new VoteSkip(this.id) - ] + this.grid = new Grid(this.width + config.margin, this.height + config.margin, new Array(config.columns).fill(1), + new Array(config.rows).fill(1)) + const allButtons = [new VisitCounter(), new TimeButton(), new PlayerCounter(), new VersionButton(), + new MapsButton(), new StatsButton(), new CommandListButton(), new SectorsButton(), new FavouriteButton(), + new PayReplay(this.id), new PaySkip(this.id), new VoteReplay(this.id), new VoteSkip(this.id), new MedalButton()] for (const e of config.order) { const b = allButtons.find(a => a.constructor.name === e) - if (b === undefined) { throw new Error(`Can't find button named ${e}`) } + if (b === undefined) { + throw new Error(`Can't find button named ${e}`) + } this.buttons.push(b) } - UiButton.onUpdate(() => { - this.constructXml() - const xml = this.display() - if (xml !== undefined) { - tm.sendManialink(xml) - } - }) - this.onPanelHide((player) => { // todo: this does not work properly due to button update - this.sendMultipleManialinks(this.displayToPlayer(player.login)) - }) + const perPlayerButtonExists = this.buttons.some(b => b.buttonData.perPlayer) + // Use per-player dedicated server calls only if necessary to improve performance + if (perPlayerButtonExists) { + UiButton.onUpdate(() => { + let calls = [] + if (this.isDisplayed) { + for (const player of tm.players.list) { + calls.push({ + method: 'SendDisplayManialinkPageToLogin', + params: [{ string: player.login }, { string: this.constructXml(player.login) }, { int: 0 }, + { boolean: false }] + }) + } + + if (calls.length > 0) { + tm.client.call('system.multicall', calls) + } + } + }) + this.onPanelHide((player) => { // todo: this does not work properly due to button update + this.sendMultipleManialinks(this.displayToPlayer(player.login)) + }) + } else { + UiButton.onUpdate(() => { + const xml = this.display() + if (xml !== undefined) { + tm.sendManialink(xml) + } + }) + } + } getHeight(): number { @@ -63,38 +77,44 @@ export default class ButtonsWidget extends StaticComponent { display() { if (!this.isDisplayed) { return } - this.constructXml() - return this.xml + return this.constructXml() } - displayToPlayer(login: string) { if (!this.isDisplayed) { return } if (config.hidePanel && this.hasPanelsHidden(login)) { return this.hideToPlayer(login) } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } - - private constructXml(): void { + private constructXml(login?: string): string { const arr: GridCellFunction[] = [] for (const e of this.buttons) { const data = e.buttonData - arr.push((i, j, w, h) => - staticButton(data.icon, data.text1, data.text2, w - config.margin, - h - config.margin, { - iconWidth: data.iconWidth, iconHeight: data.iconHeight, topPadding: data.padding, + if (e.buttonData.perPlayer) { + arr.push((i, j, w, h) => e.renderForPlayer(login ?? "", i, j, w, h)) + } else { + arr.push((i, j, w, h) => staticButton(data.icon, data.text1, data.text2, w - config.margin, h - config.margin, { + iconWidth: data.iconWidth, + iconHeight: data.iconHeight, + topPadding: data.padding, equalTexts: data.equalTexts === true ? true : undefined, - actionId: data.actionId, link: data.link + actionId: data.actionId, + link: data.link, + manialink: data.manialink, + addPlayerId: data.addPlayerId })) + } } - this.xml = ` + return ` ${this.grid.constructXml(arr)} ` } - } diff --git a/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js b/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js index 082683cfc..bc8da6ed9 100644 --- a/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js +++ b/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js @@ -1,90 +1,137 @@ import cfg from '../../../config/RaceUi.js' import icons from '../../../config/Icons.js' +const palette = tm.utils.palette + export default { height: 14.5, width: cfg.width, margin: cfg.margin, rows: 3, columns: 4, - order: [ - 'VisitCounter', - 'PlayerCounter', - 'VersionButton', - 'TimeButton', - 'MapsButton', - 'StatsButton', - 'SectorsButton', - 'CommandListButton', - 'VoteSkip', - 'VoteReplay', - 'PaySkip', - 'PayReplay' - ], + order: ['VisitCounter', 'PlayerCounter', 'VersionButton', 'TimeButton', 'MapsButton', 'StatsButton', 'SectorsButton', + 'CommandListButton', 'VoteSkip', 'VoteReplay', 'PaySkip', 'PayReplay'], visitCounter: { - texts: { 0: "#{count}", 1: "$AF4VISITOR#{plural}", equal: false }, + texts: { + 0: '#{count}', + 1: '$AF4VISITOR#{plural}', + equal: false + }, icon: icons.people, width: 2, height: 2, padding: 0.3 }, playerCounter: { - texts: { 0: "#{count} $AF4SPEC#{plural}", 1: "#{count} $AF4PLAYER#{plural}", equal: true }, + texts: { + 0: '#{count} $AF4SPEC#{plural}', + 1: '#{count} $AF4PLAYER#{plural}', + equal: true + }, icon: icons.peopleGaming, width: 2, height: 2, padding: 0.3 }, versionButton: { - texts: { 0: "#{version}", 1: "$AF4VERSION", equal: false }, + texts: { + 0: '#{version}', + 1: '$AF4VERSION', + equal: false + }, icon: icons.codeBranch, width: 1.8, height: 1.8, padding: 0.3 }, + favouriteButton: { + texts: { + 0: '$AF4ADD TO', + 1: 'FAVOURITES', + equal: false + }, + icon: icons.karmaGlobal, + width: 1.8, + height: 1.8, + padding: 0.3 + }, timeButton: { - texts: { 0: "#{time}", 1: "$AF4TIME", equal: false }, + texts: { + 0: '#{time}', + 1: '$AF4TIME', + equal: false + }, icon: icons.clock, width: 1.8, height: 1.8, padding: 0.2 }, mapsButton: { - texts: { 0: "#{count}", 1: "$AF4MAPS", equal: false }, + texts: { + 0: '#{count}', + 1: '$AF4MAPS', + equal: false + }, icon: icons.numberedList, width: 1.8, height: 1.8, padding: 0.2 }, statsButton: { - texts: { 0: "PLAYER", 1: "$AF4STATS", equal: true }, + texts: { + 0: 'PLAYER', + 1: '$AF4STATS', + equal: true + }, icon: icons.stats, width: 1.9, height: 1.9, padding: 0.2 }, sectorsButton: { - texts: { 0: "SECTOR", 1: "$AF4RECORDS", equal: true }, + texts: { + 0: 'SECTOR', + 1: '$AF4RECORDS', + equal: true + }, icon: icons.clockList, width: 1.8, height: 1.8, padding: 0.2 }, commandListButton: { - texts: { 0: "COMMAND", 1: "$AF4LIST", equal: true }, + texts: { + 0: 'COMMAND', + 1: '$AF4LIST', + equal: true + }, icon: icons.infoCircle, width: 1.8, height: 1.8, padding: 0.2 }, voteSkip: { - texts: [ - { 0: " VOTE ", 1: "TO $E22SKIP", equal: false }, - { 0: "SKIP IN", 1: "$E22#{seconds} $FFFSECS", equal: true }, - { 0: "$E22DISABLED", 1: "DUE TO RES", equal: true }, - { 0: "MAP IS", 1: "$E22SKIPPED", equal: true }, - { 0: "$E22DISABLED", 1: "DUE TO EXT", equal: true }, - ], + texts: [{ + 0: ' VOTE ', + 1: 'TO $E22SKIP', + equal: false + }, { + 0: 'SKIP IN', + 1: '$E22#{seconds} $FFFSECS', + equal: true + }, { + 0: '$E22DISABLED', + 1: 'DUE TO RES', + equal: true + }, { + 0: 'MAP IS', + 1: '$E22SKIPPED', + equal: true + }, { + 0: '$E22DISABLED', + 1: 'DUE TO EXT', + equal: true + }], header: `${tm.utils.palette.highlight}Vote to $${tm.utils.palette.red}SKIP${tm.utils.palette.highlight} the ongoing map`, icon: icons.voteSkip, voteIcon: icons.voteSkip, @@ -105,14 +152,31 @@ export default { } }, voteReplay: { - texts: [ - { 0: " VOTE ", 1: "TO $AF4RES", equal: false }, - { 0: "$AF4RES$z$s LIMIT", 1: "REACHED", equal: true }, - { 0: "MAP IS", 1: "$AF4REPLAYED", equal: true }, - { 0: "$E22DISABLED", 1: "DUE TO SKIP", equal: true }, - { 0: "VOTE TO", 1: "$AF4EXTEND", equal: true }, - { 0: "LIMIT", 1: "$AF4REACHED", equal: true } - ], + texts: [{ + 0: ' VOTE ', + 1: 'TO $AF4RES', + equal: false + }, { + 0: '$AF4RES$z$s LIMIT', + 1: 'REACHED', + equal: true + }, { + 0: 'MAP IS', + 1: '$AF4REPLAYED', + equal: true + }, { + 0: '$E22DISABLED', + 1: 'DUE TO SKIP', + equal: true + }, { + 0: 'VOTE TO', + 1: '$AF4EXTEND', + equal: true + }, { + 0: 'LIMIT', + 1: '$AF4REACHED', + equal: true + }], timeExtension: 1000 * 60 * 5, // 5 minutes extensionsLimit: 2, // 0 to disable limit resHeader: `${tm.utils.palette.highlight}Vote to $${tm.utils.palette.green}REPLAY${tm.utils.palette.highlight} the ongoing map`, @@ -141,13 +205,27 @@ export default { } }, paySkip: { - texts: [ - { 0: "PAY $E22#{cost}", 1: "TO $E22SKIP", equal: false }, - { 0: "SKIP IN", 1: "$E22#{seconds} $FFFSECS", equal: true }, - { 0: "$E22DISABLED", 1: "DUE TO RES", equal: true }, - { 0: "MAP IS", 1: "$E22SKIPPED", equal: true }, - { 0: "$E22DISABLED", 1: "DUE TO EXT", equal: true }, - ], + texts: [{ + 0: 'PAY $E22#{cost}', + 1: 'TO $E22SKIP', + equal: false + }, { + 0: 'SKIP IN', + 1: '$E22#{seconds} $FFFSECS', + equal: true + }, { + 0: '$E22DISABLED', + 1: 'DUE TO RES', + equal: true + }, { + 0: 'MAP IS', + 1: '$E22SKIPPED', + equal: true + }, { + 0: '$E22DISABLED', + 1: 'DUE TO EXT', + equal: true + }], billMessage: 'Pay to skip the ongoing map', icon: icons.paySkip, width: 1.8, @@ -158,28 +236,50 @@ export default { actionId: 3 }, payReplay: { - texts: [ - { 0: "PAY $AF4#{cost}", 1: "TO $AF4RES", equal: false }, - { 0: "$AF4RES$z$s LIMIT", 1: "REACHED", equal: true }, - { 0: "MAP IS", 1: "$AF4REPLAYED", equal: true }, - { 0: "$E22DISABLED", 1: "DUE TO SKIP", equal: true }, - { 0: "PAY $AF4#{cost}", 1: "TO $AF4EXT", equal: false }, - { 0: "$AF4EXT$z$s LIMIT", 1: "REACHED", equal: true } - ], + texts: [{ + 0: 'PAY $AF4#{cost}', + 1: 'TO $AF4RES', + equal: false + }, { + 0: '$AF4RES$z$s LIMIT', + 1: 'REACHED', + equal: true + }, { + 0: 'MAP IS', + 1: '$AF4REPLAYED', + equal: true + }, { + 0: '$E22DISABLED', + 1: 'DUE TO SKIP', + equal: true + }, { + 0: 'PAY $AF4#{cost}', + 1: 'TO $AF4EXT', + equal: false + }, { + 0: '$AF4EXT$z$s LIMIT', + 1: 'REACHED', + equal: true + }], timeExtension: 1000 * 60 * 5, // 5 minutes billMessage: 'Pay to replay the ongoing map or extend the timer', icon: icons.payReplay, width: 1.8, height: 1.8, padding: 0.2, - costs: [ - 100, - 200, - 300, - 400, - 500 - ], + costs: [100, 200, 300, 400, 500], actionId: 4 }, - hidePanel: false, -} \ No newline at end of file + medalButton: { + width: 3, + height: 3, + padding: 0.3, + textYOffset: 3.6, + textNoMedal: 'NO MEDAL', + textBronzeMedal: `$da5BRONZE`, + textSilverMedal: 'SILVER', + textGoldMedal: `$${palette.yellow}GOLD`, + textAuthorMedal: `$${palette.green}AUTHOR` + }, + hidePanel: false +} diff --git a/plugins/ui/static_components/race/buttons/FavouriteButton.ts b/plugins/ui/static_components/race/buttons/FavouriteButton.ts new file mode 100644 index 000000000..adb971181 --- /dev/null +++ b/plugins/ui/static_components/race/buttons/FavouriteButton.ts @@ -0,0 +1,28 @@ +import type { ButtonData } from "./ButtonData.js" +import { UiButton } from "./UiButton.js" +import config from "./ButtonsWidget.config.js" +import { componentIds } from '../../../UI.js' + +const cfg = config.favouriteButton + +export class FavouriteButton extends UiButton { + + buttonData: ButtonData + + constructor() { + super() + this.buttonData = { + icon: cfg.icon, + text1: cfg.texts[0], + text2: cfg.texts[1], + iconWidth: cfg.width, + iconHeight: cfg.height, + padding: cfg.padding, + actionId: componentIds.favouriteButton, + equalTexts: cfg.texts.equal, + addPlayerId: true, + manialink: `addfavorite?action=add&server=${tm.config.server.login}&name=${encodeURIComponent(tm.config.server.name)}&zone=${encodeURIComponent(tm.config.server.zone)}` + } + } + +} \ No newline at end of file diff --git a/plugins/ui/static_components/race/buttons/MedalButton.ts b/plugins/ui/static_components/race/buttons/MedalButton.ts new file mode 100644 index 000000000..6db3eb1ee --- /dev/null +++ b/plugins/ui/static_components/race/buttons/MedalButton.ts @@ -0,0 +1,89 @@ +import type { ButtonData } from "./ButtonData.js" +import { UiButton } from "./UiButton.js" +import config from "./ButtonsWidget.config.js" +import { horizontallyCenteredText } from '../../../UI.js' +import buttonConfig from '../../../../ui/utils/StaticButton.config.js' + +const cfg = config.medalButton + +export class MedalButton extends UiButton { + + buttonData: ButtonData + + constructor() { + super() + // Texts in `ButtonData` are irrelevant because we use per-player rendering so + // button xml is constructed in `renderForPlayer` + this.buttonData = { + icon: "", + text1: "", + text2: "", + iconWidth: cfg.width, + iconHeight: cfg.height, + padding: cfg.padding, + equalTexts: false, + perPlayer: true + } + + tm.addListener(['BeginMap', 'PlayerDataUpdated', 'LocalRecord'], (): void => { + this.emitUpdate() + }) + } + + renderForPlayer(login: string, i: number, j: number, w: number, h: number): string { + const record = tm.records.getLocal(login)?.time ?? 0 + + const map = tm.maps.current + const width = w - config.margin + const height = h - config.margin + const topPadding = this.buttonData.padding + + type MedalInfo = { + limit: number, + text: string, + icon: string + } + + const thresholds: MedalInfo[] = [{ + limit: map.authorTime ?? Infinity, + text: cfg.textAuthorMedal, + icon: "MedalNadeo" + }, { + limit: map.goldTime ?? Infinity, + text: cfg.textGoldMedal, + icon: "MedalGold" + }, { + limit: map.silverTime ?? Infinity, + text: cfg.textSilverMedal, + icon: "MedalSilver" + }, { + limit: map.bronzeTime ?? Infinity, + text: cfg.textBronzeMedal, + icon: "MedalBronze" + }] + + let medalText = cfg.textNoMedal + let iconSub = "MedalSlot" + + if (record > 0) { + const hit = thresholds.find(t => record <= t.limit) + if (hit) { + medalText = hit.text; + iconSub = hit.icon + } + } + + const t1: string = horizontallyCenteredText(medalText, width, height, { + yOffset: cfg.textYOffset, + textScale: buttonConfig.textScale, + padding: buttonConfig.padding + }) + + return ` + + ${t1}` + + } + +} \ No newline at end of file diff --git a/plugins/ui/static_components/race/buttons/Messages.config.js b/plugins/ui/static_components/race/buttons/Messages.config.js index 1e9e99681..04d1087fd 100644 --- a/plugins/ui/static_components/race/buttons/Messages.config.js +++ b/plugins/ui/static_components/race/buttons/Messages.config.js @@ -4,8 +4,7 @@ import buttonCfg from './ButtonsWidget.config.js' export default { paySkip: { paymentFail: `${p.error}Failed to process payment.`, - success: `${p.highlight}#{name}${p.donation} has paid ${p.highlight}#{amount}C ${p.donation}to skip the ongoing map.` + - ` Skipping in ${p.highlight}#{seconds}s${p.donation}.` + success: `${p.highlight}#{name}${p.donation} has paid ${p.highlight}#{amount}C ${p.donation}to ${p.highlight}skip ${p.donation}the ongoing map.` + ` Skipping in ${p.highlight}#{seconds}s${p.donation}.` }, payReplay: { paymentFail: `${p.error}Failed to process payment.`, @@ -13,10 +12,10 @@ export default { skipEvent: 'skipped', replayEvent: 'replayed', refund: `, coppers will be returned`, - success: `${p.highlight}#{name}${p.donation} has paid ${p.highlight}#{amount}C ${p.donation}to #{action} the ongoing map.`, + success: `${p.highlight}#{name}${p.donation} has paid ${p.highlight}#{amount}C ${p.donation}to #{action}.`, refundMail: `Refund for failed map replay on ${tm.config.server.name}`, - replayStr: `${p.highlight}replay${p.vote}`, - extendStr: `${p.highlight}extend${p.vote} the time on` + replayStr: `${p.highlight}replay ${p.donation}the ongoing map`, + extendStr: `${p.highlight}extend the time ${p.donation}on the ongoing map` }, voteSkip: { start: `${p.highlight}#{nickname} ${p.vote}started a vote to ${p.highlight}skip ${p.vote}the ongoing map.`, @@ -37,10 +36,8 @@ export default { start: `${p.highlight}#{nickname} ${p.vote}started a vote to #{action} the ongoing map.`, tooLate: `${p.error}It's too late for replay vote.`, failedRecently: `${p.error}Vote failed recently, wait a bit.`, - cantReplay: `${p.error}Can't vote for replay when the dynamic timer is enabled.` + - ` Use ${p.highlight}/${buttonCfg.voteReplay.extendCommand.aliases[0]}${p.error} to vote for time extension instead.`, - cantExtend: `${p.error}Can't vote for time extension when the dynamic timer is disabled.` + - ` Use ${p.highlight}/${buttonCfg.voteReplay.command.aliases[0]}${p.error} to vote for replay instead.`, + cantReplay: `${p.error}Can't vote for replay when the dynamic timer is enabled.` + ` Use ${p.highlight}/${buttonCfg.voteReplay.extendCommand.aliases[0]}${p.error} to vote for time extension instead.`, + cantExtend: `${p.error}Can't vote for time extension when the dynamic timer is disabled.` + ` Use ${p.highlight}/${buttonCfg.voteReplay.command.aliases[0]}${p.error} to vote for replay instead.`, tooManyFailed: `${p.error}Too many votes failed.`, tooManyReplays: `${p.error}Too many replays.`, tooManyExtensions: `${p.error}Too many time extensions.`, @@ -51,4 +48,4 @@ export default { cancelled: `${p.vote} Vote to #{action} the ongoing map was cancelled.`, cancelledBy: `${p.vote}#{title} ${p.highlight}#{nickname}${p.vote} has cancelled the vote to #{action} the ongoing map.` } -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/race/buttons/PaySkip.ts b/plugins/ui/static_components/race/buttons/PaySkip.ts index bc4a1200e..407695139 100644 --- a/plugins/ui/static_components/race/buttons/PaySkip.ts +++ b/plugins/ui/static_components/race/buttons/PaySkip.ts @@ -39,8 +39,7 @@ export class PaySkip extends UiButton { } private handleClick = async (login: string, nickname: string): Promise => { - if (this.isLastMapReplay || this.isReplay - || this.isSkip || tm.getState() === 'result') { return } + if (this.isLastMapReplay || this.isReplay || this.isSkip || tm.getState() === 'result') { return } const res: boolean | Error = await tm.utils.sendCoppers(login, cfg.cost, cfg.billMessage) if (res instanceof Error) { tm.sendMessage(msg.paymentFail, login) @@ -114,9 +113,9 @@ export class PaySkip extends UiButton { this.isReplay = true } if (this.isSkip || this.isLastMapReplay) { return } - this.buttonData.text1 = cfg.texts[2+plus][0] - this.buttonData.text2 = cfg.texts[2+plus][1] - this.buttonData.equalTexts = cfg.texts[2+plus].equal + this.buttonData.text1 = cfg.texts[2 + plus][0] + this.buttonData.text2 = cfg.texts[2 + plus][1] + this.buttonData.equalTexts = cfg.texts[2 + plus].equal this.buttonData.actionId = undefined this.emitUpdate() } diff --git a/plugins/ui/static_components/race/buttons/UiButton.ts b/plugins/ui/static_components/race/buttons/UiButton.ts index 122cb3d1f..a68596738 100644 --- a/plugins/ui/static_components/race/buttons/UiButton.ts +++ b/plugins/ui/static_components/race/buttons/UiButton.ts @@ -37,4 +37,12 @@ export abstract class UiButton { UiButton.skipListeners.push(callback) } + /** + * If `ButtonData.perPlayer` is set to true this function will be used for rendering, instead + * of static rendering based on `ButtonData` (see `MedalButton.ts`) + */ + public renderForPlayer(login: string, i: number, j: number, w: number, h: number): string { + return `` + } + } \ No newline at end of file diff --git a/plugins/ui/static_components/race/buttons/VoteReplay.ts b/plugins/ui/static_components/race/buttons/VoteReplay.ts index 11cd19b73..e1185c186 100644 --- a/plugins/ui/static_components/race/buttons/VoteReplay.ts +++ b/plugins/ui/static_components/race/buttons/VoteReplay.ts @@ -84,7 +84,10 @@ export class VoteReplay extends UiButton { } return } - const startMsg: string = tm.utils.strVar(msg.start, { action, nickname: tm.utils.strip(nickname, true) }) + const startMsg: string = tm.utils.strVar(msg.start, { + action, + nickname: tm.utils.strip(nickname, true) + }) const header = tm.timer.isDynamic ? cfg.extendHeader : cfg.resHeader const voteWindow: VoteWindow = new VoteWindow(login, cfg.goal, header, startMsg, cfg.time, cfg.voteIcon) const result = await voteWindow.startAndGetResult(tm.players.list) diff --git a/plugins/ui/static_components/race/buttons/VoteSkip.ts b/plugins/ui/static_components/race/buttons/VoteSkip.ts index c0c785ca3..60306a497 100644 --- a/plugins/ui/static_components/race/buttons/VoteSkip.ts +++ b/plugins/ui/static_components/race/buttons/VoteSkip.ts @@ -50,8 +50,7 @@ export class VoteSkip extends UiButton { } private async handleClick(login: string, nickname: string): Promise { - if (this.isLastMapReplay || this.isReplay - || this.isSkip || tm.getState() === 'result') { return } + if (this.isLastMapReplay || this.isReplay || this.isSkip || tm.getState() === 'result') { return } const autopass = tm.players.count === 1 if (!autopass && tm.timer.remainingRaceTime <= cfg.minimumRemainingTime) { tm.sendMessage(msg.tooLate, login) @@ -169,9 +168,9 @@ export class VoteSkip extends UiButton { this.isReplay = true } if (this.isSkip || this.isLastMapReplay) { return } - this.buttonData.text1 = cfg.texts[2+plus][0] - this.buttonData.text2 = cfg.texts[2+plus][1] - this.buttonData.equalTexts = cfg.texts[2+plus].equal + this.buttonData.text1 = cfg.texts[2 + plus][0] + this.buttonData.text2 = cfg.texts[2 + plus][1] + this.buttonData.equalTexts = cfg.texts[2 + plus].equal this.buttonData.actionId = undefined this.emitUpdate() } diff --git a/plugins/ui/static_components/result/AdminPanelResult.component.ts b/plugins/ui/static_components/result/AdminPanelResult.component.ts index 1dbd8a0d4..ff70b3a90 100644 --- a/plugins/ui/static_components/result/AdminPanelResult.component.ts +++ b/plugins/ui/static_components/result/AdminPanelResult.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { componentIds, StaticHeader, Grid, type GridCellFunction, addManialinkListener, StaticComponent } from '../../UI.js' +import { addManialinkListener, componentIds, Grid, type GridCellFunction, StaticComponent, StaticHeader } from '../../UI.js' import config from './AdminPanelResult.config.js' import { actions } from '../../../actions/Actions.js' @@ -24,7 +24,8 @@ export default class AdminPanelResult extends StaticComponent { constructor() { super(componentIds.adminResult) this.header = new StaticHeader('result') - this.grid = new Grid(config.width + config.margin * 2, config.height - this.header.options.height, new Array(5).fill(1), [1], { margin: config.margin }) + this.grid = new Grid(config.width + config.margin * 2, config.height - this.header.options.height, + new Array(5).fill(1), [1], { margin: config.margin }) this.renderOnEvent('PrivilegeChanged', (info) => { return this.displayToPlayer(info.login) }) @@ -45,9 +46,10 @@ export default class AdminPanelResult extends StaticComponent { tm.openManialink(componentIds.jukebox, info.login) }) addManialinkListener(this.id + this.actions.shuffle, info => { - tm.sendMessage(tm.utils.strVar(config.messages.shuffle, - { title: info.title, adminName: tm.utils.strip(info.nickname) }), - config.public ? undefined : info.login) + tm.sendMessage(tm.utils.strVar(config.messages.shuffle, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + }), config.public ? undefined : info.login) tm.jukebox.shuffle(info) }) } @@ -62,34 +64,51 @@ export default class AdminPanelResult extends StaticComponent { this.noShuffleXml = this.constructXml(true) const playerList = tm.players.list const shuffleLogins = playerList - .filter(a => a.privilege >= config.shufflePrivilege) - .map(a => a.login) + .filter(a => a.privilege >= config.shufflePrivilege) + .map(a => a.login) const noShuffleLogins = playerList - .filter(a => a.privilege < config.shufflePrivilege && a.privilege >= config.privilege) - .map(a => a.login) + .filter(a => a.privilege < config.shufflePrivilege && a.privilege >= config.privilege) + .map(a => a.login) const noPrivilegeLogins = playerList - .filter(a => a.privilege < config.privilege) - .map(a => a.login) - return [{ xml: this.xml, login: shuffleLogins }, - { xml: this.noShuffleXml, login: noShuffleLogins }, - { xml: ``, login: noPrivilegeLogins }] + .filter(a => a.privilege < config.privilege) + .map(a => a.login) + return [{ + xml: this.xml, + login: shuffleLogins + }, { + xml: this.noShuffleXml, + login: noShuffleLogins + }, { + xml: ``, + login: noPrivilegeLogins + }] } displayToPlayer(login: string) { if (!this.isDisplayed) { return } const privilege: number = tm.players.get(login)?.privilege ?? 0 if (privilege >= config.shufflePrivilege) { - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } else if (privilege >= config.privilege) { - return { xml: this.noShuffleXml, login } + return { + xml: this.noShuffleXml, + login + } } else { - return { xml: ``, login } + return { + xml: ``, + login + } } } private constructButton(width: number, height: number, icon: string, hoverIcon: string, actionId?: number): string { const actionIdStr: string = actionId === undefined ? '' : ` action="${actionId + this.id}"` - const cover: string = actionId !== undefined ? '' : `` + const cover: string = actionId !== undefined ? '' : + `` return `${cover} - this.constructButton(w, h, config.icons.players, config.iconsHover.players, this.actions.players) + const playersButton: GridCellFunction = (i, j, w, h): string => this.constructButton(w, h, config.icons.players, + config.iconsHover.players, this.actions.players) const previousButton: GridCellFunction = (i, j, w, h): string => { if (tm.jukebox.history.length < 1) { return this.constructButton(w, h, config.icons.previous, config.iconsHover.previous) } return this.constructButton(w, h, config.icons.previous, config.iconsHover.previous, this.actions.previous) } - const replayButton: GridCellFunction = (i, j, w, h): string => - this.constructButton(w, h, config.icons.requeue, config.iconsHover.requeue, this.actions.requeue) + const replayButton: GridCellFunction = (i, j, w, h): string => this.constructButton(w, h, config.icons.requeue, + config.iconsHover.requeue, this.actions.requeue) const shuffleButton: GridCellFunction = (i, j, w, h): string => { if (noShuffle) { return this.constructButton(w, h, config.icons.shuffle, config.iconsHover.shuffle) } return this.constructButton(w, h, config.icons.shuffle, config.iconsHover.shuffle, this.actions.shuffle) } - const jukeboxButton: GridCellFunction = (i, j, w, h): string => - this.constructButton(w, h, config.icons.jukebox, config.iconsHover.jukebox, this.actions.jukebox) + const jukeboxButton: GridCellFunction = (i, j, w, h): string => this.constructButton(w, h, config.icons.jukebox, + config.iconsHover.jukebox, this.actions.jukebox) return ` diff --git a/plugins/ui/static_components/result/AdminPanelResult.config.js b/plugins/ui/static_components/result/AdminPanelResult.config.js index 431b3394b..32f8dcf5e 100644 --- a/plugins/ui/static_components/result/AdminPanelResult.config.js +++ b/plugins/ui/static_components/result/AdminPanelResult.config.js @@ -1,5 +1,6 @@ import cfg from '../../config/ResultUi.js' import icons from '../../config/Icons.js' + const p = tm.utils.palette export default { @@ -9,7 +10,7 @@ export default { margin: cfg.margin, background: cfg.background, shufflePrivilege: 2, - title: "Admin Panel", + title: 'Admin Panel', icon: icons.gears, disabledColour: '333C', public: true, @@ -31,4 +32,4 @@ export default { previous: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has requeued the previous map.`, shuffle: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has shuffled the queue.` } -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/result/AveragesRanking.component.ts b/plugins/ui/static_components/result/AveragesRanking.component.ts index f27ef1484..ec4004b1b 100644 --- a/plugins/ui/static_components/result/AveragesRanking.component.ts +++ b/plugins/ui/static_components/result/AveragesRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { List, componentIds, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, List, StaticComponent, StaticHeader } from '../../UI.js' import { stats } from '../../../stats/Stats.js' import config from './AveragesRanking.config.js' @@ -16,9 +16,11 @@ export default class AveragesRanking extends StaticComponent { constructor() { super(componentIds.averagesRanking) this.header = new StaticHeader('result') - this.list = new List(config.entries, config.width, - this.getHeight() - (this.header.options.height + config.margin), config.columnProportions, - { background: config.background, headerBg: this.header.options.textBackground }) + this.list = new List(config.entries, config.width, this.getHeight() - (this.header.options.height + config.margin), + config.columnProportions, { + background: config.background, + headerBg: this.header.options.textBackground + }) stats.averages.onUpdate(() => this.sendMultipleManialinks(this.display())) stats.averages.onNicknameChange(() => this.sendMultipleManialinks(this.display())) } @@ -35,7 +37,10 @@ export default class AveragesRanking extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } constructXml(): void { @@ -45,7 +50,8 @@ export default class AveragesRanking extends StaticComponent { ${this.header.constructXml(config.title, config.icon, this.side)} - ${this.list.constructXml(list.map(a => a.average.toFixed(2)), list.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} + ${this.list.constructXml(list.map(a => a.average.toFixed(2)), + list.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} ` diff --git a/plugins/ui/static_components/result/AveragesRanking.config.js b/plugins/ui/static_components/result/AveragesRanking.config.js index 71c95519f..fcca42d68 100644 --- a/plugins/ui/static_components/result/AveragesRanking.config.js +++ b/plugins/ui/static_components/result/AveragesRanking.config.js @@ -5,15 +5,11 @@ export default { entries: 5, entryHeight: 2.15, width: cfg.width, - title: "Top Ranks", + title: 'Top Ranks', icon: icons.stats, background: cfg.background, margin: cfg.margin, - columnProportions: [ - 1, - 1.9, - 5.1 - ], + columnProportions: [1, 1.9, 5.1], topCount: 5, displayNoRecordEntry: true -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/result/BannerWidget.component.ts b/plugins/ui/static_components/result/BannerWidget.component.ts index 0bbeffb80..2e015f7b9 100644 --- a/plugins/ui/static_components/result/BannerWidget.component.ts +++ b/plugins/ui/static_components/result/BannerWidget.component.ts @@ -29,7 +29,8 @@ export default class BannerWidget extends StaticComponent { - + ${this.constructDonateButtons(w, m)} @@ -56,7 +57,10 @@ export default class BannerWidget extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } constructDonateButtons(width: number, margin: number) { @@ -69,8 +73,11 @@ export default class BannerWidget extends StaticComponent { const action = i === 0 ? '' : `action="${this.id + i}"` const bg = i === 0 ? config.mainTextBackground : config.donateBackground ret += ` - ${centeredText(arr[i], w, config.buttonHeight, - { xOffset: x, yOffset: margin, textScale: config.textScale })}` + ${centeredText(arr[i], w, config.buttonHeight, { + xOffset: x, + yOffset: margin, + textScale: config.textScale + })}` x += w + margin } return ret diff --git a/plugins/ui/static_components/result/DediRankingResult.component.ts b/plugins/ui/static_components/result/DediRankingResult.component.ts index d06d11548..db83fc809 100644 --- a/plugins/ui/static_components/result/DediRankingResult.component.ts +++ b/plugins/ui/static_components/result/DediRankingResult.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { RecordList, componentIds, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, RecordList, StaticComponent, StaticHeader } from '../../UI.js' import { dedimania } from '../../../dedimania/Dedimania.js' import config from './DediRankingResult.config.js' @@ -16,10 +16,11 @@ export default class DediRankingResult extends StaticComponent { constructor() { super(componentIds.dedisResult) this.header = new StaticHeader('result') - this.recordList = new RecordList('result', this.id, config.width, this.getHeight() - (this.header.options.height + config.margin), - config.entries, this.side, config.topCount, this.maxDedis, config.displayNoRecordEntry) + this.recordList = new RecordList('result', this.id, config.width, + this.getHeight() - (this.header.options.height + config.margin), config.entries, this.side, config.topCount, + this.maxDedis, config.displayNoRecordEntry) this.recordList.onClick((info: tm.ManialinkClickInfo): void => { - if(this.reduxModeEnabled) { return } + if (this.reduxModeEnabled) { return } const obj = this.displayToPlayer(info.login) if (obj !== undefined) { tm.sendManialink(obj.xml, obj.login) @@ -40,7 +41,7 @@ export default class DediRankingResult extends StaticComponent { display() { if (!this.isDisplayed) { return } - if(this.reduxModeEnabled) { return this.displayToPlayer('')?.xml } + if (this.reduxModeEnabled) { return this.displayToPlayer('')?.xml } const arr = [] for (const player of tm.players.list) { arr.push(this.displayToPlayer(player.login)) @@ -56,7 +57,10 @@ export default class DediRankingResult extends StaticComponent { ${this.header.constructXml(config.title, config.icon, this.side, { actionId: componentIds.dediCps })} - ${this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, dedimania.records.map(a => ({ ...a, name: a.nickname })))} + ${this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, dedimania.records.map(a => ({ + ...a, + name: a.nickname + })))} `, diff --git a/plugins/ui/static_components/result/DediRankingResult.config.js b/plugins/ui/static_components/result/DediRankingResult.config.js index 3684b515c..0b71b876c 100644 --- a/plugins/ui/static_components/result/DediRankingResult.config.js +++ b/plugins/ui/static_components/result/DediRankingResult.config.js @@ -5,7 +5,7 @@ export default { entries: 5, entryHeight: 2.15, width: cfg.width, - title: "Dedi Records", + title: 'Dedi Records', icon: icons.chartDedi, margin: cfg.margin, topCount: 5, diff --git a/plugins/ui/static_components/result/DonatorsRanking.component.ts b/plugins/ui/static_components/result/DonatorsRanking.component.ts index f61dbfc63..c51982389 100644 --- a/plugins/ui/static_components/result/DonatorsRanking.component.ts +++ b/plugins/ui/static_components/result/DonatorsRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { componentIds, List, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, List, StaticComponent, StaticHeader } from '../../UI.js' import { stats } from '../../../stats/Stats.js' import config from './DonatorsRanking.config.js' @@ -17,7 +17,10 @@ export default class DonatorsRanking extends StaticComponent { super(componentIds.donatorsRanking) this.header = new StaticHeader('result') this.list = new List(config.entries, config.width, this.getHeight() - (this.header.options.height + config.margin), - config.columnProportions, { background: config.background, headerBg: this.header.options.textBackground }) + config.columnProportions, { + background: config.background, + headerBg: this.header.options.textBackground + }) stats.donations.onUpdate(() => this.sendMultipleManialinks(this.display())) stats.donations.onNicknameChange(() => this.sendMultipleManialinks(this.display())) } @@ -34,7 +37,10 @@ export default class DonatorsRanking extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } constructXml(): void { @@ -44,7 +50,8 @@ export default class DonatorsRanking extends StaticComponent { ${this.header.constructXml(config.title, config.icon, config.side)} - ${this.list.constructXml(list.map(a => a.amount.toString()), list.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} + ${this.list.constructXml(list.map(a => a.amount.toString()), + list.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} ` diff --git a/plugins/ui/static_components/result/DonatorsRanking.config.js b/plugins/ui/static_components/result/DonatorsRanking.config.js index 3819cf7c3..2bff059c9 100644 --- a/plugins/ui/static_components/result/DonatorsRanking.config.js +++ b/plugins/ui/static_components/result/DonatorsRanking.config.js @@ -5,16 +5,12 @@ export default { entries: 5, entryHeight: 2.15, width: cfg.width, - posX: cfg.rightPosition - (cfg.marginBig + cfg.width) * 2, + posX: cfg.rightPosition - (cfg.marginBig + cfg.width) * 2, posY: cfg.topBorder, side: true, - title: "Top Donators", + title: 'Top Donators', icon: icons.cash, margin: cfg.margin, background: cfg.background, - columnProportions: [ - 1, - 1.9, - 5.1 - ] + columnProportions: [1, 1.9, 5.1] } diff --git a/plugins/ui/static_components/result/KarmaRanking.component.ts b/plugins/ui/static_components/result/KarmaRanking.component.ts index 1056fec89..6f852dbad 100644 --- a/plugins/ui/static_components/result/KarmaRanking.component.ts +++ b/plugins/ui/static_components/result/KarmaRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { componentIds, List, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, List, StaticComponent, StaticHeader } from '../../UI.js' import config from './KarmaRanking.config.js' export default class KarmaRanking extends StaticComponent { @@ -16,7 +16,10 @@ export default class KarmaRanking extends StaticComponent { super(componentIds.karmaRanking) this.header = new StaticHeader('result') this.list = new List(config.entries, config.width, this.getHeight() - (this.header.options.height + config.margin), - config.columnProportions, { background: config.background, headerBg: this.header.options.textBackground }) + config.columnProportions, { + background: config.background, + headerBg: this.header.options.textBackground + }) this.renderOnEvent('KarmaVote', () => { return this.display() }) @@ -34,17 +37,22 @@ export default class KarmaRanking extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } constructXml(): void { - const list: Readonly[] = tm.maps.list.sort((a, b): number => b.voteRatio - a.voteRatio).filter(a => a.voteCount > config.minimumVotes).slice(0, config.entries) + const list: Readonly[] = tm.maps.list.sort((a, b): number => b.voteRatio - a.voteRatio) + .filter(a => a.voteCount > config.minimumVotes).slice(0, config.entries) this.xml = ` ${this.header.constructXml(config.title, config.icon, this.side)} - ${this.list.constructXml(list.map(a => a.voteRatio === -1 ? config.defaultText : a.voteRatio.toFixed(0)), list.map(a => tm.utils.safeString(tm.utils.strip(tm.utils.decodeURI(a.name), false))))} + ${this.list.constructXml(list.map(a => a.voteRatio === -1 ? config.defaultText : a.voteRatio.toFixed(0)), + list.map(a => tm.utils.safeString(tm.utils.strip(tm.utils.decodeURI(a.name), false))))} ` diff --git a/plugins/ui/static_components/result/KarmaRanking.config.js b/plugins/ui/static_components/result/KarmaRanking.config.js index 70f1a98c8..658ef9db7 100644 --- a/plugins/ui/static_components/result/KarmaRanking.config.js +++ b/plugins/ui/static_components/result/KarmaRanking.config.js @@ -5,15 +5,11 @@ export default { entries: 5, entryHeight: 2.15, width: cfg.width, - title: "Best Maps", + title: 'Best Maps', defaultText: '-', icon: icons.karmaStats, margin: cfg.margin, background: cfg.background, minimumVotes: 5, - columnProportions: [ - 1, - 1.9, - 5.1 - ] + columnProportions: [1, 1.9, 5.1] } diff --git a/plugins/ui/static_components/result/KarmaWidgetResult.component.ts b/plugins/ui/static_components/result/KarmaWidgetResult.component.ts index 1e7ac0d24..77ba1ea00 100644 --- a/plugins/ui/static_components/result/KarmaWidgetResult.component.ts +++ b/plugins/ui/static_components/result/KarmaWidgetResult.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { centeredText, Grid, type GridCellFunction, componentIds, StaticHeader, addManialinkListener, StaticComponent } from '../../UI.js' +import { addManialinkListener, centeredText, componentIds, Grid, type GridCellFunction, StaticComponent, StaticHeader } from '../../UI.js' import { maniakarma, type MKMapVotes } from '../../../maniakarma/Maniakarma.js' import config from './KarmaWidgetResult.config.js' import { actions } from '../../../actions/Actions.js' @@ -20,8 +20,11 @@ export default class KarmaWidgetResult extends StaticComponent { super(componentIds.karmaResult) this.header = new StaticHeader('result') this.headerH = this.header.options.height - this.grid = new Grid((config.width + config.margin - config.buttonWidth) / 2, config.margin + config.height - this.headerH, - new Array(3).fill(1), new Array(3).fill(1), { background: config.background, margin: config.margin }) + this.grid = new Grid((config.width + config.margin - config.buttonWidth) / 2, + config.margin + config.height - this.headerH, new Array(3).fill(1), new Array(3).fill(1), { + background: config.background, + margin: config.margin + }) this.renderOnEvent('KarmaVote', () => { return this.display() }) maniakarma.onMapFetch(() => this.sendMultipleManialinks(this.display())) maniakarma.onVote(() => this.sendMultipleManialinks(this.display())) @@ -45,7 +48,10 @@ export default class KarmaWidgetResult extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.constructXml(login), login } + return { + xml: this.constructXml(login), + login + } } private constructXml(login: string): string { @@ -100,9 +106,11 @@ export default class KarmaWidgetResult extends StaticComponent { const karmaStr: string = karma !== -1 ? (~~karma).toString() : config.defaultText const mkKarmaStr: string = mkVoteCount !== 0 ? (~~mkKarma).toString() : config.defaultText const mkCountStr: string = maniakarma.isEnabled ? mkVoteCount.toString() : config.defaultText - const options = { padding: config.textPadding, textScale: config.textScale } - const arr: GridCellFunction[] = [ - (i, j, w, h): string => ``, + const options = { + padding: config.textPadding, + textScale: config.textScale + } + const arr: GridCellFunction[] = [(i, j, w, h): string => ``, (i, j, w, h): string => ``, (i, j, w, h): string => ` centeredText(karmaStr, w, h, options), (i, j, w, h): string => centeredText(mkKarmaStr, w, h, options), - (i, j, w, h): string => ``, + (i, j, w, + h): string => ``, (i, j, w, h): string => centeredText(voteCount.toString(), w, h, options), - (i, j, w, h): string => centeredText(mkCountStr, w, h, options), - ] + (i, j, w, h): string => centeredText(mkCountStr, w, h, options)] return this.grid.constructXml(arr) } @@ -129,8 +137,12 @@ export default class KarmaWidgetResult extends StaticComponent { const colour: string = this.options[i] === personalVote ? config.selfColour : config.mkColours[i] ret += ` - ${centeredText(e, config.buttonWidth - (config.margin * 2), h - config.margin, - { xOffset: config.margin, yOffset: config.margin + h * i + offsetFix, padding: 0, textScale })}` + ${centeredText(e, config.buttonWidth - (config.margin * 2), h - config.margin, { + xOffset: config.margin, + yOffset: config.margin + h * i + offsetFix, + padding: 0, + textScale + })}` } return ret } diff --git a/plugins/ui/static_components/result/KarmaWidgetResult.config.js b/plugins/ui/static_components/result/KarmaWidgetResult.config.js index 9ca71acbc..1bafda261 100644 --- a/plugins/ui/static_components/result/KarmaWidgetResult.config.js +++ b/plugins/ui/static_components/result/KarmaWidgetResult.config.js @@ -4,15 +4,10 @@ import icons from '../../config/Icons.js' export default { height: 8.8, width: cfg.width, - title: "Karma", + title: 'Karma', icon: icons.karmaStats, margin: cfg.margin, - icons: [ - icons.karmaLocal, - icons.karmaGlobal, - icons.karmaPulse, - icons.peopleKarma - ], + icons: [icons.karmaLocal, icons.karmaGlobal, icons.karmaPulse, icons.peopleKarma], colours: ['0F8A', '0D6A', '0B4A', 'BO4A', 'D06A', 'F08A'], mkColours: ['0F0A', '0D0A', '0B0A', 'BOOA', 'D00A', 'F00A'], options: ['+++', '++', '+', '-', '--', '---'], @@ -30,4 +25,4 @@ export default { scale: 1, offset: -0.3 } -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/result/LocalRankingResult.component.ts b/plugins/ui/static_components/result/LocalRankingResult.component.ts index e916a7440..714df8d6c 100644 --- a/plugins/ui/static_components/result/LocalRankingResult.component.ts +++ b/plugins/ui/static_components/result/LocalRankingResult.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { RecordList, componentIds, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, RecordList, StaticComponent, StaticHeader } from '../../UI.js' import config from './LocalRankingResult.config.js' export default class LocalRankingResult extends StaticComponent { @@ -14,8 +14,9 @@ export default class LocalRankingResult extends StaticComponent { constructor() { super(componentIds.localsResult) this.header = new StaticHeader('result') - this.recordList = new RecordList('result', this.id, config.width, this.getHeight() - (this.header.options.height + config.margin), - config.entries, this.side, config.topCount, tm.records.maxLocalsAmount, config.displayNoRecordEntry) + this.recordList = new RecordList('result', this.id, config.width, + this.getHeight() - (this.header.options.height + config.margin), config.entries, this.side, config.topCount, + tm.records.maxLocalsAmount, config.displayNoRecordEntry) this.recordList.onClick((info: tm.ManialinkClickInfo) => { if (this.reduxModeEnabled) { return } const obj = this.displayToPlayer(info.login) @@ -56,8 +57,14 @@ export default class LocalRankingResult extends StaticComponent { ${this.header.constructXml(config.title, config.icon, this.side, { actionId: componentIds.localCps })} ${this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, tm.records.local - .map(a => ({ name: a.nickname, time: a.time, date: a.date, checkpoints: a.checkpoints, login: a.login })) - .slice(0, tm.records.maxLocalsAmount))} + .map(a => ({ + name: a.nickname, + time: a.time, + date: a.date, + checkpoints: a.checkpoints, + login: a.login + })) + .slice(0, tm.records.maxLocalsAmount))} `, diff --git a/plugins/ui/static_components/result/LocalRankingResult.config.js b/plugins/ui/static_components/result/LocalRankingResult.config.js index 59608b826..85395ceb1 100644 --- a/plugins/ui/static_components/result/LocalRankingResult.config.js +++ b/plugins/ui/static_components/result/LocalRankingResult.config.js @@ -5,9 +5,9 @@ export default { entries: 5, entryHeight: 2.15, width: cfg.width, - title: "Local Records", + title: 'Local Records', icon: icons.chartLocal, margin: cfg.margin, topCount: 5, displayNoRecordEntry: true -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/result/MapWidgetResult.component.ts b/plugins/ui/static_components/result/MapWidgetResult.component.ts index 9fab4eb47..1917f9eec 100644 --- a/plugins/ui/static_components/result/MapWidgetResult.component.ts +++ b/plugins/ui/static_components/result/MapWidgetResult.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { componentIds, Grid, StaticHeader, StaticComponent, type StaticHeaderOptions } from '../../UI.js' +import { componentIds, Grid, StaticComponent, StaticHeader, type StaticHeaderOptions } from '../../UI.js' import flags from '../../config/FlagIcons.js' import { tmx } from '../../../tmx/Tmx.js' import { webservices, type WebservicesInfo } from '../../../webservices/Webservices.js' @@ -14,7 +14,8 @@ export default class MapWidgetResult extends StaticComponent { private readonly rows: number = 5 private readonly header: StaticHeader private readonly grid: Grid - private readonly displayEnvironment: boolean = config.displayEnvironment !== undefined ? config.displayEnvironment : process.env.SERVER_PACKMASK !== "nations" + private readonly displayEnvironment: boolean = config.displayEnvironment !== undefined ? config.displayEnvironment : + process.env.SERVER_PACKMASK !== "nations" private isRestart: boolean = false private xml: string = '' @@ -49,7 +50,10 @@ export default class MapWidgetResult extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } private updateXML(): void { @@ -77,15 +81,14 @@ export default class MapWidgetResult extends StaticComponent { timeOrScoreIcon = ic.goldScore } } - const infos: [string, string][] = [ - [config.title, ic.header], - [tm.utils.safeString(tm.utils.decodeURI(map.name)), obj.tag], - [tm.utils.safeString(author), authorIcon], + const infos: [string, string][] = [[config.title, ic.header], + [tm.utils.safeString(tm.utils.decodeURI(map.name)), obj.tag], [tm.utils.safeString(author), authorIcon], [timeOrScore, timeOrScoreIcon], [date === undefined ? config.noDateText : tm.utils.formatDate(date), ic.buildDate], [TMXMap?.awards === undefined ? config.noAwardsText : TMXMap.awards.toString(), obj.award], - this.displayEnvironment ? [map.environment, ic.environment] : [TMXMap?.validReplays?.[0]?.time === undefined ? config.noWrText : tm.utils.getTimeString(TMXMap.validReplays[0].time), ic.tmxWr] - ] + this.displayEnvironment ? [map.environment, ic.environment] : + [TMXMap?.validReplays?.[0]?.time === undefined ? config.noWrText : + tm.utils.getTimeString(TMXMap.validReplays[0].time), ic.tmxWr]] const headerCfg: StaticHeaderOptions = this.header.options const cell = (i: number, j: number, w: number, h: number): string => { if (i === 4) { @@ -97,14 +100,13 @@ export default class MapWidgetResult extends StaticComponent { textBackground: config.background })} - + ${this.header.constructXml(infos[i + 2][0], infos[i + 2][1], this.side, { - rectangleWidth: (headerCfg.rectangleWidth / 2) - (headerCfg.margin + (headerCfg.squareWidth / 2)), - textScale: config.textScale, - centerText: true, - textBackground: config.background - })} + rectangleWidth: (headerCfg.rectangleWidth / 2) - (headerCfg.margin + (headerCfg.squareWidth / 2)), + textScale: config.textScale, + centerText: true, + textBackground: config.background + })} ` } else if (i === 3) { return ` @@ -116,24 +118,23 @@ export default class MapWidgetResult extends StaticComponent { textBackground: config.background })} - + ${this.header.constructXml(infos[i + 1][0], infos[i + 1][1], this.side, { - rectangleWidth: (headerCfg.rectangleWidth / 2) - (headerCfg.margin + (headerCfg.squareWidth / 2)), - textScale: config.textScale, - centerText: true, - textBackground: config.background - })} + rectangleWidth: (headerCfg.rectangleWidth / 2) - (headerCfg.margin + (headerCfg.squareWidth / 2)), + textScale: config.textScale, + centerText: true, + textBackground: config.background + })} ` } return ` ${i === 0 ? this.header.constructXml(infos[i][0], infos[i][1], this.side) : - this.header.constructXml(tm.utils.strip(infos[i][0], false), infos[i][1], this.side, { - textScale: config.textScale, - textBackground: config.background, - horizontalPadding: config.mapPadding - })} + this.header.constructXml(tm.utils.strip(infos[i][0], false), infos[i][1], this.side, { + textScale: config.textScale, + textBackground: config.background, + horizontalPadding: config.mapPadding + })} ` } const arr: any[] = new Array(this.rows).fill(cell) @@ -146,7 +147,10 @@ export default class MapWidgetResult extends StaticComponent { ` } - private getTagAndAward(map: tm.Map, TMXMap?: tm.TMXMap): { tag: string, award: string } { + private getTagAndAward(map: tm.Map, TMXMap?: tm.TMXMap): { + tag: string, + award: string + } { let tag: string = config.icons.tags.normal let award: string = config.icons.awards.normal if (map.isNadeo) { @@ -158,12 +162,15 @@ export default class MapWidgetResult extends StaticComponent { award = config.icons.awards.classic } for (const e of config.customTags) { - if (e?.authors?.some(a => a === map.author || a === TMXMap?.author) || - e?.names?.some(a => a.test(map.name) || a.test(tm.utils.strip(map.name)) || - (TMXMap !== undefined ? (a.test(TMXMap.name) || a.test(tm.utils.strip(TMXMap.name))) : false))) { + if (e?.authors?.some(a => a === map.author || a === TMXMap?.author) || e?.names?.some( + a => a.test(map.name) || a.test(tm.utils.strip(map.name)) || (TMXMap !== undefined ? + (a.test(TMXMap.name) || a.test(tm.utils.strip(TMXMap.name))) : false))) { tag = e.icon } } - return { tag, award } + return { + tag, + award + } } } diff --git a/plugins/ui/static_components/result/MapWidgetResult.config.js b/plugins/ui/static_components/result/MapWidgetResult.config.js index 51819edc1..2022c6a74 100644 --- a/plugins/ui/static_components/result/MapWidgetResult.config.js +++ b/plugins/ui/static_components/result/MapWidgetResult.config.js @@ -6,21 +6,28 @@ export default { height: 11.4, margin: cfg.margin, width: cfg.width, - title: "Next", + title: 'Next', background: cfg.background, mapPadding: 0.3, - customTags: [ - /* Custom tags allow you to display special tag icon for certain authors or maps - If tmx name or login matches one of the authors or - map name matches one of the names regexes special tag will be displayed - map name is tested both stripped and unstripped from special characters - Tags are read from top to bottom - */ - { authors: ['Fwo Niro'], icon: icons.tagRed }, - { authors: ['tony89300'], icon: icons.tagPinkCyan }, - { names: [/{Kackiest Kacky}*/], icon: icons.tagKacky }, - { names: [/\$w\$i\$f70ARC_*/], icon: icons.tagArcade } - ], + customTags: [/* Custom tags allow you to display special tag icon for certain authors or maps + If tmx name or login matches one of the authors or + map name matches one of the names regexes special tag will be displayed + map name is tested both stripped and unstripped from special characters + Tags are read from top to bottom + */ + { + authors: ['Fwo Niro'], + icon: icons.tagRed + }, { + authors: ['tony89300'], + icon: icons.tagPinkCyan + }, { + names: [/{Kackiest Kacky}*/], + icon: icons.tagKacky + }, { + names: [/\$w\$i\$f70ARC_*/], + icon: icons.tagArcade + }], icons: { header: icons.ongoingMap, tags: { @@ -43,12 +50,12 @@ export default { }, textScale: 1, displayEnvironment: undefined, // Display environment instead of TMX time - noDateText: "N/A", - noAwardsText: "N/A", - noWrText: "-:--.--", + noDateText: 'N/A', + noAwardsText: 'N/A', + noWrText: '-:--.--', titles: { - lastTrack: "Last Map", - currTrack: "Ongoing", - nextTrack: "Next Map" + lastTrack: 'Last Map', + currTrack: 'Ongoing', + nextTrack: 'Next Map' } -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/result/MostRecordsRanking.component.ts b/plugins/ui/static_components/result/MostRecordsRanking.component.ts index 344497fa1..bd47eb491 100644 --- a/plugins/ui/static_components/result/MostRecordsRanking.component.ts +++ b/plugins/ui/static_components/result/MostRecordsRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { componentIds, List, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, List, StaticComponent, StaticHeader } from '../../UI.js' import { stats } from '../../../stats/Stats.js' import config from './MostRecordsRanking.config.js' @@ -17,7 +17,10 @@ export default class MostRecordsRanking extends StaticComponent { super(componentIds.mostRecordsRanking) this.header = new StaticHeader('result') this.list = new List(config.entries, config.width, this.getHeight() - (this.header.options.height + config.margin), - config.columnProportions, { background: config.background, headerBg: this.header.options.textBackground }) + config.columnProportions, { + background: config.background, + headerBg: this.header.options.textBackground + }) this.constructXml() tm.addListener('EndMap', (): void => { this.constructXml() @@ -38,7 +41,10 @@ export default class MostRecordsRanking extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } constructXml(): void { @@ -48,7 +54,8 @@ export default class MostRecordsRanking extends StaticComponent { ${this.header.constructXml(config.title, config.icon, config.side)} - ${this.list.constructXml(list.map(a => a.amount.toString()), list.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} + ${this.list.constructXml(list.map(a => a.amount.toString()), + list.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} ` diff --git a/plugins/ui/static_components/result/MostRecordsRanking.config.js b/plugins/ui/static_components/result/MostRecordsRanking.config.js index 710f24115..311a37488 100644 --- a/plugins/ui/static_components/result/MostRecordsRanking.config.js +++ b/plugins/ui/static_components/result/MostRecordsRanking.config.js @@ -8,13 +8,9 @@ export default { posX: cfg.leftPosition + (cfg.width + cfg.margin) * 2 + cfg.margin, posY: cfg.topBorder, side: false, - title: "Most Records", + title: 'Most Records', icon: icons.personBetter, margin: cfg.margin, background: cfg.background, - columnProportions: [ - 1, - 1.9, - 5.1 - ] + columnProportions: [1, 1.9, 5.1] } diff --git a/plugins/ui/static_components/result/NextMapRecords.component.ts b/plugins/ui/static_components/result/NextMapRecords.component.ts index 03b9199f5..c9c66948e 100644 --- a/plugins/ui/static_components/result/NextMapRecords.component.ts +++ b/plugins/ui/static_components/result/NextMapRecords.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { componentIds, RecordList, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, RecordList, StaticComponent, StaticHeader } from '../../UI.js' import config from './NextMapRecords.config.js' export default class NextMapRecords extends StaticComponent { @@ -15,8 +15,9 @@ export default class NextMapRecords extends StaticComponent { constructor() { super(componentIds.nextMapRecords) this.header = new StaticHeader('result') - this.recordList = new RecordList('result', this.id, config.width, this.getHeight() - (this.header.options.height + config.margin), - config.entries, this.side, 5, 5, false, { getColoursFromPb: true }) + this.recordList = new RecordList('result', this.id, config.width, + this.getHeight() - (this.header.options.height + config.margin), config.entries, this.side, 5, 5, false, + { getColoursFromPb: true }) this.recordList.onClick((info: tm.ManialinkClickInfo): void => { if (this.reduxModeEnabled) { return } const obj = this.displayToPlayer(info.login) @@ -31,8 +32,8 @@ export default class NextMapRecords extends StaticComponent { } else { const mapId: string = tm.jukebox.queue[0].id this.records = tm.records.getFromQueue(mapId) - .sort((a, b) => a.time - b.time) - .filter((a, i, arr) => arr.findIndex(b => b.login === a.login && a.map === b.map) === i) + .sort((a, b) => a.time - b.time) + .filter((a, i, arr) => arr.findIndex(b => b.login === a.login && a.map === b.map) === i) return this.display() } }) @@ -73,8 +74,11 @@ export default class NextMapRecords extends StaticComponent { ${this.header.constructXml(config.title, config.icon, this.side)} ${this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, this.records.map(a => ({ - name: a.nickname, time: a.time, - date: a.date, checkpoints: a.checkpoints, login: a.login + name: a.nickname, + time: a.time, + date: a.date, + checkpoints: a.checkpoints, + login: a.login })), pb)} diff --git a/plugins/ui/static_components/result/NextMapRecords.config.js b/plugins/ui/static_components/result/NextMapRecords.config.js index 0ca27c830..8c4563bb4 100644 --- a/plugins/ui/static_components/result/NextMapRecords.config.js +++ b/plugins/ui/static_components/result/NextMapRecords.config.js @@ -5,14 +5,10 @@ export default { entries: 5, entryHeight: 2.15, width: cfg.width, - title: "Next Map Records", + title: 'Next Map Records', icon: icons.chartLive, margin: cfg.margin, - columnProportions: [ - 1, - 2.9, - 4.1 - ], + columnProportions: [1, 2.9, 4.1], topCount: 5, displayNoRecordEntry: true } diff --git a/plugins/ui/static_components/result/PlaytimeRanking.component.ts b/plugins/ui/static_components/result/PlaytimeRanking.component.ts index 0398da9f6..065c9599c 100644 --- a/plugins/ui/static_components/result/PlaytimeRanking.component.ts +++ b/plugins/ui/static_components/result/PlaytimeRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { componentIds, List, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, List, StaticComponent, StaticHeader } from '../../UI.js' import { stats } from '../../../stats/Stats.js' import config from './PlaytimeRanking.config.js' @@ -17,7 +17,10 @@ export default class PlaytimeRanking extends StaticComponent { super(componentIds.playtimeRanking) this.header = new StaticHeader('result') this.list = new List(config.entries, config.width, this.getHeight() - (this.header.options.height + config.margin), - config.columnProportions, { background: config.background, headerBg: this.header.options.textBackground }) + config.columnProportions, { + background: config.background, + headerBg: this.header.options.textBackground + }) stats.playtimes.onUpdate((): void => { const xml = this.display() if (xml !== undefined) { tm.sendManialink(xml) } @@ -40,7 +43,10 @@ export default class PlaytimeRanking extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } constructXml() { diff --git a/plugins/ui/static_components/result/PlaytimeRanking.config.js b/plugins/ui/static_components/result/PlaytimeRanking.config.js index 6de1f21ad..8a4aad4a4 100644 --- a/plugins/ui/static_components/result/PlaytimeRanking.config.js +++ b/plugins/ui/static_components/result/PlaytimeRanking.config.js @@ -8,13 +8,9 @@ export default { posX: cfg.rightPosition - (cfg.marginBig + cfg.width), posY: cfg.topBorder, side: true, - title: "Top Hours Played", + title: 'Top Hours Played', icon: icons.clockList, margin: cfg.margin, background: cfg.background, - columnProportions: [ - 1, - 1.9, - 5.1 - ] -} \ No newline at end of file + columnProportions: [1, 1.9, 5.1] +} diff --git a/plugins/ui/static_components/result/RankWidgetResult.component.ts b/plugins/ui/static_components/result/RankWidgetResult.component.ts index 2db574d80..98a943bf1 100644 --- a/plugins/ui/static_components/result/RankWidgetResult.component.ts +++ b/plugins/ui/static_components/result/RankWidgetResult.component.ts @@ -26,7 +26,10 @@ export default class RankWidgetResult extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } protected onPositionChange(): void { diff --git a/plugins/ui/static_components/result/RoundAveragesRanking.component.ts b/plugins/ui/static_components/result/RoundAveragesRanking.component.ts index a3a4bfa04..bb1e9fda3 100644 --- a/plugins/ui/static_components/result/RoundAveragesRanking.component.ts +++ b/plugins/ui/static_components/result/RoundAveragesRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { List, componentIds, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, List, StaticComponent, StaticHeader } from '../../UI.js' import config from './RoundAveragesRanking.config.js' export default class RoundAveragesRanking extends StaticComponent { @@ -11,17 +11,30 @@ export default class RoundAveragesRanking extends StaticComponent { private readonly list: List private readonly header: StaticHeader private xml: string = '' - private averages: { nickname: string, login: string, average: number, finishcount: number }[] = [] + private averages: { + nickname: string, + login: string, + average: number, + finishcount: number + }[] = [] constructor() { super(componentIds.roundAveragesRanking) this.header = new StaticHeader('result') this.list = new List(config.entries, config.width, this.getHeight() - (this.header.options.height + config.margin), - config.columnProportions, { background: config.background, headerBg: this.header.options.textBackground }) + config.columnProportions, { + background: config.background, + headerBg: this.header.options.textBackground + }) tm.addListener('PlayerFinish', async (info): Promise => { const entry = this.averages.find(a => a.login === info.login) if (entry === undefined) { - this.averages.push({ nickname: info.nickname, login: info.login, average: info.time, finishcount: 1 }) + this.averages.push({ + nickname: info.nickname, + login: info.login, + average: info.time, + finishcount: 1 + }) } else { entry.average = ~~((entry.average * entry.finishcount + info.time) / (entry.finishcount + 1)) entry.finishcount++ @@ -48,7 +61,10 @@ export default class RoundAveragesRanking extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } constructXml(): void { @@ -57,7 +73,8 @@ export default class RoundAveragesRanking extends StaticComponent { ${this.header.constructXml(config.title, config.icon, this.side)} - ${this.list.constructXml(this.averages.map(a => tm.utils.getTimeString(a.average)), this.averages.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} + ${this.list.constructXml(this.averages.map(a => tm.utils.getTimeString(a.average)), + this.averages.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} ` diff --git a/plugins/ui/static_components/result/RoundAveragesRanking.config.js b/plugins/ui/static_components/result/RoundAveragesRanking.config.js index ab7c78afa..59dcc45d1 100644 --- a/plugins/ui/static_components/result/RoundAveragesRanking.config.js +++ b/plugins/ui/static_components/result/RoundAveragesRanking.config.js @@ -8,14 +8,10 @@ export default { posX: cfg.rightPosition - (cfg.marginBig + cfg.width), posY: cfg.topBorder, side: true, - title: "Round Averages", + title: 'Round Averages', icon: icons.clockList, margin: cfg.margin, background: cfg.background, - columnProportions: [ - 1, - 2.9, - 4.1 - ] + columnProportions: [1, 2.9, 4.1] } diff --git a/plugins/ui/static_components/result/TimerWidgetResult.component.ts b/plugins/ui/static_components/result/TimerWidgetResult.component.ts index 1fa1160bf..48acaa5fa 100644 --- a/plugins/ui/static_components/result/TimerWidgetResult.component.ts +++ b/plugins/ui/static_components/result/TimerWidgetResult.component.ts @@ -18,11 +18,11 @@ export default class TimerWidgetResult extends StaticComponent { getHeight(): number { return config.height } - + protected onPositionChange(): void { this.constructXml() const xml = this.display() - if(xml !== undefined) { tm.sendManialink(xml) } + if (xml !== undefined) { tm.sendManialink(xml) } } display() { @@ -32,7 +32,10 @@ export default class TimerWidgetResult extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } private constructXml(): void { diff --git a/plugins/ui/static_components/result/VisitorsRanking.component.ts b/plugins/ui/static_components/result/VisitorsRanking.component.ts index 7aefe940a..307e36b6a 100644 --- a/plugins/ui/static_components/result/VisitorsRanking.component.ts +++ b/plugins/ui/static_components/result/VisitorsRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { componentIds, List, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, List, StaticComponent, StaticHeader } from '../../UI.js' import { stats } from '../../../stats/Stats.js' import config from './VisitorsRanking.config.js' @@ -17,7 +17,10 @@ export default class VisitorsRanking extends StaticComponent { super(componentIds.visitorsRanking) this.header = new StaticHeader('result') this.list = new List(config.entries, config.width, this.getHeight() - (this.header.options.height + config.margin), - config.columnProportions, { background: config.background, headerBg: this.header.options.textBackground }) + config.columnProportions, { + background: config.background, + headerBg: this.header.options.textBackground + }) stats.visits.onUpdate((): void => { const xml = this.display() if (xml !== undefined) { tm.sendManialink(xml) } @@ -40,7 +43,10 @@ export default class VisitorsRanking extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } constructXml(): void { @@ -50,7 +56,8 @@ export default class VisitorsRanking extends StaticComponent { ${this.header.constructXml(config.title, config.icon, this.side)} - ${this.list.constructXml(list.map(a => a.visits.toString()), list.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} + ${this.list.constructXml(list.map(a => a.visits.toString()), + list.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} ` diff --git a/plugins/ui/static_components/result/VisitorsRanking.config.js b/plugins/ui/static_components/result/VisitorsRanking.config.js index a547e3f53..05a05b169 100644 --- a/plugins/ui/static_components/result/VisitorsRanking.config.js +++ b/plugins/ui/static_components/result/VisitorsRanking.config.js @@ -5,13 +5,9 @@ export default { entries: 5, entryHeight: 2.15, width: cfg.width, - title: "Most Visits", + title: 'Most Visits', icon: icons.person, margin: cfg.margin, background: cfg.background, - columnProportions: [ - 1, - 1.9, - 5.1 - ] + columnProportions: [1, 1.9, 5.1] } diff --git a/plugins/ui/static_components/result/VotersRanking.component.ts b/plugins/ui/static_components/result/VotersRanking.component.ts index 812026f63..59212ecb7 100644 --- a/plugins/ui/static_components/result/VotersRanking.component.ts +++ b/plugins/ui/static_components/result/VotersRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { componentIds, List, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, List, StaticComponent, StaticHeader } from '../../UI.js' import { stats } from '../../../stats/Stats.js' import config from './VotersRanking.config.js' @@ -17,7 +17,10 @@ export default class VotersRanking extends StaticComponent { super(componentIds.votersRanking) this.header = new StaticHeader('result') this.list = new List(config.entries, config.width, this.getHeight() - (this.header.options.height + config.margin), - config.columnProportions, { background: config.background, headerBg: this.header.options.textBackground }) + config.columnProportions, { + background: config.background, + headerBg: this.header.options.textBackground + }) stats.votes.onUpdate((): void => { const xml = this.display() if (xml !== undefined) { tm.sendManialink(xml) } @@ -40,7 +43,10 @@ export default class VotersRanking extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } constructXml(): void { @@ -50,7 +56,8 @@ export default class VotersRanking extends StaticComponent { ${this.header.constructXml(config.title, config.icon, this.side)} - ${this.list.constructXml(votes.map(a => a.count.toString()), votes.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} + ${this.list.constructXml(votes.map(a => a.count.toString()), + votes.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} ` diff --git a/plugins/ui/static_components/result/VotersRanking.config.js b/plugins/ui/static_components/result/VotersRanking.config.js index e23b20396..e2b41f67c 100644 --- a/plugins/ui/static_components/result/VotersRanking.config.js +++ b/plugins/ui/static_components/result/VotersRanking.config.js @@ -5,13 +5,9 @@ export default { entries: 5, entryHeight: 2.15, width: cfg.width, - title: "Most Votes", + title: 'Most Votes', icon: icons.peopleKarma, margin: cfg.margin, background: cfg.background, - columnProportions: [ - 1, - 1.9, - 5.1 - ] + columnProportions: [1, 1.9, 5.1] } diff --git a/plugins/ui/static_components/result/WinnersRanking.component.ts b/plugins/ui/static_components/result/WinnersRanking.component.ts index 92262b1da..051ef63f9 100644 --- a/plugins/ui/static_components/result/WinnersRanking.component.ts +++ b/plugins/ui/static_components/result/WinnersRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { componentIds, List, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, List, StaticComponent, StaticHeader } from '../../UI.js' import { stats } from '../../../stats/Stats.js' import config from './WinnersRanking.config.js' @@ -17,7 +17,10 @@ export default class WinnersRanking extends StaticComponent { super(componentIds.winnersRanking) this.header = new StaticHeader('result') this.list = new List(config.entries, config.width, this.getHeight() - (this.header.options.height + config.margin), - config.columnProportions, { background: config.background, headerBg: this.header.options.textBackground }) + config.columnProportions, { + background: config.background, + headerBg: this.header.options.textBackground + }) stats.wins.onUpdate(() => { const xml = this.display() if (xml !== undefined) { tm.sendManialink(xml) } @@ -40,7 +43,10 @@ export default class WinnersRanking extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } constructXml(): void { @@ -50,7 +56,8 @@ export default class WinnersRanking extends StaticComponent { ${this.header.constructXml(config.title, config.icon, config.side)} - ${this.list.constructXml(list.map(a => a.wins.toString()), list.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} + ${this.list.constructXml(list.map(a => a.wins.toString()), + list.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} ` diff --git a/plugins/ui/static_components/result/WinnersRanking.config.js b/plugins/ui/static_components/result/WinnersRanking.config.js index c44e26b5f..9b38e4e87 100644 --- a/plugins/ui/static_components/result/WinnersRanking.config.js +++ b/plugins/ui/static_components/result/WinnersRanking.config.js @@ -8,13 +8,9 @@ export default { posX: cfg.leftPosition + cfg.marginBig + cfg.width, posY: cfg.topBorder, side: false, - title: "Most Wins", + title: 'Most Wins', icon: icons.bestFinishes, margin: cfg.margin, background: cfg.background, - columnProportions: [ - 1, - 1.9, - 5.1 - ] + columnProportions: [1, 1.9, 5.1] } diff --git a/plugins/ui/test_widgets/TestWindow.ts b/plugins/ui/test_widgets/TestWindow.ts index 649604207..fd4ab6f7e 100644 --- a/plugins/ui/test_widgets/TestWindow.ts +++ b/plugins/ui/test_widgets/TestWindow.ts @@ -3,43 +3,44 @@ import config from './TestWidget.config.js' export default class TestWindow { - private intervals: { login: string, interval: NodeJS.Timeout }[] = [] + private intervals: { + login: string, + interval: NodeJS.Timeout + }[] = [] constructor() { - tm.commands.add( - { - aliases: config.commands.displaytest.aliases, - help: config.commands.displaytest.help, - callback: (info): void => { - if (this.intervals.some(a => a.login === info.login)) { return } - this.intervals.push({ - interval: setInterval((): void => { - this.displayToPlayer(info.login) - }, config.refreshTimeout), - login: info.login - }) - }, - privilege: config.commands.displaytest.privilege + tm.commands.add({ + aliases: config.commands.displaytest.aliases, + help: config.commands.displaytest.help, + callback: (info): void => { + if (this.intervals.some(a => a.login === info.login)) { return } + this.intervals.push({ + interval: setInterval((): void => { + this.displayToPlayer(info.login) + }, config.refreshTimeout), + login: info.login + }) }, - { - aliases: config.commands.hidetest.aliases, - help: config.commands.hidetest.help, - callback: (info): void => { - const entry = this.intervals.find(a => a.login === info.login) - if (entry !== undefined) { - this.hideToPlayer(entry.login) - clearInterval(entry.interval) - this.intervals = this.intervals.filter(a => a.login !== info.login) - } - }, - privilege: config.commands.hidetest.privilege - } - ) + privilege: config.commands.displaytest.privilege + }, { + aliases: config.commands.hidetest.aliases, + help: config.commands.hidetest.help, + callback: (info): void => { + const entry = this.intervals.find(a => a.login === info.login) + if (entry !== undefined) { + this.hideToPlayer(entry.login) + clearInterval(entry.interval) + this.intervals = this.intervals.filter(a => a.login !== info.login) + } + }, + privilege: config.commands.hidetest.privilege + }) } async displayToPlayer(login: string): Promise { if (!config.isEnabled) { return } - const file: Buffer | Error = await fs.readFile(`./plugins/ui/test_widgets/${config.file}`).catch((err: Error) => err) + const file: Buffer | Error = await fs.readFile(`./plugins/ui/test_widgets/${config.file}`) + .catch((err: Error) => err) if (file instanceof Error) { return } tm.sendManialink(`${file.toString()}`, login) } diff --git a/plugins/ui/utils/CloseButton.config.js b/plugins/ui/utils/CloseButton.config.js index 404e89ea3..c0211ca77 100644 --- a/plugins/ui/utils/CloseButton.config.js +++ b/plugins/ui/utils/CloseButton.config.js @@ -1,10 +1,10 @@ import icons from '../config/Icons.js' export default { - buttonWidth: 3, - buttonHeight: 3, - icon: icons.close, - iconHover: icons.closeHover, - padding: 0.2, - background: "000D" -} \ No newline at end of file + buttonWidth: 3, + buttonHeight: 3, + icon: icons.close, + iconHover: icons.closeHover, + padding: 0.2, + background: '000D' +} diff --git a/plugins/ui/utils/CloseButton.ts b/plugins/ui/utils/CloseButton.ts index 1a25b54cb..bcb3a189b 100644 --- a/plugins/ui/utils/CloseButton.ts +++ b/plugins/ui/utils/CloseButton.ts @@ -14,8 +14,8 @@ interface CloseButtonOptions { * @param options Optional properties * @returns Button XML string */ -export const closeButton = (actionId: number, parentWidth: number, - parentHeight: number, options?: CloseButtonOptions): string => { +export const closeButton = (actionId: number, parentWidth: number, parentHeight: number, + options?: CloseButtonOptions): string => { const width: number = options?.width ?? config.buttonWidth const height: number = options?.height ?? config.buttonHeight const padding: number = options?.padding ?? config.padding diff --git a/plugins/ui/utils/DynamicComponent.ts b/plugins/ui/utils/DynamicComponent.ts index 12849df60..9f332351b 100644 --- a/plugins/ui/utils/DynamicComponent.ts +++ b/plugins/ui/utils/DynamicComponent.ts @@ -36,7 +36,7 @@ export default abstract class DynamicComponent { } /** - * Add a callback function to execute when new component object gets created + * Add a callback function to execute when new component object gets created * @param callback Function to execute on event */ static onComponentCreated(callback: (component: DynamicComponent) => void) { diff --git a/plugins/ui/utils/FullScreenListener.ts b/plugins/ui/utils/FullScreenListener.ts index 61891e35e..b393381ba 100644 --- a/plugins/ui/utils/FullScreenListener.ts +++ b/plugins/ui/utils/FullScreenListener.ts @@ -1,7 +1,7 @@ /** * Constructs an invisible manialink covering the entire screen with given actionId and zIndex. * @param actionId Manialink Action ID - * @param zIndex Manialink Z position + * @param zIndex Manialink Z position * @returns Manialink XML string */ export const fullScreenListener = (actionId: number, zIndex: number = -100): string => { diff --git a/plugins/ui/utils/GetCpTypes.ts b/plugins/ui/utils/GetCpTypes.ts index 55ddbbf61..c3e15596e 100644 --- a/plugins/ui/utils/GetCpTypes.ts +++ b/plugins/ui/utils/GetCpTypes.ts @@ -16,7 +16,8 @@ export const getCpTypes = (checkpoints: number[][]): ('best' | 'worst' | 'equal' cps[j][i] = cpRow[j] } } - const cpTypes: ('best' | 'worst' | 'equal' | undefined)[][] = Array.from(Array(cps[0].length), (): any[] => new Array(cps.length).fill(undefined)) + const cpTypes: ('best' | 'worst' | 'equal' | undefined)[][] = Array.from(Array(cps[0].length), + (): any[] => new Array(cps.length).fill(undefined)) for (const [i, e] of cps.entries()) { if (cps?.[0]?.length < 2) { break diff --git a/plugins/ui/utils/Grid.ts b/plugins/ui/utils/Grid.ts index 6a6f87bb9..104e6028a 100644 --- a/plugins/ui/utils/Grid.ts +++ b/plugins/ui/utils/Grid.ts @@ -39,7 +39,11 @@ export class Grid { * @param rowProportions Grid row proportions * @param options Optional properties */ - constructor(width: number, height: number, columnProportions: number[], rowProportions: number[], options?: { background?: string, margin?: number, headerBackground?: string }) { + constructor(width: number, height: number, columnProportions: number[], rowProportions: number[], options?: { + background?: string, + margin?: number, + headerBackground?: string + }) { this.width = width this.height = height this.margin = options?.margin ?? 0 @@ -83,8 +87,10 @@ export class Grid { } else { callback = obj } - const posY: number = -this.rowHeights.filter((val, index): boolean => index < i).reduce((acc, cur): number => acc += cur, 0) - this.margin - const posX: number = this.columnWidths.filter((val, index): boolean => index < j).reduce((acc, cur): number => acc += cur, 0) + this.margin + const posY: number = -this.rowHeights.filter((val, index): boolean => index < i) + .reduce((acc, cur): number => acc += cur, 0) - this.margin + const posX: number = this.columnWidths.filter((val, index): boolean => index < j) + .reduce((acc, cur): number => acc += cur, 0) + this.margin let h: number = -this.margin for (let k: number = i; k < i + rowspan; k++) { h += this.rowHeights[k] diff --git a/plugins/ui/utils/KeyListener.ts b/plugins/ui/utils/KeyListener.ts index a32dad83d..dc6bbd0ed 100644 --- a/plugins/ui/utils/KeyListener.ts +++ b/plugins/ui/utils/KeyListener.ts @@ -1,6 +1,10 @@ import utilIds from '../config/UtilIds.js' -let keyListeners: { callback: ((info: tm.ManialinkClickInfo) => void), key: 'F5' | 'F6' | 'F7', importance: number }[] = [] +let keyListeners: { + callback: ((info: tm.ManialinkClickInfo) => void), + key: 'F5' | 'F6' | 'F7', + importance: number +}[] = [] /** * Registers a callback function to execute on given key press. @@ -8,8 +12,13 @@ let keyListeners: { callback: ((info: tm.ManialinkClickInfo) => void), key: 'F5' * @param callback Function to execute on key press, it takes ManialinkClickInfo as a parameter * @param importance Importance index - only the function with highest importance index will get executed */ -export const addKeyListener = (key: 'F5' | 'F6' | 'F7', callback: (info: tm.ManialinkClickInfo) => void, importance: number) => { - keyListeners.unshift({ callback, key, importance }) +export const addKeyListener = (key: 'F5' | 'F6' | 'F7', callback: (info: tm.ManialinkClickInfo) => void, + importance: number) => { + keyListeners.unshift({ + callback, + key, + importance + }) } /** @@ -49,7 +58,7 @@ tm.addListener('PlayerJoin', (info): void => { }) tm.addListener('ManialinkClick', (info: tm.ManialinkClickInfo): void => { - switch (info.actionId) { + switch(info.actionId) { case utilIds.F5: keyListeners.find(a => a.key === 'F5')?.callback(info) break diff --git a/plugins/ui/utils/List.ts b/plugins/ui/utils/List.ts index a02ba8051..f4881dd42 100644 --- a/plugins/ui/utils/List.ts +++ b/plugins/ui/utils/List.ts @@ -28,7 +28,10 @@ export class List { * @param columnProportions List column proportions (proportions are relative to eachother like in CSS flexbox) * @param options Optional properties */ - constructor(entries: number, width: number, height: number, columnProportions: number[], options?: { background?: string, headerBg?: string }) { + constructor(entries: number, width: number, height: number, columnProportions: number[], options?: { + background?: string, + headerBg?: string + }) { this.entries = entries this.height = height this.width = width @@ -47,14 +50,17 @@ export class List { constructXml(col1: string[], col2: string[]): string { const index: GridCellObject = { - callback: (i, j, w, h): string => - centeredText((col1[i] === undefined || col2[i] === undefined) ? '' : (i + 1).toString(), w, h, { textScale: config.textScale }), + callback: (i, j, w, h): string => centeredText( + (col1[i] === undefined || col2[i] === undefined) ? '' : (i + 1).toString(), w, h, + { textScale: config.textScale }), background: this.headerBg } - const col1Function: GridCellFunction = (i, j, w, h): string => centeredText(col1[i] ?? '', w, h, { textScale: config.textScale }) + const col1Function: GridCellFunction = (i, j, w, h): string => centeredText(col1[i] ?? '', w, h, + { textScale: config.textScale }) - const col2Function: GridCellFunction = (i, j, w, h): string => leftAlignedText(col2[i] ?? '', w, h, { textScale: config.textScale }) + const col2Function: GridCellFunction = (i, j, w, h): string => leftAlignedText(col2[i] ?? '', w, h, + { textScale: config.textScale }) const arr: (GridCellFunction | GridCellObject)[] = [] for (let i: number = 0; i < this.entries; i++) { @@ -62,7 +68,10 @@ export class List { } const grid = new Grid(this.width + config.margin * 2, this.height + config.margin * 2, this.columnProportions, - new Array(this.entries).fill(1), { background: this.background, margin: config.margin }) + new Array(this.entries).fill(1), { + background: this.background, + margin: config.margin + }) return ` ${grid.constructXml(arr)} ` diff --git a/plugins/ui/utils/ManialinkListener.ts b/plugins/ui/utils/ManialinkListener.ts index 6c3d9bee9..c390f7574 100644 --- a/plugins/ui/utils/ManialinkListener.ts +++ b/plugins/ui/utils/ManialinkListener.ts @@ -1,4 +1,8 @@ -let listeners: { callback: ((info: tm.ManialinkClickInfo, actionIdOffset: number) => void), actionId: number, range?: number }[] = [] +let listeners: { + callback: ((info: tm.ManialinkClickInfo, actionIdOffset: number) => void), + actionId: number, + range?: number +}[] = [] /** * Adds a callback function to execute on given Action ID. @@ -12,14 +16,22 @@ function addManialinkListener(actionId: number, callback: (info: tm.ManialinkCli * @param range Range of Action IDs (inludes starting Action ID) * @param callback Callback function, it takes ManialinkClickInfo and id offset as a parameter */ -function addManialinkListener(actionId: number, range: number, callback: (info: tm.ManialinkClickInfo, actionIdOffset: number) => void): void +function addManialinkListener(actionId: number, range: number, + callback: (info: tm.ManialinkClickInfo, actionIdOffset: number) => void): void function addManialinkListener(actionId: number, rangeOrCallback: ((info: tm.ManialinkClickInfo) => void) | number, callback?: (info: tm.ManialinkClickInfo, actionIdOffset: number) => void): void { if (typeof rangeOrCallback === 'number') { if (rangeOrCallback < 1) { throw new Error('Manialink listener range must be > 0') } - listeners.push({ actionId, range: rangeOrCallback, callback: callback as any }) + listeners.push({ + actionId, + range: rangeOrCallback, + callback: callback as any + }) } else { - listeners.push({ actionId, callback: rangeOrCallback }) + listeners.push({ + actionId, + callback: rangeOrCallback + }) } } diff --git a/plugins/ui/utils/Navbar.ts b/plugins/ui/utils/Navbar.ts index 09324a39b..5a0613ac3 100644 --- a/plugins/ui/utils/Navbar.ts +++ b/plugins/ui/utils/Navbar.ts @@ -10,7 +10,11 @@ export default class Navbar { readonly width: number /** Navbar height */ readonly height: number - private buttons: { name: string, actionId: number, privilege: number }[] + private buttons: { + name: string, + actionId: number, + privilege: number + }[] /** Background image displayed on button hover */ readonly hoverImage: string /** Background colour */ @@ -25,13 +29,17 @@ export default class Navbar { * @param hoverImgUrl Background image to display on button hover */ constructor(buttons: { - name: string, actionId: number, - privilege?: number - }[], width: number, height: number | null = config.height, - background: string = config.background, hoverImgUrl: string = config.hoverImage) { + name: string, + actionId: number, + privilege?: number + }[], width: number, height: number | null = config.height, background: string = config.background, + hoverImgUrl: string = config.hoverImage) { this.width = width this.height = height ?? config.height - this.buttons = buttons.map(a => ({ ...a, privilege: a.privilege ?? 0 })) + this.buttons = buttons.map(a => ({ + ...a, + privilege: a.privilege ?? 0 + })) this.hoverImage = hoverImgUrl this.background = background } diff --git a/plugins/ui/utils/Paginator.config.js b/plugins/ui/utils/Paginator.config.js index 73f89b575..78571c7ab 100644 --- a/plugins/ui/utils/Paginator.config.js +++ b/plugins/ui/utils/Paginator.config.js @@ -5,21 +5,9 @@ export default { buttonHeight: 3, margin: 0.4, padding: 0.2, - background: "000D", - icons: [ - icons.pageFirst, - icons.pageDoubleLeft, - icons.pageLeft, - icons.pageRight, - icons.pageDoubleRight, - icons.pageLast - ], - iconsHover: [ - icons.pageFirstHover, - icons.pageDoubleLeftHover, - icons.pageLeftHover, - icons.pageRightHover, - icons.pageDoubleRightHover, - icons.pageLastHover - ] -} \ No newline at end of file + background: '000D', + icons: [icons.pageFirst, icons.pageDoubleLeft, icons.pageLeft, icons.pageRight, icons.pageDoubleRight, + icons.pageLast], + iconsHover: [icons.pageFirstHover, icons.pageDoubleLeftHover, icons.pageLeftHover, icons.pageRightHover, + icons.pageDoubleRightHover, icons.pageLastHover] +} diff --git a/plugins/ui/utils/Paginator.ts b/plugins/ui/utils/Paginator.ts index 16a5060dd..b7eee7d48 100644 --- a/plugins/ui/utils/Paginator.ts +++ b/plugins/ui/utils/Paginator.ts @@ -12,7 +12,10 @@ export default class Paginator { private _buttonCount: number = 0 /** Parent element ID */ readonly parentId: number - private readonly loginPages: { readonly login: string, page: number }[] = [] + private readonly loginPages: { + readonly login: string, + page: number + }[] = [] /** Default page (will be displayed if no page is specified) */ defaultPage: number /** Button width */ @@ -53,7 +56,8 @@ export default class Paginator { * @param defaultPage Default page (will be displayed if no page is specified) * @param noMidGap If true there will be no bonus gap between previous and next buttons */ - constructor(parentId: number, parentWidth: number, parentHeight: number, pageCount: number, defaultPage: number = 1, noMidGap?: true) { + constructor(parentId: number, parentWidth: number, parentHeight: number, pageCount: number, defaultPage: number = 1, + noMidGap?: true) { this.parentId = parentId this.width = parentWidth this.height = parentHeight @@ -62,23 +66,15 @@ export default class Paginator { this.yPos = -(parentHeight / 2) this.noMidGap = noMidGap ?? false if (noMidGap === undefined) { - this.xPos = [ - parentWidth / 2 - (this.buttonW + this.margin) * 3, - parentWidth / 2 - (this.buttonW + this.margin) * 2, - parentWidth / 2 - (this.buttonW + this.margin) * 1, - parentWidth / 2 + (this.buttonW + this.margin) * 1, - parentWidth / 2 + (this.buttonW + this.margin) * 2, - parentWidth / 2 + (this.buttonW + this.margin) * 3, - ] + this.xPos = [parentWidth / 2 - (this.buttonW + this.margin) * 3, + parentWidth / 2 - (this.buttonW + this.margin) * 2, parentWidth / 2 - (this.buttonW + this.margin) * 1, + parentWidth / 2 + (this.buttonW + this.margin) * 1, parentWidth / 2 + (this.buttonW + this.margin) * 2, + parentWidth / 2 + (this.buttonW + this.margin) * 3] } else { - this.xPos = [ - parentWidth / 2 - (this.buttonW + this.margin) * 2.5, - parentWidth / 2 - (this.buttonW + this.margin) * 1.5, - parentWidth / 2 - (this.buttonW + this.margin) * 0.5, - parentWidth / 2 + (this.buttonW + this.margin) * 0.5, - parentWidth / 2 + (this.buttonW + this.margin) * 1.5, - parentWidth / 2 + (this.buttonW + this.margin) * 2.5, - ] + this.xPos = [parentWidth / 2 - (this.buttonW + this.margin) * 2.5, + parentWidth / 2 - (this.buttonW + this.margin) * 1.5, parentWidth / 2 - (this.buttonW + this.margin) * 0.5, + parentWidth / 2 + (this.buttonW + this.margin) * 0.5, parentWidth / 2 + (this.buttonW + this.margin) * 1.5, + parentWidth / 2 + (this.buttonW + this.margin) * 2.5] } this.ids = Object.entries(ID).map(a => this.parentId + a[1]) if (pageCount > 1) { this._buttonCount = 1 } @@ -89,7 +85,10 @@ export default class Paginator { const playerPage = this.loginPages.find(a => a.login === info.login) if (playerPage === undefined) { // Should never happen const page: number = this.getPageFromClick(info.actionId, this.defaultPage) - this.loginPages.push({ login: info.login, page: page }) + this.loginPages.push({ + login: info.login, + page: page + }) this._onPageChange(info.login, page, info) return } @@ -137,7 +136,10 @@ export default class Paginator { if (page > this._pageCount) { page = this._pageCount } if (page < 1) { page = 1 } if (loginPage === undefined) { - this.loginPages.push({ login, page }) + this.loginPages.push({ + login, + page + }) } else { loginPage.page = page } @@ -168,7 +170,7 @@ export default class Paginator { } private getPageFromClick(id: number, page: number): number { - switch (id) { + switch(id) { case this.parentId + ID.previous: page-- if (page < 1) { return 1 } @@ -178,7 +180,8 @@ export default class Paginator { page++ if (page > lastPage) { return lastPage } return page - } case this.parentId + ID.first: + } + case this.parentId + ID.first: return 1 case this.parentId + ID.last: return this._pageCount @@ -240,8 +243,7 @@ export default class Paginator { ` - } - else if (this._buttonCount > 1) { + } else if (this._buttonCount > 1) { xml += `` @@ -265,8 +267,7 @@ export default class Paginator { ` - } - else if (this._buttonCount > 1) { + } else if (this._buttonCount > 1) { xml += `` diff --git a/plugins/ui/utils/PopupWindow.ts b/plugins/ui/utils/PopupWindow.ts index 76311b754..f58d03fda 100644 --- a/plugins/ui/utils/PopupWindow.ts +++ b/plugins/ui/utils/PopupWindow.ts @@ -1,9 +1,8 @@ -import { componentIds } from '../UI.js' +import { centeredText, componentIds } from '../UI.js' import DynamicComponent from './DynamicComponent.js' import utilIds from '../config/UtilIds.js' import Navbar from './Navbar.js' import config from '../config/PopupWindow.js' -import { centeredText } from '../UI.js' /** * Abstract class for manialink popup windows. @@ -50,7 +49,11 @@ export default abstract class PopupWindow extends DynamicCo protected readonly footerHeight: number = config.footerHeight /** Right header part width (usually used for page display) */ protected readonly headerPageWidth: number = config.headerPageWidth - private static readonly playersWithWindowOpen: { login: string, id: number, params: any }[] = [] + private static readonly playersWithWindowOpen: { + login: string, + id: number, + params: any + }[] = [] /** * Abstract class for manialink popup windows @@ -61,9 +64,11 @@ export default abstract class PopupWindow extends DynamicCo * @param windowWidth Window width * @param windowHeight Window height */ - constructor(windowId: number, headerIcon: string, title: string, - navbar: { name: string, actionId: number, privilege?: number }[] = [], - windowWidth: number = config.windowWidth, windowHeight: number = config.windowHeight) { + constructor(windowId: number, headerIcon: string, title: string, navbar: { + name: string, + actionId: number, + privilege?: number + }[] = [], windowWidth: number = config.windowWidth, windowHeight: number = config.windowHeight) { super(componentIds.PopupWindow) this.headerIcon = headerIcon this.title = title @@ -75,14 +80,14 @@ export default abstract class PopupWindow extends DynamicCo this.navbarHeight = this.navbar.height this.contentWidth = windowWidth this.contentHeight = windowHeight - (2 * this.headerHeight + this.navbarHeight + 2 * this.margin); - [this.headerLeft, this.headerRight, this.frameMidTop, - this.frameMidBottom, this.frameBottom, this.noNavbarMidTop, this.noNavbarBottom] = this.constructFrame() + [this.headerLeft, this.headerRight, this.frameMidTop, this.frameMidBottom, this.frameBottom, this.noNavbarMidTop, + this.noNavbarBottom] = this.constructFrame() tm.addListener('ManialinkClick', (info: tm.ManialinkClickInfo): void => { if (info.actionId === this.openId) { - tm.log.trace(`Player ${tm.utils.strip(info.nickname)} (${info.login}) opened ${this.constructor.name} manialink.`) + tm.log.trace( + `Player ${tm.utils.strip(info.nickname)} (${info.login}) opened ${this.constructor.name} manialink.`) this.onOpen(info) - } - else if (info.actionId === this.closeId) { + } else if (info.actionId === this.closeId) { const index: number = PopupWindow.playersWithWindowOpen.findIndex(a => a.login === info.login) if (index !== -1) { PopupWindow.playersWithWindowOpen.splice(index, 1) @@ -94,7 +99,10 @@ export default abstract class PopupWindow extends DynamicCo const index: number = PopupWindow.playersWithWindowOpen.findIndex(a => a.login === info.login) if (index !== -1) { PopupWindow.playersWithWindowOpen.splice(index, 1) - this.onClose({ ...info, actionId: this.closeId }) + this.onClose({ + ...info, + actionId: this.closeId + }) } }) } @@ -122,41 +130,34 @@ export default abstract class PopupWindow extends DynamicCo -