From 0c3407b5bcc26477ed351d8d3607677c6d2c9359 Mon Sep 17 00:00:00 2001 From: jenz Date: Fri, 11 Sep 2026 13:39:42 +0200 Subject: [PATCH] initial commit of database structure, data moving and current sourcemod plugin version --- RaceTimer_2026/python3/README.MD | 1 + RaceTimer_2026/python3/columns.py | 426 ++++ RaceTimer_2026/python3/main.py | 120 ++ RaceTimer_2026/python3/settings.py | 9 + .../scripting/unloze_racetimer_2026.sp | 1802 +++++++++++++++++ RaceTimer_2026/sql/create_tables.sql | 56 + RaceTimer_2026/sql/insert_old_data.sql | 433 ++++ 7 files changed, 2847 insertions(+) create mode 100644 RaceTimer_2026/python3/README.MD create mode 100644 RaceTimer_2026/python3/columns.py create mode 100755 RaceTimer_2026/python3/main.py create mode 100644 RaceTimer_2026/python3/settings.py create mode 100755 RaceTimer_2026/scripting/unloze_racetimer_2026.sp create mode 100644 RaceTimer_2026/sql/create_tables.sql create mode 100644 RaceTimer_2026/sql/insert_old_data.sql diff --git a/RaceTimer_2026/python3/README.MD b/RaceTimer_2026/python3/README.MD new file mode 100644 index 0000000..2d7c11e --- /dev/null +++ b/RaceTimer_2026/python3/README.MD @@ -0,0 +1 @@ +pip install mysql-connector-python diff --git a/RaceTimer_2026/python3/columns.py b/RaceTimer_2026/python3/columns.py new file mode 100644 index 0000000..6a1e9ee --- /dev/null +++ b/RaceTimer_2026/python3/columns.py @@ -0,0 +1,426 @@ +MIGRATED_COLUMNS = [ # (map_name, stage, old_column_name) + ('ze_rooftop_runaway1_v4', 1, 'ze_rooftop_runaway1_v4S1'), + ('ze_boatescape6_remix_fix2', 1, 'ze_boatescape6_remix_fix2S1'), + ('ze_random_v9', 1, 'ze_random_v9S1'), + ('ze_ancient_wrath_v1_fix2', 1, 'ze_ancient_wrath_v1_fix2S1'), + ('ze_ancient_wrath_v1_fix2', 2, 'ze_ancient_wrath_v1_fix2S2'), + ('ze_death_star_escape_v4_3', 1, 'ze_death_star_escape_v4_3S1'), + ('ze_castlevania_v1_3', 1, 'ze_castlevania_v1_3S1'), + ('ze_biohazard2_rpd_v4e_004', 1, 'ze_biohazard2_rpd_v4e_004S1'), + ('ze_thelostworld_redux_101jj', 1, 'ze_thelostworld_redux_101jjS1'), + ('ze_mirrors_edge_reborn_v4_3', 1, 'ze_mirrors_edge_reborn_v4_3S1'), + ('ze_surf_shonyudo_v1_1s', 1, 'ze_surf_shonyudo_v1_1sS1'), + ('ze_surf_shonyudo_v1_1s', 2, 'ze_surf_shonyudo_v1_1sS2'), + ('ze_surf_shonyudo_v1_1s', 3, 'ze_surf_shonyudo_v1_1sS3'), + ('ze_surf_shonyudo_v1_1s', 4, 'ze_surf_shonyudo_v1_1sS4'), + ('ze_surf_facility_v2_3', 1, 'ze_surf_facility_v2_3S1'), + ('ze_surf_facility_v2_3', 2, 'ze_surf_facility_v2_3S2'), + ('ze_surf_facility_v2_3', 3, 'ze_surf_facility_v2_3S3'), + ('ze_surf_facility_v2_3', 4, 'ze_surf_facility_v2_3S4'), + ('ze_surf_facility_v3_pre2', 1, 'ze_surf_facility_v3_pre2S1'), + ('ze_surf_facility_v3_pre2', 2, 'ze_surf_facility_v3_pre2S2'), + ('ze_surf_facility_v3_pre2', 3, 'ze_surf_facility_v3_pre2S3'), + ('ze_surf_facility_v3_pre2', 4, 'ze_surf_facility_v3_pre2S4'), + ('ze_surf_facility_v3_pre2', 5, 'ze_surf_facility_v3_pre2S5'), + ('ze_surf_facility_v3_pre2', 6, 'ze_surf_facility_v3_pre2S6'), + ('ze_surf_bona_s2', 1, 'ze_surf_bona_s2S1'), + ('ze_surf_bona_s2', 2, 'ze_surf_bona_s2S2'), + ('ze_surf_bona_s2', 3, 'ze_surf_bona_s2S3'), + ('ze_surf_bona_s2', 4, 'ze_surf_bona_s2S4'), + ('ze_surf_outside_s3', 1, 'ze_surf_outside_s3S1'), + ('ze_surf_outside_s3', 2, 'ze_surf_outside_s3S2'), + ('ze_surf_outside_s3', 3, 'ze_surf_outside_s3S3'), + ('ze_jurassicpark_escape_v1', 1, 'ze_jurassicpark_escape_v1S1'), + ('ze_elevator_escape_jbg_final', 1, 'ze_elevator_escape_jbg_finalS1'), + ('ze_minecraft_v1_1a', 1, 'ze_minecraft_v1_1aS1'), + ('ze_echo_boatescape_extended', 1, 'ze_echo_boatescape_extendedS1'), + ('ze_potc_v3_4fix', 1, 'ze_potc_v3_4fixS1'), + ('ze_potc_v3_4fix', 2, 'ze_potc_v3_4fixS2'), + ('ze_potc_v3_4fix', 3, 'ze_potc_v3_4fixS3'), + ('ze_potc_v3_4fix', 4, 'ze_potc_v3_4fixS4'), + ('ze_potc_v3_4fix', 5, 'ze_potc_v3_4fixS5'), + ('ze_licciana_escape_v2', 1, 'ze_licciana_escape_v2S1'), + ('ze_30_seconds__b21', 1, 'ze_30_seconds__b21S1'), + ('ze_sorrento_escape_v5', 1, 'ze_sorrento_escape_v5S1'), + ('ze_lotr_mines_of_moria_v6_3testfix', 1, 'ze_lotr_mines_of_moria_v6_3testfixS1'), + ('ze_pirates_port_royal_v5_4s2', 1, 'ze_pirates_port_royal_v5_4s2S1'), + ('ze_lotr_mount_doom_v3', 1, 'ze_lotr_mount_doom_v3S1'), + ('ze_firewall_laboratory_part1_3', 1, 'ze_firewall_laboratory_part1_3S1'), + ('ze_firewall_laboratory_part1_3', 2, 'ze_firewall_laboratory_part1_3S2'), + ('ze_surf_vortex_v1_9s', 1, 'ze_surf_vortex_v1_9sS1'), + ('ze_surf_vortex_v1_9s', 2, 'ze_surf_vortex_v1_9sS2'), + ('ze_surf_vortex_v1_9s', 3, 'ze_surf_vortex_v1_9sS3'), + ('ze_surf_vortex_v1_9s', 4, 'ze_surf_vortex_v1_9sS4'), + ('ze_surf_vortex_v1_9s', 5, 'ze_surf_vortex_v1_9sS5'), + ('ze_surf_sahok_t5_9', 1, 'ze_surf_sahok_t5_9S1'), + ('ze_surf_sahok_t5_9', 2, 'ze_surf_sahok_t5_9S2'), + ('ze_rooftop_runaway2_v5', 1, 'ze_rooftop_runaway2_v5S1'), + ('ze_haunted_lab_escape_v2c', 1, 'ze_haunted_lab_escape_v2cS1'), + ('ze_jurassicpark_c1v4', 1, 'ze_jurassicpark_c1v4S1'), + ('ze_predator_ultimate_v3', 1, 'ze_predator_ultimate_v3S1'), + ('ze_predator_ultimate_v3', 2, 'ze_predator_ultimate_v3S2'), + ('ze_predator_ultimate_v3', 3, 'ze_predator_ultimate_v3S3'), + ('ze_rocket_escape_r1', 1, 'ze_rocket_escape_r1S1'), + ('ze_trainescape_final', 1, 'ze_trainescape_finalS1'), + ('ze_pirates_port_royal_v3_6', 1, 'ze_pirates_port_royal_v3_6S1'), + ('ze_pirates_port_royal_v3_6', 2, 'ze_pirates_port_royal_v3_6S2'), + ('ze_pirates_port_royal_v3_6', 3, 'ze_pirates_port_royal_v3_6S3'), + ('ze_pirates_port_royal_v3_6', 4, 'ze_pirates_port_royal_v3_6S4'), + ('ze_chicken_lords_tower_b2mix5', 1, 'ze_chicken_lords_tower_b2mix5S1'), + ('ze_halo_v1_3', 1, 'ze_halo_v1_3S1'), + ('ze_dangerous_waters_v1a', 1, 'ze_dangerous_waters_v1aS1'), + ('ze_lotr_helms_deep_v5', 1, 'ze_lotr_helms_deep_v5S1'), + ('ze_lotr_helms_deep_v5', 2, 'ze_lotr_helms_deep_v5S2'), + ('ze_lotr_helms_deep_v5', 3, 'ze_lotr_helms_deep_v5S3'), + ('ze_isla_nublar_v3', 1, 'ze_isla_nublar_v3S1'), + ('ze_isla_nublar_v3', 2, 'ze_isla_nublar_v3S2'), + ('ze_gargantua_v2_0', 1, 'ze_gargantua_v2_0S1'), + ('ze_gargantua_v2_0', 2, 'ze_gargantua_v2_0S2'), + ('ze_gargantua_v2_0', 3, 'ze_gargantua_v2_0S3'), + ('ze_skill_escape_v09s', 1, 'ze_skill_escape_v09sS1'), + ('ze_skill_escape_v09s', 2, 'ze_skill_escape_v09sS2'), + ('ze_infected_tramway_v3_3', 1, 'ze_infected_tramway_v3_3S1'), + ('ze_minecraft_adventure_v1_2c', 1, 'ze_minecraft_adventure_v1_2cS1'), + ('ze_minecraft_adventure_v1_2c', 2, 'ze_minecraft_adventure_v1_2cS2'), + ('ze_arctic_escape_c1_fix3', 1, 'ze_arctic_escape_c1_fix3S1'), + ('ze_sorrento_escape_v5', 2, 'ze_sorrento_escape_v5S2'), + ('ze_sorrento_escape_v5', 3, 'ze_sorrento_escape_v5S3'), + ('ze_sorrento_escape_v5', 4, 'ze_sorrento_escape_v5S4'), + ('ze_sorrento_escape_v5', 5, 'ze_sorrento_escape_v5S5'), + ('ze_boatescape6_remix_fix2', 2, 'ze_boatescape6_remix_fix2S2'), + ('ze_minecraft_adventure_v1_2c', 3, 'ze_minecraft_adventure_v1_2cS3'), + ('ze_minecraft_adventure_v1_2c', 4, 'ze_minecraft_adventure_v1_2cS4'), + ('ze_lotr_helms_deep_v5', 4, 'ze_lotr_helms_deep_v5S4'), + ('ze_lotr_helms_deep_v5', 5, 'ze_lotr_helms_deep_v5S5'), + ('ze_elevator_escape_jbg_final', 2, 'ze_elevator_escape_jbg_finalS2'), + ('ze_elevator_escape_jbg_final', 3, 'ze_elevator_escape_jbg_finalS3'), + ('ze_elevator_escape_jbg_final', 4, 'ze_elevator_escape_jbg_finalS4'), + ('ze_skill_escape_v09s', 3, 'ze_skill_escape_v09sS3'), + ('ze_skill_escape_v09s', 4, 'ze_skill_escape_v09sS4'), + ('ze_pirates_port_royal_v5_4s2', 2, 'ze_pirates_port_royal_v5_4s2S2'), + ('ze_isla_nublar_v3', 3, 'ze_isla_nublar_v3S3'), + ('ze_rooftop_runaway2_v5', 2, 'ze_rooftop_runaway2_v5S2'), + ('ze_dangerous_waters_v1a', 2, 'ze_dangerous_waters_v1aS2'), + ('ze_dangerous_waters_v1a', 3, 'ze_dangerous_waters_v1aS3'), + ('ze_dangerous_waters_v1a', 4, 'ze_dangerous_waters_v1aS4'), + ('ze_jurassicpark_v2_10_ob', 1, 'ze_jurassicpark_v2_10_obS1'), + ('ze_jurassicpark_v2_10_ob', 2, 'ze_jurassicpark_v2_10_obS2'), + ('ze_jurassicpark_v2_10_ob', 3, 'ze_jurassicpark_v2_10_obS3'), + ('ze_jurassicpark_v2_10_ob', 4, 'ze_jurassicpark_v2_10_obS4'), + ('ze_jurassicpark_v2_10_ob', 5, 'ze_jurassicpark_v2_10_obS5'), + ('ze_biohazard2_rpd_v4e_004', 2, 'ze_biohazard2_rpd_v4e_004S2'), + ('ze_biohazard2_rpd_v4e_004', 3, 'ze_biohazard2_rpd_v4e_004S3'), + ('ze_biohazard2_rpd_v4e_004', 4, 'ze_biohazard2_rpd_v4e_004S4'), + ('ze_biohazard2_rpd_v4e_004', 5, 'ze_biohazard2_rpd_v4e_004S5'), + ('ze_rooftop_runaway2_v5', 3, 'ze_rooftop_runaway2_v5S3'), + ('ze_rooftop_runaway2_v5', 4, 'ze_rooftop_runaway2_v5S4'), + ('ze_rooftop_runaway2_v5', 5, 'ze_rooftop_runaway2_v5S5'), + ('ze_rooftop_runaway2_v5', 6, 'ze_rooftop_runaway2_v5S6'), + ('ze_indiana_jones_v4a_004', 1, 'ze_indiana_jones_v4a_004S1'), + ('ze_indiana_jones_v4a_004', 2, 'ze_indiana_jones_v4a_004S2'), + ('ze_indiana_jones_v4a_004', 3, 'ze_indiana_jones_v4a_004S3'), + ('ze_indiana_jones_v4a_004', 4, 'ze_indiana_jones_v4a_004S4'), + ('ze_indiana_jones_v4a_004', 5, 'ze_indiana_jones_v4a_004S5'), + ('ze_random_v9', 2, 'ze_random_v9S2'), + ('ze_random_v9', 3, 'ze_random_v9S3'), + ('ze_skill_escape_v09s', 5, 'ze_skill_escape_v09sS5'), + ('ze_surf_sahok_t5_9', 3, 'ze_surf_sahok_t5_9S3'), + ('ze_echo_boatescape_extended', 2, 'ze_echo_boatescape_extendedS2'), + ('ze_echo_boatescape_extended', 3, 'ze_echo_boatescape_extendedS3'), + ('ze_echo_boatescape_extended', 4, 'ze_echo_boatescape_extendedS4'), + ('ze_echo_boatescape_extended', 5, 'ze_echo_boatescape_extendedS5'), + ('ze_ffvii_mako_reactor_v5_3', 1, 'ze_ffvii_mako_reactor_v5_3S1'), + ('ze_ffvii_mako_reactor_v5_3', 2, 'ze_ffvii_mako_reactor_v5_3S2'), + ('ze_ffvii_mako_reactor_v5_3', 3, 'ze_ffvii_mako_reactor_v5_3S3'), + ('ze_ffvii_mako_reactor_v5_3', 4, 'ze_ffvii_mako_reactor_v5_3S4'), + ('ze_ffvii_mako_reactor_v5_3', 5, 'ze_ffvii_mako_reactor_v5_3S5'), + ('ze_ffvii_mako_reactor_v5_3', 6, 'ze_ffvii_mako_reactor_v5_3S6'), + ('ze_licciana_escape_v2', 2, 'ze_licciana_escape_v2S2'), + ('ze_licciana_escape_v2', 3, 'ze_licciana_escape_v2S3'), + ('ze_licciana_escape_v2', 4, 'ze_licciana_escape_v2S4'), + ('ze_licciana_escape_v2', 5, 'ze_licciana_escape_v2S5'), + ('ze_river_escape_v3_fix', 1, 'ze_river_escape_v3_fixS1'), + ('ze_get_to_the_chopper_undead_6', 1, 'ze_get_to_the_chopper_undead_6S1'), + ('ze_rocket_escape_r1', 2, 'ze_rocket_escape_r1S2'), + ('ze_rocket_escape_r1', 3, 'ze_rocket_escape_r1S3'), + ('ze_rocket_escape_r1', 4, 'ze_rocket_escape_r1S4'), + ('ze_rocket_escape_r1', 5, 'ze_rocket_escape_r1S5'), + ('ze_rocket_escape_r1', 6, 'ze_rocket_escape_r1S6'), + ('ze_jurassicpark_escape_v1', 2, 'ze_jurassicpark_escape_v1S2'), + ('ze_jurassicpark_escape_v1', 3, 'ze_jurassicpark_escape_v1S3'), + ('ze_jurassicpark_escape_v1', 4, 'ze_jurassicpark_escape_v1S4'), + ('ze_jurassicpark_escape_v1', 5, 'ze_jurassicpark_escape_v1S5'), + ('ze_rooftop_runaway1_v4', 2, 'ze_rooftop_runaway1_v4S2'), + ('ze_rooftop_runaway1_v4', 3, 'ze_rooftop_runaway1_v4S3'), + ('ze_hypernova_v2fix', 1, 'ze_hypernova_v2fixS1'), + ('ze_30_seconds__b21', 2, 'ze_30_seconds__b21S2'), + ('ze_30_seconds__b21', 3, 'ze_30_seconds__b21S3'), + ('ze_30_seconds__b21', 4, 'ze_30_seconds__b21S4'), + ('ze_30_seconds__b21', 5, 'ze_30_seconds__b21S5'), + ('ze_30_seconds__b21', 6, 'ze_30_seconds__b21S6'), + ('ze_gargantua_v2_0', 4, 'ze_gargantua_v2_0S4'), + ('ze_haunted_lab_escape_v2c', 2, 'ze_haunted_lab_escape_v2cS2'), + ('ze_haunted_lab_escape_v2c', 3, 'ze_haunted_lab_escape_v2cS3'), + ('ze_surf_shonyudo_v1_1s', 5, 'ze_surf_shonyudo_v1_1sS5'), + ('ze_surf_shonyudo_v1_1s', 6, 'ze_surf_shonyudo_v1_1sS6'), + ('ze_surf_sahok_t5_9', 4, 'ze_surf_sahok_t5_9S4'), + ('ze_surf_sahok_t5_9', 5, 'ze_surf_sahok_t5_9S5'), + ('ze_castlevania_v1_3', 2, 'ze_castlevania_v1_3S2'), + ('ze_castlevania_v1_3', 3, 'ze_castlevania_v1_3S3'), + ('ze_castlevania_v1_3', 4, 'ze_castlevania_v1_3S4'), + ('ze_trainescape_final', 2, 'ze_trainescape_finalS2'), + ('ze_trainescape_final', 3, 'ze_trainescape_finalS3'), + ('ze_elevator_escape_jbg_final', 5, 'ze_elevator_escape_jbg_finalS5'), + ('ze_trainescape_final', 4, 'ze_trainescape_finalS4'), + ('ze_trainescape_final', 5, 'ze_trainescape_finalS5'), + ('ze_random_v9', 5, 'ze_random_v9S5'), + ('ze_random_v9', 6, 'ze_random_v9S6'), + ('ze_minecraft_adventure_v1_2c', 5, 'ze_minecraft_adventure_v1_2cS5'), + ('ze_lotr_mines_of_moria_v6_3testfix', 2, 'ze_lotr_mines_of_moria_v6_3testfixS2'), + ('ze_hypernova_v2fix', 2, 'ze_hypernova_v2fixS2'), + ('ze_hypernova_v2fix', 3, 'ze_hypernova_v2fixS3'), + ('ze_hypernova_v2fix', 4, 'ze_hypernova_v2fixS4'), + ('ze_hypernova_v2fix', 5, 'ze_hypernova_v2fixS5'), + ('ze_hypernova_v2fix', 6, 'ze_hypernova_v2fixS6'), + ('ze_surf_sahok_t5_9', 6, 'ze_surf_sahok_t5_9S6'), + ('ze_pirates_port_royal_v3_6', 5, 'ze_pirates_port_royal_v3_6S5'), + ('ze_minecraft_v1_1a', 2, 'ze_minecraft_v1_1aS2'), + ('ze_minecraft_v1_1a', 3, 'ze_minecraft_v1_1aS3'), + ('ze_lotr_mount_doom_v3', 2, 'ze_lotr_mount_doom_v3S2'), + ('ze_lotr_mount_doom_v3', 3, 'ze_lotr_mount_doom_v3S3'), + ('ze_lotr_mount_doom_v3', 4, 'ze_lotr_mount_doom_v3S4'), + ('ze_lotr_mount_doom_v3', 5, 'ze_lotr_mount_doom_v3S5'), + ('ze_lotr_mines_of_moria_v6_3testfix', 3, 'ze_lotr_mines_of_moria_v6_3testfixS3'), + ('ze_rush_b_s1', 1, 'ze_rush_b_s1S1'), + ('ze_death_star_escape_v4_3', 2, 'ze_death_star_escape_v4_3S2'), + ('ze_death_star_escape_v4_3', 3, 'ze_death_star_escape_v4_3S3'), + ('ze_death_star_escape_v4_3', 4, 'ze_death_star_escape_v4_3S4'), + ('ze_death_star_escape_v4_3', 5, 'ze_death_star_escape_v4_3S5'), + ('ze_jurassicpark_c1v4', 2, 'ze_jurassicpark_c1v4S2'), + ('ze_jurassicpark_c1v4', 3, 'ze_jurassicpark_c1v4S3'), + ('ze_rooftop_runaway1_v4', 4, 'ze_rooftop_runaway1_v4S4'), + ('ze_rooftop_runaway1_v4', 5, 'ze_rooftop_runaway1_v4S5'), + ('ze_arctic_escape_c1_fix3', 2, 'ze_arctic_escape_c1_fix3S2'), + ('ze_arctic_escape_c1_fix3', 3, 'ze_arctic_escape_c1_fix3S3'), + ('ze_arctic_escape_c1_fix3', 4, 'ze_arctic_escape_c1_fix3S4'), + ('ze_arctic_escape_c1_fix3', 5, 'ze_arctic_escape_c1_fix3S5'), + ('ze_minecraft_v1_1a', 4, 'ze_minecraft_v1_1aS4'), + ('ze_infected_tramway_v3_3', 2, 'ze_infected_tramway_v3_3S2'), + ('ze_infected_tramway_v3_3', 3, 'ze_infected_tramway_v3_3S3'), + ('ze_infected_tramway_v3_3', 4, 'ze_infected_tramway_v3_3S4'), + ('ze_infected_tramway_v3_3', 5, 'ze_infected_tramway_v3_3S5'), + ('ze_games_v2_2a', 1, 'ze_games_v2_2aS1'), + ('ze_jurassicpark_c1v4', 4, 'ze_jurassicpark_c1v4S4'), + ('ze_jurassicpark_c1v4', 5, 'ze_jurassicpark_c1v4S5'), + ('ze_lotr_mines_of_moria_v6_3testfix', 4, 'ze_lotr_mines_of_moria_v6_3testfixS4'), + ('ze_get_to_the_chopper_undead_6', 2, 'ze_get_to_the_chopper_undead_6S2'), + ('ze_get_to_the_chopper_undead_6', 3, 'ze_get_to_the_chopper_undead_6S3'), + ('ze_get_to_the_chopper_undead_6', 4, 'ze_get_to_the_chopper_undead_6S4'), + ('ze_get_to_the_chopper_undead_6', 5, 'ze_get_to_the_chopper_undead_6S5'), + ('ze_predator_ultimate_v3', 4, 'ze_predator_ultimate_v3S4'), + ('ze_surf_bona_s2', 5, 'ze_surf_bona_s2S5'), + ('ze_surf_outside_s3', 4, 'ze_surf_outside_s3S4'), + ('ze_isla_nublar_v3', 4, 'ze_isla_nublar_v3S4'), + ('ze_surf_outside_s3', 5, 'ze_surf_outside_s3S5'), + ('ze_dangerous_waters_v1a', 5, 'ze_dangerous_waters_v1aS5'), + ('ze_barrage_v1', 1, 'ze_barrage_v1S1'), + ('ze_sewer_escape_2012_b1', 1, 'ze_sewer_escape_2012_b1S1'), + ('ze_sewer_escape_2012_b1', 2, 'ze_sewer_escape_2012_b1S2'), + ('ze_motanuminc_ob', 1, 'ze_motanuminc_obS1'), + ('ze_pizzatime_v4s', 1, 'ze_pizzatime_v4sS1'), + ('ze_surf_saltfish_css2', 1, 'ze_surf_saltfish_css2S1'), + ('ze_surf_saltfish_css2', 2, 'ze_surf_saltfish_css2S2'), + ('ze_surf_saltfish_css2', 3, 'ze_surf_saltfish_css2S3'), + ('ze_sorrento_xmas_v4', 1, 'ze_sorrento_xmas_v4S1'), + ('ze_licciana_xmas_v1', 1, 'ze_licciana_xmas_v1S1'), + ('ze_tesv_skyrim_v4fix', 1, 'ze_tesv_skyrim_v4fixS1'), + ('ze_tesv_skyrim_v4fix', 2, 'ze_tesv_skyrim_v4fixS2'), + ('ze_lotr_mines_of_moria_v6_3', 1, 'ze_lotr_mines_of_moria_v6_3S1'), + ('ze_surf_happy_b7s2', 1, 'ze_surf_happy_b7s2S1'), + ('ze_surf_happy_b7s2', 2, 'ze_surf_happy_b7s2S2'), + ('ze_surf_happy_b7s2', 3, 'ze_surf_happy_b7s2S3'), + ('ze_surf_happy_b7s2', 4, 'ze_surf_happy_b7s2S4'), + ('ze_jurassic_park_story_v1', 1, 'ze_jurassic_park_story_v1S1'), + ('ze_randomizer_c1s', 1, 'ze_randomizer_c1sS1'), + ('ze_escape_horizon_c1v2', 1, 'ze_escape_horizon_c1v2S1'), + ('ze_grand_boat_escape_day_v4_3', 1, 'ze_grand_boat_escape_day_v4_3S1'), + ('ze_surf_facility_v2_3', 5, 'ze_surf_facility_v2_3S5'), + ('ze_stargate_escape_v8', 1, 'ze_stargate_escape_v8S1'), + ('ze_icecave_escape_v2', 1, 'ze_icecave_escape_v2S1'), + ('ze_parkour_fix', 1, 'ze_parkour_fixS1'), + ('ze_madness_v2_1', 1, 'ze_madness_v2_1S1'), + ('ze_surf_shonyudo_v5_2s', 1, 'ze_surf_shonyudo_v5_2sS1'), + ('ze_surf_shonyudo_v5_2s', 2, 'ze_surf_shonyudo_v5_2sS2'), + ('ze_surf_shonyudo_v5_2s', 3, 'ze_surf_shonyudo_v5_2sS3'), + ('ze_surf_shonyudo_v5_2s', 4, 'ze_surf_shonyudo_v5_2sS4'), + ('ze_surf_shonyudo_v5_2s', 5, 'ze_surf_shonyudo_v5_2sS5'), + ('ze_surf_shonyudo_v5_2s', 6, 'ze_surf_shonyudo_v5_2sS6'), + ('ze_surf_shonyudo_v5_2s', 7, 'ze_surf_shonyudo_v5_2sS7'), + ('ze_tesv_skyrim_v4fix', 3, 'ze_tesv_skyrim_v4fixS3'), + ('ze_grand_boat_escape_v1_9', 1, 'ze_grand_boat_escape_v1_9S1'), + ('ze_icecap_escape_v3', 1, 'ze_icecap_escape_v3S1'), + ('ze_titanic_escape_v2_3', 1, 'ze_titanic_escape_v2_3S1'), + ('ze_bathroom_v2_5s', 1, 'ze_bathroom_v2_5sS1'), + ('ze_ashen_keep_v0_3', 1, 'ze_ashen_keep_v0_3S1'), + ('ze_surf_froyo_css3', 1, 'ze_surf_froyo_css3S1'), + ('ze_surf_froyo_css3', 2, 'ze_surf_froyo_css3S2'), + ('ze_surf_froyo_css3', 3, 'ze_surf_froyo_css3S3'), + ('ze_surf_froyo_css3', 4, 'ze_surf_froyo_css3S4'), + ('ze_surf_froyo_css3', 5, 'ze_surf_froyo_css3S5'), + ('ze_surf_froyo_css3', 6, 'ze_surf_froyo_css3S6'), + ('ze_surf_froyo_css3', 7, 'ze_surf_froyo_css3S7'), + ('ze_surf_froyo_css3', 8, 'ze_surf_froyo_css3S8'), + ('ze_surf_froyo_css3', 9, 'ze_surf_froyo_css3S9'), + ('ze_surf_froyo_css3', 10, 'ze_surf_froyo_css3S10'), + ('ze_surf_froyo_css3', 11, 'ze_surf_froyo_css3S11'), + ('ze_surf_froyo_css3', 12, 'ze_surf_froyo_css3S12'), + ('ze_surf_gypt_v1_3s', 1, 'ze_surf_gypt_v1_3sS1'), + ('ze_surf_gypt_v1_3s', 2, 'ze_surf_gypt_v1_3sS2'), + ('ze_surf_gypt_v1_3s', 3, 'ze_surf_gypt_v1_3sS3'), + ('ze_surf_gypt_v1_3s', 4, 'ze_surf_gypt_v1_3sS4'), + ('ze_surf_gypt_v1_3s', 5, 'ze_surf_gypt_v1_3sS5'), + ('ze_randomizer_c1s4', 1, 'ze_randomizer_c1s4S1'), + ('ze_randomizer_c1s4', 2, 'ze_randomizer_c1s4S2'), + ('ze_randomizer_c1s4', 3, 'ze_randomizer_c1s4S3'), + ('ze_randomizer_c1s4', 4, 'ze_randomizer_c1s4S4'), + ('ze_collab_ware_a5_8', 1, 'ze_collab_ware_a5_8S1'), + ('ze_surf_gypt_v1_3s', 6, 'ze_surf_gypt_v1_3sS6'), + ('ze_surf_gypt_v1_3s', 7, 'ze_surf_gypt_v1_3sS7'), + ('ze_surf_facility_v2_3', 6, 'ze_surf_facility_v2_3S6'), + ('ze_ancient_wrath_v2_test27', 1, 'ze_ancient_wrath_v2_test27S1'), + ('ze_rush_b_s1', 2, 'ze_rush_b_s1S2'), + ('ze_lotr_mount_doom_v4_2', 1, 'ze_lotr_mount_doom_v4_2S1'), + ('ze_paradise_v1', 1, 'ze_paradise_v1S1'), + ('ze_2012_fix2', 1, 'ze_2012_fix2S1'), + ('ze_2012_fix2', 2, 'ze_2012_fix2S2'), + ('ze_djinn_v1', 1, 'ze_djinn_v1S1'), + ('ze_djinn_v1', 2, 'ze_djinn_v1S2'), + ('ze_djinn_v1', 3, 'ze_djinn_v1S3'), + ('ze_jump_king_b3', 1, 'ze_jump_king_b3S1'), + ('ze_jump_king_b3', 2, 'ze_jump_king_b3S2'), + ('ze_jump_king_b3', 3, 'ze_jump_king_b3S3'), + ('ze_jump_king_b3', 4, 'ze_jump_king_b3S4'), + ('ze_jump_king_b3', 5, 'ze_jump_king_b3S5'), + ('ze_jump_king_b3', 6, 'ze_jump_king_b3S6'), + ('ze_jump_king_b3', 7, 'ze_jump_king_b3S7'), + ('ze_best_korea_css3', 1, 'ze_best_korea_css3S1'), + ('ze_surf_cherry_blossom_v4_1', 1, 'ze_surf_cherry_blossom_v4_1S1'), + ('ze_surf_cherry_blossom_v4_1', 2, 'ze_surf_cherry_blossom_v4_1S2'), + ('ze_surf_cherry_blossom_v4_1', 3, 'ze_surf_cherry_blossom_v4_1S3'), + ('ze_lumiverse_a3', 1, 'ze_lumiverse_a3S1'), + ('ze_indiana_jones_iv_v2_4', 1, 'ze_indiana_jones_iv_v2_4S1'), + ('ze_jump_king_b3', 8, 'ze_jump_king_b3S8'), + ('ze_randomizer_c1s4', 5, 'ze_randomizer_c1s4S5'), + ('ze_surf_daybreak_v1_1', 1, 'ze_surf_daybreak_v1_1S1'), + ('ze_surf_daybreak_v1_1', 2, 'ze_surf_daybreak_v1_1S2'), + ('ze_surf_daybreak_v1_1', 3, 'ze_surf_daybreak_v1_1S3'), + ('ze_surf_daybreak_v1_1', 4, 'ze_surf_daybreak_v1_1S4'), + ('ze_traak_b1', 1, 'ze_traak_b1S1'), + ('ze_illya_b4', 1, 'ze_illya_b4S1'), + ('ze_surf_sparks_v3_f3', 1, 'ze_surf_sparks_v3_f3S1'), + ('ze_surf_sparks_v3_f3', 2, 'ze_surf_sparks_v3_f3S2'), + ('ze_surf_sparks_v3_f3', 3, 'ze_surf_sparks_v3_f3S3'), + ('ze_surf_hp_v1_2_s2', 1, 'ze_surf_hp_v1_2_s2S1'), + ('ze_surf_hp_v1_2_s2', 2, 'ze_surf_hp_v1_2_s2S2'), + ('ze_surf_sparks_v3_f4', 1, 'ze_surf_sparks_v3_f4S1'), + ('ze_surf_sparks_v3_f4', 2, 'ze_surf_sparks_v3_f4S2'), + ('ze_surf_sparks_v3_f4', 3, 'ze_surf_sparks_v3_f4S3'), + ('ze_surf_hp_v1_2_s2', 3, 'ze_surf_hp_v1_2_s2S3'), + ('ze_surf_hp_v1_2_s2', 4, 'ze_surf_hp_v1_2_s2S4'), + ('ze_surf_hp_v1_2_s2', 5, 'ze_surf_hp_v1_2_s2S5'), + ('ze_survivors_b5', 1, 'ze_survivors_b5S1'), + ('ze_shaun_of_the_dead_b3', 1, 'ze_shaun_of_the_dead_b3S1'), + ('ze_barrage_v1', 2, 'ze_barrage_v1S2'), + ('ze_surf_sphinx_stronghold_a8', 1, 'ze_surf_sphinx_stronghold_a8S1'), + ('ze_surf_sphinx_stronghold_a8', 2, 'ze_surf_sphinx_stronghold_a8S2'), + ('ze_arid_lands_of_king_theba_b1_0', 1, 'ze_arid_lands_of_king_theba_b1_0S1'), + ('ze_surf_sphinx_stronghold_a8', 3, 'ze_surf_sphinx_stronghold_a8S3'), + ('ze_mirrors_edge_reborn_v4_3', 2, 'ze_mirrors_edge_reborn_v4_3S2'), + ('ze_surf_charge_v2_f2', 1, 'ze_surf_charge_v2_f2S1'), + ('ze_surf_charge_v2_f2', 2, 'ze_surf_charge_v2_f2S2'), + ('ze_surf_charge_v2_f2', 3, 'ze_surf_charge_v2_f2S3'), + ('ze_christmas_infection_v2_3', 1, 'ze_christmas_infection_v2_3S1'), + ('ze_parkers_pit_b8', 1, 'ze_parkers_pit_b8S1'), + ('ze_parkers_pit_b8', 2, 'ze_parkers_pit_b8S2'), + ('ze_necromanteion_v3_1s_fix', 1, 'ze_necromanteion_v3_1s_fixS1'), + ('ze_natural_css', 1, 'ze_natural_cssS1'), + ('ze_ancient_wrath_v3_a', 1, 'ze_ancient_wrath_v3_aS1'), + ('ze_ancient_wrath_v3_d', 1, 'ze_ancient_wrath_v3_dS1'), + ('ze_stone_pit_v2_b5', 1, 'ze_stone_pit_v2_b5S1'), + ('ze_a_e_s_t_h_e_t_i_c_v1_1s', 1, 'ze_a_e_s_t_h_e_t_i_c_v1_1sS1'), + ('ze_super_mario_64_v2_b9', 1, 'ze_super_mario_64_v2_b9S1'), + ('ze_pirates_port_royal_v5_4s2', 3, 'ze_pirates_port_royal_v5_4s2S3'), + ('ze_pirates_port_royal_v5_4s2', 4, 'ze_pirates_port_royal_v5_4s2S4'), + ('ze_gibletz_establishment_b6', 1, 'ze_gibletz_establishment_b6S1'), + ('ze_sunlight_v2_0', 1, 'ze_sunlight_v2_0S1'), + ('ze_lotr_isengard_v2_3', 1, 'ze_lotr_isengard_v2_3S1'), + ('ze_ffxii_feywood_b3_1', 1, 'ze_ffxii_feywood_b3_1S1'), + ('ze_ffvii_cosmo_canyon_v5fix', 1, 'ze_ffvii_cosmo_canyon_v5fixS1'), + ('ze_crashbandicoot_v1fix', 1, 'ze_crashbandicoot_v1fixS1'), + ('ze_crashbandicoot_v1fix', 2, 'ze_crashbandicoot_v1fixS2'), + ('ze_surf_sphinx_stronghold_a8', 4, 'ze_surf_sphinx_stronghold_a8S4'), + ('ze_no_title_s1', 1, 'ze_no_title_s1S1'), + ('ze_necromanteion_v3_1s_fix', 2, 'ze_necromanteion_v3_1s_fixS2'), + ('ze_shroomforest_v4_5', 1, 'ze_shroomforest_v4_5S1'), + ('ze_shroomforest_v4_5', 2, 'ze_shroomforest_v4_5S2'), + ('ze_shroomforest_v4_5', 3, 'ze_shroomforest_v4_5S3'), + ('ze_indiana_jones_iv_v2_4', 2, 'ze_indiana_jones_iv_v2_4S2'), + ('ze_shroomforest3_b6_2', 1, 'ze_shroomforest3_b6_2S1'), + ('ze_shroomforest3_b6_2', 2, 'ze_shroomforest3_b6_2S2'), + ('ze_shroomforest3_b6_2', 3, 'ze_shroomforest3_b6_2S3'), + ('ze_eternal_grove_v3_css', 1, 'ze_eternal_grove_v3_cssS1'), + ('ze_eternal_grove_v3_css', 2, 'ze_eternal_grove_v3_cssS2'), + ('ze_bioshock_v7_1', 1, 'ze_bioshock_v7_1S1'), + ('ze_immortal_flame_v1_2', 1, 'ze_immortal_flame_v1_2S1'), + ('ze_silent_hill_2_illusion_kn1', 1, 'ze_silent_hill_2_illusion_kn1S1'), + ('ze_bathroom_v2_5s', 2, 'ze_bathroom_v2_5sS2'), + ('ze_prototype_v2', 1, 'ze_prototype_v2S1'), + ('ze_dark_souls_ptd_v0_4', 1, 'ze_dark_souls_ptd_v0_4S1'), + ('ze_kage_a2s', 1, 'ze_kage_a2sS1'), + ('ze_ffxii_westersand_v7_2', 1, 'ze_ffxii_westersand_v7_2S1'), + ('ze_ffxii_westersand_v7_2', 2, 'ze_ffxii_westersand_v7_2S2'), + ('ze_ffxii_westersand_v8zeta1', 1, 'ze_ffxii_westersand_v8zeta1S1'), + ('bhop_monster_jam', 1, 'bhop_monster_jamS1'), + ('ze_halo_poa', 1, 'ze_halo_poaS1'), + ('ze_shroomforest2_v1', 1, 'ze_shroomforest2_v1S1'), + ('ze_crazy_escape_css2', 1, 'ze_crazy_escape_css2S1'), + ('ze_crazy_escape_css2', 2, 'ze_crazy_escape_css2S2'), + ('ze_venice_escape_b2', 1, 'ze_venice_escape_b2S1'), + ('ze_icecap_escape_v5', 1, 'ze_icecap_escape_v5S1'), + ('ze_moon_base_v1', 1, 'ze_moon_base_v1S1'), + ('ze_sewage_processing_b7b', 1, 'ze_sewage_processing_b7bS1'), + ('ze_standart_map_final_fix3', 1, 'ze_standart_map_final_fix3S1'), + ('ze_fapescape_v1_2', 1, 'ze_fapescape_v1_2S1'), + ('ze_totemo_roka_b5s', 1, 'ze_totemo_roka_b5sS1'), + ('ze_abandoned_industry_va2s', 1, 'ze_abandoned_industry_va2sS1'), + ('ze_dystopia_a4_css1', 1, 'ze_dystopia_a4_css1S1'), + ('ze_dystopia_a4_css1', 2, 'ze_dystopia_a4_css1S2'), + ('ze_exit_this_earths_atomosphere_c1', 1, 'ze_exit_this_earths_atomosphere_c1S1'), + ('ze_exit_this_earths_atomosphere_c1', 2, 'ze_exit_this_earths_atomosphere_c1S2'), + ('ze_exit_this_earths_atomosphere_c1', 3, 'ze_exit_this_earths_atomosphere_c1S3'), + ('ze_aztecorum_b3', 1, 'ze_aztecorum_b3S1'), + ('ze_aztecorum_b3', 2, 'ze_aztecorum_b3S2'), + ('ze_a_e_s_t_h_e_t_i_c_v1_1sv', 1, 'ze_a_e_s_t_h_e_t_i_c_v1_1svS1'), + ('ze_scourge_v1', 1, 'ze_scourge_v1S1'), + ('ze_scourge_v1', 2, 'ze_scourge_v1S2'), + ('ze_scourge_v1', 3, 'ze_scourge_v1S3'), + ('ze_scourge_v1', 4, 'ze_scourge_v1S4'), + ('ze_cyberia_css1', 1, 'ze_cyberia_css1S1'), + ('ze_voodoo_islands_v8_5', 1, 'ze_voodoo_islands_v8_5S1'), + ('ze_boring_map_alpha4', 1, 'ze_boring_map_alpha4S1'), + ('ze_boring_map_alpha4', 2, 'ze_boring_map_alpha4S2'), + ('ze_boring_map_alpha4', 3, 'ze_boring_map_alpha4S3'), + ('ze_lotr_minas_tirith_v3_3', 1, 'ze_lotr_minas_tirith_v3_3S1'), + ('ze_lotr_minas_tirith_v3_3', 2, 'ze_lotr_minas_tirith_v3_3S2'), + ('ze_fapescape_v1_2', 2, 'ze_fapescape_v1_2S2'), + ('ze_lotr_minas_tirith_v3_3', 3, 'ze_lotr_minas_tirith_v3_3S3'), + ('ze_surf_aerial_archipelago_v1', 1, 'ze_surf_aerial_archipelago_v1S1'), + ('ze_surf_aerial_archipelago_v1', 2, 'ze_surf_aerial_archipelago_v1S2'), + ('ze_surf_aerial_archipelago_v1', 3, 'ze_surf_aerial_archipelago_v1S3'), + ('ze_surf_aerial_archipelago_v1', 4, 'ze_surf_aerial_archipelago_v1S4'), + ('ze_surf_bona_s3', 1, 'ze_surf_bona_s3S1'), + ('ze_surf_bona_s3', 2, 'ze_surf_bona_s3S2'), + ('ze_surf_bona_s3', 3, 'ze_surf_bona_s3S3'), + ('ze_surf_bona_s3', 4, 'ze_surf_bona_s3S4'), +] + diff --git a/RaceTimer_2026/python3/main.py b/RaceTimer_2026/python3/main.py new file mode 100755 index 0000000..24e90c9 --- /dev/null +++ b/RaceTimer_2026/python3/main.py @@ -0,0 +1,120 @@ +#!/home/nonroot/unloze_racetimer_2026_insert/venv/bin/python3 +from settings import get_connection_unloze_racetimer +from columns import MIGRATED_COLUMNS + +BATCH_SIZE = 2000 +FIXED_CVARS_HASH = -706992435 # FNV-1a of "CLASSIC RACETIMER ze1", same value used for the zone_categories migration + + +def load_zone_category_lookup(conn): + """Returns {(map_name, stage): zone_category_id} for the migrated legacy categories.""" + cur = conn.cursor() + cur.execute( + "SELECT id, map_name, stage FROM unloze_racetimer_css_2026.zone_categories WHERE cvars_hash = %s", + (FIXED_CVARS_HASH,), + ) + lookup = {(map_name, stage): zid for zid, map_name, stage in cur.fetchall()} + cur.close() + print(f"loaded {len(lookup)} zone_category rows for lookup") + return lookup + + +def flush_batch(write_conn, zone_lookup, batch, stats): + """batch: list of (steam_auth, steam_name, map_name, stage, time_value)""" + if not batch: + return + + cur = write_conn.cursor() + + # --- timer_improvements: one multi-row INSERT for the whole batch --- + placeholders = ", ".join(["(%s, %s)"] * len(batch)) + params = [] + for steam_auth, steam_name, _map_name, _stage, _time_value in batch: + params.extend((steam_auth, steam_name)) + + cur.execute( + f"INSERT INTO unloze_racetimer_css_2026.timer_improvements (steam_auth, steam_name) VALUES {placeholders}", + params, + ) + first_id = cur.lastrowid + if not first_id: + raise RuntimeError("lastrowid was 0/None after batch insert - aborting, correlation would be wrong") + + # --- timer_records: same batch, ids computed as first_id + offset --- + records_placeholders = ", ".join(["(%s, %s, %s)"] * len(batch)) + records_params = [] + skipped_no_category = 0 + included = 0 + for i, (steam_auth, steam_name, map_name, stage, time_value) in enumerate(batch): + improvement_id = first_id + i + zone_category_id = zone_lookup.get((map_name, stage)) + if zone_category_id is None: + skipped_no_category += 1 + continue + records_params.extend((improvement_id, zone_category_id, time_value)) + included += 1 + + if included: + # rebuild placeholders in case some rows were skipped for missing categories + records_placeholders = ", ".join(["(%s, %s, %s)"] * included) + cur.execute( + f"INSERT INTO unloze_racetimer_css_2026.timer_records (improvement_id, zone_category_id, time_value) VALUES {records_placeholders}", + records_params, + ) + + write_conn.commit() + cur.close() + + stats["improvements"] += len(batch) + stats["records"] += included + stats["skipped_no_category"] += skipped_no_category + + +def main(): + read_conn = get_connection_unloze_racetimer() + write_conn = get_connection_unloze_racetimer() + write_conn.autocommit = False + + zone_lookup = load_zone_category_lookup(write_conn) + + column_names = ", ".join(f"`{colname}`" for _m, _s, colname in MIGRATED_COLUMNS) + query = f"SELECT steam_auth, name, {column_names} FROM unloze_racetimer_css.zetimer_table_new" + + # unbuffered cursor: streams rows instead of loading all ~137k into memory at once + read_cur = read_conn.cursor(buffered=False) + read_cur.execute(query) + + stats = {"players": 0, "improvements": 0, "records": 0, "skipped_no_category": 0} + batch = [] + + for row in read_cur: + stats["players"] += 1 + steam_auth = row[0] + steam_name = row[1] + values = row[2:] + + for (map_name, stage, _colname), time_value in zip(MIGRATED_COLUMNS, values): + if time_value is None or time_value <= 0: + continue + batch.append((steam_auth, steam_name, map_name, stage, time_value)) + if len(batch) >= BATCH_SIZE: + flush_batch(write_conn, zone_lookup, batch, stats) + batch = [] + + if stats["players"] % 5000 == 0: + print(f"...processed {stats['players']} players, {stats['improvements']} improvements so far") + + flush_batch(write_conn, zone_lookup, batch, stats) + + read_cur.close() + read_conn.close() + write_conn.close() + + print("done.") + print(stats) + if stats["skipped_no_category"]: + print(f"WARNING: {stats['skipped_no_category']} rows had no matching zone_categories entry and were skipped - investigate before trusting the migration is complete.") + + +if __name__ == '__main__': + main() diff --git a/RaceTimer_2026/python3/settings.py b/RaceTimer_2026/python3/settings.py new file mode 100644 index 0000000..e120630 --- /dev/null +++ b/RaceTimer_2026/python3/settings.py @@ -0,0 +1,9 @@ +import mysql.connector + +def get_connection_unloze_racetimer(): + return mysql.connector.connect( + host="127.0.0.1", + port=3306, + user="", + password="") + diff --git a/RaceTimer_2026/scripting/unloze_racetimer_2026.sp b/RaceTimer_2026/scripting/unloze_racetimer_2026.sp new file mode 100755 index 0000000..179f427 --- /dev/null +++ b/RaceTimer_2026/scripting/unloze_racetimer_2026.sp @@ -0,0 +1,1802 @@ +#pragma semicolon 1 +#define DEBUG +#define PLUGIN_AUTHOR "jenz" +#define PLUGIN_VERSION "2.0" +#define g_dLength 400 +#define g_dQueryLength 2048 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma newdecls required +char g_cMapname[g_dLength]; +char g_cSpecialMapStart[g_dLength]; +char g_cSpecialMapEnd[g_dLength]; +char g_csSID[MAXPLAYERS + 1][65]; +char g_sSelectedMap[MAXPLAYERS + 1][512]; + +float g_fStartTime[MAXPLAYERS + 1]; +float g_fClientVectors[MAXPLAYERS + 1][3]; +float g_fClient_End_time[MAXPLAYERS + 1]; + +int g_iClientFrames[MAXPLAYERS + 1]; +int player_stage[MAXPLAYERS + 1]; +int g_iSelectedStage[MAXPLAYERS + 1]; + +bool g_bClientsIgnoring[MAXPLAYERS + 1]; +bool g_bHumansAllowedTime[MAXPLAYERS + 1]; +bool g_bHideTimer[MAXPLAYERS + 1]; +bool g_bClient_allowed_to_leave_again[MAXPLAYERS + 1]; +bool g_bClientTimesFetched[MAXPLAYERS + 1]; + +Handle g_hClientCookie = INVALID_HANDLE; +Handle hText; + +Database g_dDatabase; + +ArrayList g_hMapStageCategoryCvars; +enum struct MapStageCategoryCvars +{ + char map[512]; + char serverCvars[4096]; + int stage; + int CvarsHash; +} + +CurrentMapClientRecords g_CurrentClientRaceZone[MAXPLAYERS +1]; +ArrayList g_hCurrentMapClientRecords; +enum struct CurrentMapClientRecords +{ + int client; + char csTime_record[MAXPLAYERS + 1]; + int stage; + int CvarsHash; + int position; + int recordedAt; // Unix timestamp - fetched via UNIX_TIMESTAMP(recorded_at) + int categoryNumber; +} + +ArrayList g_hCurrentMapAllTopRecords; +enum struct CurrentMapAllTopRecords +{ + char sName[MAX_NAME_LENGTH]; + char csTime_record[MAXPLAYERS + 1]; + int stage; + int CvarsHash; + int recordedAt; // Unix timestamp - fetched via UNIX_TIMESTAMP(recorded_at) +} + +public Plugin myinfo = +{ + name = "UNLOZE_racetimer_css", + author = PLUGIN_AUTHOR, + description = "tracks times on race maps on ze", + version = PLUGIN_VERSION, + url = "www.unloze.com" +}; + +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public void OnPluginStart() +{ + //cmds + RegConsoleCmd("sm_ignoretimer", cmd_timerIgnoreTime, "All racetimer will be ignored for you"); + RegConsoleCmd("sm_ignoretime", cmd_timerIgnoreTime, "All racetimer will be ignored for you"); + RegConsoleCmd("sm_hidetimer", cmd_hideTimerHUD, "Hides timer HUD"); + + //just repeating many commands leading to the same overall navigation menu + RegConsoleCmd("sm_toptime", cmd_OverallMapsMenu, "checking top times for results"); + RegConsoleCmd("sm_mytime", cmd_OverallMapsMenu, "checking your personal time"); + RegConsoleCmd("sm_time", cmd_OverallMapsMenu, "Checking race stages"); + RegConsoleCmd("sm_times", cmd_OverallMapsMenu, "Checking race stages"); + RegConsoleCmd("sm_timer", cmd_OverallMapsMenu, "Checking race stages"); + RegConsoleCmd("sm_timers", cmd_OverallMapsMenu, "Checking race stages"); + + //hooks + HookEvent("round_start", Event_RoundStart, EventHookMode_PostNoCopy); + HookEntityOutput("trigger_multiple", "OnTrigger", Trigger_Multiple); + HookEntityOutput("trigger_multiple", "OnStartTouch", Trigger_Multiple); + HookEntityOutput("trigger_teleport", "OnTrigger", trigger_teleport); + HookEntityOutput("trigger_teleport", "OnStartTouch", trigger_teleport); + + g_hCurrentMapClientRecords = new ArrayList(sizeof(CurrentMapClientRecords)); + g_hCurrentMapAllTopRecords = new ArrayList(sizeof(CurrentMapAllTopRecords)); + g_hMapStageCategoryCvars = new ArrayList(sizeof(MapStageCategoryCvars)); + + //HUD + hText = CreateHudSynchronizer(); + + //cookies + g_hClientCookie = RegClientCookie("hide_timer_cookie", "Hides the timer HUD", CookieAccess_Private); + for (int i = MaxClients; i > 0; --i) + { + if (!AreClientCookiesCached(i)) + continue; + OnClientCookiesCached(i); + } + + //admin commands to monitor for the sake of stopping a timer from being cheated + AddCommandListener(CommandAdminMonitor, "sm_slap"); + AddCommandListener(CommandAdminMonitor, "sm_noclip"); + AddCommandListener(CommandAdminMonitor, "sm_gravity"); + AddCommandListener(CommandAdminMonitor, "sm_speed"); + AddCommandListener(CommandAdminMonitor, "sm_resize"); + AddCommandListener(CommandAdminMonitor, "sm_modelscale"); + AddCommandListener(CommandAdminMonitor, "sm_bring"); + AddCommandListener(CommandAdminMonitor, "sm_goto"); +} + +public Action CommandAdminMonitor(int client, const char[] command, int argc) +{ + char sArgs[64]; + char sTargetName[MAX_TARGET_LENGTH]; + int iTargets[MAXPLAYERS]; + bool bIsML; + GetCmdArg(1, sArgs, sizeof(sArgs)); + + if ((ProcessTargetString(sArgs, client, iTargets, MAXPLAYERS, COMMAND_FILTER_NO_BOTS, sTargetName, sizeof(sTargetName), bIsML)) <= 0) + { + return Plugin_Continue; + } + + int target = iTargets[0]; + if (!g_bHumansAllowedTime[target]) + { + return Plugin_Continue; + } + PrintToChat(target, "Command Monitor: Disabled your timer due to admin interferrence caused by admin %N.", client); + g_bHumansAllowedTime[target] = false; + ResetCurrentClientRaceZone(target); + return Plugin_Continue; +} + +public Action allow_leaving_again(Handle hTimer, int Serial) +{ + int client; + if ((client = GetClientFromSerial(Serial)) == 0) + { + return Plugin_Handled; + } + if (IsValidClient(client)) + { + g_bClient_allowed_to_leave_again[client] = true; + } + return Plugin_Handled; +} + +public void trigger_teleport(const char[] output, int entity_index, int client, float delay) +{ + if (IsValidEdict(entity_index) && IsValidClient(client) && g_bHumansAllowedTime[client]) + { + //if its surf maps there most likely are needed teleports + if (StrContains(g_cMapname, "surf", false) == -1) + { + g_bHumansAllowedTime[client] = false; + ResetCurrentClientRaceZone(client); + resetClientVectors(client); + g_bClient_allowed_to_leave_again[client] = false; + PrintToChat(client, "Disabled timer due to potential teleport abuse, type 1."); + CreateTimer(1.0, allow_leaving_again, GetClientSerial(client)); + } + } +} + +public void Trigger_Multiple(const char[] output, int entity_index, int client, float delay) +{ + if (IsValidEdict(entity_index) && origin_command_check(entity_index) && IsValidClient(client) && g_bHumansAllowedTime[client]) + { + if (StrContains(g_cMapname, "surf", false) == -1) + { + g_bHumansAllowedTime[client] = false; + ResetCurrentClientRaceZone(client); + resetClientVectors(client); + g_bClient_allowed_to_leave_again[client] = false; + PrintToChat(client, "Disabled timer due to potential teleport abuse, type 1."); + CreateTimer(1.0, allow_leaving_again, GetClientSerial(client)); + } + } +} + +public bool origin_command_check(int entity_index) +{ + int count_trigger = GetOutputCount(entity_index, "m_OnTrigger"); + int count_starttouch = GetOutputCount(entity_index, "m_OnStartTouch"); + for (int i = 0; i < count_trigger; i++) + { + char buffer[g_dLength]; + GetOutputParameter(entity_index, "m_OnTrigger", i, buffer, sizeof(buffer)); + if (StrContains(buffer, "origin", true) != -1) + return true; + } + for(int i = 0; i < count_starttouch; i++) + { + char buffer[g_dLength]; + GetOutputParameter(entity_index, "m_OnStartTouch", i, buffer, sizeof(buffer)); + if (StrContains(buffer, "origin", true) != -1) + return true; + } + return false; +} + +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public void SQL_OnDatabaseConnect(Database db, const char[] error, any data) +{ + if(!db || strlen(error)) + { + LogError("Database error: %s", error); + return; + } + g_dDatabase = db; + + for(int i = 1; i <= MaxClients; i++) + { + if (IsValidClient(i)) + { + OnClientPostAdminCheck(i); + } + } + OnMapStart(); +} + +public void SQL_FinishedQuery(Database db, DBResultSet results, const char[] error, DataPack data) +{ + delete results; + if (!db || strlen(error)) + { + char sQuery[g_dQueryLength]; + ResetPack(data); + data.ReadString(sQuery, sizeof(sQuery)); + LogError("Query error 3: %s", error); + LogError("actual query: %s", sQuery); + } + delete data; +} + +public void OnMapStart() +{ + PrecacheSound("unloze/4echo_finish.wav"); + AddFileToDownloadsTable("sound/unloze/4echo_finish.wav"); + + if (!g_dDatabase) + { + Database.Connect(SQL_OnDatabaseConnect, "racetimercss_2026"); + return; + } + GetCurrentMap(g_cMapname, sizeof(g_cMapname)); + + Format(g_cSpecialMapStart, sizeof(g_cSpecialMapStart), ""); + Format(g_cSpecialMapEnd, sizeof(g_cSpecialMapEnd), ""); + g_hCurrentMapClientRecords.Clear(); + g_hCurrentMapAllTopRecords.Clear(); + g_hMapStageCategoryCvars.Clear(); + FillMapStageCategoryCvars(); +} + +public void FillMapStageCategoryCvars() +{ + char sQuery[g_dQueryLength]; + Format(sQuery, sizeof(sQuery), "select zc.map_name, zc.stage, zc.cvars_hash, zc.server_cvars from `zone_categories` zc where zc.is_invalid = 0 order by zc.map_name, zc.stage, zc.cvars_hash"); + DataPack hDataPack = new DataPack(); + hDataPack.WriteString(sQuery); + g_dDatabase.Query(SQL_FinishedQueryFillMapStageCategoryCvars, sQuery, hDataPack, DBPrio_Normal); +} + +public void SQL_FinishedQueryFillMapStageCategoryCvars(Database db, DBResultSet results, const char[] error, DataPack data) +{ + ResetPack(data); + if (!db || strlen(error)) + { + char sQuery[g_dQueryLength]; + data.ReadString(sQuery, sizeof(sQuery)); + LogError("Query error 1: %s", error); + LogError("actual query: %s", sQuery); + delete data; + delete results; + return; + } + delete data; + while (results.RowCount && results.FetchRow()) + { + MapStageCategoryCvars entry; + results.FetchString(0, entry.map, sizeof(entry.map)); + entry.stage = results.FetchInt(1); + entry.CvarsHash = results.FetchInt(2); + results.FetchString(3, entry.serverCvars, sizeof(entry.serverCvars)); + g_hMapStageCategoryCvars.PushArray(entry); + } + delete results; + for (int i = 0; i < g_hMapStageCategoryCvars.Length; i++) + { + MapStageCategoryCvars entry; + GetArrayArray(g_hMapStageCategoryCvars, i, entry); + if (StrEqual(entry.map, g_cMapname)) + { + //each stage/category combo has the top 200 results ordered by best times. + GetAllCurrentMapTopRecords(entry.CvarsHash, entry.stage); + } + } +} + +public void GetAllCurrentMapTopRecords(int CvarsHash, int stage) +{ + char serverTag[64]; + FormatServerTag(serverTag); + + char sQuery[g_dQueryLength]; + Format(sQuery, sizeof(sQuery), "select ti2.steam_name, ti.time_value, zc.stage, zc.cvars_hash, UNIX_TIMESTAMP(ti2.recorded_at) as recorded_at from `timer_records` ti inner join `timer_improvements` ti2 on ti.improvement_id = ti2.id inner join `zone_categories` zc on ti.zone_category_id = zc.id where zc.map_name = '%s' and zc.server_tag = '%s' and zc.stage = '%i' and zc.cvars_hash = '%i' and ti.is_invalid = 0 and zc.is_invalid = 0 order by ti.time_value asc limit 200", g_cMapname, serverTag, stage, CvarsHash); + DataPack hDataPack = new DataPack(); + hDataPack.WriteString(sQuery); + g_dDatabase.Query(SQL_FinishedQueryCurrentMapTopRecords, sQuery, hDataPack, DBPrio_Normal); +} + +public void SQL_FinishedQueryCurrentMapTopRecords(Database db, DBResultSet results, const char[] error, DataPack data) +{ + ResetPack(data); + if (!db || strlen(error)) + { + char sQuery[g_dQueryLength]; + data.ReadString(sQuery, sizeof(sQuery)); + LogError("Query error 1: %s", error); + LogError("actual query: %s", sQuery); + delete data; + delete results; + return; + } + delete data; + while (results.RowCount && results.FetchRow()) + { + CurrentMapAllTopRecords entry; + results.FetchString(0, entry.sName, sizeof(entry.sName)); + results.FetchString(1, entry.csTime_record, sizeof(entry.csTime_record)); + entry.stage = results.FetchInt(2); + entry.CvarsHash = results.FetchInt(3); + entry.recordedAt = results.FetchInt(4); + g_hCurrentMapAllTopRecords.PushArray(entry); + } + delete results; +} +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public void Event_RoundStart(Handle event, const char[] name, bool dontBroadcast) +{ + Format(g_cSpecialMapStart, sizeof(g_cSpecialMapStart), ""); + Format(g_cSpecialMapEnd, sizeof(g_cSpecialMapEnd), ""); + + int race_zone_count = GetTotalRaceZones(); + int l_iZoneCount = unloze_zoneCount(); + if (!race_zone_count) + return; + for (int i = 1; i <= MaxClients; i++) + { + if (IsValidClient(i) && !IsFakeClient(i)) + { + g_fClient_End_time[i] = 0.0; + resetClientVectors(i); + char first_zone[512]; + ZoneNameBasedOnIndex(0, first_zone); + g_bClient_allowed_to_leave_again[i] = true; + g_bHumansAllowedTime[i] = false; + ResetCurrentClientRaceZone(i); + if (l_iZoneCount == 1 && race_zone_count == 1) + { + g_bHumansAllowedTime[i] = true; + PrintToChat(i, "Started timer: There is only a finish zone"); + g_fStartTime[i] = GetEngineTime(); + } + } + } +} +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public void OnClientPostAdminCheck(int client) +{ + if (IsFakeClient(client)) + { + return; + } + GetClientAuthId(client, AuthId_Steam2, g_csSID[client], sizeof(g_csSID[])); + resetClient(client); + InsertPlayer(client); + client_ignoring_racetimer(client); + mysql_get_player_times(client); +} + +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public void mysql_get_player_times(int client) +{ + char serverTag[64]; + FormatServerTag(serverTag); + + char query[g_dQueryLength]; + Format(query, sizeof(query), "select ti.time_value, zc.cvars_hash, zc.stage, UNIX_TIMESTAMP(ti2.recorded_at) as recorded_at, (select count(*) + 1 from `timer_records` ti3 inner join `zone_categories` zc2 on ti3.zone_category_id = zc2.id where zc2.map_name = '%s' and zc2.server_tag = '%s' and ti3.time_value < ti.time_value and ti3.is_invalid = 0 and zc2.is_invalid = 0) as position from `timer_records` ti inner join `timer_improvements` ti2 on ti.improvement_id = ti2.id inner join `zone_categories` zc on ti.zone_category_id = zc.id where ti2.steam_auth = '%s' and zc.map_name = '%s' and zc.server_tag = '%s' and ti.time_value = (select min(ti4.time_value) from `timer_records` ti4 inner join `timer_improvements` ti5 on ti4.improvement_id = ti5.id where ti5.steam_auth = ti2.steam_auth and ti4.zone_category_id = ti.zone_category_id and ti4.is_invalid = 0 and zc.is_invalid = 0) order by zc.stage asc", g_cMapname, serverTag, g_csSID[client], g_cMapname, serverTag); + //only return the best timevalue for a clients category. + DataPack hDataPack = new DataPack(); + hDataPack.WriteCell(GetClientSerial(client)); + hDataPack.WriteString(query); + g_dDatabase.Query(SQL_OnQueryCompleted, query, hDataPack); +} + +public void SQL_OnQueryCompleted(Database db, DBResultSet results, const char[] error, DataPack data) +{ + ResetPack(data); + int client_serial = data.ReadCell(); + if (!db || strlen(error)) + { + char sQuery[g_dQueryLength]; + data.ReadString(sQuery, sizeof(sQuery)); + LogError("Query error 1: %s", error); + LogError("actual query: %s", sQuery); + delete data; + delete results; + return; + } + delete data; + int client; + if ((client = GetClientFromSerial(client_serial)) == 0) + { + delete results; + return; + } + while (results.RowCount && results.FetchRow()) + { + CurrentMapClientRecords entry; + entry.client = client; + results.FetchString(0, entry.csTime_record, sizeof(entry.csTime_record)); + entry.CvarsHash = results.FetchInt(1); + entry.stage = results.FetchInt(2); //just redundantly the same stage each time + entry.recordedAt = results.FetchInt(3); + entry.position = results.FetchInt(4); + g_hCurrentMapClientRecords.PushArray(entry); + } + delete results; + g_bClientTimesFetched[client] = true; +} + +public void InsertPlayer(int client) +{ + char sName[MAX_NAME_LENGTH]; + EscapeName(client, sName); + + char sQuery[g_dQueryLength]; + Format(sQuery, sizeof(sQuery), "INSERT IGNORE INTO `players` (`steam_auth`, `name`) VALUES ('%s', '%s')", g_csSID[client], sName); + DataPack hDataPack = new DataPack(); + hDataPack.WriteString(sQuery); + g_dDatabase.Query(SQL_FinishedQuery, sQuery, hDataPack, DBPrio_Normal); +} + +public void client_ignoring_racetimer(int client) +{ + char query[g_dQueryLength]; + Format(query, sizeof(query), "SELECT is_ignoring FROM `players` where steam_auth = '%s'", g_csSID[client]); + g_dDatabase.Query(SQL_OnQueryCompleted_ignoring, query, GetClientSerial(client)); +} + +public void SQL_OnQueryCompleted_ignoring(Database db, DBResultSet results, const char[] error, int Serial) +{ + if (!db || strlen(error)) + { + LogError("error resolving zone_categories: %s", error); + delete results; + return; + } + int client; + if ((client = GetClientFromSerial(Serial)) == 0) + { + delete results; + return; + } + if (!IsValidClient(client)) + { + delete results; + return; + } + if (results.RowCount && results.FetchRow()) + { + g_bClientsIgnoring[client] = results.FetchInt(0) == 0 ? false : true; + } + else + { + g_bClientsIgnoring[client] = false; + } + delete results; +} +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public void OnClientCookiesCached(int client) +{ + char sValue[8]; + GetClientCookie(client, g_hClientCookie, sValue, sizeof(sValue)); + g_bHideTimer[client] = (sValue[0] != '\0' && !!StringToInt(sValue)); +} +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public void OnClientDisconnect(int client) +{ + Format(g_csSID[client], sizeof(g_csSID[]), ""); + resetClient(client); +} +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public void resetClient(int client) +{ + if (0 < client <= MaxClients) + { + g_bClientTimesFetched[client] = false; + g_bClientsIgnoring[client] = false; + g_bHumansAllowedTime[client] = false; + g_fClient_End_time[client] = 0.0; + g_bClient_allowed_to_leave_again[client] = false; + resetClientVectors(client); + g_fStartTime[client] = 0.0; + player_stage[client] = 0; + + for (int j = g_hCurrentMapClientRecords.Length - 1; j >= 0; j--) + { + CurrentMapClientRecords entry; + GetArrayArray(g_hCurrentMapClientRecords, j, entry); + if (entry.client == client) + { + g_hCurrentMapClientRecords.Erase(j); + } + } + ResetCurrentClientRaceZone(client); + } +} +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public void OnPlayerRunCmdPost(int client, int buttons, int impulse, const float vel[3], const float angles[3], int weapon, int subtype, int cmdnum, int tickcount, int seed, const int mouse[2]) +{ + if (!IsValidClient(client)) + return; + if (!g_bHumansAllowedTime[client]) + return; + + if (!IsPlayerAlive(client)) + { + g_bHumansAllowedTime[client] = false; + ResetCurrentClientRaceZone(client); + resetClientVectors(client); + PrintToChat(client, "Disabled timer due to dying."); + return; + } + if (GetClientTeam(client) == CS_TEAM_CT) + { + float clientVectors[3]; + GetClientAbsOrigin(client, clientVectors); + if (checkClientOrigin(g_fClientVectors[client], clientVectors, client)) + { + g_bHumansAllowedTime[client] = false; + ResetCurrentClientRaceZone(client); + resetClientVectors(client); + g_bClient_allowed_to_leave_again[client] = false; + PrintToChat(client, "Disabled timer due to potential teleport abuse, type 2."); + CreateTimer(1.0, allow_leaving_again, GetClientSerial(client)); + return; + } + int frameCap = 11; + if (g_iClientFrames[client] >= frameCap) + { + if (GetEntityMoveType(client) == MOVETYPE_NOCLIP) + { + PrintToChat(client, "Disabled your Timer due to Noclip mode."); + g_bHumansAllowedTime[client] = false; + ResetCurrentClientRaceZone(client); + resetClientVectors(client); + return; + } + g_iClientFrames[client] = 0; + g_fClientVectors[client] = clientVectors; + if (hText != INVALID_HANDLE && !g_bHideTimer[client]) + { + SetHudTextParams(0.35, 0.85, 0.1, 125, 255, 255, 85); + float total_time = client_current_race_time(client); + if (total_time != -1.0) + { + //if position is -1 the client has no record yet for this stage category + if (g_CurrentClientRaceZone[client].client != 0) + { + ShowSyncHudText(client, hText, "Time: %06.3f\nRecord: %s\nPosition: %i\nMap: %s\nStage: %i\nCategory: %i", total_time, g_CurrentClientRaceZone[client].csTime_record, g_CurrentClientRaceZone[client].position, g_cMapname, player_stage[client], g_CurrentClientRaceZone[client].categoryNumber); + } + else + { + ShowSyncHudText(client, hText, "Time: %06.3f\nRecord: None\nMap: %s\nStage: %i", total_time, g_cMapname, player_stage[client]); + } + } + } + } + g_iClientFrames[client]++; + } + else + { + //is zm + g_bHumansAllowedTime[client] = false; + ResetCurrentClientRaceZone(client); + //called if teleported to some place far away or teleported from inside start zone to end zone + resetClientVectors(client); + PrintToChat(client, "Disabled timer due to ZM infection."); + return; + } + return; +} + +public float client_current_race_time(int i_client) +{ + if (g_fStartTime[i_client] == 0.0) + return -1.0; + return GetEngineTime() - g_fStartTime[i_client]; +} + +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public void resetClientVectors(int client) +{ + g_fClientVectors[client][0] = 0.000000; + g_fClientVectors[client][1] = 0.000000; + g_fClientVectors[client][2] = 0.000000; +} +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public bool checkClientOrigin(float oldVals[3], float newVals[3], int client) +{ + float zero = 0.000000; + if ((oldVals[0] == zero && oldVals[1] == zero && oldVals[2] == zero) || (newVals[0] == zero && newVals[1] == zero && newVals[2] == zero)) + { + return false; + } + float teleport_range = 1090450.0; + int velocityCap = 625; + float distance = GetVectorDistance(oldVals, newVals, true); + bool bInAir = (GetEntPropEnt(client, Prop_Send, "m_hGroundEntity") == -1); + if (distance > teleport_range) + { + if (StrContains(g_cMapname, "surf", false) != -1) + return false; + float fVelocity[3]; + GetEntPropVector(client, Prop_Data, "m_vecVelocity", fVelocity); + float currentspeed = SquareRoot(Pow(fVelocity[0],2.0)+Pow(fVelocity[1],2.0)); + //PrintToChat(client, "currentspeed: %f", currentspeed); + if (bInAir && currentspeed > velocityCap) + return false; + return true; + } + return false; +} +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public void unloze_zoneEntry(int client, char[] zone) +{ + if (g_bClientsIgnoring[client]) + { + return; + } + int zoneIndex = RetrieveZoneIndex(zone); + int race_zone_count = GetTotalRaceZones(); + if (!(StrEqual(zone, g_cSpecialMapEnd)) && !race_zone_count) + return; + + int l_iZoneCount = unloze_zoneCount(); + + if (GetClientTeam(client) == CS_TEAM_CT && (g_bHumansAllowedTime[client] || StrEqual(zone, g_cSpecialMapEnd))) + { + if ((StrContains(zone, "ZONE_PREFIX_RACE") > -1) || StrEqual(zone, g_cSpecialMapEnd)) + { + if (l_iZoneCount < 2) + player_stage[client] = 0; + if (player_stage[client] == (zoneIndex / 2) || l_iZoneCount < 2) + { + g_fClient_End_time[client] = client_current_race_time(client); + g_iClientFrames[client] = 0; + resetClientVectors(client); + g_bHumansAllowedTime[client] = false; + ResetCurrentClientRaceZone(client); + FinishedRaceZone(client); + } + } + } +} + +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public void unloze_zoneLeave(int client, char[] zone) +{ + //only maps with multiple zones need ZONE_PREFIX_START + if (g_bClientsIgnoring[client]) + { + return; + } + int race_zone_count = GetTotalRaceZones(); + if (!(StrEqual(zone, g_cSpecialMapStart)) && !race_zone_count) + return; + + if (GetClientTeam(client) == CS_TEAM_CT) + { + if ((StrContains(zone, "ZONE_PREFIX_START") > -1) || StrEqual(zone, g_cSpecialMapStart)) + { + if (!PM_IsPlayerSteam(client)) + { + PrintToChat(client, "Not starting timer due to being listed as nosteamer"); + return; + } + if (!g_bClient_allowed_to_leave_again[client]) + { + PrintToChat(client, "Did not start timer due to teleport cooldown."); + return; + } + + if (!g_bClientTimesFetched[client]) + { + PrintToChat(client, "Not starting timer because your records have not been loaded from the database yet!"); + return; + } + + g_fStartTime[client] = GetEngineTime(); + float notRounded = float(RetrieveZoneIndex(zone)); + player_stage[client] = RoundToCeil(notRounded / 2); + g_bHumansAllowedTime[client] = true; + + char serverTag[32]; + FormatServerTag(serverTag); + char serverCvars[4096]; + int Hash = FormatServerCvarHash(serverCvars, sizeof(serverCvars), serverTag, client); + + bool found = false; + int categoryNumber = 0; + for (int i = 0; i < g_hMapStageCategoryCvars.Length; i++) + { + MapStageCategoryCvars entry; + g_hMapStageCategoryCvars.GetArray(i, entry); + if (StrEqual(entry.map, g_cMapname) && entry.stage == player_stage[client]) + { + categoryNumber++; + if (entry.CvarsHash == Hash) + { + found = true; + break; + } + } + } + //its a new category + if (!found) + { + categoryNumber++; + } + CPrintToChat(client, "Timer started for Stage: %i. Category: %i", player_stage[client], categoryNumber); + + for (int i = 0; i < g_hCurrentMapClientRecords.Length; i++) + { + CurrentMapClientRecords entry; + g_hCurrentMapClientRecords.GetArray(i, entry); + if (entry.client == client && entry.stage == player_stage[client] && entry.CvarsHash == Hash) + { + entry.categoryNumber = categoryNumber; + //client already has a previous record entry, we display its recordtime and position. + g_CurrentClientRaceZone[client] = entry; + } + } + } + } +} + +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public void CheckIfSpecialRoundZones(char[] resultstart, char[] resultend) +{ + Format(g_cSpecialMapStart, sizeof(g_cSpecialMapStart), resultstart); + Format(g_cSpecialMapEnd, sizeof(g_cSpecialMapEnd), resultend); +} +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public void CheckifAntiZones(int client, bool reset) +{ + if (reset && g_bHumansAllowedTime[client]) + { + g_bHumansAllowedTime[client] = false; + ResetCurrentClientRaceZone(client); + PrintToChat(client, "Disabled Timer due to using illegal shortcut"); + } +} +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public int RetrieveZoneIndex(char[] zone) +{ + //if you leave zone_2 you want the corresponding racezone to be zone_3 + int iterator = strlen(zone) - 1; + if (iterator == -1) + return 1; + char l_sZone[MAXPLAYERS + 1]; + Format(l_sZone, sizeof(l_sZone), zone); + while (IsCharNumeric(l_sZone[iterator])) + iterator--; + iterator++; + strcopy(l_sZone, sizeof(l_sZone), l_sZone[iterator]); + return StringToInt(l_sZone[iterator]); +} + +public void mysql_get_client_position(CurrentMapClientRecords entry, float client_time) +{ + char serverTag[32]; + FormatServerTag(serverTag); + + char sQuery[g_dQueryLength]; + Format(sQuery, sizeof(sQuery), "select count(*) + 1 from `timer_records` ti3 inner join `zone_categories` zc2 on ti3.zone_category_id = zc2.id where zc2.map_name = '%s' and zc2.server_tag = '%s' and zc2.stage = '%i' and zc2.cvars_hash = '%i' and ti3.time_value < %06.3f and ti3.is_invalid = 0 and zc2.is_invalid = 0", g_cMapname, serverTag, entry.stage, entry.CvarsHash, client_time); + DataPack hDataPack = new DataPack(); + hDataPack.WriteString(sQuery); + hDataPack.WriteCell(GetClientSerial(entry.client)); + hDataPack.WriteCell(entry.stage); + hDataPack.WriteCell(entry.CvarsHash); + g_dDatabase.Query(SQL_FetchPlayersPosition, sQuery, hDataPack, DBPrio_High); +} + +public void SQL_FetchPlayersPosition(Database db, DBResultSet results, const char[] error, DataPack data) +{ + ResetPack(data); + char sQuery[g_dQueryLength]; + data.ReadString(sQuery, sizeof(sQuery)); + if (!db || strlen(error)) + { + LogError("Query error resolving zone_categories: %s. actual sQuery: %s", error, sQuery); + delete results; + delete data; + return; + } + + int clientSerial = data.ReadCell(); + int client; + if ((client = GetClientFromSerial(clientSerial)) == 0) + { + delete results; + delete data; + return; + } + if (results.RowCount && results.FetchRow()) + { + int stage = data.ReadCell(); + int cvarsHash = data.ReadCell(); + for (int i = 0; i < g_hCurrentMapClientRecords.Length; i++) + { + CurrentMapClientRecords entry; + g_hCurrentMapClientRecords.GetArray(i, entry); + if (entry.client == client && entry.stage == stage && entry.CvarsHash == cvarsHash) + { + entry.position = results.FetchInt(0); + g_hCurrentMapClientRecords.SetArray(i, entry); + + //we potentially also have a new entry to add to the arraylist for map top times + if (entry.position < 200) + { + if (entry.position < 10) + { + CPrintToChatAll("{green}[UNLOZE] Player %N just got a top10 record for Stage %i", client, player_stage[client]); + //hehe + EmitSoundToAll("unloze/4echo_finish.wav", .entity=client, .volume=1.0); + } + bool pushedEntry = false; + int categoryPosition = 0; + for (int j = 0; j < g_hCurrentMapAllTopRecords.Length; j++) + { + CurrentMapAllTopRecords entry1; + g_hCurrentMapAllTopRecords.GetArray(j, entry1); + //correct stage, correct category, first entry with a higher position than our newly added one + if (entry1.stage == stage && entry1.CvarsHash == cvarsHash) + { + categoryPosition++; + //this should put in the new position and move the old one up. + if (categoryPosition >= entry.position) + { + //we now have more than 200 entries for the maps continued duration in the stage category. + g_hCurrentMapAllTopRecords.ShiftUp(j); + + CurrentMapAllTopRecords newEntry; + EscapeName(client, newEntry.sName); + newEntry.stage = stage; + newEntry.CvarsHash = cvarsHash; + Format(newEntry.csTime_record, sizeof(newEntry.csTime_record), entry.csTime_record); + newEntry.recordedAt = entry.recordedAt; + g_hCurrentMapAllTopRecords.SetArray(j, newEntry); + pushedEntry = true; + break; + } + } + } + + //maybe the category is empty or has to be put at the very end when less than 200 results + if (g_hCurrentMapAllTopRecords.Length == 0 || !pushedEntry) + { + CurrentMapAllTopRecords newEntry; + EscapeName(client, newEntry.sName); + newEntry.stage = stage; + newEntry.CvarsHash = cvarsHash; + Format(newEntry.csTime_record, sizeof(newEntry.csTime_record), entry.csTime_record); + newEntry.recordedAt = entry.recordedAt; + g_hCurrentMapAllTopRecords.PushArray(newEntry); + } + } + break; + } + } + } + delete results; + delete data; +} +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public void FinishedRaceZone(int client) +{ + float client_time = g_fClient_End_time[client]; + if (client_time == -1.0) + { + //if client disconnected we dont want to cary timer over to other client + return; + } + + if (StrEqual(g_csSID[client], "STEAM_ID_STOP_IGNORING_RETVALS") || StrEqual(g_csSID[client], "STEAM_ID_PENDING")) + { + PrintToChat(client, "Your steam ID is not working, not updating timer"); + return; + } + + if (!g_dDatabase) + { + PrintToChat(client, "Sorry but the database connection is dead so your time cannot be saved."); + return; + } + + char serverTag[32]; + FormatServerTag(serverTag); + char serverCvars[4096]; + int Hash = FormatServerCvarHash(serverCvars, sizeof(serverCvars), serverTag, client); + + float old_client_time = 0.0; + char old_client_timeS[64]; + int ClientRecordOldIndex = -1; + for (int i = 0; i < g_hCurrentMapClientRecords.Length; i++) + { + CurrentMapClientRecords entry; + g_hCurrentMapClientRecords.GetArray(i, entry); + if (entry.client == client && entry.stage == player_stage[client] && entry.CvarsHash == Hash) + { + ClientRecordOldIndex = i; + if (StrContains(entry.csTime_record, ":") != -1) + { + int generic_length = 65; + char[][] sPart = new char[2][generic_length]; + ExplodeString(entry.csTime_record, ":", sPart, 2, generic_length); + old_client_time = (StringToFloat(sPart[0]) * 60) + StringToFloat(sPart[1]); + } + else + { + old_client_time = StringToFloat(entry.csTime_record); + } + Format(old_client_timeS, sizeof(old_client_timeS), entry.csTime_record); + break; + } + } + + char extraInfo[64]; + int categoryNumber = 0; + bool found = false; + for (int i = 0; i < g_hMapStageCategoryCvars.Length; i++) + { + MapStageCategoryCvars entry; + g_hMapStageCategoryCvars.GetArray(i, entry); + if (!StrEqual(entry.map, g_cMapname) || entry.stage != player_stage[client]) + { + continue; + } + categoryNumber++; + if (entry.CvarsHash == Hash) + { + found = true; + break; + } + } + + //we got a new category added right now, put it into the arraylist + if (!found) + { + MapStageCategoryCvars entry; + Format(entry.map, sizeof(entry.map), g_cMapname); + entry.stage = player_stage[client]; + entry.CvarsHash = Hash; + Format(entry.serverCvars, sizeof(entry.serverCvars), serverCvars); + g_hMapStageCategoryCvars.PushArray(entry); + categoryNumber++; + } + + Format(extraInfo, sizeof(extraInfo), "Category: %i", categoryNumber); + CPrintToChat(client, "{green}[UNLOZE] Stage: %i %s\nTime: %06.3f", player_stage[client], extraInfo, client_time); + + if (client_time >= old_client_time && old_client_time > 0.0) + { + CPrintToChat(client, "{green}[UNLOZE] Did not improve your personal best time of %s.", old_client_timeS); + return; + } + + CurrentMapClientRecords entry; + //we got an entry for the client arraylist to add or replace + //the client array only has unique entries meanwhile the Arraylist for toptimes may have client duplicates (worse and better times for same player) + if (ClientRecordOldIndex == -1) + { + entry.client = client; + Format(entry.csTime_record, sizeof(entry.csTime_record), "%06.3f", client_time); + entry.CvarsHash = Hash; + entry.stage = player_stage[client]; + entry.recordedAt = GetTime(); + g_hCurrentMapClientRecords.PushArray(entry); + } + else + { + g_hCurrentMapClientRecords.GetArray(ClientRecordOldIndex, entry); + Format(entry.csTime_record, sizeof(entry.csTime_record), "%06.3f", client_time); + entry.recordedAt = GetTime(); + g_hCurrentMapClientRecords.SetArray(ClientRecordOldIndex, entry); + } + mysql_get_client_position(entry, client_time); + + if (old_client_time == 0.0) + { + CPrintToChat(client, "{green}[UNLOZE] Your record: None yet. Command: !timer"); + } + else + { + CPrintToChat(client, "{green}[UNLOZE] You beat your personal record of %s! Command: !timer", old_client_timeS); + } + + InsertZoneCategory(client); +} + +public void EscapeName(int client, char[] sNameOutput) +{ + GetClientName(client, sNameOutput, MAX_NAME_LENGTH); + int size2 = 2 * strlen(sNameOutput) + 1; + g_dDatabase.Escape(sNameOutput, sNameOutput, size2 + 1); +} + +public void FormatServerTag(char[] serverTag) +{ + int i_port = GetConVarInt(FindConVar("hostport")); + if (i_port == 27015) + { + Format(serverTag, 32, "ze1"); + } + else if (i_port == 27035) + { + Format(serverTag, 32, "ze2"); + } + else if (i_port == 27019) + { + Format(serverTag, 32, "dev"); + } +} + +public int FormatServerCvarHash(char[] serverCvarsOutput, int size, char[] serverTag, int client) +{ + int sv_gravity = GetConVarInt(FindConVar("sv_gravity")); + int sv_enablebunnyhopping = GetConVarInt(FindConVar("sv_enablebunnyhopping")); + int sv_autobunnyhopping = GetConVarInt(FindConVar("sv_autobunnyhopping")); + int sv_accelerate = GetConVarInt(FindConVar("sv_accelerate")); + int sv_friction = GetConVarInt(FindConVar("sv_friction")); + int sv_airaccelerate = GetConVarInt(FindConVar("sv_airaccelerate")); + //< 0.014 = 100 tick + int tickrate = GetConVarFloat(FindConVar("sm_interval_per_tick")) < 0.014? 100 : 66; + float client_speed = GetEntPropFloat(client, Prop_Data, "m_flLaggedMovementValue"); + float client_gravity = GetEntPropFloat(client, Prop_Data, "m_flGravity"); + if (client_gravity == 0.0) + { + client_gravity = 1.0; + } + + Format(serverCvarsOutput, size, "sv_gravity %i, sv_enablebunnyhopping %i, sv_autobunnyhopping %i, sv_accelerate %i, sv_airaccelerate %i, tickrate %i, sv_friction %i, client_speed %04.2f, client_gravity %04.2f", sv_gravity, sv_enablebunnyhopping, sv_autobunnyhopping, sv_accelerate, sv_airaccelerate, tickrate, sv_friction, client_speed, client_gravity); + + char sHashInput[4096]; + Format(sHashInput, sizeof(sHashInput), "%s %s", serverCvarsOutput, serverTag); + return FNV1aHash(sHashInput); +} + +public void InsertZoneCategory(int client) +{ + char sName[MAX_NAME_LENGTH]; + EscapeName(client, sName); + + char sSID[64]; + Format(sSID, sizeof(sSID), g_csSID[client]); + + char serverTag[32]; + FormatServerTag(serverTag); + + char serverCvars[4096]; + int Hash = FormatServerCvarHash(serverCvars, sizeof(serverCvars), serverTag, client); + //LogMessage("Hash: %i", Hash); + + char sQuery[g_dQueryLength]; + Format(sQuery, sizeof(sQuery), "INSERT IGNORE INTO `zone_categories` (`map_name`, `stage`, `server_tag`, `server_cvars`, `cvars_hash`) VALUES ('%s', %i, '%s', '%s', %i)", g_cMapname, player_stage[client], serverTag, serverCvars, Hash); + + DataPack hDataPack = new DataPack(); + hDataPack.WriteString(sQuery); + + // not taking any chances so just carrying all this information along + float client_time = g_fClient_End_time[client]; + int client_stage = player_stage[client]; + char mapname[64]; + Format(mapname, sizeof(mapname), g_cMapname); + hDataPack.WriteFloat(client_time); + hDataPack.WriteString(mapname); + hDataPack.WriteCell(client_stage); + hDataPack.WriteString(serverTag); + hDataPack.WriteCell(Hash); + hDataPack.WriteString(sName); + hDataPack.WriteString(sSID); + g_dDatabase.Query(SQL_OnCategoryInserted, sQuery, hDataPack, DBPrio_High); +} + +public void SQL_OnCategoryInserted(Database db, DBResultSet results, const char[] error, DataPack data) +{ + ResetPack(data); + char sQuery[g_dQueryLength]; + data.ReadString(sQuery, sizeof(sQuery)); + if (!db || strlen(error)) + { + LogError("Query error resolving zone_categories: %s. actual sQuery: %s", error, sQuery); + delete results; + delete data; + return; + } + + float client_time = data.ReadFloat(); + char mapname[128]; + data.ReadString(mapname, sizeof(mapname)); + int client_stage = data.ReadCell(); + char serverTag[32]; + data.ReadString(serverTag, sizeof(serverTag)); + int Hash = data.ReadCell(); + char sName[MAX_NAME_LENGTH]; + data.ReadString(sName, sizeof(sName)); + char sSID[64]; + data.ReadString(sSID, sizeof(sSID)); + delete results; + delete data; + SubmitTimeImprovement(client_time, mapname, client_stage, serverTag, Hash, sName, sSID); +} + +public void SubmitTimeImprovement(float client_time, char[] mapname, int client_stage, char[] serverTag, int Hash, char[] sName, char[] sSID) +{ + char sQuery[g_dQueryLength]; + Format(sQuery, sizeof(sQuery), "INSERT INTO `timer_improvements` (`steam_auth`, `steam_name`) VALUES ('%s', '%s')", sSID, sName); + DataPack hDataPack = new DataPack(); + hDataPack.WriteString(sQuery); + // not taking any chances so just carrying all this information along + hDataPack.WriteFloat(client_time); + hDataPack.WriteString(mapname); + hDataPack.WriteCell(client_stage); + hDataPack.WriteString(serverTag); + hDataPack.WriteCell(Hash); + hDataPack.WriteString(sSID); + g_dDatabase.Query(SQL_OnTimerImprovementsInserted, sQuery, hDataPack, DBPrio_High); +} + +public void SQL_OnTimerImprovementsInserted(Database db, DBResultSet results, const char[] error, DataPack data) +{ + ResetPack(data); + char sQuery[g_dQueryLength]; + data.ReadString(sQuery, sizeof(sQuery)); + if (!db || strlen(error)) + { + LogError("Query error timer improvement insert: %s. actual sQuery: %s", error, sQuery); + delete results; + delete data; + return; + } + + float client_time = data.ReadFloat(); + char mapname[128]; + data.ReadString(mapname, sizeof(mapname)); + int client_stage = data.ReadCell(); + char serverTag[32]; + data.ReadString(serverTag, sizeof(serverTag)); + int Hash = data.ReadCell(); + char sSID[64]; + data.ReadString(sSID, sizeof(sSID)); + + delete results; + delete data; + SubmitTimeRecord(client_time, mapname, client_stage, serverTag, Hash, sSID); +} + +public void SubmitTimeRecord(float client_time, char[] mapname, int client_stage, char[] serverTag, int Hash, char[] sSID) +{ + //there is a small window for a race condition theoretically if the client would finish another racetimer inside 2-3 seconds of the query running + //as the last selected row would point to a more recently inserted row. We expect that not to happen in practise. + char sQuery[g_dQueryLength]; + Format(sQuery, sizeof(sQuery), "INSERT INTO `timer_records` (`improvement_id`, `zone_category_id`, `time_value`) VALUES ((select id from `timer_improvements` where steam_auth = '%s' order by recorded_at desc limit 1), (select id from `zone_categories` where map_name = '%s' and stage = '%i' and server_tag = '%s' and cvars_hash = '%i'), %06.3f)", sSID, mapname, client_stage, serverTag, Hash, client_time); + + DataPack hDataPack = new DataPack(); + hDataPack.WriteString(sQuery); + g_dDatabase.Query(SQL_FinishedQuery, sQuery, hDataPack, DBPrio_Normal); +} + +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public int GetTotalRaceZones() +{ + int l_iZoneCount = unloze_zoneCount(); + char l_cIndexName[g_dLength]; + int l_iCountRace; + for (int iterator = 0; iterator < l_iZoneCount; iterator++) + { + ZoneNameBasedOnIndex(iterator, l_cIndexName[iterator]); + if (StrContains(l_cIndexName[iterator], "ZONE_PREFIX_RACE") > -1) + l_iCountRace++; + } + return l_iCountRace; +} +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public Action cmd_timerIgnoreTime(int client, int args) +{ + update_client_ignoring_racetimer(client); + return Plugin_Handled; +} +public void update_client_ignoring_racetimer(int client) +{ + if (!IsValidClient(client)) + return; + + if (StrEqual(g_csSID[client], "STEAM_ID_STOP_IGNORING_RETVALS") || StrEqual(g_csSID[client], "STEAM_ID_PENDING")) + { + PrintToChat(client, "Your steam ID is not working, not updating settings."); + return; + } + + if (g_bClientsIgnoring[client]) + { + g_bClientsIgnoring[client] = false; + g_bHideTimer[client] = false; + SetClientCookie(client, g_hClientCookie, "0"); + PrintToChat(client, "You are now not ignoring the racetimer"); + } + else + { + g_bClientsIgnoring[client] = true; + g_bHideTimer[client] = true; + SetClientCookie(client, g_hClientCookie, "1"); + PrintToChat(client, "You are now ignoring the racetimer"); + } + char sQuery[g_dQueryLength]; + if (!g_dDatabase) + { + return; + } + + Format(sQuery, sizeof(sQuery), "update `players` set `is_ignoring` = '%i' where steam_auth = '%s' ON DUPLICATE KEY UPDATE `is_ignoring` = '%i'", g_bClientsIgnoring[client], g_csSID[client], g_bClientsIgnoring[client]); + DataPack hDataPack = new DataPack(); + hDataPack.WriteString(sQuery); + g_dDatabase.Query(SQL_FinishedQuery, sQuery, hDataPack, DBPrio_Normal); +} + +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public Action cmd_hideTimerHUD(int client, int args) +{ + if (!g_bHideTimer[client]) + { + g_bHideTimer[client] = true; + SetClientCookie(client, g_hClientCookie, "1"); + PrintToChat(client, "Disabled timer HUD"); + } else { g_bHideTimer[client] = false; PrintToChat(client, "Enabled timer HUD"); SetClientCookie(client, g_hClientCookie, "0"); } + return Plugin_Handled; +} +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public Action cmd_OverallMapsMenu(int client, int args) +{ + OverAllMapsMenu(client); + return Plugin_Handled; +} + +public void OverAllMapsMenu(int client) +{ + Menu menu = new Menu(MenuHandler_SelectMap); + menu.SetTitle("[UNLOZE Racetimer] Select a map you want to see times for"); + + // current map first, if it has any recorded categories at all + for (int i = 0; i < g_hMapStageCategoryCvars.Length; i++) + { + MapStageCategoryCvars entry; + g_hMapStageCategoryCvars.GetArray(i, entry); + if (StrEqual(entry.map, g_cMapname)) + { + menu.AddItem(entry.map, entry.map); + break; // only need to add the current map once + } + } + + // then every other distinct map, in whatever order the array has them + char iteratedMap[512]; + for (int i = 0; i < g_hMapStageCategoryCvars.Length; i++) + { + MapStageCategoryCvars entry; + g_hMapStageCategoryCvars.GetArray(i, entry); + if (!StrEqual(entry.map, iteratedMap) && !StrEqual(entry.map, g_cMapname)) + { + menu.AddItem(entry.map, entry.map); + Format(iteratedMap, sizeof(iteratedMap), entry.map); + } + } + + if (menu.ItemCount == 0) + { + menu.AddItem("-1", "No maps with recorded times yet", ITEMDRAW_DISABLED); + } + + menu.ExitButton = true; + menu.Display(client, 0); +} + +public int MenuHandler_SelectMap(Menu menu, MenuAction action, int client, int selection) +{ + switch (action) + { + case MenuAction_Select: + { + char selectedMap[512]; + menu.GetItem(selection, selectedMap, sizeof(selectedMap)); + strcopy(g_sSelectedMap[client], sizeof(g_sSelectedMap[]), selectedMap); + OpenStageMenu(client, selectedMap); + } + case MenuAction_End: + { + delete menu; + } + } + return 0; +} + +public void OpenStageMenu(int client, const char[] mapName) +{ + Menu menu = new Menu(MenuHandler_SelectStage); + menu.SetTitle("[UNLOZE Racetimer] Stages on: %s", mapName); + menu.ExitBackButton = true; + + int currentStage = -1; + for (int i = 0; i < g_hMapStageCategoryCvars.Length; i++) + { + MapStageCategoryCvars entry; + g_hMapStageCategoryCvars.GetArray(i, entry); + if (!StrEqual(entry.map, mapName)) + { + continue; + } + if (entry.stage != currentStage) + { + currentStage = entry.stage; + char info[16]; + IntToString(currentStage, info, sizeof(info)); + char display[32]; + Format(display, sizeof(display), "Stage %i", currentStage); + menu.AddItem(info, display); + } + } + + if (menu.ItemCount == 0) + { + menu.AddItem("-1", "No stages found", ITEMDRAW_DISABLED); + } + + menu.ExitButton = true; + menu.Display(client, 0); +} + +public int MenuHandler_SelectStage(Menu menu, MenuAction action, int client, int selection) +{ + switch (action) + { + case MenuAction_Select: + { + char info[16]; + menu.GetItem(selection, info, sizeof(info)); + int stage = StringToInt(info); + OpenCategoryMenu(client, g_sSelectedMap[client], stage); + } + case MenuAction_Cancel: + { + if (selection == MenuCancel_ExitBack) + { + OverAllMapsMenu(client); + } + } + case MenuAction_End: + { + delete menu; + } + } + return 0; +} + +public void OpenCategoryMenu(int client, const char[] mapName, int stage) +{ + g_iSelectedStage[client] = stage; + + Menu menu = new Menu(MenuHandler_SelectCategory); + menu.SetTitle("[UNLOZE Racetimer] %s - Stage %i", mapName, stage); + menu.ExitBackButton = true; + menu.Pagination = 4; //otherwise the text cuts off. + + int categoryNumber = 0; + for (int i = 0; i < g_hMapStageCategoryCvars.Length; i++) + { + MapStageCategoryCvars entry; + g_hMapStageCategoryCvars.GetArray(i, entry); + if (!StrEqual(entry.map, mapName) || entry.stage != stage) + { + continue; + } + + categoryNumber++; + char info[32]; + Format(info, sizeof(info), "%i:%i", categoryNumber, entry.CvarsHash); + + char header[32]; + Format(header, sizeof(header), "Category %i", categoryNumber); + menu.AddItem(info, header); + menu.AddItem("-1", entry.serverCvars, ITEMDRAW_DISABLED); + } + + if (categoryNumber == 0) + { + menu.AddItem("-1", "No categories found", ITEMDRAW_DISABLED); + } + + menu.ExitButton = true; + menu.Display(client, 0); +} + +public int MenuHandler_SelectCategory(Menu menu, MenuAction action, int client, int selection) +{ + switch (action) + { + case MenuAction_Select: + { + char info[32]; + menu.GetItem(selection, info, sizeof(info)); + + char parts[2][16]; + ExplodeString(info, ":", parts, 2, sizeof(parts[])); + int categoryNumber = StringToInt(parts[0]); + int cvarsHash = StringToInt(parts[1]); + + OpenCategoryRecordsMenu(client, g_sSelectedMap[client], g_iSelectedStage[client], categoryNumber, cvarsHash); + } + case MenuAction_Cancel: + { + if (selection == MenuCancel_ExitBack) + { + OpenStageMenu(client, g_sSelectedMap[client]); + } + } + case MenuAction_End: + { + delete menu; + } + } + return 0; +} + +public void OpenCategoryRecordsMenu(int client, const char[] mapName, int stage, int categoryNumber, int cvarsHash) +{ + if (StrEqual(mapName, g_cMapname)) + { + OpenCurrentMapCategoryRecordsMenu(client, mapName, stage, categoryNumber, cvarsHash); + } + else + { + FetchOtherMapCategoryRecords(client, mapName, stage, categoryNumber, cvarsHash); + } +} + +public void OpenCurrentMapCategoryRecordsMenu(int client, const char[] mapName, int stage, int categoryNumber, int cvarsHash) +{ + char ownTime[MAXPLAYERS + 1]; + char formatted[32]; + int ownPosition = -1; + for (int i = 0; i < g_hCurrentMapClientRecords.Length; i++) + { + CurrentMapClientRecords entry; + g_hCurrentMapClientRecords.GetArray(i, entry); + if (entry.client == client && entry.stage == stage && entry.CvarsHash == cvarsHash) + { + strcopy(ownTime, sizeof(ownTime), entry.csTime_record); + ownPosition = entry.position; + FormatTime(formatted, sizeof(formatted), "%m/%d/%Y %H:%M", entry.recordedAt); + break; + } + } + + Menu menu = new Menu(MenuHandler_RecordsInfo); + menu.ExitBackButton = true; + if (ownPosition != -1) + { + menu.SetTitle("%s - Stage %i - Category %i\nYour time: %s (#%i): %s", mapName, stage, categoryNumber, ownTime, ownPosition, formatted); + } + else + { + menu.SetTitle("%s - Stage %i - Category %i\nYou have no record here yet", mapName, stage, categoryNumber); + } + + int position = 0; + for (int i = 0; i < g_hCurrentMapAllTopRecords.Length; i++) + { + CurrentMapAllTopRecords entry; + g_hCurrentMapAllTopRecords.GetArray(i, entry); + if (entry.stage != stage || entry.CvarsHash != cvarsHash) + { + continue; + } + position++; + FormatTime(formatted, sizeof(formatted), "%m/%d/%Y %H:%M", entry.recordedAt); + char display[g_dLength]; + Format(display, sizeof(display), "#%i: %s - %s: %s", position, entry.csTime_record, entry.sName, formatted); + menu.AddItem("-1", display, ITEMDRAW_DISABLED); + } + + if (g_hCurrentMapAllTopRecords.Length == 0) + { + menu.AddItem("-1", "No records yet", ITEMDRAW_DISABLED); + } + + menu.ExitButton = true; + menu.Display(client, 0); +} + +public void FetchOtherMapCategoryRecords(int client, const char[] mapName, int stage, int categoryNumber, int cvarsHash) +{ + char serverTag[64]; + FormatServerTag(serverTag); + + char sQuery[g_dQueryLength]; + Format(sQuery, sizeof(sQuery),"select ti2.steam_name, ti.time_value, UNIX_TIMESTAMP(ti2.recorded_at) as recorded_at from `timer_records` ti inner join `timer_improvements` ti2 on ti.improvement_id = ti2.id inner join `zone_categories` zc on ti.zone_category_id = zc.id where zc.map_name = '%s' and zc.stage = '%i' and zc.cvars_hash = '%i' and zc.server_tag = '%s' and ti.is_invalid = 0 and zc.is_invalid = 0 order by ti.time_value asc limit 200", mapName, stage, cvarsHash, serverTag); + DataPack hDataPack = new DataPack(); + hDataPack.WriteCell(GetClientSerial(client)); + hDataPack.WriteString(mapName); + hDataPack.WriteCell(stage); + hDataPack.WriteCell(categoryNumber); + hDataPack.WriteCell(cvarsHash); + g_dDatabase.Query(SQL_OnOtherMapTopRecordsFetched, sQuery, hDataPack, DBPrio_Normal); +} + +public void SQL_OnOtherMapTopRecordsFetched(Database db, DBResultSet results, const char[] error, DataPack data) +{ + ResetPack(data); + int clientSerial = data.ReadCell(); + char mapName[512]; + data.ReadString(mapName, sizeof(mapName)); + int stage = data.ReadCell(); + int categoryNumber = data.ReadCell(); + int cvarsHash = data.ReadCell(); + delete data; + + int client; + if ((client = GetClientFromSerial(clientSerial)) == 0) + { + delete results; + return; + } + + if (!db || strlen(error)) + { + LogError("Query error fetching other-map top records: %s", error); + delete results; + PrintToChat(client, "Failed to load records, try again."); + return; + } + + ArrayList topRecords = new ArrayList(sizeof(CurrentMapAllTopRecords)); + while (results.RowCount && results.FetchRow()) + { + CurrentMapAllTopRecords entry; + results.FetchString(0, entry.sName, sizeof(entry.sName)); + results.FetchString(1, entry.csTime_record, sizeof(entry.csTime_record)); + entry.stage = stage; + entry.CvarsHash = cvarsHash; + entry.recordedAt = results.FetchInt(2); + topRecords.PushArray(entry); + } + delete results; + + FetchOtherMapOwnPosition(client, mapName, stage, categoryNumber, cvarsHash, topRecords); +} + +public void FetchOtherMapOwnPosition(int client, const char[] mapName, int stage, int categoryNumber, int cvarsHash, ArrayList topRecords) +{ + char serverTag[64]; + FormatServerTag(serverTag); + + char sQuery[g_dQueryLength]; + Format(sQuery, sizeof(sQuery), "select ti.time_value, (select count(*) + 1 from `timer_records` ti3 inner join `zone_categories` zc2 on ti3.zone_category_id = zc2.id where zc2.map_name = '%s' and zc2.stage = %i and zc2.cvars_hash = %i and zc2.server_tag = '%s' and ti3.time_value < ti.time_value and ti3.is_invalid = 0 and zc2.is_invalid = 0) as position, UNIX_TIMESTAMP(ti2.recorded_at) as recorded_at from `timer_records` ti inner join `timer_improvements` ti2 on ti.improvement_id = ti2.id inner join `zone_categories` zc on ti.zone_category_id = zc.id where ti2.steam_auth = '%s' and zc.map_name = '%s' and zc.stage = %i and zc.cvars_hash = %i ti.is_invalid = 0 and zc.is_invalid = 0 order by ti.time_value asc limit 1", mapName, stage, cvarsHash, serverTag, g_csSID[client], mapName, stage, cvarsHash); + + DataPack hDataPack = new DataPack(); + hDataPack.WriteCell(GetClientSerial(client)); + hDataPack.WriteString(mapName); + hDataPack.WriteCell(stage); + hDataPack.WriteCell(categoryNumber); + hDataPack.WriteCell(view_as(topRecords)); + g_dDatabase.Query(SQL_OnOtherMapOwnPositionFetched, sQuery, hDataPack, DBPrio_Normal); +} + +public void SQL_OnOtherMapOwnPositionFetched(Database db, DBResultSet results, const char[] error, DataPack data) +{ + ResetPack(data); + int clientSerial = data.ReadCell(); + char mapName[512]; + data.ReadString(mapName, sizeof(mapName)); + int stage = data.ReadCell(); + int categoryNumber = data.ReadCell(); + ArrayList topRecords = view_as(data.ReadCell()); + delete data; + + int client; + if ((client = GetClientFromSerial(clientSerial)) == 0) + { + delete results; + delete topRecords; + return; + } + + bool hasOwnRecord = false; + char ownTime[32]; + int ownPosition = 0; + char formattedOwn[32]; + if (!db || strlen(error)) + { + LogError("Query error fetching own position: %s", error); + } + else if (results.RowCount && results.FetchRow()) + { + results.FetchString(0, ownTime, sizeof(ownTime)); + ownPosition = results.FetchInt(1); + FormatTime(formattedOwn, sizeof(formattedOwn), "%m/%d/%Y %H:%M", results.FetchInt(2)); + hasOwnRecord = true; + } + delete results; + + Menu menu = new Menu(MenuHandler_RecordsInfo); + menu.ExitBackButton = true; + if (hasOwnRecord) + { + menu.SetTitle("%s - Stage %i - Category %i\nYour time: %s (#%i): %s", mapName, stage, categoryNumber, ownTime, ownPosition, formattedOwn); + } + else + { + menu.SetTitle("%s - Stage %i - Category %i\nYou have no record here yet", mapName, stage, categoryNumber); + } + + for (int i = 0; i < topRecords.Length; i++) + { + CurrentMapAllTopRecords entry; + topRecords.GetArray(i, entry); + char formatted[32]; + FormatTime(formatted, sizeof(formatted), "%m/%d/%Y %H:%M", entry.recordedAt); + char display[g_dLength]; + Format(display, sizeof(display), "#%i: %s - %s: %s", i + 1, entry.csTime_record, entry.sName, formatted); + menu.AddItem("-1", display, ITEMDRAW_DISABLED); + } + if (topRecords.Length == 0) + { + menu.AddItem("-1", "No records yet", ITEMDRAW_DISABLED); + } + delete topRecords; + + menu.ExitButton = true; + menu.Display(client, 0); +} + +public int MenuHandler_RecordsInfo(Menu menu, MenuAction action, int client, int selection) +{ + if (action == MenuAction_End) + { + delete menu; + } + else if (action == MenuAction_Cancel) + { + if (selection == MenuCancel_ExitBack) + { + OpenCategoryMenu(client, g_sSelectedMap[client], g_iSelectedStage[client]); + } + } + return 0; +} + +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +stock bool IsValidClient(int client) +{ + if (client > 0 && client <= MaxClients && IsClientConnected(client) && IsClientInGame(client)) + return true; + return false; +} + +stock int FNV1aHash(const char[] str) +{ + int hash = 0x811C9DC5; // FNV offset basis + int len = strlen(str); + for (int i = 0; i < len; i++) + { + hash ^= str[i]; + hash *= 16777619; // FNV prime + } + return hash; +} + +public Action OnClientCommand(int client, int argc) +{ + if (!IsValidClient(client) || argc < 1) + { + return Plugin_Continue; + } + + char sArgs[64]; + char sTargetName[MAX_TARGET_LENGTH]; + int iTargets[MAXPLAYERS]; + bool bIsML; + GetCmdArg(1, sArgs, sizeof(sArgs)); + + if ((ProcessTargetString(sArgs, client, iTargets, MAXPLAYERS, COMMAND_FILTER_NO_BOTS, sTargetName, sizeof(sTargetName), bIsML)) <= 0) + { + return Plugin_Continue; + } + + int target = iTargets[0]; + if (!g_bHumansAllowedTime[target]) + { + return Plugin_Continue; + } + if (CheckCommandAccess(client, "", ADMFLAG_GENERIC, false)) + { + PrintToChat(target, "Disabled your timer due to admin interferrence caused by admin %N.", client); + g_bHumansAllowedTime[target] = false; + ResetCurrentClientRaceZone(target); + } + return Plugin_Continue; +} + +void ResetCurrentClientRaceZone(int client) +{ + g_CurrentClientRaceZone[client].client = 0; + g_CurrentClientRaceZone[client].csTime_record[0] = '\0'; + g_CurrentClientRaceZone[client].stage = 0; + g_CurrentClientRaceZone[client].CvarsHash = 0; + g_CurrentClientRaceZone[client].position = 0; + g_CurrentClientRaceZone[client].recordedAt = 0; +} diff --git a/RaceTimer_2026/sql/create_tables.sql b/RaceTimer_2026/sql/create_tables.sql new file mode 100644 index 0000000..d381388 --- /dev/null +++ b/RaceTimer_2026/sql/create_tables.sql @@ -0,0 +1,56 @@ +-- unloze_racetimer_css_2026.players definition + +CREATE TABLE `players` ( + `steam_auth` varchar(64) NOT NULL, + `name` varchar(254) NOT NULL, + `is_ignoring` tinyint(1) NOT NULL DEFAULT 0, + `first_seen` timestamp NOT NULL DEFAULT current_timestamp(), + PRIMARY KEY (`steam_auth`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + + +-- unloze_racetimer_css_2026.zone_categories definition + +CREATE TABLE `zone_categories` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `map_name` varchar(254) NOT NULL, + `stage` int(10) unsigned NOT NULL DEFAULT 1, + `server_tag` varchar(16) NOT NULL, + `server_cvars` text NOT NULL, + `cvars_hash` int(11) NOT NULL, + `is_invalid` tinyint(1) NOT NULL DEFAULT 0, + `first_recorded_at` timestamp NOT NULL DEFAULT current_timestamp(), + PRIMARY KEY (`id`), + UNIQUE KEY `uq_zone_cvarset` (`map_name`,`stage`,`cvars_hash`), + KEY `idx_zone_category_order` (`map_name`,`stage`,`first_recorded_at`,`id`) +) ENGINE=InnoDB AUTO_INCREMENT=447 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- unloze_racetimer_css_2026.timer_improvements definition + +CREATE TABLE `timer_improvements` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `steam_auth` varchar(64) NOT NULL, + `steam_name` varchar(254) NOT NULL, + `recorded_at` timestamp NOT NULL DEFAULT current_timestamp(), + PRIMARY KEY (`id`), + KEY `idx_player_history` (`steam_auth`,`recorded_at`), + CONSTRAINT `fk_improvement_player` FOREIGN KEY (`steam_auth`) REFERENCES `players` (`steam_auth`) +) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + + +-- unloze_racetimer_css_2026.timer_records definition + +CREATE TABLE `timer_records` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `improvement_id` bigint(20) unsigned NOT NULL, + `zone_category_id` int(10) unsigned NOT NULL, + `time_value` decimal(10,3) NOT NULL, + `is_invalid` tinyint(1) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + KEY `idx_leaderboard` (`zone_category_id`,`time_value`), + KEY `idx_improvement` (`improvement_id`), + CONSTRAINT `fk_record_category` FOREIGN KEY (`zone_category_id`) REFERENCES `zone_categories` (`id`), + CONSTRAINT `fk_record_improvement` FOREIGN KEY (`improvement_id`) REFERENCES `timer_improvements` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + + diff --git a/RaceTimer_2026/sql/insert_old_data.sql b/RaceTimer_2026/sql/insert_old_data.sql new file mode 100644 index 0000000..ab01d59 --- /dev/null +++ b/RaceTimer_2026/sql/insert_old_data.sql @@ -0,0 +1,433 @@ +INSERT INTO players (steam_auth, name) +SELECT steam_auth, name +FROM zetimer_table_new +WHERE steam_auth NOT IN ('STEAM_ID_STOP_IGNORING_RETVALS', 'STEAM_ID_PENDING'); + + +INSERT INTO zone_categories (map_name, stage, server_tag, server_cvars, cvars_hash) VALUES +('ze_rooftop_runaway1_v4', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_boatescape6_remix_fix2', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_random_v9', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_ancient_wrath_v1_fix2', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_ancient_wrath_v1_fix2', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_death_star_escape_v4_3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_castlevania_v1_3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_biohazard2_rpd_v4e_004', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_thelostworld_redux_101jj', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_mirrors_edge_reborn_v4_3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_shonyudo_v1_1s', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_shonyudo_v1_1s', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_shonyudo_v1_1s', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_shonyudo_v1_1s', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_facility_v2_3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_facility_v2_3', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_facility_v2_3', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_facility_v2_3', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_facility_v3_pre2', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_facility_v3_pre2', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_facility_v3_pre2', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_facility_v3_pre2', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_facility_v3_pre2', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_facility_v3_pre2', 6, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_bona_s2', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_bona_s2', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_bona_s2', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_bona_s2', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_outside_s3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_outside_s3', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_outside_s3', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_jurassicpark_escape_v1', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_elevator_escape_jbg_final', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_minecraft_v1_1a', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_echo_boatescape_extended', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_potc_v3_4fix', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_potc_v3_4fix', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_potc_v3_4fix', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_potc_v3_4fix', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_potc_v3_4fix', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_licciana_escape_v2', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_30_seconds__b21', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_sorrento_escape_v5', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_lotr_mines_of_moria_v6_3testfix', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_pirates_port_royal_v5_4s2', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_lotr_mount_doom_v3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_firewall_laboratory_part1_3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_firewall_laboratory_part1_3', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_vortex_v1_9s', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_vortex_v1_9s', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_vortex_v1_9s', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_vortex_v1_9s', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_vortex_v1_9s', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_sahok_t5_9', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_sahok_t5_9', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_rooftop_runaway2_v5', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_haunted_lab_escape_v2c', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_jurassicpark_c1v4', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_predator_ultimate_v3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_predator_ultimate_v3', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_predator_ultimate_v3', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_rocket_escape_r1', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_trainescape_final', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_pirates_port_royal_v3_6', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_pirates_port_royal_v3_6', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_pirates_port_royal_v3_6', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_pirates_port_royal_v3_6', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_chicken_lords_tower_b2mix5', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_halo_v1_3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_dangerous_waters_v1a', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_lotr_helms_deep_v5', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_lotr_helms_deep_v5', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_lotr_helms_deep_v5', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_isla_nublar_v3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_isla_nublar_v3', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_gargantua_v2_0', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_gargantua_v2_0', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_gargantua_v2_0', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_skill_escape_v09s', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_skill_escape_v09s', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_infected_tramway_v3_3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_minecraft_adventure_v1_2c', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_minecraft_adventure_v1_2c', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_arctic_escape_c1_fix3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_sorrento_escape_v5', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_sorrento_escape_v5', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_sorrento_escape_v5', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_sorrento_escape_v5', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_boatescape6_remix_fix2', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_minecraft_adventure_v1_2c', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_minecraft_adventure_v1_2c', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_lotr_helms_deep_v5', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_lotr_helms_deep_v5', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_elevator_escape_jbg_final', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_elevator_escape_jbg_final', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_elevator_escape_jbg_final', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_skill_escape_v09s', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_skill_escape_v09s', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_pirates_port_royal_v5_4s2', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_isla_nublar_v3', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_rooftop_runaway2_v5', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_dangerous_waters_v1a', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_dangerous_waters_v1a', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_dangerous_waters_v1a', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_jurassicpark_v2_10_ob', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_jurassicpark_v2_10_ob', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_jurassicpark_v2_10_ob', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_jurassicpark_v2_10_ob', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_jurassicpark_v2_10_ob', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_biohazard2_rpd_v4e_004', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_biohazard2_rpd_v4e_004', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_biohazard2_rpd_v4e_004', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_biohazard2_rpd_v4e_004', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_rooftop_runaway2_v5', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_rooftop_runaway2_v5', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_rooftop_runaway2_v5', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_rooftop_runaway2_v5', 6, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_indiana_jones_v4a_004', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_indiana_jones_v4a_004', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_indiana_jones_v4a_004', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_indiana_jones_v4a_004', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_indiana_jones_v4a_004', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_random_v9', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_random_v9', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_skill_escape_v09s', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_sahok_t5_9', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_echo_boatescape_extended', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_echo_boatescape_extended', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_echo_boatescape_extended', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_echo_boatescape_extended', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_ffvii_mako_reactor_v5_3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_ffvii_mako_reactor_v5_3', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_ffvii_mako_reactor_v5_3', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_ffvii_mako_reactor_v5_3', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_ffvii_mako_reactor_v5_3', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_ffvii_mako_reactor_v5_3', 6, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_licciana_escape_v2', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_licciana_escape_v2', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_licciana_escape_v2', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_licciana_escape_v2', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_river_escape_v3_fix', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_get_to_the_chopper_undead_6', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_rocket_escape_r1', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_rocket_escape_r1', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_rocket_escape_r1', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_rocket_escape_r1', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_rocket_escape_r1', 6, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_jurassicpark_escape_v1', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_jurassicpark_escape_v1', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_jurassicpark_escape_v1', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_jurassicpark_escape_v1', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_rooftop_runaway1_v4', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_rooftop_runaway1_v4', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_hypernova_v2fix', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_30_seconds__b21', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_30_seconds__b21', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_30_seconds__b21', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_30_seconds__b21', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_30_seconds__b21', 6, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_gargantua_v2_0', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_haunted_lab_escape_v2c', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_haunted_lab_escape_v2c', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_shonyudo_v1_1s', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_shonyudo_v1_1s', 6, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_sahok_t5_9', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_sahok_t5_9', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_castlevania_v1_3', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_castlevania_v1_3', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_castlevania_v1_3', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_trainescape_final', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_trainescape_final', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_elevator_escape_jbg_final', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_trainescape_final', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_trainescape_final', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_random_v9', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_random_v9', 6, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_minecraft_adventure_v1_2c', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_lotr_mines_of_moria_v6_3testfix', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_hypernova_v2fix', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_hypernova_v2fix', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_hypernova_v2fix', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_hypernova_v2fix', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_hypernova_v2fix', 6, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_sahok_t5_9', 6, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_pirates_port_royal_v3_6', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_minecraft_v1_1a', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_minecraft_v1_1a', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_lotr_mount_doom_v3', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_lotr_mount_doom_v3', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_lotr_mount_doom_v3', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_lotr_mount_doom_v3', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_lotr_mines_of_moria_v6_3testfix', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_rush_b_s1', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_death_star_escape_v4_3', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_death_star_escape_v4_3', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_death_star_escape_v4_3', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_death_star_escape_v4_3', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_jurassicpark_c1v4', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_jurassicpark_c1v4', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_rooftop_runaway1_v4', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_rooftop_runaway1_v4', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_arctic_escape_c1_fix3', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_arctic_escape_c1_fix3', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_arctic_escape_c1_fix3', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_arctic_escape_c1_fix3', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_minecraft_v1_1a', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_infected_tramway_v3_3', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_infected_tramway_v3_3', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_infected_tramway_v3_3', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_infected_tramway_v3_3', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_games_v2_2a', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_jurassicpark_c1v4', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_jurassicpark_c1v4', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_lotr_mines_of_moria_v6_3testfix', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_get_to_the_chopper_undead_6', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_get_to_the_chopper_undead_6', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_get_to_the_chopper_undead_6', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_get_to_the_chopper_undead_6', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_predator_ultimate_v3', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_bona_s2', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_outside_s3', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_isla_nublar_v3', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_outside_s3', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_dangerous_waters_v1a', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_barrage_v1', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_sewer_escape_2012_b1', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_sewer_escape_2012_b1', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_motanuminc_ob', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_pizzatime_v4s', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_saltfish_css2', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_saltfish_css2', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_saltfish_css2', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_sorrento_xmas_v4', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_licciana_xmas_v1', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_tesv_skyrim_v4fix', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_tesv_skyrim_v4fix', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_lotr_mines_of_moria_v6_3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_happy_b7s2', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_happy_b7s2', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_happy_b7s2', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_happy_b7s2', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_jurassic_park_story_v1', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_randomizer_c1s', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_escape_horizon_c1v2', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_grand_boat_escape_day_v4_3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_facility_v2_3', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_stargate_escape_v8', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_icecave_escape_v2', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_parkour_fix', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_madness_v2_1', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_shonyudo_v5_2s', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_shonyudo_v5_2s', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_shonyudo_v5_2s', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_shonyudo_v5_2s', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_shonyudo_v5_2s', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_shonyudo_v5_2s', 6, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_shonyudo_v5_2s', 7, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_tesv_skyrim_v4fix', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_grand_boat_escape_v1_9', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_icecap_escape_v3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_titanic_escape_v2_3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_bathroom_v2_5s', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_ashen_keep_v0_3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_froyo_css3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_froyo_css3', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_froyo_css3', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_froyo_css3', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_froyo_css3', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_froyo_css3', 6, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_froyo_css3', 7, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_froyo_css3', 8, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_froyo_css3', 9, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_froyo_css3', 10, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_froyo_css3', 11, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_froyo_css3', 12, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_gypt_v1_3s', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_gypt_v1_3s', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_gypt_v1_3s', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_gypt_v1_3s', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_gypt_v1_3s', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_randomizer_c1s4', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_randomizer_c1s4', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_randomizer_c1s4', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_randomizer_c1s4', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_collab_ware_a5_8', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_gypt_v1_3s', 6, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_gypt_v1_3s', 7, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_facility_v2_3', 6, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_ancient_wrath_v2_test27', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_rush_b_s1', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_lotr_mount_doom_v4_2', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_paradise_v1', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_2012_fix2', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_2012_fix2', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_djinn_v1', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_djinn_v1', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_djinn_v1', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_jump_king_b3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_jump_king_b3', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_jump_king_b3', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_jump_king_b3', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_jump_king_b3', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_jump_king_b3', 6, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_jump_king_b3', 7, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_best_korea_css3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_cherry_blossom_v4_1', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_cherry_blossom_v4_1', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_cherry_blossom_v4_1', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_lumiverse_a3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_indiana_jones_iv_v2_4', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_jump_king_b3', 8, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_randomizer_c1s4', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_daybreak_v1_1', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_daybreak_v1_1', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_daybreak_v1_1', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_daybreak_v1_1', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_traak_b1', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_illya_b4', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_sparks_v3_f3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_sparks_v3_f3', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_sparks_v3_f3', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_hp_v1_2_s2', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_hp_v1_2_s2', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_sparks_v3_f4', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_sparks_v3_f4', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_sparks_v3_f4', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_hp_v1_2_s2', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_hp_v1_2_s2', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_hp_v1_2_s2', 5, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_survivors_b5', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_shaun_of_the_dead_b3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_barrage_v1', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_sphinx_stronghold_a8', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_sphinx_stronghold_a8', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_arid_lands_of_king_theba_b1_0', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_sphinx_stronghold_a8', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_mirrors_edge_reborn_v4_3', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_charge_v2_f2', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_charge_v2_f2', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_charge_v2_f2', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_christmas_infection_v2_3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_parkers_pit_b8', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_parkers_pit_b8', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_necromanteion_v3_1s_fix', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_natural_css', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_ancient_wrath_v3_a', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_ancient_wrath_v3_d', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_stone_pit_v2_b5', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_a_e_s_t_h_e_t_i_c_v1_1s', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_super_mario_64_v2_b9', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_pirates_port_royal_v5_4s2', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_pirates_port_royal_v5_4s2', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_gibletz_establishment_b6', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_sunlight_v2_0', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_lotr_isengard_v2_3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_ffxii_feywood_b3_1', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_ffvii_cosmo_canyon_v5fix', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_crashbandicoot_v1fix', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_crashbandicoot_v1fix', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_sphinx_stronghold_a8', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_no_title_s1', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_necromanteion_v3_1s_fix', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_shroomforest_v4_5', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_shroomforest_v4_5', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_shroomforest_v4_5', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_indiana_jones_iv_v2_4', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_shroomforest3_b6_2', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_shroomforest3_b6_2', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_shroomforest3_b6_2', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_eternal_grove_v3_css', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_eternal_grove_v3_css', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_bioshock_v7_1', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_immortal_flame_v1_2', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_silent_hill_2_illusion_kn1', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_bathroom_v2_5s', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_prototype_v2', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_dark_souls_ptd_v0_4', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_kage_a2s', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_ffxii_westersand_v7_2', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_ffxii_westersand_v7_2', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_ffxii_westersand_v8zeta1', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('bhop_monster_jam', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_halo_poa', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_shroomforest2_v1', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_crazy_escape_css2', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_crazy_escape_css2', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_venice_escape_b2', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_icecap_escape_v5', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_moon_base_v1', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_sewage_processing_b7b', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_standart_map_final_fix3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_fapescape_v1_2', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_totemo_roka_b5s', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_abandoned_industry_va2s', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_dystopia_a4_css1', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_dystopia_a4_css1', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_exit_this_earths_atomosphere_c1', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_exit_this_earths_atomosphere_c1', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_exit_this_earths_atomosphere_c1', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_aztecorum_b3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_aztecorum_b3', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_a_e_s_t_h_e_t_i_c_v1_1sv', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_scourge_v1', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_scourge_v1', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_scourge_v1', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_scourge_v1', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_cyberia_css1', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_voodoo_islands_v8_5', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_boring_map_alpha4', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_boring_map_alpha4', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_boring_map_alpha4', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_lotr_minas_tirith_v3_3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_lotr_minas_tirith_v3_3', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_fapescape_v1_2', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_lotr_minas_tirith_v3_3', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_aerial_archipelago_v1', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_aerial_archipelago_v1', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_aerial_archipelago_v1', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_aerial_archipelago_v1', 4, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_bona_s3', 1, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_bona_s3', 2, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_bona_s3', 3, 'ze1', 'CLASSIC RACETIMER', -706992435), +('ze_surf_bona_s3', 4, 'ze1', 'CLASSIC RACETIMER', -706992435); + + +