diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/client/client.lua b/resources/[qb]/[qb_casino]/DLCiplLoader/client/client.lua new file mode 100644 index 0000000..5a5f69d --- /dev/null +++ b/resources/[qb]/[qb_casino]/DLCiplLoader/client/client.lua @@ -0,0 +1,474 @@ + local function getInteriorByType(x, y, z, name, iplName) + local id = 0 + + if not IsIplActive(iplName) then + RequestIpl(iplName) + + while not IsIplActive(iplName) do + RequestIpl(iplName) + Wait(20) + end + end + + while id == 0 do + id = GetInteriorAtCoordsWithType(x, y, z, name) + Wait(20) + end + + return id +end + + +CreateThread(function() + RequestIpl("h4_mph4_terrain_occ_09") + RequestIpl("h4_mph4_terrain_occ_06") + RequestIpl("h4_mph4_terrain_occ_05") + RequestIpl("h4_mph4_terrain_occ_01") + RequestIpl("h4_mph4_terrain_occ_00") + RequestIpl("h4_mph4_terrain_occ_08") + RequestIpl("h4_mph4_terrain_occ_04") + RequestIpl("h4_mph4_terrain_occ_07") + RequestIpl("h4_mph4_terrain_occ_03") + RequestIpl("h4_mph4_terrain_occ_02") + RequestIpl("h4_islandx_terrain_04") + RequestIpl("h4_islandx_terrain_05_slod") + RequestIpl("h4_islandx_terrain_props_05_d_slod") + RequestIpl("h4_islandx_terrain_02") + RequestIpl("h4_islandx_terrain_props_05_a_lod") + RequestIpl("h4_islandx_terrain_props_05_c_lod") + RequestIpl("h4_islandx_terrain_01") + RequestIpl("h4_mph4_terrain_04") + RequestIpl("h4_mph4_terrain_06") + RequestIpl("h4_islandx_terrain_04_lod") + RequestIpl("h4_islandx_terrain_03_lod") + RequestIpl("h4_islandx_terrain_props_06_a") + RequestIpl("h4_islandx_terrain_props_06_a_slod") + RequestIpl("h4_islandx_terrain_props_05_f_lod") + RequestIpl("h4_islandx_terrain_props_06_b") + RequestIpl("h4_islandx_terrain_props_05_b_lod") + RequestIpl("h4_mph4_terrain_lod") + RequestIpl("h4_islandx_terrain_props_05_e_lod") + RequestIpl("h4_islandx_terrain_05_lod") + RequestIpl("h4_mph4_terrain_02") + RequestIpl("h4_islandx_terrain_props_05_a") + RequestIpl("h4_mph4_terrain_01_long_0") + RequestIpl("h4_islandx_terrain_03") + RequestIpl("h4_islandx_terrain_props_06_b_slod") + RequestIpl("h4_islandx_terrain_01_slod") + RequestIpl("h4_islandx_terrain_04_slod") + RequestIpl("h4_islandx_terrain_props_05_d_lod") + RequestIpl("h4_islandx_terrain_props_05_f_slod") + RequestIpl("h4_islandx_terrain_props_05_c") + RequestIpl("h4_islandx_terrain_02_lod") + RequestIpl("h4_islandx_terrain_06_slod") + RequestIpl("h4_islandx_terrain_props_06_c_slod") + RequestIpl("h4_islandx_terrain_props_06_c") + RequestIpl("h4_islandx_terrain_01_lod") + RequestIpl("h4_mph4_terrain_06_strm_0") + RequestIpl("h4_islandx_terrain_05") + RequestIpl("h4_islandx_terrain_props_05_e_slod") + RequestIpl("h4_islandx_terrain_props_06_c_lod") + RequestIpl("h4_mph4_terrain_03") + RequestIpl("h4_islandx_terrain_props_05_f") + RequestIpl("h4_islandx_terrain_06_lod") + RequestIpl("h4_mph4_terrain_01") + RequestIpl("h4_islandx_terrain_06") + RequestIpl("h4_islandx_terrain_props_06_a_lod") + RequestIpl("h4_islandx_terrain_props_06_b_lod") + RequestIpl("h4_islandx_terrain_props_05_b") + RequestIpl("h4_islandx_terrain_02_slod") + RequestIpl("h4_islandx_terrain_props_05_e") + RequestIpl("h4_islandx_terrain_props_05_d") + RequestIpl("h4_mph4_terrain_05") + RequestIpl("h4_mph4_terrain_02_grass_2") + RequestIpl("h4_mph4_terrain_01_grass_1") + RequestIpl("h4_mph4_terrain_05_grass_0") + RequestIpl("h4_mph4_terrain_01_grass_0") + RequestIpl("h4_mph4_terrain_02_grass_1") + RequestIpl("h4_mph4_terrain_02_grass_0") + RequestIpl("h4_mph4_terrain_02_grass_3") + RequestIpl("h4_mph4_terrain_04_grass_0") + RequestIpl("h4_mph4_terrain_06_grass_0") + RequestIpl("h4_mph4_terrain_04_grass_1") + RequestIpl("island_distantlights") + RequestIpl("island_lodlights") + RequestIpl("h4_yacht_strm_0") + RequestIpl("h4_yacht") + RequestIpl("h4_yacht_long_0") + RequestIpl("h4_islandx_yacht_01_lod") + RequestIpl("h4_clubposter_palmstraxx") + RequestIpl("h4_islandx_yacht_02_int") + RequestIpl("h4_islandx_yacht_02") + RequestIpl("h4_clubposter_moodymann") + RequestIpl("h4_islandx_yacht_01") + RequestIpl("h4_clubposter_keinemusik") + RequestIpl("h4_islandx_yacht_03") + RequestIpl("h4_ch2_mansion_final") + RequestIpl("h4_islandx_yacht_03_int") + RequestIpl("h4_yacht_critical_0") + RequestIpl("h4_islandx_yacht_01_int") + RequestIpl("h4_mph4_island_placement") + RequestIpl("h4_islandx_mansion_vault") + RequestIpl("h4_islandx_checkpoint_props") + RequestIpl("h4_islandairstrip_hangar_props_slod") + RequestIpl("h4_se_ipl_01_lod") + RequestIpl("h4_ne_ipl_00_slod") + RequestIpl("h4_se_ipl_06_slod") + RequestIpl("h4_ne_ipl_00") + RequestIpl("h4_se_ipl_02") + RequestIpl("h4_islandx_barrack_props_lod") + RequestIpl("h4_se_ipl_09_lod") + RequestIpl("h4_ne_ipl_05") + RequestIpl("h4_mph4_island_se_placement") + RequestIpl("h4_ne_ipl_09") + RequestIpl("h4_islandx_mansion_props_slod") + RequestIpl("h4_se_ipl_09") + RequestIpl("h4_mph4_mansion_b") + RequestIpl("h4_islandairstrip_hangar_props_lod") + RequestIpl("h4_islandx_mansion_entrance_fence") + RequestIpl("h4_nw_ipl_09") + RequestIpl("h4_nw_ipl_02_lod") + RequestIpl("h4_ne_ipl_09_slod") + RequestIpl("h4_sw_ipl_02") + RequestIpl("h4_islandx_checkpoint") + RequestIpl("h4_islandxdock_water_hatch") + RequestIpl("h4_nw_ipl_04_lod") + RequestIpl("h4_islandx_maindock_props") + RequestIpl("h4_beach") + RequestIpl("h4_islandx_mansion_lockup_03_lod") + RequestIpl("h4_ne_ipl_04_slod") + RequestIpl("h4_mph4_island_nw_placement") + RequestIpl("h4_ne_ipl_08_slod") + RequestIpl("h4_nw_ipl_09_lod") + RequestIpl("h4_se_ipl_08_lod") + RequestIpl("h4_islandx_maindock_props_lod") + RequestIpl("h4_se_ipl_03") + RequestIpl("h4_sw_ipl_02_slod") + RequestIpl("h4_nw_ipl_00") + RequestIpl("h4_islandx_mansion_b_side_fence") + RequestIpl("h4_ne_ipl_01_lod") + RequestIpl("h4_se_ipl_06_lod") + RequestIpl("h4_ne_ipl_03") + RequestIpl("h4_islandx_maindock") + RequestIpl("h4_se_ipl_01") + RequestIpl("h4_sw_ipl_07") + RequestIpl("h4_islandx_maindock_props_2") + RequestIpl("h4_islandxtower_veg") + RequestIpl("h4_mph4_island_sw_placement") + RequestIpl("h4_se_ipl_01_slod") + RequestIpl("h4_mph4_wtowers") + RequestIpl("h4_se_ipl_02_lod") + RequestIpl("h4_islandx_mansion") + RequestIpl("h4_nw_ipl_04") + RequestIpl("h4_mph4_airstrip_interior_0_airstrip_hanger") + RequestIpl("h4_islandx_mansion_lockup_01") + RequestIpl("h4_islandx_barrack_props") + RequestIpl("h4_nw_ipl_07_lod") + RequestIpl("h4_nw_ipl_00_slod") + RequestIpl("h4_sw_ipl_08_lod") + RequestIpl("h4_islandxdock_props_slod") + RequestIpl("h4_islandx_mansion_lockup_02") + RequestIpl("h4_islandx_mansion_slod") + RequestIpl("h4_sw_ipl_07_lod") + RequestIpl("h4_sw_ipl_02_lod") + RequestIpl("h4_se_ipl_04_slod") + RequestIpl("h4_islandx_checkpoint_props_lod") + RequestIpl("h4_se_ipl_04") + RequestIpl("h4_se_ipl_07") + RequestIpl("h4_mph4_mansion_b_strm_0") + RequestIpl("h4_nw_ipl_09_slod") + RequestIpl("h4_se_ipl_07_lod") + RequestIpl("h4_islandx_maindock_slod") + RequestIpl("h4_islandx_mansion_lod") + RequestIpl("h4_sw_ipl_05_lod") + RequestIpl("h4_nw_ipl_08") + RequestIpl("h4_islandairstrip_slod") + RequestIpl("h4_nw_ipl_07") + RequestIpl("h4_islandairstrip_propsb_lod") + RequestIpl("h4_islandx_checkpoint_props_slod") + RequestIpl("h4_aa_guns_lod") + RequestIpl("h4_sw_ipl_06") + RequestIpl("h4_islandx_maindock_props_2_slod") + RequestIpl("h4_islandx_mansion_office") + RequestIpl("h4_islandx_maindock_lod") + RequestIpl("h4_mph4_dock") + RequestIpl("h4_islandairstrip_propsb") + RequestIpl("h4_islandx_mansion_lockup_03") + RequestIpl("h4_nw_ipl_01_lod") + RequestIpl("h4_se_ipl_05_slod") + RequestIpl("h4_sw_ipl_01_lod") + RequestIpl("h4_nw_ipl_05") + RequestIpl("h4_islandxdock_props_2_lod") + RequestIpl("h4_ne_ipl_04_lod") + RequestIpl("h4_ne_ipl_01") + RequestIpl("h4_beach_party_lod") + RequestIpl("h4_islandx_mansion_lights") + RequestIpl("h4_sw_ipl_00_lod") + RequestIpl("h4_islandx_mansion_guardfence") + RequestIpl("h4_beach_props_party") + RequestIpl("h4_ne_ipl_03_lod") + RequestIpl("h4_islandx_mansion_b") + RequestIpl("h4_beach_bar_props") + RequestIpl("h4_ne_ipl_04") + RequestIpl("h4_sw_ipl_08_slod") + RequestIpl("h4_islandxtower") + RequestIpl("h4_se_ipl_00_slod") + RequestIpl("h4_islandx_barrack_hatch") + RequestIpl("h4_ne_ipl_06_slod") + RequestIpl("h4_ne_ipl_03_slod") + RequestIpl("h4_sw_ipl_09_slod") + RequestIpl("h4_ne_ipl_02_slod") + RequestIpl("h4_nw_ipl_04_slod") + RequestIpl("h4_ne_ipl_05_lod") + RequestIpl("h4_nw_ipl_08_slod") + RequestIpl("h4_sw_ipl_05_slod") + RequestIpl("h4_islandx_mansion_b_lod") + RequestIpl("h4_ne_ipl_08") + RequestIpl("h4_islandxdock_props") + RequestIpl("h4_islandairstrip_doorsopen_lod") + RequestIpl("h4_se_ipl_05_lod") + RequestIpl("h4_islandxcanal_props_slod") + RequestIpl("h4_se_ipl_02_slod") + RequestIpl("h4_nw_ipl_02") + RequestIpl("h4_ne_ipl_08_lod") + RequestIpl("h4_sw_ipl_08") + RequestIpl("h4_islandairstrip") + RequestIpl("h4_islandairstrip_props_lod") + RequestIpl("h4_se_ipl_05") + RequestIpl("h4_ne_ipl_02_lod") + RequestIpl("h4_islandx_maindock_props_2_lod") + RequestIpl("h4_sw_ipl_03_slod") + RequestIpl("h4_ne_ipl_01_slod") + RequestIpl("h4_beach_props_slod") + RequestIpl("h4_underwater_gate_closed") + RequestIpl("h4_ne_ipl_00_lod") + RequestIpl("h4_islandairstrip_doorsopen") + RequestIpl("h4_sw_ipl_01_slod") + RequestIpl("h4_se_ipl_00") + RequestIpl("h4_se_ipl_06") + RequestIpl("h4_islandx_mansion_lockup_02_lod") + RequestIpl("h4_islandxtower_veg_lod") + RequestIpl("h4_sw_ipl_00") + RequestIpl("h4_se_ipl_04_lod") + RequestIpl("h4_nw_ipl_07_slod") + RequestIpl("h4_islandx_mansion_props_lod") + RequestIpl("h4_islandairstrip_hangar_props") + RequestIpl("h4_nw_ipl_06_lod") + RequestIpl("h4_islandxtower_lod") + RequestIpl("h4_islandxdock_lod") + RequestIpl("h4_islandxdock_props_lod") + RequestIpl("h4_beach_party") + RequestIpl("h4_nw_ipl_06_slod") + RequestIpl("h4_nw_ipl_00_lod") + RequestIpl("h4_ne_ipl_02") + RequestIpl("h4_islandxdock_slod") + RequestIpl("h4_se_ipl_07_slod") + RequestIpl("h4_islandxdock") + RequestIpl("h4_islandxdock_props_2_slod") + RequestIpl("h4_islandairstrip_props") + RequestIpl("h4_sw_ipl_09") + RequestIpl("h4_ne_ipl_06") + RequestIpl("h4_se_ipl_03_lod") + RequestIpl("h4_nw_ipl_03") + RequestIpl("h4_islandx_mansion_lockup_01_lod") + RequestIpl("h4_beach_lod") + RequestIpl("h4_ne_ipl_07_lod") + RequestIpl("h4_nw_ipl_01") + RequestIpl("h4_mph4_island_lod") + RequestIpl("h4_islandx_mansion_office_lod") + RequestIpl("h4_islandairstrip_lod") + RequestIpl("h4_beach_props_lod") + RequestIpl("h4_nw_ipl_05_slod") + RequestIpl("h4_islandx_checkpoint_lod") + RequestIpl("h4_nw_ipl_05_lod") + RequestIpl("h4_nw_ipl_03_slod") + RequestIpl("h4_nw_ipl_03_lod") + RequestIpl("h4_sw_ipl_05") + RequestIpl("h4_mph4_mansion") + RequestIpl("h4_sw_ipl_03") + RequestIpl("h4_se_ipl_08_slod") + RequestIpl("h4_mph4_island_ne_placement") + RequestIpl("h4_aa_guns") + RequestIpl("h4_islandairstrip_propsb_slod") + RequestIpl("h4_sw_ipl_01") + RequestIpl("h4_mansion_remains_cage") + RequestIpl("h4_nw_ipl_01_slod") + RequestIpl("h4_ne_ipl_06_lod") + RequestIpl("h4_se_ipl_08") + RequestIpl("h4_sw_ipl_04_slod") + RequestIpl("h4_sw_ipl_04_lod") + RequestIpl("h4_mph4_beach") + RequestIpl("h4_sw_ipl_06_lod") + RequestIpl("h4_sw_ipl_06_slod") + RequestIpl("h4_se_ipl_00_lod") + RequestIpl("h4_ne_ipl_07_slod") + RequestIpl("h4_mph4_mansion_strm_0") + RequestIpl("h4_nw_ipl_02_slod") + RequestIpl("h4_mph4_airstrip") + RequestIpl("h4_island_padlock_props") + RequestIpl("h4_islandairstrip_props_slod") + RequestIpl("h4_nw_ipl_06") + RequestIpl("h4_sw_ipl_09_lod") + RequestIpl("h4_islandxcanal_props_lod") + RequestIpl("h4_ne_ipl_05_slod") + RequestIpl("h4_se_ipl_09_slod") + RequestIpl("h4_islandx_mansion_vault_lod") + RequestIpl("h4_se_ipl_03_slod") + RequestIpl("h4_nw_ipl_08_lod") + RequestIpl("h4_islandx_barrack_props_slod") + RequestIpl("h4_islandxtower_veg_slod") + RequestIpl("h4_sw_ipl_04") + RequestIpl("h4_islandx_mansion_props") + RequestIpl("h4_islandxtower_slod") + RequestIpl("h4_beach_props") + RequestIpl("h4_islandx_mansion_b_slod") + RequestIpl("h4_islandx_maindock_props_slod") + RequestIpl("h4_sw_ipl_07_slod") + RequestIpl("h4_ne_ipl_07") + RequestIpl("h4_islandxdock_props_2") + RequestIpl("h4_ne_ipl_09_lod") + RequestIpl("h4_islandxcanal_props") + RequestIpl("h4_beach_slod") + RequestIpl("h4_sw_ipl_00_slod") + RequestIpl("h4_sw_ipl_03_lod") + RequestIpl("h4_islandx_disc_strandedshark") + RequestIpl("h4_islandx_disc_strandedshark_lod") + RequestIpl("h4_islandx") + RequestIpl("h4_islandx_props_lod") + RequestIpl("h4_mph4_island_strm_0") + RequestIpl("h4_islandx_sea_mines") + RequestIpl("h4_mph4_island") + RequestIpl("h4_boatblockers") + RequestIpl("h4_mph4_island_long_0") + RequestIpl("h4_islandx_disc_strandedwhale") + RequestIpl("h4_islandx_disc_strandedwhale_lod") + RequestIpl("h4_islandx_props") + RequestIpl("h4_int_placement_h4_interior_1_dlc_int_02_h4_milo_") + RequestIpl("h4_int_placement_h4_interior_0_int_sub_h4_milo_") + RequestIpl("h4_int_placement_h4") + RequestIpl("vw_casino_penthouse") + RequestIpl("hei_dlc_windows_casino") + + local penthouse = getInteriorByType(976.6364,70.2947,115.1641,"vw_dlc_casino_apart", "vw_casino_penthouse") + local ks_casino_vault = getInteriorByType(946.251,43.2715,58.9172,"ks_casino_vault", "ks_casino_vault_milo_") + local ks_casino_main = getInteriorByType(935.1050,42.5656,71.2737,"ks_casino_main", "ks_casino_main_milo_") + local ks_casino_back = getInteriorByType(974.5600,22.5161,70.8396,"ks_casino_back", "ks_casino_back_milo_") + local ks_casino_tunnel = getInteriorByType(930.1539,-0.2010,59.1323,"ks_casino_tunnel", "ks_casino_tunnel_milo_") + +EnableInteriorProp(penthouse, "set_pent_tint_shell") + DisableInteriorProp(penthouse, "set_pent_bar_party_1") + EnableInteriorProp(penthouse, "set_pent_media_bar_open") + EnableInteriorProp(penthouse, "set_pent_spa_bar_open") + EnableInteriorProp(penthouse, "set_pent_dealer") + DisableInteriorProp(penthouse, "set_pent_nodealer") + DisableInteriorProp(penthouse, "set_pent_media_bar_closed") + DisableInteriorProp(penthouse, "set_pent_spa_bar_closed") + DisableInteriorProp(penthouse, "set_pent_pattern_01") + DisableInteriorProp(penthouse, "set_pent_pattern_03") + DisableInteriorProp(penthouse, "set_pent_pattern_02") + DisableInteriorProp(penthouse, "set_pent_pattern_04") + DisableInteriorProp(penthouse, "set_pent_pattern_05") + DisableInteriorProp(penthouse, "set_pent_pattern_06") + DisableInteriorProp(penthouse, "set_pent_pattern_07") + DisableInteriorProp(penthouse, "set_pent_pattern_08") + EnableInteriorProp(penthouse, "set_pent_pattern_09") + DisableInteriorProp(penthouse, "set_pent_arcade_modern") + EnableInteriorProp(penthouse, "set_pent_arcade_retro") + EnableInteriorProp(penthouse, "set_pent_clutter_03") + EnableInteriorProp(penthouse, "set_pent_clutter_02") + EnableInteriorProp(penthouse, "set_pent_clutter_01") + DisableInteriorProp(penthouse, "set_pent_lounge_blocker") + DisableInteriorProp(penthouse, "set_pent_guest_blocker") + DisableInteriorProp(penthouse, "set_pent_office_blocker") + DisableInteriorProp(penthouse, "set_pent_cine_blocker") + DisableInteriorProp(penthouse, "set_pent_spa_blocker") + DisableInteriorProp(penthouse, "set_pent_bar_blocker") + DisableInteriorProp(penthouse, "set_pent_bar_party_after") + DisableInteriorProp(penthouse, "set_pent_bar_clutter") + EnableInteriorProp(penthouse, "set_pent_bar_party_2") + DisableInteriorProp(penthouse, "set_pent_bar_light_0") + DisableInteriorProp(penthouse, "set_pent_bar_light_01") + DisableInteriorProp(penthouse, "set_pent_bar_light_02") + DisableInteriorProp(penthouse, "set_pent_bar_party_0") + DisableInteriorProp(penthouse, "set_pent_bar_party_1") + + SetInteriorEntitySetColor(penthouse,"set_pent_tint_shell","0") + SetInteriorEntitySetColor(penthouse, "set_pent_bar_party_1","0") + SetInteriorEntitySetColor(penthouse, "set_pent_tint_shell","0") + SetInteriorEntitySetColor(penthouse, "set_pent_media_bar_open","0") + SetInteriorEntitySetColor(penthouse, "set_pent_spa_bar_open","0") + SetInteriorEntitySetColor(penthouse, "set_pent_dealer","0") -- Дверцы + SetInteriorEntitySetColor(penthouse, "set_pent_nodealer","0") + SetInteriorEntitySetColor(penthouse, "set_pent_media_bar_closed","0") + SetInteriorEntitySetColor(penthouse, "set_pent_spa_bar_closed","0") + SetInteriorEntitySetColor(penthouse, "set_pent_pattern_01","0") + SetInteriorEntitySetColor(penthouse, "set_pent_pattern_03","0") + SetInteriorEntitySetColor(penthouse, "set_pent_pattern_02","0") + SetInteriorEntitySetColor(penthouse, "set_pent_pattern_04","0") + SetInteriorEntitySetColor(penthouse, "set_pent_pattern_05","0") + SetInteriorEntitySetColor(penthouse, "set_pent_pattern_06","0") + SetInteriorEntitySetColor(penthouse, "set_pent_pattern_07","0") + SetInteriorEntitySetColor(penthouse, "set_pent_pattern_08","0") + SetInteriorEntitySetColor(penthouse, "set_pent_pattern_09","0") + SetInteriorEntitySetColor(penthouse, "set_pent_arcade_modern","0") + SetInteriorEntitySetColor(penthouse, "set_pent_arcade_retro","0") + SetInteriorEntitySetColor(penthouse, "set_pent_clutter_03","0") + SetInteriorEntitySetColor(penthouse, "set_pent_clutter_02","0") + SetInteriorEntitySetColor(penthouse, "set_pent_clutter_01","0") + SetInteriorEntitySetColor(penthouse, "set_pent_lounge_blocker","0") + SetInteriorEntitySetColor(penthouse, "set_pent_guest_blocker","0") + SetInteriorEntitySetColor(penthouse, "set_pent_office_blocker","0") + SetInteriorEntitySetColor(penthouse, "set_pent_cine_blocker","0") + SetInteriorEntitySetColor(penthouse, "set_pent_spa_blocker","0") + SetInteriorEntitySetColor(penthouse, "set_pent_bar_blocker","0") + SetInteriorEntitySetColor(penthouse, "set_pent_bar_party_after","0") + SetInteriorEntitySetColor(penthouse, "set_pent_bar_clutter","0") + SetInteriorEntitySetColor(penthouse, "set_pent_bar_party_2","0") + SetInteriorEntitySetColor(penthouse, "set_pent_bar_light_0","0") + SetInteriorEntitySetColor(penthouse, "set_pent_bar_light_01","0") + SetInteriorEntitySetColor(penthouse, "set_pent_bar_light_02","0") + SetInteriorEntitySetColor(penthouse, "set_pent_bar_party_0","0") + SetInteriorEntitySetColor(penthouse, "set_pent_bar_party_1","0") + RefreshInterior(penthouse) + + EnableInteriorProp(ks_casino_vault, "set_vault_diamonds_02") + EnableInteriorProp(ks_casino_vault, "set_vault_diamonds_01") + EnableInteriorProp(ks_casino_vault, "set_vault_gold_02") + EnableInteriorProp(ks_casino_vault, "set_vault_gold_01") + EnableInteriorProp(ks_casino_vault, "set_vault_art_02") + EnableInteriorProp(ks_casino_vault, "set_vault_art_01") + EnableInteriorProp(ks_casino_vault, "set_vault_cash_02") + EnableInteriorProp(ks_casino_vault, "set_vault_cash_01") + EnableInteriorProp(ks_casino_vault, "set_vault_dressing") + EnableInteriorProp(ks_casino_vault, "set_spawn_group2") + EnableInteriorProp(ks_casino_vault, "set_spawn_group1") + DisableInteriorProp(ks_casino_vault, "set_vault_door_broken") + EnableInteriorProp(ks_casino_vault, "set_vault_door") + DisableInteriorProp(ks_casino_vault, "set_vault_door_closed") + RefreshInterior(ks_casino_vault) + + EnableInteriorProp(ks_casino_main, "casino_manager_default") + EnableInteriorProp(ks_casino_main, "casino_manager_workout") + RefreshInterior(ks_casino_main) + + EnableInteriorProp(ks_casino_back, "casino_back_laundry_damage") + RefreshInterior(ks_casino_back) + + EnableInteriorProp(ks_casino_tunnel, "set_tunnel_collapse") + RefreshInterior(ks_casino_tunnel) +end) + + + +CreateThread(function() + +while true do + + SetRadarAsExteriorThisFrame() + + SetRadarAsInteriorThisFrame(`h4_fake_islandx`, vec(4700.0, -5145.0), 0, 0) + + Wait(0) + +end +end) \ No newline at end of file diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/client/mph4_gtxd.meta b/resources/[qb]/[qb_casino]/DLCiplLoader/client/mph4_gtxd.meta new file mode 100644 index 0000000..4a5411e --- /dev/null +++ b/resources/[qb]/[qb_casino]/DLCiplLoader/client/mph4_gtxd.meta @@ -0,0 +1,185 @@ + + + + + gdIslXTerrain + gdIslXTerTracks + + + gdIslXTerTracks + h4_mph4_ter02_dirttracks + + + gdIslXTerTracks + h4_mph4_ter06_islandx_tracks + + + gdIslXTerTracks + h4_mph4_ter04_islandx_tracks + + + gdIslXTerrain + gdIslXTerWest + + + gdIslXTerWest + h4_mph4_ter01_islandx_terrain_01 + + + gdIslXTerWest + h4_mph4_ter04_islandx_terrain_04 + + + gdIslXTerrain + gdIslXTerEast + + + gdIslXTerEast + h4_mph4_ter02_terr + + + gdIslXTerEast + h4_mph4_ter02_airstrip + + + gdIslXTerEast + h4_mph4_ter05 + + + gdIslXTerrain + h4_mph4_ter05_cropfield_large + + + gdIslXTerrain + h4_mph4_ter05_cropfield_small + + + gdIslXTerrain + h4_mph4_ter05_crops + + + gdIslXTerrain + h4_mph4_wtowers_structures_02 + + + gdIslXTerrain + h4_mph4_bch_structures + + + gdIslXTerrain + h4_mph4_bch_structures_party + + + gdIslXTerrain + h4_mph4_bch_structures_dock + + + gdIslXTerrain + h4_mph4_bch_gates + + + gdIslXTerrain + h4_mph4_bch_dock_ground + + + gdIslXTerrain + h4_mph4_wtowers_structures_04 + + + gdIslXTerrain + h4_mph4_wtowers_structures_03 + + + gdIslXTerrain + h4_mph4_wtowers_lighthouse + + + gdIslXTerrain + h4_mph4_wtowers_islandx_terrainplugs + + + gdIslXTerrain + h4_mph4_wtowers_detail + + + gdIslXTerrain + h4_mph4_wtowers_barriers + + + gdIslXTerrain + h4_mph4_wtowers_checkpoint_01 + + + gdIslXTerrain + gdIslXMansion + + + gdIslXMansion + gdIslXMansionBuildings + + + gdIslXMansionBuildings + h4_mph4_manb_islandx_mansion_b + + + gdIslXMansionBuildings + h4_mph4_man_islandx_mansion + + + gdIslXMansion + h4_mph4_wtowers_mansionwalls + + + gdIslXTerrain + h4_mph4_air_islandairstrip_tower + + + gdIslXTerrain + h4_mph4_air_islandairstrip_structures + + + gdIslXTerrain + h4_mph4_air_islandairstrip_hanger + + + gdIslXTerrain + h4_mph4_air_islandairstrip_compound + + + gdIslXTerrain + h4_mph4_air_hanger_doors_open + + + gdIslXTerrain + h4_mph4_air_hanger_doors_closed + + + gdIslXTerrain + h4_mph4_air_islandairstrip_abandoned + + + gdIslXTerrain + h4_mph4_air_islandairstrip_ruins + + + gdIslXTerrain + gdIslXDockBuildings + + + gdIslXDockBuildings + gdIslXDockWarehouses + + + gdIslXDockWarehouses + h4_mph4_dock_warehouses + + + gdIslXDockWarehouses + h4_mph4_dock_islandx_maindocks + + + gdIslXDockBuildings + h4_mph4_dock_barracks + + + diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/client/water.lua b/resources/[qb]/[qb_casino]/DLCiplLoader/client/water.lua new file mode 100644 index 0000000..87f497c --- /dev/null +++ b/resources/[qb]/[qb_casino]/DLCiplLoader/client/water.lua @@ -0,0 +1,8 @@ +Citizen.CreateThread(function() + while true do + Wait(0) + Citizen.InvokeNative(0xC54A08C85AE4D410, 0.5) + end +end) + +--0xB96B00E976BE977F \ No newline at end of file diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/fxmanifest.lua b/resources/[qb]/[qb_casino]/DLCiplLoader/fxmanifest.lua new file mode 100644 index 0000000..3f2fb94 --- /dev/null +++ b/resources/[qb]/[qb_casino]/DLCiplLoader/fxmanifest.lua @@ -0,0 +1,22 @@ +fx_version 'cerulean' + +description 'Casino and Cayo Perico IPL loader by Kromstar Gaming#8228' + +version '1.0' + +game 'gta5' + +this_is_a_map 'yes' + + + +client_scripts { + 'client/client.lua', + 'client/mph4_gtxd.meta', + 'client/water.lua', +} + +data_file 'GTXD_PARENTING_DATA' 'client/mph4_gtxd.meta' + + +--IMPORTANT For this DLC Loader to render correctly please ensure your game build is set to the latest version in your server.cfg. sv_enforceGameBuild 2189 and ensure that you are running Canary on all clients connecting-- diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/_manifest.ymf b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/_manifest.ymf new file mode 100644 index 0000000..9cc8721 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/_manifest.ymf differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ch_cutscene_casino.ymap b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ch_cutscene_casino.ymap new file mode 100644 index 0000000..5542328 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ch_cutscene_casino.ymap differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_11.ybn b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_11.ybn new file mode 100644 index 0000000..3112ced Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_11.ybn differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_3.ybn b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_3.ybn new file mode 100644 index 0000000..e3995e6 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_3.ybn differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_4.ybn b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_4.ybn new file mode 100644 index 0000000..80ebe81 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_4.ybn differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_5.ybn b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_5.ybn new file mode 100644 index 0000000..733c680 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_5.ybn differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_6.ybn b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_6.ybn new file mode 100644 index 0000000..585bdcf Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_6.ybn differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_8.ybn b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_8.ybn new file mode 100644 index 0000000..4d335b8 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_8.ybn differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_casino_decals.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_casino_decals.ydr new file mode 100644 index 0000000..0ea1d1b Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_casino_decals.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_casinonew_bottom.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_casinonew_bottom.ydr new file mode 100644 index 0000000..5dd642f Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_casinonew_bottom.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_casinonew_penthouse.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_casinonew_penthouse.ydr new file mode 100644 index 0000000..dc0521f Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_casinonew_penthouse.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_strm_2.ymap b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_strm_2.ymap new file mode 100644 index 0000000..469ac13 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_ch3_12_strm_2.ymap differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_dlc_casino_door.ymap b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_dlc_casino_door.ymap new file mode 100644 index 0000000..c1922ba Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_dlc_casino_door.ymap differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_dlc_casino_door_broken.ymap b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_dlc_casino_door_broken.ymap new file mode 100644 index 0000000..31c2312 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_dlc_casino_door_broken.ymap differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_dlc_windows_casino.ymap b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_dlc_windows_casino.ymap new file mode 100644 index 0000000..bd37d84 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_dlc_windows_casino.ymap differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_vw_dlc_casino_door_replay.ymap b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_vw_dlc_casino_door_replay.ymap new file mode 100644 index 0000000..54fe995 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hei_vw_dlc_casino_door_replay.ymap differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hi@ks_casino_back.ybn b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hi@ks_casino_back.ybn new file mode 100644 index 0000000..49aa78e Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hi@ks_casino_back.ybn differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hi@ks_casino_heist.ybn b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hi@ks_casino_heist.ybn new file mode 100644 index 0000000..5a119cb Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hi@ks_casino_heist.ybn differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hi@ks_casino_hotel.ybn b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hi@ks_casino_hotel.ybn new file mode 100644 index 0000000..972b2fb Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hi@ks_casino_hotel.ybn differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hi@ks_casino_utility.ybn b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hi@ks_casino_utility.ybn new file mode 100644 index 0000000..81bbbc8 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hi@ks_casino_utility.ybn differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hi@ks_casino_vault.ybn b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hi@ks_casino_vault.ybn new file mode 100644 index 0000000..055260f Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/hi@ks_casino_vault.ybn differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_back.ybn b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_back.ybn new file mode 100644 index 0000000..99a9718 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_back.ybn differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_back.ytyp b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_back.ytyp new file mode 100644 index 0000000..e7582a1 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_back.ytyp differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_back_milo_.ymap b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_back_milo_.ymap new file mode 100644 index 0000000..5c7ed5c Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_back_milo_.ymap differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_garage.ybn b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_garage.ybn new file mode 100644 index 0000000..796d1a4 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_garage.ybn differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_garage.ytyp b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_garage.ytyp new file mode 100644 index 0000000..d598848 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_garage.ytyp differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_garage_milo_.ymap b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_garage_milo_.ymap new file mode 100644 index 0000000..77c87be Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_garage_milo_.ymap differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_heist.ybn b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_heist.ybn new file mode 100644 index 0000000..dc57ccf Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_heist.ybn differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_heist.ytyp b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_heist.ytyp new file mode 100644 index 0000000..229d39f Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_heist.ytyp differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_heist_milo_.ymap b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_heist_milo_.ymap new file mode 100644 index 0000000..3410b68 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_heist_milo_.ymap differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_hotel.ybn b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_hotel.ybn new file mode 100644 index 0000000..ee69a34 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_hotel.ybn differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_hotel.ytyp b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_hotel.ytyp new file mode 100644 index 0000000..86b8214 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_hotel.ytyp differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_hotel_milo_.ymap b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_hotel_milo_.ymap new file mode 100644 index 0000000..1623653 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_hotel_milo_.ymap differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_loading.ybn b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_loading.ybn new file mode 100644 index 0000000..e899bb1 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_loading.ybn differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_loading.ytyp b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_loading.ytyp new file mode 100644 index 0000000..fd1bfa5 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_loading.ytyp differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_loading_milo_.ymap b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_loading_milo_.ymap new file mode 100644 index 0000000..80f6df7 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_loading_milo_.ymap differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_main.ybn b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_main.ybn new file mode 100644 index 0000000..434e99a Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_main.ybn differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_main.ytyp b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_main.ytyp new file mode 100644 index 0000000..3def184 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_main.ytyp differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_main_milo_.ymap b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_main_milo_.ymap new file mode 100644 index 0000000..cf2d5b2 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_main_milo_.ymap differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_shaft.ybn b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_shaft.ybn new file mode 100644 index 0000000..f0a3cf0 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_shaft.ybn differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_shaft.ytyp b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_shaft.ytyp new file mode 100644 index 0000000..cf35ba0 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_shaft.ytyp differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_shaft_milo_.ymap b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_shaft_milo_.ymap new file mode 100644 index 0000000..fdcbb52 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_shaft_milo_.ymap differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_tunnel.ybn b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_tunnel.ybn new file mode 100644 index 0000000..8b4037e Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_tunnel.ybn differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_tunnel.ytyp b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_tunnel.ytyp new file mode 100644 index 0000000..17ef287 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_tunnel.ytyp differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_tunnel_milo_.ymap b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_tunnel_milo_.ymap new file mode 100644 index 0000000..869f2d0 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_tunnel_milo_.ymap differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_utility.ybn b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_utility.ybn new file mode 100644 index 0000000..8f2ff08 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_utility.ybn differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_utility.ytyp b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_utility.ytyp new file mode 100644 index 0000000..30ffdd9 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_utility.ytyp differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_utility_milo_.ymap b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_utility_milo_.ymap new file mode 100644 index 0000000..3e7d375 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_utility_milo_.ymap differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_vault.ybn b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_vault.ybn new file mode 100644 index 0000000..1ccd862 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_vault.ybn differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_vault.ytyp b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_vault.ytyp new file mode 100644 index 0000000..5723e3e Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_vault.ytyp differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_vault_milo_.ymap b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_vault_milo_.ymap new file mode 100644 index 0000000..f09a04a Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_casino_vault_milo_.ymap differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_ch3_12_casino_new_door.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_ch3_12_casino_new_door.ydr new file mode 100644 index 0000000..1a3ccfe Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_ch3_12_casino_new_door.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_dlc_casino_main.ybn b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_dlc_casino_main.ybn new file mode 100644 index 0000000..bcb0b16 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_dlc_casino_main.ybn differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_garage_door.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_garage_door.ydr new file mode 100644 index 0000000..a2fdb2e Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_garage_door.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint01_casino_shell_01.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint01_casino_shell_01.ydr new file mode 100644 index 0000000..d205073 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint01_casino_shell_01.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint01_gamingr1_decals.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint01_gamingr1_decals.ydr new file mode 100644 index 0000000..6c28d9c Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint01_gamingr1_decals.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint01_gamingr1_details.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint01_gamingr1_details.ydr new file mode 100644 index 0000000..4d29a8a Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint01_gamingr1_details.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint01_gamingr1_entry.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint01_gamingr1_entry.ydr new file mode 100644 index 0000000..4a7c432 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint01_gamingr1_entry.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint01_gamingr1_lamps.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint01_gamingr1_lamps.ydr new file mode 100644 index 0000000..959a857 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint01_gamingr1_lamps.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint01_gamingr1_signage1.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint01_gamingr1_signage1.ydr new file mode 100644 index 0000000..4c3198b Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint01_gamingr1_signage1.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint01_reception_desk.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint01_reception_desk.ydr new file mode 100644 index 0000000..979d6d8 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint01_reception_desk.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint01_reception_details.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint01_reception_details.ydr new file mode 100644 index 0000000..e1f1a3d Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint01_reception_details.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_beams.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_beams.ydr new file mode 100644 index 0000000..e399bc6 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_beams.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_garage_extras2.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_garage_extras2.ydr new file mode 100644 index 0000000..b4defa4 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_garage_extras2.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_garage_shell.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_garage_shell.ydr new file mode 100644 index 0000000..b079103 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_garage_shell.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_garage_shell_doors01.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_garage_shell_doors01.ydr new file mode 100644 index 0000000..ce6d06a Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_garage_shell_doors01.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_garage_shell_edgeblend.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_garage_shell_edgeblend.ydr new file mode 100644 index 0000000..aa2103c Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_garage_shell_edgeblend.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_garagedecal.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_garagedecal.ydr new file mode 100644 index 0000000..7292798 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_garagedecal.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_pipes2.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_pipes2.ydr new file mode 100644 index 0000000..cf621e8 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_pipes2.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_vents01.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_vents01.ydr new file mode 100644 index 0000000..ff238f9 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_vents01.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_vents02.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_vents02.ydr new file mode 100644 index 0000000..02c66d6 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/ks_vwint03_vents02.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_casino_penthouse.ymap b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_casino_penthouse.ymap new file mode 100644 index 0000000..754673a Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_casino_penthouse.ymap differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_ch3_additions_2.ybn b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_ch3_additions_2.ybn new file mode 100644 index 0000000..3b5a1e0 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_ch3_additions_2.ybn differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_ch3_additions_long_0.ymap b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_ch3_additions_long_0.ymap new file mode 100644 index 0000000..0b1dee3 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_ch3_additions_long_0.ymap differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_ch3_additions_strm_0.ymap b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_ch3_additions_strm_0.ymap new file mode 100644 index 0000000..8835812 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_ch3_additions_strm_0.ymap differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_dlc_casino_door.ymap b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_dlc_casino_door.ymap new file mode 100644 index 0000000..6c4015b Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_dlc_casino_door.ymap differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwdlc_int_02.ytyp b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwdlc_int_02.ytyp new file mode 100644 index 0000000..0e18cf9 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwdlc_int_02.ytyp differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint01_casino_shell_02.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint01_casino_shell_02.ydr new file mode 100644 index 0000000..17c73d0 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint01_casino_shell_02.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint01_elevator_02_details.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint01_elevator_02_details.ydr new file mode 100644 index 0000000..a0bd9a5 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint01_elevator_02_details.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_01_lobby.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_01_lobby.ydr new file mode 100644 index 0000000..72068fc Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_01_lobby.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_02_lobby.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_02_lobby.ydr new file mode 100644 index 0000000..61ecb4e Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_02_lobby.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_03_lobby.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_03_lobby.ydr new file mode 100644 index 0000000..0955d54 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_03_lobby.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_04_lobby.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_04_lobby.ydr new file mode 100644 index 0000000..2f78a97 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_04_lobby.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_05_lobby.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_05_lobby.ydr new file mode 100644 index 0000000..91b665d Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_05_lobby.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_06_lobby.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_06_lobby.ydr new file mode 100644 index 0000000..2d9d5e0 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_06_lobby.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_07_lobby.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_07_lobby.ydr new file mode 100644 index 0000000..dfe93a3 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_07_lobby.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_08_lobby.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_08_lobby.ydr new file mode 100644 index 0000000..60e5ed7 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_08_lobby.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_09_lobby.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_09_lobby.ydr new file mode 100644 index 0000000..823966b Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pattern_09_lobby.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pent_hal_nontint.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pent_hal_nontint.ydr new file mode 100644 index 0000000..f2b5e64 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pent_hal_nontint.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pent_hal_shell.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pent_hal_shell.ydr new file mode 100644 index 0000000..ff6c75f Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pent_hal_shell.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pent_signs.ydr b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pent_signs.ydr new file mode 100644 index 0000000..f98071e Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/casinointerior/vw_vwint02_pent_signs.ydr differ diff --git a/resources/[qb]/[qb_casino]/DLCiplLoader/stream/cpminimap/int3232302352.gfx b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/cpminimap/int3232302352.gfx new file mode 100644 index 0000000..f3f7300 Binary files /dev/null and b/resources/[qb]/[qb_casino]/DLCiplLoader/stream/cpminimap/int3232302352.gfx differ diff --git a/resources/[qb]/[qb_casino]/LICENSE b/resources/[qb]/[qb_casino]/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/resources/[qb]/[qb_casino]/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/resources/[qb]/[qb_casino]/README.md b/resources/[qb]/[qb_casino]/README.md new file mode 100644 index 0000000..0eafe07 --- /dev/null +++ b/resources/[qb]/[qb_casino]/README.md @@ -0,0 +1,84 @@ +# doj-casino + +Edited Casino Pack for QBus Framework + + +# Updated 7/9/23 +- Added **[KGV](https://github.com/Xinerki/kgv-blackjack)** & **[Rubbertoe98](https://github.com/rubbertoe98)** Blackjack +# Updated 7/7/23 +- Fixed most of the casino & added & removed a few things + + + + +# Dependencies +**[PolyZone](https://github.com/mkafrin/PolyZone)** + +**[qb-menu](https://github.com/qbcore-framework/qb-menu)** + +**[qb-target](https://github.com/BerkieBb/qb-target)** + +**[casinoUi](https://github.com/dojwun/casinoUi)** + +# Images +![casino_goldchip](https://i.imgur.com/7NPjx6H.png) +![casino_member](https://i.imgur.com/SOxFphs.png) +![casino_vip](https://i.imgur.com/nBvSini.png) + + + +## casino-walls + +**[preview](https://streamable.com/jem98k)** OLD VIDEO + +**[source](https://forum.cfx.re/t/cayo-perico-casino-dlc-ipl-loader/2099391)** + +## casino-blackjackKGV + +**[preview](https://streamable.com/jpabhl)** OLD VIDEO + +**[source](https://github.com/Xinerki/kgv-blackjack)** + +## casino-blackjackRT98 + +**[preview](https://streamable.com/rfjiol)** OLD VIDEO + +**[source](https://github.com/rubbertoe98/DiamondBlackjack)** + + +## casino-luckywheel + +**[preview](https://streamable.com/ucv48w)** OLD VIDEO + +**[source](https://github.com/Sn0wBiT/esx_tpnrp_luckywheel)** + +## casino-insidetrack + +**[preview](https://streamable.com/m5eyk)** OLD VIDEO + +**[source](https://github.com/MRV6/mp_insidetrack)** + +## casino-slots + +**[preview](https://streamable.com/5xwkki)** OLD VIDEO + +**[source](https://github.com/ChatDisabled/dc-casino/tree/nice)** + +## casino-roulette + +**[preview](https://streamable.com/85vjqc)** OLD VIDEO + +**[source](https://forum.cfx.re/t/standalone-paid-aquiver-casino-roulette/2925508)** + +## casino-poker + +**[preview](NONE)** Coming soon + +**[source](https://forum.cfx.re/t/standalone-paid-aquiver-three-card-poker/3378769)** + +# qb-core/shared.lua info +``` + ["casino_goldchip"] = {["name"] = "casino_goldchip", ["label"] = "Casino Chip", ["weight"] = 0, ["type"] = "item", ["image"] = "casino_goldchip.png", ["unique"] = false, ["useable"] = false, ["shouldClose"] = false, ["combinable"] = nil, ["description"] = "Diamond Casino Chip"}, + ["casino_member"] = {["name"] = "casino_member", ["label"] = "Casino Membership", ["weight"] = 500, ["type"] = "item", ["image"] = "casino_member.png", ["unique"] = true, ["useable"] = false, ["shouldClose"] = false, ["combinable"] = nil, ["description"] = "Diamond Casino Member Card"}, + ["casino_vip"] = {["name"] = "casino_vip", ["label"] = "V.I.P Membership", ["weight"] = 500, ["type"] = "item", ["image"] = "casino_vip.png", ["unique"] = true, ["useable"] = false, ["shouldClose"] = false, ["combinable"] = nil, ["description"] = "Diamond Casino V.I.P Card"}, +``` diff --git a/resources/[qb]/[qb_casino]/casino-blackjackKGV/client/client.lua b/resources/[qb]/[qb_casino]/casino-blackjackKGV/client/client.lua new file mode 100644 index 0000000..051b1e8 --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-blackjackKGV/client/client.lua @@ -0,0 +1,1080 @@ +local QBCore = exports['qb-core']:GetCoreObject() + +local seatSideAngle = 30 +local bet = 0 +local hand = {} +local splitHand = {} +local timeLeft = 0 +local satDownCallback = nil +local standUpCallback = nil +local leaveCheckCallback = nil +local _lambo = nil +local canSitDownCallback = nil + +-- isEscorted = false +-- isHandcuffed = false +-- isDead = false + +CreateThread(function() + while true do + sleep = 1000 + local playerCoords = GetEntityCoords(PlayerPedId()) + local closestChairDist = #(playerCoords - vector3(948.54760742188, 32.051155090332, 76.101249084473)) + if closestChairDist < 55.0 then + sleep = 10 + DisableControlAction(0, 140, true) + DisableControlAction(0, 135, true) + DisableControlAction(0, 122, true) + DisableControlAction(0, 92, true) + DisableControlAction(0, 24, true) + DisableControlAction(0, 69, true) + DisableControlAction(0, 142, true) + DisableControlAction(0, 135, true) + DisableControlAction(0, 19, true) + FreezeEntityPosition(_lambo, true) + end + Wait(sleep) + end +end) + +function SetSatDownCallback(cb) + satDownCallback = cb +end + +function SetStandUpCallback(cb) + standUpCallback = cb +end + +function SetLeaveCheckCallback(cb) + leaveCheckCallback = cb +end + +function SetCanSitDownCallback(cb) + canSitDownCallback = cb +end + +function findRotation( x1, y1, x2, y2 ) + local t = -math.deg( math.atan2( x2 - x1, y2 - y1 ) ) + return t < -180 and t + 180 or t +end + +function cardValue(card) + local rank = 10 + for i=2,11 do + if string.find(card, tostring(i)) then + rank = i + end + end + if string.find(card, 'ACE') then + rank = 11 + end + + return rank +end + +function handValue(hand) + local tmpValue = 0 + local numAces = 0 + + for i,v in pairs(hand) do + tmpValue = tmpValue + cardValue(v) + end + + for i,v in pairs(hand) do + if string.find(v, 'ACE') then numAces = numAces + 1 end + end + + repeat + if tmpValue > 21 and numAces > 0 then + tmpValue = tmpValue - 10 + numAces = numAces - 1 + else + break + end + until numAces == 0 + + return tmpValue +end + +function CanSplitHand(hand) + if hand[1] and hand[2] then + if hand[1]:sub(-3) == hand[2]:sub(-3) and #hand == 2 then + if cardValue(hand[1]) == cardValue(hand[2]) then + return true + end + end + end + return _DEBUG +end + +--[[ + vw_prop_vw_chips_pile_01a.ydr -- $511,000 + vw_prop_vw_chips_pile_02a.ydr -- $3,250,000 + vw_prop_vw_chips_pile_03a.ydr -- $1,990,000 +--]] + +function getChips(amount) + if amount < 500000 then + local props = {} + local propTypes = {} + + local d = #chipValues + + for i = 1, #chipValues do + local iter = #props + 1 + while amount >= chipValues[d] do + local model = chipModels[chipValues[d]] + + if not props[iter] then + local propType = string.sub(model, 0, string.len(model) - 3) + + if propTypes[propType] then + iter = propTypes[propType] + else + props[iter] = {} + propTypes[propType] = iter + end + end + + props[iter][#props[iter] + 1] = model + amount = amount - chipValues[d] + end + + d = d - 1 + end + + return false, props + elseif amount <= 500000 then + return true, "vw_prop_vw_chips_pile_01a" + elseif amount <= 5000000 then + return true, "vw_prop_vw_chips_pile_03a" + else + return true, "vw_prop_vw_chips_pile_02a" + end +end + +function leaveBlackjack() + leavingBlackjack = true + selectedBet = 1 + hand = {} + splitHand = {} +end + +RegisterNetEvent("BLACKJACK:client:stop", function() + leaveBlackjack() +end) + + + +-- RegisterCommand("bet", function(source, args, rawCommand) +-- if args[1] and _DEBUG == true then +-- TriggerServerEvent("BLACKJACK:SetPlayerBet", g_seat, closestChair, args[1]) +-- end +-- end, false) + + +spawnedPeds = {} +spawnedObjects = {} +AddEventHandler("onResourceStop", function(r) + if r == GetCurrentResourceName() then + + for i,v in ipairs(spawnedPeds) do + DeleteEntity(v) + end + for i,v in ipairs(spawnedObjects) do + DeleteEntity(v) + end + end +end) + + + + + +function CheckGender(dealerPed) + local models = { + [`s_f_y_casino_01`] = "", + [`s_m_y_casino_01`] = "female_" + } + return models[GetEntityModel(dealerPed)] +end + +function IsSeatOccupied(coords, radius) + local players = GetActivePlayers() + local playerId = PlayerId() + for i = 1, #players do + if players[i] ~= playerId then + local ped = GetPlayerPed(players[i]) + if IsEntityAtCoord(ped, coords, radius, radius, radius, 0, 0, 0) then + return true + end + end + end + + return false +end + +dealerHand = {} +dealerValue = {} +dealerHandObjs = {} +handObjs = {} + +function CreatePeds() + if not HasAnimDictLoaded("anim_casino_b@amb@casino@games@blackjack@dealer") then + RequestAnimDict("anim_casino_b@amb@casino@games@blackjack@dealer") + repeat Wait(0) until HasAnimDictLoaded("anim_casino_b@amb@casino@games@blackjack@dealer") + end + + if not HasAnimDictLoaded("anim_casino_b@amb@casino@games@shared@dealer@") then + RequestAnimDict("anim_casino_b@amb@casino@games@shared@dealer@") + repeat Wait(0) until HasAnimDictLoaded("anim_casino_b@amb@casino@games@shared@dealer@") + end + + if not HasAnimDictLoaded("anim_casino_b@amb@casino@games@blackjack@player") then + RequestAnimDict("anim_casino_b@amb@casino@games@blackjack@player") + repeat Wait(0) until HasAnimDictLoaded("anim_casino_b@amb@casino@games@blackjack@player") + end + + for i,v in pairs(customTables) do + -- local model = { + -- `vw_prop_casino_3cardpoker_01`, + -- `vw_prop_casino_3cardpoker_01b`, + -- `vw_prop_casino_blckjack_01`, + -- `vw_prop_casino_blckjack_01b` + -- } + local model = `vw_prop_casino_3cardpoker_01b` + if v.highStakes == true then + model = `vw_prop_casino_blckjack_01b` + end + if not HasModelLoaded(model) then + RequestModel(model) + repeat Wait(0) until HasModelLoaded(model) + end + local tableObj = CreateObjectNoOffset(model, v.coords.x, v.coords.y, v.coords.z, false, false, false) + SetEntityRotation(tableObj, 0.0, 0.0, v.coords.w, 2, 1) + SetObjectTextureVariant(tableObj, v.color or 3) + table.insert(spawnedObjects, tableObj) + end + chips = {} + hand = {} + splitHand = {} + handObjs = {} + for i,v in pairs(tables) do + dealerHand[i] = {} + dealerValue[i] = {} + dealerHandObjs[i] = {} + local models = { + `s_f_y_casino_01`, + `s_m_y_casino_01` + } + local model = models[1] + if ((i+6) % 13) < 7 then + model = models[2] + end + + chips[i] = {} + + for x=1,4 do + chips[i][x] = {} + end + handObjs[i] = {} + + for x=1,4 do + handObjs[i][x] = {} + end + + if not HasModelLoaded(model) then + RequestModel(model) + repeat Wait(0) until HasModelLoaded(model) + end + + local dealer = CreatePed(4, model, v.coords.x, v.coords.y, v.coords.z, v.coords.w, false, true) + SetEntityCanBeDamaged(dealer, false) + SetBlockingOfNonTemporaryEvents(dealer, true) + SetPedCanRagdollFromPlayerImpact(dealer, false) + SetPedResetFlag(dealer, 249, true) + SetPedConfigFlag(dealer, 185, true) + SetPedConfigFlag(dealer, 108, true) + SetPedConfigFlag(dealer, 208, true) + SetDealerOutfit(dealer, i+6) + + local scene = CreateSynchronizedScene(v.coords.x, v.coords.y, v.coords.z, 0.0, 0.0, v.coords.w, 2) + TaskSynchronizedScene(dealer, scene, "anim_casino_b@amb@casino@games@shared@dealer@", "idle", 1000.0, -8.0, 4, 1, 1148846080, 0) + + spawnedPeds[i] = dealer + end +end + +RegisterNetEvent("BLACKJACK:SyncTimer", function(_timeLeft) + timeLeft = _timeLeft +end) + +RegisterNetEvent("BLACKJACK:PlayDealerAnim", function(i, animDict, anim) + CreateThread(function() + local Gender = CheckGender(spawnedPeds[i]) + if Gender ~= "" then anim = string.gsub(anim, Gender,"") end + + local v = tables[i] + + if not HasAnimDictLoaded(animDict) then + RequestAnimDict(animDict) + repeat Wait(0) until HasAnimDictLoaded(animDict) + end + DebugPrint("PLAYING "..anim:upper().." ON DEALER "..i) + local scene = CreateSynchronizedScene(v.coords.x, v.coords.y, v.coords.z, 0.0, 0.0, v.coords.w, 2) + TaskSynchronizedScene(spawnedPeds[i], scene, animDict, anim, 8.0, 8.0, 4, 1, 1148846080, 0) + end) +end) + +RegisterNetEvent("BLACKJACK:PlayDealerSpeech", function(i, speech) + CreateThread(function() + DebugPrint("PLAYING SPEECH "..speech:upper().." ON DEALER "..i) + StopCurrentPlayingAmbientSpeech(spawnedPeds[i]) + PlayPedAmbientSpeechNative(spawnedPeds[i], speech, "SPEECH_PARAMS_FORCE_NORMAL_CLEAR") + end) +end) + +RegisterNetEvent("BLACKJACK:DealerTurnOverCard", function(i) + hideUiOnStart() + local cardX,cardY,cardZ = GetEntityCoords(dealerHandObjs[i][1]) + AttachEntityToEntity(dealerHandObjs[i][1], spawnedPeds[i], GetPedBoneIndex(spawnedPeds[i],28422), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 1, 2, 1) + while not HasAnimEventFired(spawnedPeds[i],585557868) do + Wait(0) + end + DetachEntity(dealerHandObjs[i][1],false,true) + SetEntityCoordsNoOffset(dealerHandObjs[i][1], cardX,cardY,cardZ) + SetEntityRotation(dealerHandObjs[i][1], 0.0, 0.0, tables[i].coords.w + cardRotationOffsetsDealer[1].z) +end) + +RegisterNetEvent("BLACKJACK:DealerCheckCard", function(i) + local cardX,cardY,cardZ = GetEntityCoords(dealerHandObjs[i][1]) + AttachEntityToEntity(dealerHandObjs[i][1], spawnedPeds[i], GetPedBoneIndex(spawnedPeds[i],28422), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 1, 2, 1) + while not HasAnimEventFired(spawnedPeds[i],585557868) do + Wait(0) + end + Wait(100) + DetachEntity(dealerHandObjs[i][1],false,true) + SetEntityCoordsNoOffset(dealerHandObjs[i][1], cardX,cardY,cardZ) +end) + +RegisterNetEvent("BLACKJACK:SplitHand", function(index, seat, splitHandSize, _hand, _splitHand) + hand = _hand + splitHand = _splitHand + + DebugPrint("splitHandSize = "..splitHandSize) + DebugPrint("split card coord = "..tostring(GetObjectOffsetFromCoords(tables[index].coords.x, tables[index].coords.y, tables[index].coords.z, tables[index].coords.w, cardSplitOffsets[seat][1]))) + + SetEntityCoordsNoOffset(handObjs[index][seat][#handObjs[index][seat]], GetObjectOffsetFromCoords(tables[index].coords.x, tables[index].coords.y, tables[index].coords.z, tables[index].coords.w, cardSplitOffsets[5-seat][1])) + SetEntityRotation(handObjs[index][seat][#handObjs[index][seat]], 0.0, 0.0, cardSplitRotationOffsets[seat][splitHandSize]) +end) + +selectedBet = 1 + + +RegisterNetEvent("BLACKJACK:PlaceBetChip", function(index, seat, bet, double, split) + CreateThread(function() + local chipPile, props = getChips(bet) + if chipPile then + local model = GetHashKey(props) + DebugPrint(bet) + DebugPrint(seat) + DebugPrint(tostring(props)) + DebugPrint(tostring(pileOffsets[seat])) + RequestModel(model) + repeat Wait(0) until HasModelLoaded(model) + local location = 1 + if double == true then location = 2 end + local chip = CreateObjectNoOffset(model, tables[index].coords.x, tables[index].coords.y, tables[index].coords.z, false, false, false) + table.insert(spawnedObjects, chip) + table.insert(chips[index][seat], chip) + if split == false then + SetEntityCoordsNoOffset(chip, GetObjectOffsetFromCoords(tables[index].coords.x, tables[index].coords.y, tables[index].coords.z, tables[index].coords.w, pileOffsets[seat][location].x, pileOffsets[seat][location].y, chipHeights[1])) + SetEntityRotation(chip, 0.0, 0.0, tables[index].coords.w + pileRotationOffsets[seat][3 - location].z) + else + SetEntityCoordsNoOffset(chip, GetObjectOffsetFromCoords(tables[index].coords.x, tables[index].coords.y, tables[index].coords.z, tables[index].coords.w, pileOffsets[seat][2].x, pileOffsets[seat][2].y, chipHeights[1])) + SetEntityRotation(chip, 0.0, 0.0, tables[index].coords.w + pileRotationOffsets[seat][3 - location].z) + end + else + local chipXOffset = 0.0 + local chipYOffset = 0.0 + if split or double then + if seat == 1 then + chipXOffset = chipXOffset + 0.03 + chipYOffset = chipYOffset + 0.05 + elseif seat == 2 then + chipXOffset = chipXOffset + 0.05 + chipYOffset = chipYOffset + 0.02 + elseif seat == 3 then + chipXOffset = chipXOffset + 0.05 + chipYOffset = chipYOffset - 0.02 + elseif seat == 4 then + chipXOffset = chipXOffset + 0.02 + chipYOffset = chipYOffset - 0.05 + end + end + for i = 1, #props do + local chipGap = 0.0 + for j = 1, #props[i] do + local model = GetHashKey(props[i][j]) + DebugPrint(bet) + DebugPrint(seat) + DebugPrint(tostring(props[i][j])) + DebugPrint(tostring(chipOffsets[seat])) + RequestModel(model) + repeat Wait(0) until HasModelLoaded(model) + local location = i + local chip = CreateObjectNoOffset(model, tables[index].coords.x, tables[index].coords.y, tables[index].coords.z, false, false, false) + table.insert(spawnedObjects, chip) + table.insert(chips[index][seat], chip) + SetEntityCoordsNoOffset(chip, GetObjectOffsetFromCoords(tables[index].coords.x, tables[index].coords.y, tables[index].coords.z, tables[index].coords.w, chipOffsets[seat][location].x + chipXOffset, chipOffsets[seat][location].y + chipYOffset, chipHeights[1] + chipGap)) + SetEntityRotation(chip, 0.0, 0.0, tables[index].coords.w + chipRotationOffsets[seat][location].z) + chipGap = chipGap + ((chipThickness[model] ~= nil) and chipThickness[model] or 0.0) + end + end + end + end) +end) + +function hideUi() + exports["qb-core"]:HideText() + exports['casinoUi']:HideCasinoUi('hide') +end + +function hideUiOnStart() + exports["qb-core"]:HideText() + exports['casinoUi']:HideCasinoUi('hide') + exports['qb-menu']:closeMenu() +end + +RegisterNetEvent("BLACKJACK:BetReceived") + +local upPressed = false +local downPressed = false + + +RegisterNetEvent("BLACKJACK:RequestBets", function(index) + -- timeLeft = _timeLeft + if leavingBlackjack == true then leaveBlackjack() return end + QBCore.Functions.TriggerCallback('BLACKJACKKGV:server:blackChipsAmount', function(result) + retval = result + CreateThread(function() + scrollerIndex = index + exports["qb-core"]:DrawText("Place Bet:

Adjust Bet: ↑/↓

Exit: ←", "top") + while true do + Wait(0) + exports['casinoUi']:DrawCasinoUi('show', "Diamond Casino Blackjack

Time Left: 0:"..timeLeft.."

Current Bet: "..bet.."

Availble chips: "..math.floor(retval)) + local tableLimit = (tables[scrollerIndex].highStakes == true) and #bettingNums or lowTableLimit + if IsControlJustPressed(1, 205) then -- Q / Y + selectedBet = tableLimit + elseif IsControlJustPressed(1, 202) then -- ESC / B + leaveBlackjack() + return + end + if not upPressed then + if IsControlJustPressed(1, 172) then -- UP ARROW + upPressed = true + CreateThread(function() + selectedBet = selectedBet + 1 + if selectedBet > tableLimit then selectedBet = 1 end + Wait(175) + while IsControlPressed(1, 172) do + selectedBet = selectedBet + 1 + if selectedBet > tableLimit then selectedBet = 1 end + Wait(125) + end + + upPressed = false + end) + end + end + if not downPressed then + if IsControlJustPressed(1, 173) then -- DOWN ARROW + downPressed = true + CreateThread(function() + selectedBet = selectedBet - 1 + if selectedBet < 1 then selectedBet = tableLimit end + Wait(175) + while IsControlPressed(1, 173) do + selectedBet = selectedBet - 1 + if selectedBet < 1 then selectedBet = tableLimit end + Wait(125) + end + + downPressed = false + end) + end + end + bet = bettingNums[selectedBet] or 10000 + if #bettingNums < lowTableLimit and tables[scrollerIndex].highStakes == true then + bet = bet * 10 + end + if IsControlJustPressed(1, 201) then -- ENTER / A + TriggerServerEvent("BLACKJACK:CheckPlayerBet", g_seat, bet) + local betCheckRecieved = false + local canBet = false + local eventHandler = AddEventHandler("BLACKJACK:BetReceived", function(_canBet) + betCheckRecieved = true + canBet = _canBet + end) + repeat Wait(0) until betCheckRecieved == true + RemoveEventHandler(eventHandler) + if canBet then + hideUi() + if selectedBet < 27 then + if leavingBlackjack == true then leaveBlackjack() return end + local ped = PlayerPedId() + local anim = "place_bet_small" + playerBusy = true + local scene = NetworkCreateSynchronisedScene(g_coords, g_rot, 2, true, false, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(ped, scene, "anim_casino_b@amb@casino@games@blackjack@player", anim, 2.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(scene) + Wait(math.floor(GetAnimDuration("anim_casino_b@amb@casino@games@blackjack@player", anim)*500)) + if leavingBlackjack == true then leaveBlackjack() return end + TriggerServerEvent("BLACKJACK:SetPlayerBet", g_seat, closestChair, bet, selectedBet, false) + Wait(math.floor(GetAnimDuration("anim_casino_b@amb@casino@games@blackjack@player", anim)*500)) + if leavingBlackjack == true then leaveBlackjack() return end + playerBusy = false + local idleVar = "idle_var_0"..math.random(1,5) + DebugPrint("IDLING POsh-BUSY: "..idleVar) + local scene = NetworkCreateSynchronisedScene(g_coords, g_rot, 2, true, true, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(ped, scene, "anim_casino_b@amb@casino@games@shared@player@", idleVar, 2.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(scene) + else + if leavingBlackjack == true then leaveBlackjack() return end + local ped = PlayerPedId() + local anim = "place_bet_large" + playerBusy = true + local scene = NetworkCreateSynchronisedScene(g_coords, g_rot, 2, true, false, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(ped, scene, "anim_casino_b@amb@casino@games@blackjack@player", anim, 2.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(scene) + Wait(math.floor(GetAnimDuration("anim_casino_b@amb@casino@games@blackjack@player", anim)*500)) + if leavingBlackjack == true then leaveBlackjack() return end + TriggerServerEvent("BLACKJACK:SetPlayerBet", g_seat, closestChair, bet, selectedBet, false) + Wait(math.floor(GetAnimDuration("anim_casino_b@amb@casino@games@blackjack@player", anim)*500)) + if leavingBlackjack == true then leaveBlackjack() return end + playerBusy = false + local idleVar = "idle_var_0"..math.random(1,5) + DebugPrint("IDLING POsh-BUSY: "..idleVar) + local scene = NetworkCreateSynchronisedScene(g_coords, g_rot, 2, true, true, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(ped, scene, "anim_casino_b@amb@casino@games@shared@player@", idleVar, 2.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(scene) + end + return + else + QBCore.Functions.Notify('You dont have any casino chips...', 'error', 3500) + end + end + end + end) + end) +end) + +RegisterNetEvent("doj:client:blackjackMenu", function(args) + local ped = PlayerPedId() + local args = tonumber(args) + hideUi() + if args == 1 then + -- print('casino hit') + if leavingBlackjack == true then DebugPrint("returning") return end + TriggerServerEvent("BLACKJACK:ReceivedMove", "hit") + local anim = requestCardAnims[math.random(1,#requestCardAnims)] + playerBusy = true + local scene = NetworkCreateSynchronisedScene(g_coords, g_rot, 2, true, false, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(ped, scene, "anim_casino_b@amb@casino@games@blackjack@player", anim, 2.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(scene) + Wait(math.floor(GetAnimDuration("anim_casino_b@amb@casino@games@blackjack@player", anim)*990)) + if leavingBlackjack == true then leaveBlackjack() return end + playerBusy = false + local idleVar = "idle_var_0"..math.random(1,5) + DebugPrint("IDLING POsh-BUSY: "..idleVar) + local scene = NetworkCreateSynchronisedScene(g_coords, g_rot, 2, true, true, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(ped, scene, "anim_casino_b@amb@casino@games@shared@player@", idleVar, 2.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(scene) + return + elseif args == 2 then + -- print('casino stand') + if leavingBlackjack == true then leaveBlackjack() return end + TriggerServerEvent("BLACKJACK:ReceivedMove", "stand") + local anim = declineCardAnims[math.random(1,#declineCardAnims)] + playerBusy = true + local scene = NetworkCreateSynchronisedScene(g_coords, g_rot, 2, true, false, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(ped, scene, "anim_casino_b@amb@casino@games@blackjack@player", anim, 2.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(scene) + Wait(math.floor(GetAnimDuration("anim_casino_b@amb@casino@games@blackjack@player", anim)*990)) + if leavingBlackjack == true then leaveBlackjack() return end + playerBusy = false + local idleVar = "idle_var_0"..math.random(1,5) + DebugPrint("IDLING POsh-BUSY: "..idleVar) + local scene = NetworkCreateSynchronisedScene(g_coords, g_rot, 2, true, true, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(ped, scene, "anim_casino_b@amb@casino@games@shared@player@", idleVar, 2.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(scene) + return + elseif args == 3 then + -- print('casino double') + if leavingBlackjack == true then leaveBlackjack() return end + TriggerServerEvent("BLACKJACK:CheckPlayerBet", g_seat, bet) + local betCheckRecieved = false + local canBet = false + local eventHandler = AddEventHandler("BLACKJACK:BetReceived", function(_canBet) + betCheckRecieved = true + canBet = _canBet + end) + repeat Wait(0) until betCheckRecieved == true + RemoveEventHandler(eventHandler) + if canBet then + if leavingBlackjack == true then leaveBlackjack() return end + TriggerServerEvent("BLACKJACK:ReceivedMove", "double") + local anim = "place_bet_double_down" + playerBusy = true + local scene = NetworkCreateSynchronisedScene(g_coords, g_rot, 2, true, false, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(ped, scene, "anim_casino_b@amb@casino@games@blackjack@player", anim, 2.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(scene) + Wait(math.floor(GetAnimDuration("anim_casino_b@amb@casino@games@blackjack@player", anim)*500)) + if leavingBlackjack == true then leaveBlackjack() return end + TriggerServerEvent("BLACKJACK:SetPlayerBet", g_seat, closestChair, bet, selectedBet, true) + Wait(math.floor(GetAnimDuration("anim_casino_b@amb@casino@games@blackjack@player", anim)*500)) + if leavingBlackjack == true then leaveBlackjack() return end + playerBusy = false + local idleVar = "idle_var_0"..math.random(1,5) + DebugPrint("IDLING POsh-BUSY: "..idleVar) + local scene = NetworkCreateSynchronisedScene(g_coords, g_rot, 2, true, true, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(ped, scene, "anim_casino_b@amb@casino@games@shared@player@", idleVar, 2.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(scene) + return + else + QBCore.Functions.Notify("You don't have enough casino chips to double down.", "error") + exports['casinoUi']:DrawCasinoUi('show', "Diamond Casino Blackjack

Dealer: "..dealerValue[g_seat].."

Hand: "..handValue(hand)) + TriggerEvent("casino:context:hit&stand") + end + else + -- print('casino split') + if leavingBlackjack == true then leaveBlackjack() return end + TriggerServerEvent("BLACKJACK:CheckPlayerBet", g_seat, bet) + local betCheckRecieved = false + local canBet = false + local eventHandler = AddEventHandler("BLACKJACK:BetReceived", function(_canBet) + betCheckRecieved = true + canBet = _canBet + end) + repeat Wait(0) until betCheckRecieved == true + RemoveEventHandler(eventHandler) + if canBet then + if leavingBlackjack == true then leaveBlackjack() return end + TriggerServerEvent("BLACKJACK:ReceivedMove", "split") + local anim = "place_bet_small_split" + if selectedBet > 27 then + anim = "place_bet_large_split" + end + playerBusy = true + local scene = NetworkCreateSynchronisedScene(g_coords, g_rot, 2, true, false, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(ped, scene, "anim_casino_b@amb@casino@games@blackjack@player", anim, 2.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(scene) + Wait(math.floor(GetAnimDuration("anim_casino_b@amb@casino@games@blackjack@player", anim)*500)) + if leavingBlackjack == true then leaveBlackjack() return end + TriggerServerEvent("BLACKJACK:SetPlayerBet", g_seat, closestChair, bet, selectedBet, false, true) + Wait(math.floor(GetAnimDuration("anim_casino_b@amb@casino@games@blackjack@player", anim)*500)) + if leavingBlackjack == true then leaveBlackjack() return end + playerBusy = false + local idleVar = "idle_var_0"..math.random(1,5) + DebugPrint("IDLING POsh-BUSY: "..idleVar) + local scene = NetworkCreateSynchronisedScene(g_coords, g_rot, 2, true, true, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(ped, scene, "anim_casino_b@amb@casino@games@shared@player@", idleVar, 2.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(scene) + return + else + QBCore.Functions.Notify("You don't have enough casino chips to split.", "error") + exports['casinoUi']:DrawCasinoUi('show', "Diamond Casino Blackjack

Dealer: "..dealerValue[g_seat].."

Hand: "..handValue(hand)) + TriggerEvent("casino:context:hit&stand") + end + end +end) + +RegisterNetEvent("BLACKJACK:RequestMove", function() + exports["qb-core"]:DrawText("Bets closing in 30 seconds...", "top") + if leavingBlackjack == true then + leaveBlackjack() + return + elseif #hand < 3 and #splitHand == 0 then + TriggerEvent("casino:context:hit&doubledown") + exports['casinoUi']:DrawCasinoUi('show', "Diamond Casino Blackjack

Dealer: "..dealerValue[g_seat].."

Hand: "..handValue(hand)) + elseif CanSplitHand(hand) == true then + TriggerEvent("casino:context:hit&split") + exports['casinoUi']:DrawCasinoUi('show', "Diamond Casino Blackjack

Dealer: "..dealerValue[g_seat].."

Hand: "..handValue(hand).."

[Split Hand: "..handValue(splitHand).."]") + elseif leavingBlackjack == false then + TriggerEvent("casino:context:hit&stand") + exports['casinoUi']:DrawCasinoUi('show', "Diamond Casino Blackjack

Dealer: "..dealerValue[g_seat].."

Hand: "..handValue(hand)) + end +end) + +RegisterNetEvent("BLACKJACK:GameEndReaction", function(result) + CreateThread(function() + if #hand == 2 and handValue(hand) == 21 and result == "good" then + QBCore.Functions.Notify("You have BLACKJACK!", "success") + PlaySoundFrontend(-1, "TENNIS_MATCH_POINT", "HUD_AWARDS", 1) + elseif handValue(hand) > 21 and result ~= "good" then + QBCore.Functions.Notify("You BUST", "error", 3500) + PlaySoundFrontend(-1, "ERROR", "HUD_AMMO_SHOP_SOUNDSET", 1) + else + PlaySoundFrontend(-1, "CHALLENGE_UNLOCKED", "HUD_AWARDS", 1) + QBCore.Functions.Notify("You "..resultNames[result].." with the hand: "..handValue(hand)) + end + + hand = {} + splitHand = {} + if leavingBlackjack == true then leaveBlackjack() return end + local anim = "reaction_"..result.."_var_0"..math.random(1,4) + DebugPrint("Reacting: "..anim) + playerBusy = true + local scene = NetworkCreateSynchronisedScene(g_coords, g_rot, 2, false, false, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(PlayerPedId(), scene, "anim_casino_b@amb@casino@games@shared@player@", anim, 2.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(scene) + Wait(math.floor(GetAnimDuration("anim_casino_b@amb@casino@games@shared@player@", anim)*990)) + if leavingBlackjack == true then leaveBlackjack() return end + playerBusy = false + idleVar = "idle_var_0"..math.random(1,5) + local scene = NetworkCreateSynchronisedScene(g_coords, g_rot, 2, true, true, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(PlayerPedId(), scene, "anim_casino_b@amb@casino@games@shared@player@", idleVar, 2.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(scene) + end) +end) + +RegisterNetEvent("BLACKJACK:RetrieveCards", function(i, seat) + DebugPrint("TABLE "..i..": DELETE SEAT ".. seat .." CARDS") + if seat == 0 then + for x,v in pairs(dealerHandObjs[i]) do + DeleteEntity(v) + dealerHandObjs[i][x] = nil + end + else + for x,v in pairs(handObjs[i][seat]) do + DeleteEntity(v) + end + for x,v in pairs(chips[i][5-seat]) do + DeleteEntity(v) + end + end +end) + +RegisterNetEvent("BLACKJACK:UpdateDealerHand", function(i, v) + dealerValue[i] = v +end) + +RegisterNetEvent("BLACKJACK:RetrieveCardsWithAnim", function(i, seat) + DebugPrint("TABLE "..i..": DELETE SEAT ".. seat .." CARDS") + if seat == 0 then + for x,v in pairs(dealerHandObjs[i]) do + AttachEntityToEntity(v, spawnedPeds[i], GetPedBoneIndex(spawnedPeds[i],28422), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 1, 2, 1) + end + while not HasAnimEventFired(spawnedPeds[i],585557868) do + Wait(0) + end + for x,v in pairs(dealerHandObjs[i]) do + DeleteEntity(v) + dealerHandObjs[i][x] = nil + end + else + for x,v in pairs(handObjs[i][seat]) do + AttachEntityToEntity(v, spawnedPeds[i], GetPedBoneIndex(spawnedPeds[i],28422), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 1, 2, 1) + end + while not HasAnimEventFired(spawnedPeds[i],585557868) do + Wait(0) + end + for x,v in pairs(handObjs[i][seat]) do + DeleteEntity(v) + end + for x,v in pairs(chips[i][5-seat]) do + DeleteEntity(v) + end + end +end) + +RegisterNetEvent("BLACKJACK:GiveCard", function(i, seat, handSize, card, flipped, split) + flipped = flipped or false + split = split or false + if i == g_seat and seat == closestChair then + if split == true then + table.insert(splitHand, card) + else + table.insert(hand, card) + end + DebugPrint("GOT CARD "..card.." ("..cardValue(card)..")") + DebugPrint("HAND VALUE "..handValue(hand)) + elseif seat == 0 then + table.insert(dealerHand[i], card) + end + local model = GetHashKey("vw_prop_cas_card_"..card) + RequestModel(model) + repeat Wait(0) until HasModelLoaded(model) + local card = CreateObjectNoOffset(model, tables[i].coords.x, tables[i].coords.y, tables[i].coords.z, false, false, false) + table.insert(spawnedObjects, card) + if seat > 0 then + table.insert(handObjs[i][seat], card) + end + AttachEntityToEntity(card, spawnedPeds[i], GetPedBoneIndex(spawnedPeds[i], 28422), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 1, 2, 1) + Wait(1300) + DetachEntity(card, 0, true) + if seat == 0 then + table.insert(dealerHandObjs[i], card) + SetEntityCoordsNoOffset(card, GetObjectOffsetFromCoords(tables[i].coords.x, tables[i].coords.y, tables[i].coords.z, tables[i].coords.w, cardOffsetsDealer[handSize])) + if flipped == true then + SetEntityRotation(card, 180.0, 0.0, tables[i].coords.w + cardRotationOffsetsDealer[handSize].z) + else + SetEntityRotation(card, 0.0, 0.0, tables[i].coords.w + cardRotationOffsetsDealer[handSize].z) + end + else + if split == true then + SetEntityCoordsNoOffset(card, GetObjectOffsetFromCoords(tables[i].coords.x, tables[i].coords.y, tables[i].coords.z, tables[i].coords.w, cardSplitOffsets[5-seat][handSize])) + SetEntityRotation(card, 0.0, 0.0, tables[i].coords.w + cardSplitRotationOffsets[5-seat][handSize]) + else + SetEntityCoordsNoOffset(card, GetObjectOffsetFromCoords(tables[i].coords.x, tables[i].coords.y, tables[i].coords.z, tables[i].coords.w, cardOffsets[5-seat][handSize])) + SetEntityRotation(card, 0.0, 0.0, tables[i].coords.w + cardRotationOffsets[5-seat][handSize]) + textCoords = GetObjectOffsetFromCoords(tables[i].coords.x, tables[i].coords.y, tables[i].coords.z, tables[i].coords.w, cardOffsets[5-seat][handSize]) + end + end +end) + +function ProcessTables() + RequestAnimDict("anim_casino_b@amb@casino@games@shared@player@") + local alreadyEnteredZone = false + while true do + local sleep = 5 + local inZone = false + if not IsEntityDead(PlayerPedId()) then + for i,v in pairs(tables) do + local cord = v.coords + local highStakes = v.highStakes + if #(GetEntityCoords(PlayerPedId()) - vector3(cord.x, cord.y, cord.z)) < 3.0 then + local pCoords = GetEntityCoords(PlayerPedId()) + + -- local tableObj = GetClosestObjectOfType(pCoords, 1.0, `vw_prop_casino_3cardpoker_01`, false, false, false) + -- if GetEntityCoords(tableObj) == vector3(0.0, 0.0, 0.0) then + -- tableObj = GetClosestObjectOfType(pCoords, 1.0, `vw_prop_casino_3cardpoker_01`, false, false, false) + -- end + + local tableObj = 0 + local TableModels = { + `vw_prop_casino_3cardpoker_01`, + `vw_prop_casino_3cardpoker_01b`, + `vw_prop_casino_blckjack_01`, + `vw_prop_casino_blckjack_01b` + } + for i = 1 , #TableModels do + local model = TableModels[i] + tableObj = GetClosestObjectOfType(pCoords, 1.0, model, false, false, false) + if GetEntityCoords(tableObj) ~= vector3(0.0, 0.0, 0.0) then + break + end + end + + if GetEntityCoords(tableObj) ~= vector3(0.0, 0.0, 0.0) then + closestChair = 1 + local coords = GetWorldPositionOfEntityBone(tableObj, GetEntityBoneIndexByName(tableObj, "Chair_Base_0"..closestChair)) + local rot = GetWorldRotationOfEntityBone(tableObj, GetEntityBoneIndexByName(tableObj, "Chair_Base_0"..closestChair)) + dist = #(pCoords - coords) + for i=1,4 do + local coords = GetWorldPositionOfEntityBone(tableObj, GetEntityBoneIndexByName(tableObj, "Chair_Base_0"..i)) + if #(pCoords - coords) < dist then + dist = dist + closestChair = i + end + end + local coords = GetWorldPositionOfEntityBone(tableObj, GetEntityBoneIndexByName(tableObj, "Chair_Base_0"..closestChair)) + local rot = GetWorldRotationOfEntityBone(tableObj, GetEntityBoneIndexByName(tableObj, "Chair_Base_0"..closestChair)) + g_coords = coords + g_rot = rot + local angle = rot.z-findRotation(coords.x, coords.y, pCoords.x, pCoords.y)+90.0 + local seatAnim = "sit_enter_" + if angle > 0 then seatAnim = "sit_enter_left" end + if angle < 0 then seatAnim = "sit_enter_right" end + if angle > seatSideAngle or angle < -seatSideAngle then seatAnim = seatAnim .. "_side" end + local canSit = true + if canSitDownCallback ~= nil then + canSit = canSitDownCallback() + end + + + if #(pCoords - coords) < 1.7 and not IsSeatOccupied(coords, 0.5) and canSit then + wait = 5 + inZone = true + + if highStakes then + text = "The Diamond Casino & Resort

Blackjack KGV(High-Limit)

Press E to sit" + else + text = "The Diamond Casino & Resort

Blackjack KGV

Press E to sit" + end + + + if IsControlJustPressed(1, 51) then + -- QBCore.Functions.TriggerCallback('QBCore.Functions.HasItem', function(HasItem) + -- if HasItem then + if satDownCallback ~= nil then + satDownCallback() + end + local ped = PlayerPedId() + local initPos = GetAnimInitialOffsetPosition("anim_casino_b@amb@casino@games@shared@player@", seatAnim, coords, rot, 0.01, 2) + local initRot = GetAnimInitialOffsetRotation("anim_casino_b@amb@casino@games@shared@player@", seatAnim, coords, rot, 0.01, 2) + TaskGoStraightToCoord(ped, initPos, 1.0, 5000, initRot.z, 0.01) + repeat Wait(0) until GetScriptTaskStatus(ped, 2106541073) == 7 + Wait(50) + SetPedCurrentWeaponVisible(ped, 0, true, 0, 0) + local scene = NetworkCreateSynchronisedScene(coords, rot, 2, true, true, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(ped, scene, "anim_casino_b@amb@casino@games@shared@player@", seatAnim, 2.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(scene) + local scene = NetworkConvertSynchronisedSceneToSynchronizedScene(scene) + repeat Wait(0) until GetSynchronizedScenePhase(scene) >= 0.99 or HasAnimEventFired(ped, 2038294702) or HasAnimEventFired(ped, -1424880317) + Wait(1000) + idleVar = "idle_cardgames" + scene = NetworkCreateSynchronisedScene(coords, rot, 2, true, true, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(ped, scene, "anim_casino_b@amb@casino@games@shared@player@", "idle_cardgames", 2.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(scene) + repeat Wait(0) until IsEntityPlayingAnim(ped, "anim_casino_b@amb@casino@games@shared@player@", "idle_cardgames", 3) == 1 + g_seat = i + leavingBlackjack = false + TriggerServerEvent("BLACKJACK:PlayerSatDown", i, closestChair) + local endTime = GetGameTimer() + math.floor(GetAnimDuration("anim_casino_b@amb@casino@games@shared@player@", idleVar)*990) + + CreateThread(function() + local startCount = false + local count = 0 + while true do + Wait(0) + SetPauseMenuActive(false) + if leavingBlackjack == true then + startCount = true + end + if startCount == true then + count = count + 1 + end + if count > 100 then + break + end + end + end) + + while true do + Wait(0) + if GetGameTimer() >= endTime then + if playerBusy == true then + while playerBusy == true do + Wait(0) + local playerPed = PlayerPedId() + if IsEntityDead(playerPed) then + TriggerServerEvent("BLACKJACK:PlayerRemove", i) + ClearPedTasks(playerPed) + leaveBlackjack() + break + elseif leaveCheckCallback ~= nil then + if leaveCheckCallback() then + TriggerServerEvent("BLACKJACK:PlayerRemove", i) + ClearPedTasks(playerPed) + leaveBlackjack() + break + end + end + end + end + if leavingBlackjack == false then + idleVar = "idle_var_0"..math.random(1,5) + local scene = NetworkCreateSynchronisedScene(coords, rot, 2, true, true, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(PlayerPedId(), scene, "anim_casino_b@amb@casino@games@shared@player@", idleVar, 2.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(scene) + endTime = GetGameTimer() + math.floor(GetAnimDuration("anim_casino_b@amb@casino@games@shared@player@", idleVar)*990) + end + end + if leavingBlackjack == true then + if standUpCallback ~= nil then + standUpCallback() + end + local scene = NetworkCreateSynchronisedScene(coords, rot, 2, false, false, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(PlayerPedId(), scene, "anim_casino_b@amb@casino@games@shared@player@", "sit_exit_left", 2.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(scene) + TriggerServerEvent("BLACKJACK:PlayerSatUp", i) + Wait(math.floor(GetAnimDuration("anim_casino_b@amb@casino@games@shared@player@", "sit_exit_left")*800)) + ClearPedTasks(PlayerPedId()) + break + else + local playerPed = PlayerPedId() + if IsEntityDead(playerPed) then + TriggerServerEvent("BLACKJACK:PlayerRemove", i) + ClearPedTasks(playerPed) + leaveBlackjack() + if standUpCallback ~= nil then + standUpCallback() + end + break + elseif leaveCheckCallback ~= nil then + if leaveCheckCallback() then + TriggerServerEvent("BLACKJACK:PlayerRemove", i) + ClearPedTasks(playerPed) + leaveBlackjack() + if standUpCallback ~= nil then + standUpCallback() + end + break + end + end + end + end + -- else + -- QBCore.Functions.Notify('You are not a member of the casino', 'error', 3500) + -- end + -- end, 'casino_member') + end + end + end + if inZone and not alreadyEnteredZone then + alreadyEnteredZone = true + exports["qb-core"]:DrawText(text, "top") + end + if not inZone and alreadyEnteredZone then + alreadyEnteredZone = false + hideUi() + end + end + end + end + Wait(sleep) + end +end + + + +CreateThread(function() + if IsModelInCdimage(`vw_prop_casino_3cardpoker_01`) and IsModelInCdimage(`s_f_y_casino_01`) then + CreateThread(ProcessTables) + CreateThread(CreatePeds) + else + ThefeedSetAnimpostfxColor(255, 0, 0, 255) + print("ERROR: This server is missing objects required for casino-blackjack!") + end +end) + +exports("SetSatDownCallback", SetSatDownCallback) +exports("SetStandUpCallback", SetStandUpCallback) +exports("SetLeaveCheckCallback", SetLeaveCheckCallback) +exports("SetCanSitDownCallback", SetCanSitDownCallback) + + +exports["casino-blackjackKGV"]:SetSatDownCallback(function() + exports["qb-core"]:HideText() +end) + +exports["casino-blackjackKGV"]:SetStandUpCallback(function() + hideUi() +end) + +-- exports["casino-blackjack"]:SetLeaveCheckCallback(function() +-- if isDead or isHandcuffed or isEscorted then +-- return true +-- else +-- return false +-- end +-- end) + +-- exports["casino-blackjack"]:SetCanSitDownCallback(function() +-- if not isDead and not isHandcuffed and not isEscorted then +-- return true +-- else +-- return false +-- end +-- end) diff --git a/resources/[qb]/[qb_casino]/casino-blackjackKGV/client/context-menu.lua b/resources/[qb]/[qb_casino]/casino-blackjackKGV/client/context-menu.lua new file mode 100644 index 0000000..a138a3f --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-blackjackKGV/client/context-menu.lua @@ -0,0 +1,90 @@ +RegisterNetEvent('casino:context:hit&stand', function() + exports['qb-menu']:openMenu({ + { + header = "Diamond Casino Blackjack", + isMenuHeader = true, + }, + { + header = "Hit", + txt = "Draw another card", + params = { + event = "doj:client:blackjackMenu", + args = 1 + } + }, + { + header = "Stand", + txt = "Be a pussy", + params = { + event = "doj:client:blackjackMenu", + args = 2 + } + }, + }) +end) + +RegisterNetEvent('casino:context:hit&doubledown', function() + exports['qb-menu']:openMenu({ + { + header = "Diamond Casino Blackjack", + isMenuHeader = true, + }, + { + header = "Hit", + txt = "Draw another card", + params = { + event = "doj:client:blackjackMenu", + args = 1 + } + }, + { + header = "Stand", + txt = "Be a pussy", + params = { + event = "doj:client:blackjackMenu", + args = 2 + } + }, + { + header = "Double Down", + txt = "Double your initial bet", + params = { + event = "doj:client:blackjackMenu", + args = 3 + } + }, + }) +end) + +RegisterNetEvent('casino:context:hit&split', function() + exports['qb-menu']:openMenu({ + { + header = "Diamond Casino Blackjack", + isMenuHeader = true, + }, + { + header = "Hit", + txt = "Draw another card", + params = { + event = "doj:client:blackjackMenu", + args = 1 + } + }, + { + header = "Stand", + txt = "Be a pussy", + params = { + event = "doj:client:blackjackMenu", + args = 2 + } + }, + { + header = "Split", + txt = "Split", + params = { + event = "doj:client:blackjackMenu", + args = 4 + } + }, + }) +end) diff --git a/resources/[qb]/[qb_casino]/casino-blackjackKGV/coords.lua b/resources/[qb]/[qb_casino]/casino-blackjackKGV/coords.lua new file mode 100644 index 0000000..0b4e967 --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-blackjackKGV/coords.lua @@ -0,0 +1,880 @@ + +_DEBUG = false + +function DebugPrint(str) + if _DEBUG == true and str then + return print("BLACKJACK: "..tostring(str)) + end +end + +ChackCardPropAnim = true + +tables = { -- Spawns Ped Only -- Setup Currently For This https://forum.cfx.re/t/cayo-perico-casino-dlc-ipl-loader/2099391 + -- { + -- coords = vector4(996.17, 51.69, 68.45, 318.91), --Table 1 -- poker + -- highStakes = false + -- }, + -- { + -- coords = vector4(1000.66, 50.88, 68.45, 6.73), --Table 2 -- poker + -- highStakes = false + -- }, + { + coords = vector4(1004.09, 53.11, 68.45, 55.30), --Table 3 + highStakes = false + }, + { + coords = vector4(1002.36, 60.52, 68.45, 142.53), --Table 4 + highStakes = false + }, + -- { + -- coords = vector4(998.44, 60.99, 68.45, 191.77), --Table 5 -- poker + -- highStakes = false + -- }, + -- { + -- coords = vector4(994.89, 58.29, 68.45, 237.13), --Table 6 -- poker + -- highStakes = false + -- }, + -- { + -- coords = vector4(985.95, 60.56, 69.25, 187.91), --Table 7 --USED BY Rubbertoe98 Blackjack + -- highStakes = true + -- }, + -- { + -- coords = vector4(982.50, 62.85, 69.25, 101.44), --Table 8 -USED BY Rubbertoe98 Blackjack + -- highStakes = true + -- }, + -- { + -- coords = vector4(984.97, 66.64, 69.25, 3.39), --Table 9 -- poker + -- highStakes = true + -- }, + -- { + -- coords = vector4(988.45, 64.38, 69.25, 278.52), --Table 10 -- poker + -- highStakes = true + -- }, + -- { + -- coords = vector4(989.05, 45.69, 69.25, 22.31), --Table 11 -USED BY Rubbertoe98 Blackjack + -- highStakes = true + -- }, + -- { + -- coords = vector4(987.30, 42.19, 69.25, 102.30), --Table 12 -USED BY Rubbertoe98 Blackjack + -- highStakes = true + -- }, + -- { + -- coords = vector4(991.56, 40.10, 69.25, 200.84), --Table 13 -- poker + -- highStakes = true + -- }, + -- { + -- coords = vector4(993.20, 43.70, 69.25, 279.25), --Table 14 -- poker + -- highStakes = true + -- }, +} + +--[[ + Table colors + 0: Green + 1: Red + 2: Blue + 3: Purple +--]] + +customTables = { -- Spawns ped with table, example below +{ + coords = vector4(967.33, 32.0191, 115.1742, -122.0), + highStakes = false, + color = 1, +}, + -- { + -- coords = vector4(996.77, 52.43, 68.43, 318.91), + -- highStakes = false, + -- color = 0 + -- }, +} + +for i,v in pairs(customTables) do + table.insert(tables, v) +end + +resultNames = { + ["good"] = "WON", + ["bad"] = "BUST", + ["impartial"] = "Got a PUSH", +} + +requestCardAnims = { + "request_card", + "request_card_alt1", + "request_card_alt2", +} +declineCardAnims = { + "decline_card_001", + "decline_card_alt1", + "decline_card_alt2", +} + +cardOffsets = { + [1] = { + vector3(0.5737, 0.2376, 0.948025), + vector3(0.562975, 0.2523, 0.94875), + vector3(0.553875, 0.266325, 0.94955), + vector3(0.5459, 0.282075, 0.9501), + vector3(0.536125, 0.29645, 0.95085), + vector3(0.524975, 0.30975, 0.9516), + vector3(0.515775, 0.325325, 0.95235), + }, + + [2] = { + vector3(0.2325, -0.1082, 0.94805), + vector3(0.23645, -0.0918, 0.949), + vector3(0.2401, -0.074475, 0.950225), + vector3(0.244625, -0.057675, 0.951125), + vector3(0.249675, -0.041475, 0.95205), + vector3(0.257575, -0.0256, 0.9532), + vector3(0.2601, -0.008175, 0.954375), + }, + + [3] = { + vector3(-0.2359, -0.1091, 0.9483), + vector3(-0.221025, -0.100675, 0.949), + vector3(-0.20625, -0.092875, 0.949725), + vector3(-0.193225, -0.07985, 0.950325), + vector3(-0.1776, -0.072, 0.951025), + vector3(-0.165, -0.060025, 0.951825), + vector3(-0.14895, -0.05155, 0.95255), + }, + + [4] = { + vector3(-0.5765, 0.2229, 0.9482), + vector3(-0.558925, 0.2197, 0.949175), + vector3(-0.5425, 0.213025, 0.9499), + vector3(-0.525925, 0.21105, 0.95095), + vector3(-0.509475, 0.20535, 0.9519), + vector3(-0.491775, 0.204075, 0.952825), + vector3(-0.4752, 0.197525, 0.9543), + } +} + +cardRotationOffsets = { + [1] = { + 69.12, + 67.8, + 66.6, + 70.44, + 70.84, + 67.88, + 69.56, + }, + + [2] = { + 22.11, + 22.32, + 20.8, + 19.8, + 19.44, + 26.28, + 22.68, + }, + + [3] = { + -21.43, + -20.16, + -16.92, + -23.4, + -21.24, + -23.76, + -19.44, + }, + + [4] = { + -67.03, + -69.12, + -64.44, + -67.68, + -63.72, + -68.4, + -64.44, + }, +} + +cardSplitOffsets = { + + [1] = { + vector3(0.6083, 0.3523, 0.94795), + vector3(0.598475, 0.366475, 0.948925), + vector3(0.589525, 0.3807, 0.94975), + vector3(0.58045, 0.39435, 0.950375), + vector3(0.571975, 0.4092, 0.951075), + vector3(0.5614, 0.4237, 0.951775), + vector3(0.554325, 0.4402, 0.952525), + }, + + [2] = { + vector3(0.3431, -0.0527, 0.94855), + vector3(0.348575, -0.0348, 0.949425), + vector3(0.35465, -0.018825, 0.9502), + vector3(0.3581, -0.001625, 0.95115), + vector3(0.36515, 0.015275, 0.952075), + vector3(0.368525, 0.032475, 0.95335), + vector3(0.373275, 0.0506, 0.9543), + }, + + [3] = { + vector3(-0.116, -0.1501, 0.947875), + vector3(-0.102725, -0.13795, 0.948525), + vector3(-0.08975, -0.12665, 0.949175), + vector3(-0.075025, -0.1159, 0.949875), + vector3(-0.0614, -0.104775, 0.9507), + vector3(-0.046275, -0.095025, 0.9516), + vector3(-0.031425, -0.0846, 0.952675), + }, + + [4] = { + vector3(-0.5205, 0.1122, 0.9478), + vector3(-0.503175, 0.108525, 0.94865), + vector3(-0.485125, 0.10475, 0.949175), + vector3(-0.468275, 0.099175, 0.94995), + vector3(-0.45155, 0.09435, 0.95085), + vector3(-0.434475, 0.089725, 0.95145), + vector3(-0.415875, 0.0846, 0.9523), + } +} + +cardSplitRotationOffsets = { + [1] = { + 68.57, + 67.52, + 67.76, + 67.04, + 68.84, + 65.96, + 67.76, + }, + + [2] = { + 22.11, + 22.0, + 24.44, + 21.08, + 25.96, + 26.16, + 28.76, + }, + + [3] = { + -14.04, + -15.48, + -16.56, + -15.84, + -16.92, + -14.4, + -14.28, + }, + + [4] = { + -67.03, + -67.6, + -69.4, + -69.04, + -68.68, + -66.16, + -63.28, + }, +} + +cardOffsetsDealer = { + vector3(0.0436, 0.21205, 0.948875), + vector3(-0.0636, 0.213825, 0.9496), + vector3(-0.0806, 0.2137, 0.950225), + vector3(-0.1006, 0.21125, 0.950875), + vector3(-0.1256, 0.21505, 0.951875), + vector3(-0.1416, 0.21305, 0.953), + vector3(-0.1656, 0.21205, 0.954025), + vector3(-0.1836, 0.21255, 0.95495), + vector3(-0.2076, 0.21105, 0.956025), + vector3(-0.2246, 0.21305, 0.957), +} + +cardRotationOffsetsDealer = { -- doesnt have to be vector3 + vector3(0.0, 0.0, 178.92), + vector3(0.0, 0.0, -180.0), + vector3(0.0, 0.0, -178.92), + vector3(0.0, 0.0, -177.12), + vector3(0.0, 0.0, 180.0), + vector3(0.0, 0.0, 178.56), + vector3(0.0, 0.0, 180.0), + vector3(0.0, 0.0, 178.2), + vector3(0.0, 0.0, -177.12), + vector3(0.0, 0.0, 180.0), + vector3(0.0, 0.0, 178.56), +} + +chipSplitOffsets = { + [1] = { + vector3(0.6931, 0.1952, 0.0), + vector3(0.724925, 0.26955, 0.0), + vector3(0.7374, 0.349625, 0.0), + vector3(0.76415, 0.419225, 0.0), + }, + + [2] = { + vector3(0.2827, -0.227825, 0.0), + vector3(0.3605, -0.1898, 0.0), + vector3(0.4309, -0.16365, 0.0), + vector3(0.49275, -0.111575, 0.0), + }, + + [3] = { + vector3(-0.279425, -0.2238, 0.0), + vector3(-0.200775, -0.25855, 0.0), + vector3(-0.125775, -0.26815, 0.0), + vector3(-0.05615, -0.29435, 0.0), + }, + + [4] = { + vector3(-0.685925, 0.173275, 0.0), + vector3(-0.6568, 0.092525, 0.0), + vector3(-0.612875, 0.033025, 0.0), + vector3(-0.58465, -0.0374, 0.0), + } +} + + +chipSplitRotationOffsets = { -- doesnt have to be vector3 + [1] = { + vector3(0, 0, -16.56), + vector3(0, 0, -22.32), + vector3(0, 0, -10.8), + vector3(0, 0, -9.72), + }, + + [2] = { + vector3(0, 0, -69.12), + vector3(0, 0, -64.8), + vector3(0, 0, -58.68), + vector3(0, 0, -51.12), + }, + + [3] = { + vector3(0, 0, -112.32), + vector3(0, 0, -108.36), + vector3(0, 0, -99.72), + vector3(0, 0, -102.6), + }, + + [4] = { + vector3(0, 0, -155.88), + vector3(0, 0, -151.92), + vector3(0, 0, -147.24), + vector3(0, 0, -146.52), + } +} + +chipOffsets = { + [1] = { + vector3(0.712625, 0.170625, 0.0), + vector3(0.6658, 0.218375, 0.0), + vector3(0.756775, 0.292775, 0.0), + vector3(0.701875, 0.3439, 0.0), + }, + + [2] = { + vector3(0.278125, -0.2571, 0.0), + vector3(0.280375, -0.190375, 0.0), + vector3(0.397775, -0.208525, 0.0), + vector3(0.39715, -0.1354, 0.0), + }, + + [3] = { + vector3(-0.30305, -0.2464, 0.0), + vector3(-0.257975, -0.19715, 0.0), + vector3(-0.186575, -0.2861, 0.0), + vector3(-0.141675, -0.237925, 0.0), + }, + + [4] = { + vector3(-0.72855, 0.17345, 0.0), + vector3(-0.652825, 0.177525, 0.0), + vector3(-0.6783, 0.0744, 0.0), + vector3(-0.604425, 0.082575, 0.0), + } +} + +pileOffsets = { + [1] = { + vector3(0.61, -0.02, 0.0), + vector3(0.73, 0.47, 0.0), + vector3(0.756775, 0.292775, 0.0), + vector3(0.701875, 0.3439, 0.0), + }, + + [2] = { + vector3(0.03, -0.29, 0.0), + vector3(0.05, -0.06, 0.0), + vector3(0.397775, -0.208525, 0.0), + vector3(0.39715, -0.1354, 0.0), + }, + + [3] = { + vector3(-0.48, -0.13, 0.0), + vector3(-0.35, 0.07, 0.0), + vector3(-0.186575, -0.2861, 0.0), + vector3(-0.141675, -0.237925, 0.0), + }, + + [4] = { + vector3(-0.7, 0.32, 0.0), + vector3(-0.7, 0.53, 0.0), + vector3(-0.6783, 0.0744, 0.0), + vector3(-0.604425, 0.082575, 0.0), + } +} + +chipRotationOffsets = { -- doesnt have to be vector3 + [1] = { + vector3(0, 0, 72.0), + vector3(0, 0, 64.8), + vector3(0, 0, 74.52), + vector3(0, 0, 72.0), + }, + + [2] = { + vector3(0, 0, 12.96), + vector3(0, 0, 29.16), + vector3(0, 0, 32.04), + vector3(0, 0, 32.04), + }, + + [3] = { + vector3(0, 0, -18.36), + vector3(0, 0, -18.72), + vector3(0, 0, -15.48), + vector3(0, 0, -18.0), + }, + + [4] = { + vector3(0, 0, -79.2), + vector3(0, 0, -68.76), + vector3(0, 0, -57.6), + vector3(0, 0, -64.8), + } +} + +pileRotationOffsets = { -- doesnt have to be vector3 + [1] = { + vector3(0, 0, 8.1), + vector3(0, 0, 229.49), + vector3(0, 0, 74.52), + vector3(0, 0, 72.0), + }, + + [2] = { + vector3(0, 0, 78.7), + vector3(0, 0, 4.6), + vector3(0, 0, 32.04), + vector3(0, 0, 32.04), + }, + + [3] = { + vector3(0, 0, 44.89), + vector3(0, 0, 144.49), + vector3(0, 0, -15.48), + vector3(0, 0, -18.0), + }, + + [4] = { + vector3(0, 0, 15.6), + vector3(0, 0, 15.6), + vector3(0, 0, -57.6), + vector3(0, 0, -64.8), + } +} + +lowTableLimit = 40 +bettingNums = { + 10, + 20, + 30, + 40, + 50, + 60, + 70, + 80, + 90, + 100, + 150, + 200, + 250, + 300, + 350, + 400, + 450, + 500, + 1000, + 1500, + 2000, + 2500, + 3000, + 3500, + 4000, + 4500, + 5000, + 6000, + 7000, + 8000, + 9000, + 10000, + 15000, + 20000, + 25000, + 30000, + 35000, + 40000, + 45000, + 50000, -- 40 Low table limit, betting numbers added after this will be high stakes only + 55000, + 60000, + 65000, + 70000, + 75000, + 80000, + 85000, + 90000, + 95000, + 100000, + 150000, + 200000, + 250000, + 300000, + 350000, + 400000, + 450000, + 500000, + 550000, + 600000, + 650000, + 700000, + 750000, + 800000, + 850000, + 900000, + 950000, + 1000000, + 1500000, + 2000000, + 2500000, + 3000000, + 3500000, + 4000000, + 4500000, + 5000000, + 5500000, + 6000000, + 6500000, + 7000000, + 7500000, + 8000000, + 8500000, + 9000000, + 9500000, + 10000000, +} + +bettingTime = 50 +moveTime = 30 +--[[ + case 10: + return joaat("vw_prop_chip_10dollar_x1"); + case 50: + return joaat("vw_prop_chip_50dollar_x1"); + case 100: + return joaat("vw_prop_chip_100dollar_x1"); + case 500: + return joaat("vw_prop_chip_500dollar_x1"); + case 1000: + return joaat("vw_prop_chip_1kdollar_x1"); + case 5000: + return joaat("vw_prop_plaq_5kdollar_x1"); + case 10000: + return joaat("vw_prop_plaq_10kdollar_x1"); + + vw_prop_chip_10kdollar_x1.ydr + vw_prop_chip_5kdollar_x1.ydr + vw_prop_chip_10kdollar_st.ydr -- $120,000 + vw_prop_chip_5kdollar_st.ydr -- $60,000 + vw_prop_vw_chips_pile_01a.ydr -- $511,000 + vw_prop_vw_chips_pile_02a.ydr -- $3,250,000 + vw_prop_vw_chips_pile_03a.ydr -- $1,990,000 + +--]] + +chipModels = { + [10] = "vw_prop_chip_10dollar_x1", + [50] = "vw_prop_chip_50dollar_x1", + [100] = "vw_prop_chip_100dollar_x1", + [120] = "vw_prop_chip_10dollar_st", + [500] = "vw_prop_chip_500dollar_x1", + [600] = "vw_prop_chip_50dollar_st", + [1000] = "vw_prop_chip_1kdollar_x1", + [1200] = "vw_prop_chip_100dollar_st", + [5000] = "vw_prop_chip_5kdollar_x1", + [6000] = "vw_prop_chip_500dollar_st", + [10000] = "vw_prop_chip_10kdollar_x1", + [60000] = "vw_prop_chip_5kdollar_st", + [120000] = "vw_prop_chip_10kdollar_st", +} + +chipValues = { + 10, + 50, + 100, + 120, + 500, + 600, + 1000, + 1200, + 5000, + 6000, + 10000, + 60000, + 120000, +} + +chipThickness = { + [`vw_prop_chip_10dollar_x1`] = 0.0054170000366867, + [`vw_prop_chip_50dollar_x1`] = 0.0056730001233518, + [`vw_prop_chip_100dollar_x1`] = 0.0056900000199676, + [`vw_prop_chip_10dollar_st`] = 0.061421997845173, + [`vw_prop_chip_500dollar_x1`] = 0.0057030003517866, + [`vw_prop_chip_50dollar_st`] = 0.061151999980211, + [`vw_prop_chip_1kdollar_x1`] = 0.0056910002604127, + [`vw_prop_chip_100dollar_st`] = 0.060908999294043, + [`vw_prop_chip_500dollar_st`] = 0.060989998281002, + + [`vw_prop_chip_5kdollar_x1`] = 0.005538, + [`vw_prop_chip_10kdollar_x1`] = 0.005562, + [`vw_prop_chip_5kdollar_st`] = 0.061049, + [`vw_prop_chip_10kdollar_st`] = 0.06095, + [`vw_prop_plaq_5kdollar_x1`] = 0.006891000084579, + [`vw_prop_plaq_10kdollar_x1`] = 0.0057889996096492, + [`vw_prop_plaq_5kdollar_st`] = 0.060949999839067, + [`vw_prop_plaq_10kdollar_st`] = 0.060749001801014, +} + +chipHeights = { -- whatever the fuck this even means + 0.95, + 0.896, + 0.901, + 0.907, + 0.95, + 0.917, + 0.922, + 0.927, + 0.932, + 0.95, + 0.904, + 0.899, + 0.914, + 0.904, + 0.924, + 0.91, + 0.935, + 0.95, + 0.95, + 0.904, + 0.899, + 0.915, + 0.904, + 0.925, + 0.91, + 0.935, + 0.95, + 0.919, + 0.924, + 0.93, + 0.935, + 0.95, + 0.902, + 0.897, + 0.912, + 0.902, + 0.922, + 0.907, + 0.932, + 0.912, +} + +function SetDealerOutfit(ped, outfit) + local outfit = (outfit % 13) or math.random(0, 13) + + SetPedDefaultComponentVariation(ped) + + if outfit == 0 then + SetPedComponentVariation(ped, 0, 3, 0, 0) + SetPedComponentVariation(ped, 1, 1, 0, 0) + SetPedComponentVariation(ped, 2, 3, 0, 0) + SetPedComponentVariation(ped, 3, 1, 0, 0) + SetPedComponentVariation(ped, 4, 0, 0, 0) + SetPedComponentVariation(ped, 6, 1, 0, 0) + SetPedComponentVariation(ped, 7, 2, 0, 0) + SetPedComponentVariation(ped, 8, 3, 0, 0) + SetPedComponentVariation(ped, 10, 1, 0, 0) + SetPedComponentVariation(ped, 11, 1, 0, 0) + return + elseif outfit == 1 then + SetPedComponentVariation(ped, 0, 2, 2, 0) + SetPedComponentVariation(ped, 1, 1, 0, 0) + SetPedComponentVariation(ped, 2, 4, 0, 0) + SetPedComponentVariation(ped, 3, 0, 3, 0) + SetPedComponentVariation(ped, 4, 0, 0, 0) + SetPedComponentVariation(ped, 6, 1, 0, 0) + SetPedComponentVariation(ped, 7, 2, 0, 0) + SetPedComponentVariation(ped, 8, 1, 0, 0) + SetPedComponentVariation(ped, 10, 1, 0, 0) + SetPedComponentVariation(ped, 11, 1, 0, 0) + return + elseif outfit == 2 then + SetPedComponentVariation(ped, 0, 2, 1, 0) + SetPedComponentVariation(ped, 1, 1, 0, 0) + SetPedComponentVariation(ped, 2, 2, 0, 0) + SetPedComponentVariation(ped, 3, 0, 3, 0) + SetPedComponentVariation(ped, 4, 0, 0, 0) + SetPedComponentVariation(ped, 6, 1, 0, 0) + SetPedComponentVariation(ped, 7, 2, 0, 0) + SetPedComponentVariation(ped, 8, 1, 0, 0) + SetPedComponentVariation(ped, 10, 1, 0, 0) + SetPedComponentVariation(ped, 11, 1, 0, 0) + return + elseif outfit == 3 then + SetPedComponentVariation(ped, 0, 2, 0, 0) + SetPedComponentVariation(ped, 1, 1, 0, 0) + SetPedComponentVariation(ped, 2, 3, 0, 0) + SetPedComponentVariation(ped, 3, 1, 3, 0) + SetPedComponentVariation(ped, 4, 0, 0, 0) + SetPedComponentVariation(ped, 6, 1, 0, 0) + SetPedComponentVariation(ped, 7, 2, 0, 0) + SetPedComponentVariation(ped, 8, 3, 0, 0) + SetPedComponentVariation(ped, 10, 1, 0, 0) + SetPedComponentVariation(ped, 11, 1, 0, 0) + return + elseif outfit == 4 then + SetPedComponentVariation(ped, 0, 4, 2, 0) + SetPedComponentVariation(ped, 1, 1, 0, 0) + SetPedComponentVariation(ped, 2, 3, 0, 0) + SetPedComponentVariation(ped, 3, 0, 0, 0) + SetPedComponentVariation(ped, 4, 0, 0, 0) + SetPedComponentVariation(ped, 6, 1, 0, 0) + SetPedComponentVariation(ped, 7, 2, 0, 0) + SetPedComponentVariation(ped, 8, 1, 0, 0) + SetPedComponentVariation(ped, 10, 1, 0, 0) + SetPedComponentVariation(ped, 11, 1, 0, 0) + return + elseif outfit == 5 then + SetPedComponentVariation(ped, 0, 4, 0, 0) + SetPedComponentVariation(ped, 1, 1, 0, 0) + SetPedComponentVariation(ped, 2, 0, 0, 0) + SetPedComponentVariation(ped, 3, 0, 0, 0) + SetPedComponentVariation(ped, 4, 0, 0, 0) + SetPedComponentVariation(ped, 6, 1, 0, 0) + SetPedComponentVariation(ped, 7, 2, 0, 0) + SetPedComponentVariation(ped, 8, 1, 0, 0) + SetPedComponentVariation(ped, 10, 1, 0, 0) + SetPedComponentVariation(ped, 11, 1, 0, 0) + return + elseif outfit == 6 then + SetPedComponentVariation(ped, 0, 4, 1, 0) + SetPedComponentVariation(ped, 1, 1, 0, 0) + SetPedComponentVariation(ped, 2, 4, 0, 0) + SetPedComponentVariation(ped, 3, 1, 0, 0) + SetPedComponentVariation(ped, 4, 0, 0, 0) + SetPedComponentVariation(ped, 6, 1, 0, 0) + SetPedComponentVariation(ped, 7, 2, 0, 0) + SetPedComponentVariation(ped, 8, 3, 0, 0) + SetPedComponentVariation(ped, 10, 1, 0, 0) + SetPedComponentVariation(ped, 11, 1, 0, 0) + return + elseif outfit == 7 then + SetPedComponentVariation(ped, 0, 1, 1, 0) + SetPedComponentVariation(ped, 1, 0, 0, 0) + SetPedComponentVariation(ped, 2, 1, 0, 0) + SetPedComponentVariation(ped, 3, 0, 3, 0) + SetPedComponentVariation(ped, 4, 0, 0, 0) + SetPedComponentVariation(ped, 6, 0, 0, 0) + SetPedComponentVariation(ped, 7, 0, 0, 0) + SetPedComponentVariation(ped, 8, 0, 0, 0) + SetPedComponentVariation(ped, 10, 0, 0, 0) + SetPedComponentVariation(ped, 11, 0, 0, 0) + return + elseif outfit == 8 then + SetPedComponentVariation(ped, 0, 1, 1, 0) + SetPedComponentVariation(ped, 1, 0, 0, 0) + SetPedComponentVariation(ped, 2, 1, 1, 0) + SetPedComponentVariation(ped, 3, 1, 3, 0) + SetPedComponentVariation(ped, 4, 0, 0, 0) + SetPedComponentVariation(ped, 6, 0, 0, 0) + SetPedComponentVariation(ped, 7, 2, 0, 0) + SetPedComponentVariation(ped, 8, 1, 0, 0) + SetPedComponentVariation(ped, 10, 0, 0, 0) + SetPedComponentVariation(ped, 11, 0, 0, 0) + return + elseif outfit == 9 then + SetPedComponentVariation(ped, 0, 2, 0, 0) + SetPedComponentVariation(ped, 1, 0, 0, 0) + SetPedComponentVariation(ped, 2, 2, 0, 0) + SetPedComponentVariation(ped, 3, 2, 3, 0) + SetPedComponentVariation(ped, 4, 0, 0, 0) + SetPedComponentVariation(ped, 6, 0, 0, 0) + SetPedComponentVariation(ped, 7, 0, 0, 0) + SetPedComponentVariation(ped, 8, 2, 0, 0) + SetPedComponentVariation(ped, 10, 0, 0, 0) + SetPedComponentVariation(ped, 11, 0, 0, 0) + return + elseif outfit == 10 then + SetPedComponentVariation(ped, 0, 2, 1, 0) + SetPedComponentVariation(ped, 1, 0, 0, 0) + SetPedComponentVariation(ped, 2, 2, 1, 0) + SetPedComponentVariation(ped, 3, 3, 3, 0) + SetPedComponentVariation(ped, 4, 1, 0, 0) + SetPedComponentVariation(ped, 6, 1, 0, 0) + SetPedComponentVariation(ped, 7, 2, 0, 0) + SetPedComponentVariation(ped, 8, 3, 0, 0) + SetPedComponentVariation(ped, 10, 0, 0, 0) + SetPedComponentVariation(ped, 11, 0, 0, 0) + return + elseif outfit == 11 then + SetPedComponentVariation(ped, 0, 3, 0, 0) + SetPedComponentVariation(ped, 1, 0, 0, 0) + SetPedComponentVariation(ped, 2, 3, 0, 0) + SetPedComponentVariation(ped, 3, 0, 1, 0) + SetPedComponentVariation(ped, 4, 1, 0, 0) + SetPedComponentVariation(ped, 6, 1, 0, 0) + SetPedComponentVariation(ped, 7, 1, 0, 0) + SetPedComponentVariation(ped, 8, 0, 0, 0) + SetPedComponentVariation(ped, 10, 0, 0, 0) + SetPedComponentVariation(ped, 11, 0, 0, 0) + SetPedPropIndex(ped, 1, 0, 0, false) + return + elseif outfit == 12 then + SetPedComponentVariation(ped, 0, 3, 1, 0) + SetPedComponentVariation(ped, 1, 0, 0, 0) + SetPedComponentVariation(ped, 2, 3, 1, 0) + SetPedComponentVariation(ped, 3, 1, 1, 0) + SetPedComponentVariation(ped, 4, 1, 0, 0) + SetPedComponentVariation(ped, 6, 1, 0, 0) + SetPedComponentVariation(ped, 7, 2, 0, 0) + SetPedComponentVariation(ped, 8, 1, 0, 0) + SetPedComponentVariation(ped, 10, 0, 0, 0) + SetPedComponentVariation(ped, 11, 0, 0, 0) + return + elseif outfit == 13 then + SetPedComponentVariation(ped, 0, 4, 0, 0) + SetPedComponentVariation(ped, 1, 0, 0, 0) + SetPedComponentVariation(ped, 2, 4, 0, 0) + SetPedComponentVariation(ped, 3, 2, 1, 0) + SetPedComponentVariation(ped, 4, 1, 0, 0) + SetPedComponentVariation(ped, 6, 1, 0, 0) + SetPedComponentVariation(ped, 7, 1, 0, 0) + SetPedComponentVariation(ped, 8, 2, 0, 0) + SetPedComponentVariation(ped, 10, 0, 0, 0) + SetPedComponentVariation(ped, 11, 0, 0, 0) + SetPedPropIndex(ped, 1, 0, 0, false) + return + end +end diff --git a/resources/[qb]/[qb_casino]/casino-blackjackKGV/fxmanifest.lua b/resources/[qb]/[qb_casino]/casino-blackjackKGV/fxmanifest.lua new file mode 100644 index 0000000..67df964 --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-blackjackKGV/fxmanifest.lua @@ -0,0 +1,20 @@ +fx_version 'cerulean' +game "gta5" + +name 'kgv-blackjack' +description 'Playable Blackjack at the casino, similar to GTAOnline.' +author 'Xinerki - https://github.com/Xinerki/' +url 'https://github.com/Xinerki/kgv-blackjack' + +shared_scripts { + 'coords.lua', +} + +client_scripts{ + 'client/*.lua' +} + +server_scripts{ + 'server/*.lua' +} + diff --git a/resources/[qb]/[qb_casino]/casino-blackjackKGV/server/server.lua b/resources/[qb]/[qb_casino]/casino-blackjackKGV/server/server.lua new file mode 100644 index 0000000..77ee443 --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-blackjackKGV/server/server.lua @@ -0,0 +1,965 @@ +local QBCore = exports['qb-core']:GetCoreObject() + +-- =========================================== + +ranks = {'02', '03', '04', '05', '06', '07', '08', '09', '10', --[['11',]] 'JACK', 'QUEEN', 'KING', 'ACE'} +suits = {'SPD', 'HRT', 'DIA', 'CLUB'} + +function shuffle(tbl) + for i = #tbl, 2, -1 do + local j = math.random(i) + tbl[i], tbl[j] = tbl[j], tbl[i] + end + return tbl +end + +function getDeck() + local tDeck = {} + for _,rank in pairs(ranks) do + for _,suit in pairs(suits) do + table.insert(tDeck, suit .. "_" .. rank) + end + end + return shuffle(tDeck) +end + +function takeCard(tDeck) + return table.remove(tDeck, math.random(1,#tDeck)) +end + +function cardValue(card) + local rank = 10 + for i=2,11 do + if string.find(card, tostring(i)) then + rank = i + end + end + if string.find(card, 'ACE') then + rank = 11 + end + + return rank +end + +function handValue(hand) + local tmpValue = 0 + local numAces = 0 + + for i,v in pairs(hand) do + tmpValue = tmpValue + cardValue(v) + end + + for i,v in pairs(hand) do + if string.find(v, 'ACE') then numAces = numAces + 1 end + end + + repeat + if tmpValue > 21 and numAces > 0 then + tmpValue = tmpValue - 10 + numAces = numAces - 1 + else + break + end + until numAces == 0 + + return tmpValue +end + +players = {} +timeTracker = {} +tableTracker = {} + + +getChipsCallback = nil +takeChipsCallback = nil +giveChipsCallback = nil + +function FindPlayerIdx(tbl, src) + + for i = 1, #tbl do + if tbl[i].player == src then + return i + end + end + + return nil +end + +function SetGetChipsCallback(cb) + getChipsCallback = cb +end + +function SetTakeChipsCallback(cb) + takeChipsCallback = cb +end + +function SetGiveChipsCallback(cb) + giveChipsCallback = cb +end + +function GiveMoney(player, money) + if giveChipsCallback ~= nil then + giveChipsCallback(player, tonumber(money)) + end +end + +function TakeMoney(player, money) + if takeChipsCallback ~= nil then + takeChipsCallback(player, tonumber(money)) + end +end + +function HaveAllPlayersBetted(table) + for i,v in pairs(table) do + if v.bet < 1 then + return false + end + end + return true +end + +function ArePlayersStillIn(table) + for i,v in pairs(table) do + if v.player_in == true then + return true + end + end + return false +end + +function PlayDealerAnim(dealer, animDict, anim) + TriggerClientEvent("BLACKJACK:PlayDealerAnim", -1, dealer, animDict, anim) +end + +function PlayDealerSpeech(dealer, speech) + TriggerClientEvent("BLACKJACK:PlayDealerSpeech", -1, dealer, speech) +end + +function SetPlayerBet(i, seat, bet, betId, double, split) + split = split or false + double = double or false + + + local num = FindPlayerIdx(players[i], source) + + if num ~= nil then + if double == false and split == false then + TakeMoney(source, bet) + + players[i][num].bet = tonumber(bet) + end + + TriggerClientEvent("BLACKJACK:PlaceBetChip", -1, i, 5-seat, bet, double, split) + else + DebugPrint("TABLE "..i..": PLAYER "..source.." ATTEMPTED BET BUT NO LONGER TRACKED?") + end +end + +RegisterServerEvent("BLACKJACK:SetPlayerBet") +AddEventHandler('BLACKJACK:SetPlayerBet', SetPlayerBet) + +function CheckPlayerBet(i, bet) + local Player = QBCore.Functions.GetPlayer(source) + local ItemList = { + ["casino_redchip"] = 1, + } + local playerChips = Player.Functions.GetItemByName("casino_redchip") + local canBet = false + if Player.PlayerData.items ~= nil and next(Player.PlayerData.items) ~= nil then + for k, v in pairs(Player.PlayerData.items) do + if Player.PlayerData.items[k] ~= nil then + if ItemList[Player.PlayerData.items[k].name] ~= nil then + if playerChips.amount >= bet then + canBet = true + end + end + end + end + end + TriggerClientEvent("BLACKJACK:BetReceived", source, canBet) +end + +RegisterServerEvent("BLACKJACK:CheckPlayerBet") +AddEventHandler("BLACKJACK:CheckPlayerBet", CheckPlayerBet) + +function SortPlayers(pTable) + local temp + for i=1,#pTable-1 do + for j=i+1,#pTable do + if pTable[i].seat < pTable[j].seat then + temp = pTable[i] + pTable[i] = pTable[j] + pTable[j] = temp + end + end + end + return pTable +end + +RegisterServerEvent("BLACKJACK:ReceivedMove") + +function StartTableThread(i) + Citizen.CreateThread(function() + local index = i + -- DebugPrint(index) + while true do Wait(0) + if players[index] and #players[index] ~= 0 then + DebugPrint("WAITING FOR ALL PLAYERS AT TABLE "..index.." TO PLACE THEIR BETS.") + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_place_bet_request") + PlayDealerSpeech(index, "MINIGAME_DEALER_PLACE_CHIPS") + + repeat + for i,v in pairs(players[index]) do + TriggerClientEvent("BLACKJACK:SyncTimer", v.player, bettingTime - timeTracker[index]) + end -- Remove players from round who didn't bet in time + Wait(1000) + timeTracker[index] = timeTracker[index] + 1 + until HaveAllPlayersBetted(players[index]) or #players[index] == 0 or timeTracker[index] >= bettingTime + + if #players[index] == 0 then + DebugPrint("BETTING ENDED AT TABLE "..index..", NO MORE PLAYERS") + -- break + else + for i,v in pairs(players[index]) do + if v.bet < 1 then + v.player_in = false + end + end -- Remove players from round who didn't bet in time + + if ArePlayersStillIn(players[index]) then -- did everyone just not bet? + DebugPrint("BETS PLACED AT TABLE "..index..", STARTING GAME") + + PlayDealerSpeech(index, "MINIGAME_DEALER_CLOSED_BETS") + + + local currentPlayers = {table.unpack(players[i])} + local deck = getDeck() + local dealerHand = {} + local dealerVisibleHand = {} + TriggerClientEvent("BLACKJACK:UpdateDealerHand", -1, index, handValue(dealerVisibleHand)) + + currentPlayers = SortPlayers(currentPlayers) + + local gameRunning = true + + Wait(1500) + + for x=1,2 do + local card = takeCard(deck) + table.insert(dealerHand, card) + + TriggerClientEvent("BLACKJACK:GiveCard", -1, index, 0, #dealerHand, card, #dealerHand == 1) + + if #dealerHand == 1 then + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_deal_card_self") + DebugPrint("TABLE "..index..": DEALT DEALER [HIDDEN]") + else + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_deal_card_self_second_card") + DebugPrint("TABLE "..index..": DEALT DEALER "..card) + table.insert(dealerVisibleHand, card) + + end + Wait(2000) + TriggerClientEvent("BLACKJACK:UpdateDealerHand", -1, index, handValue(dealerVisibleHand)) + + if #dealerHand > 1 then + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_"..cardValue(dealerHand[2])) + end + + for i,v in pairs(currentPlayers) do + if v.player_in then + local card = takeCard(deck) + TriggerClientEvent("BLACKJACK:GiveCard", -1, index, v.seat, #v.hand+1, card) + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_deal_card_player_0" .. 5-v.seat) + table.insert(v.hand, card) + + Wait(2000) + + + DebugPrint("TABLE "..index..": DEALT "..GetPlayerName(v.player):upper().." "..card) + + if handValue(v.hand) == 21 then + TriggerClientEvent("BLACKJACK:GameEndReaction", v.player, "good") + DebugPrint("TABLE "..index..": "..GetPlayerName(v.player):upper().." HAS BLACKJACK") + GiveMoney(v.player, v.bet*2.5) + v.player_in = false + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_BLACKJACK") + else + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_"..handValue(v.hand)) + end + end + end + end + + -- female_dealer_focus_player_01_idle + + if handValue(dealerHand) == 21 then + DebugPrint("TABLE "..index..": DEALER HAS BLACKJACK") + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_check_and_turn_card") + dealerVisibleHand = dealerHand + TriggerClientEvent("BLACKJACK:DealerTurnOverCard", -1, index) + + Wait(2000) + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_BLACKJACK") + TriggerClientEvent("BLACKJACK:UpdateDealerHand", -1, index, handValue(dealerVisibleHand)) + + for i,v in pairs(currentPlayers) do + TriggerClientEvent("BLACKJACK:GameEndReaction", v.player, "bad") + end + + gameRunning = false + elseif cardValue(dealerHand[2]) == 10 or cardValue(dealerHand[2]) == 11 then + DebugPrint("TABLE "..index..": DEALER HAS A 10, CHECKING..") + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_check_card") + if ChackCardPropAnim then + TriggerClientEvent("BLACKJACK:DealerCheckCard", -1, index) + end + Wait(2000) + end + + if gameRunning == true then + for i,v in pairs(currentPlayers) do + if v.player_in then + if tableTracker[tostring(v.player)] == nil then + DebugPrint("TABLE "..index..": "..v.player.." WAS PUT OUT DUE TO LEAVING") + v.player_in = false + TriggerClientEvent("BLACKJACK:RetrieveCards", -1, index, v.seat) + else + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_intro") + Wait(1500) + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_ANOTHER_CARD") + while v.player_in == true and #v.hand < 5 do + timeTracker[index] = 0 + Wait(0) + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle") + DebugPrint("TABLE "..index..": AWAITING MOVE FROM "..GetPlayerName(v.player):upper()) + TriggerClientEvent("BLACKJACK:RequestMove", v.player, moveTime - timeTracker[index]) + local receivedMove = false + local move = "stand" + local eventHandler = AddEventHandler("BLACKJACK:ReceivedMove", function(m) + if source ~= v.player then return end + move = m + receivedMove = true + end) + + while receivedMove == false and tableTracker[tostring(v.player)] ~= nil and timeTracker[index] < moveTime do + for i,v in pairs(currentPlayers) do + TriggerClientEvent("BLACKJACK:SyncTimer", v.player, moveTime - timeTracker[index]) + end + Wait(1000) + timeTracker[index] = timeTracker[index] + 1 + end + --repeat Wait(0) until receivedMove == true + RemoveEventHandler(eventHandler) + + if tableTracker[tostring(v.player)] == nil then + DebugPrint("TABLE "..index..": "..v.player.." WAS PUT OUT DUE TO LEAVING") + v.player_in = false + TriggerClientEvent("BLACKJACK:RetrieveCards", -1, index, v.seat) + else + if move == "hit" then + local card = takeCard(deck) + TriggerClientEvent("BLACKJACK:GiveCard", -1, index, v.seat, #v.hand+1, card) + -- PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_outro") + -- Wait(1500) + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_hit_card_player_0" .. 5-v.seat) + table.insert(v.hand, card) + Wait(1500) + DebugPrint("TABLE "..index..": DEALT "..GetPlayerName(v.player):upper().." "..card) + + if handValue(v.hand) == 21 then + -- PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_outro") + -- Wait(1500) + DebugPrint("TABLE "..index..": "..GetPlayerName(v.player):upper().." HAS 21") + -- v.player_in = false + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_"..handValue(v.hand)) + break + elseif handValue(v.hand) > 21 then + -- PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_outro") + -- Wait(1500) + TriggerClientEvent("BLACKJACK:GameEndReaction", v.player, "bad") + DebugPrint("TABLE "..index..": "..GetPlayerName(v.player):upper().." WENT BUST") + v.player_in = false + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_PLAYER_BUST") + else + -- Wait(1000) + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_"..handValue(v.hand)) + end + elseif move == "double" then + TakeMoney(v.player, v.bet) + v.bet = v.bet*2 + + -- TriggerClientEvent("BLACKJACK:PlaceBetChip", -1, i, 5-v.seat, betId) + + local card = takeCard(deck) + TriggerClientEvent("BLACKJACK:GiveCard", -1, index, v.seat, #v.hand+1, card) + -- PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_outro") + -- Wait(1500) + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_hit_card_player_0" .. 5-v.seat) + table.insert(v.hand, card) + Wait(1500) + DebugPrint("TABLE "..index..": DEALT "..GetPlayerName(v.player):upper().." "..card) + + if handValue(v.hand) == 21 then + -- PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_outro") + -- Wait(1500) + DebugPrint("TABLE "..index..": "..GetPlayerName(v.player):upper().." HAS 21") + -- v.player_in = false + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_"..handValue(v.hand)) + break + elseif handValue(v.hand) > 21 then + -- PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_outro") + -- Wait(1500) + TriggerClientEvent("BLACKJACK:GameEndReaction", v.player, "bad") + DebugPrint("TABLE "..index..": "..GetPlayerName(v.player):upper().." WENT BUST") + v.player_in = false + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_PLAYER_BUST") + else + -- Wait(2000) + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_"..handValue(v.hand)) + end + + break + elseif move == "split" then + TakeMoney(v.player, v.bet) + v.bet = v.bet*2 + + -- TriggerClientEvent("BLACKJACK:PlaceBetChip", -1, i, 5-v.seat, betId) + + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_split_card_player_0" .. 5-v.seat) + + v.splitHand = {} + + local splitCard = table.remove(v.hand, 2) + table.insert(v.splitHand, splitCard) + + Wait(500) + + TriggerClientEvent("BLACKJACK:SplitHand", -1, index, v.seat, #v.splitHand, v.hand, v.splitHand) + + Wait(1000) + + local card = takeCard(deck) + TriggerClientEvent("BLACKJACK:GiveCard", -1, index, v.seat, #v.hand+1, card, false, false) + -- PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_outro") + -- Wait(1500) + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_hit_card_player_0" .. 5-v.seat) + + -- female_dealer_focus_player_01_idle_split + + table.insert(v.hand, card) + Wait(1500) + DebugPrint("TABLE "..index..": DEALT "..GetPlayerName(v.player):upper().." "..card) + + if handValue(v.hand) == 21 then + -- PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_outro") + -- Wait(1500) + DebugPrint("TABLE "..index..": "..GetPlayerName(v.player):upper().." HAS 21") + -- v.player_in = false + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_BLACKJACK") + break + elseif handValue(v.hand) > 21 then + -- PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_outro") + -- Wait(1500) + TriggerClientEvent("BLACKJACK:GameEndReaction", v.player, "bad") + DebugPrint("TABLE "..index..": "..GetPlayerName(v.player):upper().." WENT BUST") + -- v.player_in = false + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_PLAYER_BUST") + else + -- Wait(2000) + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_"..handValue(v.hand)) + end + + local card = takeCard(deck) + TriggerClientEvent("BLACKJACK:GiveCard", -1, index, v.seat, #v.splitHand+1, card, false, true) + -- PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_outro") + -- Wait(1500) + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_hit_second_card_player_0" .. 5-v.seat) + + table.insert(v.splitHand, card) + Wait(1500) + DebugPrint("TABLE "..index..": DEALT "..GetPlayerName(v.player):upper().." "..card) + + if handValue(v.splitHand) == 21 then + -- PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_outro") + -- Wait(1500) + DebugPrint("TABLE "..index..": "..GetPlayerName(v.player):upper().." HAS 21") + -- v.player_in = false + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_"..handValue(v.splitHand)) + break + elseif handValue(v.splitHand) > 21 then + -- PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_outro") + -- Wait(1500) + TriggerClientEvent("BLACKJACK:GameEndReaction", v.player, "bad") + DebugPrint("TABLE "..index..": "..GetPlayerName(v.player):upper().." WENT BUST") + -- v.player_in = false + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_PLAYER_BUST") + else + -- Wait(2000) + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_"..handValue(v.splitHand)) + end + + -- PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_intro") + -- Wait(1500) + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_ANOTHER_CARD") + repeat Wait(0) + timeTracker[index] = 0 + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle") + DebugPrint("TABLE "..index..": AWAITING MOVE FROM "..GetPlayerName(v.player):upper()) + TriggerClientEvent("BLACKJACK:RequestMove", v.player, moveTime - timeTracker[index]) + local receivedMove = false + local move = "stand" + local eventHandler = AddEventHandler("BLACKJACK:ReceivedMove", function(m) + if source ~= v.player then return end + move = m + receivedMove = true + end) + + while receivedMove == false and tableTracker[tostring(v.player)] ~= nil and timeTracker[index] < moveTime do + for i,v in pairs(currentPlayers) do + TriggerClientEvent("BLACKJACK:SyncTimer", v.player, moveTime - timeTracker[index]) + end + Wait(1000) + timeTracker[index] = timeTracker[index] + 1 + end + + --repeat Wait(0) until receivedMove == true + RemoveEventHandler(eventHandler) + + if tableTracker[tostring(v.player)] == nil then + DebugPrint("TABLE "..index..": "..v.player.." WAS PUT OUT DUE TO LEAVING") + v.player_in = false + TriggerClientEvent("BLACKJACK:RetrieveCards", -1, index, v.seat) + print("breaking on 1st hand") + break + else + if move == "hit" then + local card = takeCard(deck) + TriggerClientEvent("BLACKJACK:GiveCard", -1, index, v.seat, #v.hand+1, card, false, false) + -- PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_outro") + -- Wait(1500) + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_hit_card_player_0" .. 5-v.seat) + table.insert(v.hand, card) + Wait(1500) + DebugPrint("TABLE "..index..": DEALT "..GetPlayerName(v.player):upper().." "..card) + + if handValue(v.hand) == 21 then + -- PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_outro") + -- Wait(1500) + DebugPrint("TABLE "..index..": "..GetPlayerName(v.player):upper().." HAS 21") + -- v.player_in = false + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_"..handValue(v.hand)) + break + elseif handValue(v.hand) > 21 then + -- PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_outro") + -- Wait(1500) + TriggerClientEvent("BLACKJACK:GameEndReaction", v.player, "bad") + DebugPrint("TABLE "..index..": "..GetPlayerName(v.player):upper().." WENT BUST") + -- v.player_in = false + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_PLAYER_BUST") + else + -- Wait(1000) + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_"..handValue(v.hand)) + end + elseif move == "stand" then + -- PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_outro_split") + -- Wait(1500) + break + end + end + until handValue(v.hand) >= 21 or #v.hand == 5 + + if v.player_in == true then + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_outro") + Wait(1500) + + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_intro_split") + Wait(1500) + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_ANOTHER_CARD") + + repeat Wait(0) + timeTracker[index] = 0 + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_split") + print(""..v.player) + DebugPrint("TABLE "..index..": AWAITING MOVE FROM "..GetPlayerName(v.player):upper()) + TriggerClientEvent("BLACKJACK:RequestMove", v.player, moveTime - timeTracker[index]) + local receivedMove = false + local move = "stand" + local eventHandler = AddEventHandler("BLACKJACK:ReceivedMove", function(m) + if source ~= v.player then return end + move = m + receivedMove = true + end) + + while receivedMove == false and tableTracker[tostring(v.player)] ~= nil and timeTracker[index] < moveTime do + for i,v in pairs(currentPlayers) do + TriggerClientEvent("BLACKJACK:SyncTimer", v.player, moveTime - timeTracker[index]) + end + Wait(1000) + timeTracker[index] = timeTracker[index] + 1 + end + --repeat Wait(0) until receivedMove == true + RemoveEventHandler(eventHandler) + + if tableTracker[tostring(v.player)] == nil then + DebugPrint("TABLE "..index..": "..v.player.." WAS PUT OUT DUE TO LEAVING") + v.player_in = false + TriggerClientEvent("BLACKJACK:RetrieveCards", -1, index, v.seat) + break + else + if move == "hit" then + local card = takeCard(deck) + TriggerClientEvent("BLACKJACK:GiveCard", -1, index, v.seat, #v.splitHand+1, card, false, true) + -- PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_outro") + -- Wait(1500) + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_hit_second_card_player_0" .. 5-v.seat) + table.insert(v.splitHand, card) + Wait(1500) + DebugPrint("TABLE "..index..": DEALT "..GetPlayerName(v.player):upper().." "..card) + + if handValue(v.splitHand) == 21 then + -- PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_outro") + -- Wait(1500) + DebugPrint("TABLE "..index..": "..GetPlayerName(v.player):upper().." HAS 21") + -- v.player_in = false + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_"..handValue(v.splitHand)) + break + elseif handValue(v.splitHand) > 21 then + -- PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_outro") + -- Wait(1500) + TriggerClientEvent("BLACKJACK:GameEndReaction", v.player, "bad") + DebugPrint("TABLE "..index..": "..GetPlayerName(v.player):upper().." WENT BUST") + -- v.player_in = false + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_PLAYER_BUST") + else + -- Wait(1000) + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_"..handValue(v.splitHand)) + end + elseif move == "stand" then + break + end + end + until handValue(v.splitHand) >= 21 or #v.splitHand == 5 + + if handValue(v.hand) > 21 and handValue(v.splitHand) > 21 then + v.player_in = false + end + + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_outro_split") + Wait(1500) + end + + break + + -- end + elseif move == "stand" then + -- PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_outro") + -- Wait(1500) + break + end + end + end + + if not v.splitHand then + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_dealer_focus_player_0".. 5-v.seat .."_idle_outro") + Wait(1500) + end + end + end + end + + -- Remove offline players from table + local j = 1 + + while j <= #currentPlayers do + local player = currentPlayers[j] + + if tableTracker[tostring(player.player)] == nil then + DebugPrint("TABLE "..index..": "..player.player.." WAS REMOVED FROM PLAYERS LIST FOR LEAVING") + table.remove(currentPlayers, j) + else + j = j + 1 + end + end + + if ArePlayersStillIn(currentPlayers) then + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_turn_card") + TriggerClientEvent("BLACKJACK:DealerTurnOverCard", -1, index) + dealerVisibleHand = dealerHand + Wait(2000) + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_"..handValue(dealerHand)) + TriggerClientEvent("BLACKJACK:UpdateDealerHand", -1, index, handValue(dealerVisibleHand)) + end + + if handValue(dealerHand) < 17 and ArePlayersStillIn(currentPlayers) then + repeat + local card = takeCard(deck) + table.insert(dealerHand, card) + TriggerClientEvent("BLACKJACK:GiveCard", -1, index, 0, #dealerHand, card, #dealerHand == 1) + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_deal_card_self_second_card") + DebugPrint("TABLE "..index..": DEALT DEALER "..card) + Wait(2000) + PlayDealerSpeech(index, "MINIGAME_BJACK_DEALER_"..handValue(dealerHand)) + TriggerClientEvent("BLACKJACK:UpdateDealerHand", -1, index, handValue(dealerVisibleHand)) + until handValue(dealerHand) >= 17 + end + end + + if handValue(dealerHand) > 21 then + PlayDealerSpeech(index, "MINIGAME_DEALER_BUSTS") + -- elseif handValue(dealerHand) < 21 and ArePlayersStillIn(currentPlayers) then + -- PlayDealerSpeech(index, "MINIGAME_DEALER_WINS") + end + + DebugPrint("TABLE "..index..": DEALER HAS "..handValue(dealerHand)) + + for i,v in pairs(currentPlayers) do + -- if v.player_in then + + DebugPrint("TABLE "..index..": "..GetPlayerName(v.player):upper().." HAS "..handValue(v.hand)) + + if v.player_in == true and (handValue(v.hand) > handValue(dealerHand) or handValue(dealerHand) > 21) then -- WIN + if v.splitHand then + if handValue(v.splitHand) > handValue(dealerHand) or handValue(dealerHand) > 21 then -- WIN + TriggerClientEvent("BLACKJACK:GameEndReaction", v.player, "good") + DebugPrint("TABLE "..index..": "..GetPlayerName(v.player):upper().." WON") + v.player_in = false + end + end + TriggerClientEvent("BLACKJACK:GameEndReaction", v.player, "good") + DebugPrint("TABLE "..index..": "..GetPlayerName(v.player):upper().." WON") + GiveMoney(v.player, v.bet*2) + v.player_in = false + end + if v.player_in == true and handValue(v.hand) == handValue(dealerHand) then -- PUSH + if v.splitHand then + if handValue(v.splitHand) == handValue(dealerHand) then -- PUSH + TriggerClientEvent("BLACKJACK:GameEndReaction", v.player, "impartial") + DebugPrint("TABLE "..index..": "..GetPlayerName(v.player):upper().." IS PUSH") + v.player_in = false + end + end + TriggerClientEvent("BLACKJACK:GameEndReaction", v.player, "impartial") + DebugPrint("TABLE "..index..": "..GetPlayerName(v.player):upper().." IS PUSH") + GiveMoney(v.player, v.bet) + v.player_in = false + end + if v.player_in == true and handValue(v.hand) < handValue(dealerHand) and handValue(dealerHand) <= 21 then -- LOSE + if v.splitHand then + if handValue(v.splitHand) < handValue(dealerHand) and handValue(dealerHand) <= 21 then -- LOSE + TriggerClientEvent("BLACKJACK:GameEndReaction", v.player, "bad") + DebugPrint("TABLE "..index..": "..GetPlayerName(v.player):upper().." LOST") + v.player_in = false + end + end + TriggerClientEvent("BLACKJACK:GameEndReaction", v.player, "bad") + DebugPrint("TABLE "..index..": "..GetPlayerName(v.player):upper().." LOST") + v.player_in = false + end + -- end + end + + if handValue(dealerHand) >= 17 then + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@shared@dealer@", "female_dealer_reaction_impartial_var0"..math.random(1,3)) + elseif handValue(dealerHand) > 21 then + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@shared@dealer@", "female_dealer_reaction_good_var0"..math.random(1,3)) + else + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@shared@dealer@", "female_dealer_reaction_bad_var0"..math.random(1,3)) + end + + Wait(2500) + + for i,v in pairs(currentPlayers) do + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_retrieve_cards_player_0".. 5-v.seat) + Wait(600) + TriggerClientEvent("BLACKJACK:RetrieveCardsWithAnim", -1, index, v.seat) + Wait(1400) + + v.bet = 0 + v.player_in = true + v.hand = {} + v.splitHand = nil + end + + PlayDealerAnim(index, "anim_casino_b@amb@casino@games@blackjack@dealer", "female_retrieve_own_cards_and_remove") + Wait(600) + TriggerClientEvent("BLACKJACK:RetrieveCardsWithAnim", -1, index, 0) + Wait(1400) + + timeTracker[index] = 0 + + for i,v in pairs(currentPlayers) do + TriggerClientEvent("BLACKJACK:RequestBets", v.player, index, timeTracker[index]) + end + -- while true do Wait(0) end + else + for i,v in pairs(players[index]) do + v.bet = 0 + v.player_in = true + v.hand = {} + v.splitHand = nil + end + + timeTracker[index] = 0 + end + end + end + end + end) +end + +Citizen.CreateThread(function() -- INIT + for i,_ in pairs(tables) do + StartTableThread(i) + players[i] = {} + timeTracker[i] = 0 + end +end) + +function PlayerSatDown(i, seat) + table.insert(players[i], {player = source, seat = seat, hand = {}, player_in = true, bet = 0}) + tableTracker[tostring(source)] = i + TriggerClientEvent("BLACKJACK:RequestBets", source, i) + + DebugPrint(GetPlayerName(source):upper() .. " SAT DOWN AT TABLE " .. i) + DebugPrint(#players[i]) +end + + + +RegisterServerEvent("BLACKJACK:PlayerSatDown") +AddEventHandler('BLACKJACK:PlayerSatDown', PlayerSatDown) + + +function PlayerSatUp(i) + DebugPrint(GetPlayerName(source):upper() .. " LEFT TABLE "..i) + local num = FindPlayerIdx(players[i], source) + if num ~= nil then + DebugPrint(GetPlayerName(source):upper() .. " SUCCESSFULLY REMOVED FROM TABLE "..i) + table.remove(players[i], num) + tableTracker[tostring(source)] = nil + PlayDealerSpeech(i, "MINIGAME_DEALER_LEAVE_NEUTRAL_GAME") + end +end + +RegisterServerEvent("BLACKJACK:PlayerSatUp") +AddEventHandler('BLACKJACK:PlayerSatUp', PlayerSatUp) + +function PlayerLeft() + local playerTbl = tableTracker[tostring(source)] + + if playerTbl ~= nil then + DebugPrint(GetPlayerName(source):upper() .. " LEFT SERVER") + + local num = FindPlayerIdx(players[playerTbl], source) + + if num ~= nil then + DebugPrint(GetPlayerName(source):upper() .. " REMOVED FROM TABLE FOR LEAVING") + table.remove(players[playerTbl], num) + end + + tableTracker[tostring(source)] = nil + end +end + +AddEventHandler("playerDropped", PlayerLeft) + +function PlayerRemove(i) + DebugPrint(GetPlayerName(source):upper() .. " LEFT TABLE "..i) + + local num = FindPlayerIdx(players[i], source) + + if num ~= nil then + DebugPrint(GetPlayerName(source):upper() .. " SUCCESSFULLY REMOVED FROM TABLE "..i) + + local playerInfo = players[i][num] + + if playerInfo.player_in then + if playerInfo.bet > 0 then + GiveMoney(source, playerInfo.bet) -- give money back as player was removed before losing or winning? + end + end + + table.remove(players[i], num) + tableTracker[tostring(source)] = nil + + PlayDealerSpeech(i, "MINIGAME_DEALER_LEAVE_NEUTRAL_GAME") + end +end + +RegisterServerEvent("BLACKJACK:PlayerRemove") +AddEventHandler('BLACKJACK:PlayerRemove', PlayerRemove) + +exports("SetGetChipsCallback", SetGetChipsCallback) +exports("SetTakeChipsCallback", SetTakeChipsCallback) +exports("SetGiveChipsCallback", SetGiveChipsCallback) + + +-- ==================================================================================== Added + +local ItemList = { + ["casino_redchip"] = 1 +} +QBCore.Functions.CreateCallback('BLACKJACKKGV:server:blackChipsAmount', function(source, cb) + local retval = 0 + local Player = QBCore.Functions.GetPlayer(source) + if Player.PlayerData.items ~= nil and next(Player.PlayerData.items) ~= nil then + for k, v in pairs(Player.PlayerData.items) do + if Player.PlayerData.items[k] ~= nil then + if ItemList[Player.PlayerData.items[k].name] ~= nil then + retval = retval + (ItemList[Player.PlayerData.items[k].name] * Player.PlayerData.items[k].amount) + end + end + end + end + cb(retval) +end) + +function SetExports() + exports["casino-blackjackKGV"]:SetGetChipsCallback(function(source) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local Chips = Player.Functions.GetItemByName("casino_redchip") + local minAmount = 10 + if Chips ~= nil then + if Chips.amount >= minAmount then + Chips = Chips + else + return TriggerClientEvent('QBCore:Notify', src, 'You dont have enough Casino Chips', 'error') + end + else + return TriggerClientEvent('QBCore:Notify', src, 'You dont have any Casino Chips', 'error') + end + end) + + exports["casino-blackjackKGV"]:SetTakeChipsCallback(function(source, amount) + local Player = QBCore.Functions.GetPlayer(source) + if Player ~= nil then + Player.Functions.RemoveItem("casino_redchip", amount) + TriggerClientEvent('inventory:client:ItemBox', source, QBCore.Shared.Items['casino_redchip'], "remove", amount) + end + end) + + exports["casino-blackjackKGV"]:SetGiveChipsCallback(function(source, amount) + local src = source + local Player = QBCore.Functions.GetPlayer(source) + if Player ~= nil then + if Player.Functions.AddItem('casino_redchip', amount, nil, {["quality"] = 100}) then + TriggerClientEvent('inventory:client:ItemBox', src, QBCore.Shared.Items["casino_redchip"], "add", amount) + TriggerClientEvent('QBCore:Notify', src, "You Won "..math.floor(amount).." Casino Chips!") + else + TriggerClientEvent('QBCore:Notify', src, 'You have to much in your pockets', 'error') + end + end + end) +end + +AddEventHandler("onResourceStart", function(resourceName) + if ("casino-blackjackKGV" == resourceName) then + Citizen.Wait(1000) + SetExports() + end +end) + +SetExports() + diff --git a/resources/[qb]/[qb_casino]/casino-blackjackRT98/cl_blackjack.lua b/resources/[qb]/[qb_casino]/casino-blackjackRT98/cl_blackjack.lua new file mode 100644 index 0000000..98ca950 --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-blackjackRT98/cl_blackjack.lua @@ -0,0 +1,3087 @@ +-------------------------------------------- +--Created by Robbster, do not redistribute-- +-------------------------------------------- +--If you're snooping here, before you ask why some function names are horrible things like func_368, its so I could keep track of where I was at in the decompiled scripts :P + +local QBCore = exports['qb-core']:GetCoreObject() + +local closeToCasino = false +local closestChair = -1 +local closestChairDist = 1000 +local Local_198f_247 = -1 --this is just closestChair pretty sure +local closestDealerPed = nil +local closestDealerPedDist = 1000 +local dealerPeds = {} +local Local_198f_255 = nil +local waitingForBetState = false +local waitingForSitDownState = false +local waitingForStandOrHitState = false +-- local blackjackInstructional = nil +-- local bettingInstructional = nil +local blackjackTableData = {} +-- local timeoutHowToBlackjack = false +local currentBlackjackGameID = 0 +local timeLeft = 20 +local drawTimerBar = false +local bettedThisRound = false +local standOrHitThisRound = false +local globalGameId = -1 +local globalNextCardCount = -1 +cardObjects = {} +local drawCurrentHand = false +local currentHand = 0 +local dealersHand = 0 +currentBetAmount = 0 +sittingAtBlackjackTable = false +local canExitBlackjack = false +local dealerSecondCardFromGameId = {} +local blackjackGameInProgress = false +local shouldForceIdleCardGames = false + +local cfg = {} + +--Please note the config order is important, dealerPositions must start from 0 and increase consecutively +cfg.blackjackTables = { + --[id] = {x,y,z,heading} + [0] = { + dealerPos = vector3(986.018, 59.759, 70.238), + dealerHeading = 10.804, + tablePos = vector3(985.9037, 60.55936, 69.23269), + tableHeading = 188.309, + distance = 1000.0, + prop = "vw_prop_casino_blckjack_01b" + }, + [1] = { + dealerPos = vector3(981.661, 62.706, 70.238), + dealerHeading = 282.591, + tablePos = vector3(982.48, 62.90, 69.32), + tableHeading = 103.309, + distance = 1000.0, + prop = "vw_prop_casino_blckjack_01b" + }, + [2] = { + dealerPos = vector3(988.644, 46.497, 70.238), + dealerHeading = 199.665, + tablePos = vector3(989.93, 45.7245, 69.23), + tableHeading = 23.309, + distance = 1000.0, + prop = "vw_prop_casino_blckjack_01b" + }, + [3] = { + dealerPos = vector3(986.486, 41.996, 70.238), + dealerHeading = 284.971, + tablePos = vector3(987.26, 42.203, 69.232), + tableHeading = 103.309, + distance = 1000.0, + prop = "vw_prop_casino_blckjack_01b" + }, +} + +--Use this command to get the coords you need for setting up new tables. +--Some maps use the prop vw_prop_casino_blckjack_01 some use vw_prop_casino_blckjack_01b, so change accordingly. +RegisterCommand("getcasinotable",function() + local playerCoords = GetEntityCoords(PlayerPedId()) + local blackjackTable = GetClosestObjectOfType(playerCoords.x,playerCoords.y,playerCoords.z,3.0,GetHashKey("vw_prop_casino_blckjack_01"),0,0,0) + if DoesEntityExist(blackjackTable) then + print("Found entity") + print("tablePos pos",GetEntityCoords(blackjackTable)) + print("tableHeading heading",GetEntityHeading(blackjackTable)) + print("prop: vw_prop_casino_blckjack_01") + else + local blackjackTable2 = GetClosestObjectOfType(playerCoords.x,playerCoords.y,playerCoords.z,3.0,GetHashKey("vw_prop_casino_blckjack_01b"),0,0,0) + if DoesEntityExist(blackjackTable2) then + print("Found entity") + print("tablePos pos:",GetEntityCoords(blackjackTable2)) + print("tableHeading heading:",GetEntityHeading(blackjackTable2)) + print("prop: vw_prop_casino_blckjack_01") + else + print("Could not find entity") + end + end +end) + +CreateThread(function() + TriggerServerEvent("Blackjack:requestBlackjackTableData") +end) + +RegisterNetEvent("Blackjack:sendBlackjackTableData") +AddEventHandler("Blackjack:sendBlackjackTableData", function(newBlackjackTableData) + blackjackTableData = newBlackjackTableData +end) + +CreateThread(function() + while not closeToCasino do + Wait(0) + end + maleCasinoDealer = GetHashKey("S_M_Y_Casino_01") + femaleCasinoDealer = GetHashKey("S_F_Y_Casino_01") + math.randomseed(GetGameTimer()) + + dealerAnimDict = "anim_casino_b@amb@casino@games@shared@dealer@" + RequestAnimDict(dealerAnimDict) + while not HasAnimDictLoaded(dealerAnimDict) do + Wait(0) + end + for i=0,#cfg.blackjackTables,1 do + math.random() math.random() math.random() + randomBlackShit = math.random(1,13) + if randomBlackShit < 7 then + dealerModel = maleCasinoDealer + else + dealerModel = femaleCasinoDealer + end + RequestModel(dealerModel) + while not HasModelLoaded(dealerModel) do + RequestModel(dealerModel) + Wait(0) + end + dealerEntity = CreatePed(26,dealerModel,cfg.blackjackTables[i].dealerPos.x,cfg.blackjackTables[i].dealerPos.y,cfg.blackjackTables[i].dealerPos.z,cfg.blackjackTables[i].dealerHeading,false,true) + table.insert(dealerPeds,dealerEntity) + SetModelAsNoLongerNeeded(dealerModel) + SetEntityCanBeDamaged(dealerEntity, 0) + SetPedAsEnemy(dealerEntity, 0) + SetBlockingOfNonTemporaryEvents(dealerEntity, 1) + SetPedResetFlag(dealerEntity, 249, 1) + SetPedConfigFlag(dealerEntity, 185, true) + SetPedConfigFlag(dealerEntity, 108, true) + SetPedCanEvasiveDive(dealerEntity, 0) + SetPedCanRagdollFromPlayerImpact(dealerEntity, 0) + SetPedConfigFlag(dealerEntity, 208, true) + setBlackjackDealerPedVoiceGroup(randomBlackShit,dealerEntity) + setBlackjackDealerClothes(randomBlackShit,dealerEntity) + SetEntityCoordsNoOffset(dealerEntity, cfg.blackjackTables[i].dealerPos.x,cfg.blackjackTables[i].dealerPos.y,cfg.blackjackTables[i].dealerPos.z, 0,0,1) + SetEntityHeading(dealerEntity, cfg.blackjackTables[i].dealerHeading) + if dealerModel == maleCasinoDealer then + TaskPlayAnim(dealerEntity, dealerAnimDict, "idle", 1000.0, -2.0, -1, 2, 1148846080, 0) --anim_name is idle or female_idle depending on gender + else + TaskPlayAnim(dealerEntity, dealerAnimDict, "female_idle", 1000.0, -2.0, -1, 2, 1148846080, 0) --anim_name is idle or female_idle depending on gender + end + PlayFacialAnim(dealerEntity, "idle_facial", dealerAnimDict) + RemoveAnimDict(dealerAnimDict) + end + local blackjackTable = GetClosestObjectOfType(1129.406, 262.3578, -52.041,1.0,GetHashKey("vw_prop_casino_blckjack_01b"),0,0,0) + SetObjectTextureVariant(blackjackTable,3) + local rouletteTable = GetClosestObjectOfType(1132.7875976563,262.42929077148,-51.035781860352,1.0,GetHashKey("vw_prop_casino_roulette_01b"),0,0,0) + SetObjectTextureVariant(rouletteTable,3) + local rouletteTable2 = GetClosestObjectOfType(1130.5859375,266.35610961914,-51.035778045654,1.0,GetHashKey("vw_prop_casino_roulette_01b"),0,0,0) + SetObjectTextureVariant(rouletteTable2,3) + local threeCardPoker = GetClosestObjectOfType(1132.9125976563,265.86212158203,-51.035766601563,1.0,GetHashKey("vw_prop_casino_3cardpoker_01b"),0,0,0) + SetObjectTextureVariant(threeCardPoker,3) + + local blackjackTable = GetClosestObjectOfType(1145.3294677734,248.06758117676,-51.035781860352,1.0,GetHashKey("vw_prop_casino_blckjack_01b"),0,0,0) + SetObjectTextureVariant(blackjackTable,3) + local rouletteTable = GetClosestObjectOfType(1147.9478759766,247.95536804199,-51.035766601563,1.0,GetHashKey("vw_prop_casino_roulette_01b"),0,0,0) + SetObjectTextureVariant(rouletteTable,3) + local rouletteTable2 = GetClosestObjectOfType(1144.6773681641,250.74932861328,-51.035762786865,1.0,GetHashKey("vw_prop_casino_roulette_01b"),0,0,0) + SetObjectTextureVariant(rouletteTable2,3) + local threeCardPoker = GetClosestObjectOfType(1147.9067382813,250.86437988281,-51.035781860352,1.0,GetHashKey("vw_prop_casino_3cardpoker_01b"),0,0,0) + SetObjectTextureVariant(threeCardPoker,3) +end) + +function resetDealerIdle(dealerPed) + local gender = getDealerGenderFromPed(dealerPed) + if DoesEntityExist(dealerPed) then + if gender == "male" then + genderAnimString = "" + end + if gender == "female" then + genderAnimString = "female_" + end + dealerAnimDict = "anim_casino_b@amb@casino@games@shared@dealer@" + RequestAnimDict(dealerAnimDict) + while not HasAnimDictLoaded(dealerAnimDict) do + Wait(0) + end + -- -- print("playing idle animation: " .. tostring(genderAnimString .. "idle")) + TaskPlayAnim(dealerPed, dealerAnimDict, genderAnimString .. "idle", 1000.0, -2.0, -1, 2, 1148846080, 0) --anim_name is idle or female_idle depending on gender + PlayFacialAnim(dealerPed, "idle_facial", dealerAnimDict) + TaskPlayAnim(PlayerPedId(),"anim_casino_b@amb@casino@games@shared@player@", "idle_cardgames", 1.0, 1.0, -1, 0) + end +end + +CreateThread(function() + while true do + if shouldForceIdleCardGames and sittingAtBlackjackTable then + TaskPlayAnim(PlayerPedId(),"anim_casino_b@amb@casino@games@shared@player@", "idle_cardgames", 1.0, 1.0, -1, 0) + end + Wait(0) + end +end) + + + + + + + +RegisterNetEvent('doj:client:openBetMenu', function() + exports['qb-menu']:openMenu({ + { + header = "The Diamond Casino & Resort Blackjack", + isMenuHeader = true, + }, + { + header = "Increase Bet", + txt = "+10", + params = { + event = "doj:client:startingBets", + args = 1 + } + }, + { + header = "Decrease bet", + txt = "-10", + params = { + event = "doj:client:startingBets", + args = 2 + } + }, + { + header = "Submit bet", + txt = "", + params = { + event = "doj:client:startingBets", + args = 3 + } + }, + -- { + -- header = "Custom Bet", + -- txt = "", + -- params = { + -- event = "doj:client:startingBets", + -- args = 4 + -- } + -- }, + { + header = "Exit", + txt = "", + params = { + event = "doj:client:startingBets", + args = 5 + } + }, + }) +end) + +RegisterNetEvent("doj:client:startingBets", function(args) + local args = tonumber(args) + if waitingForBetState then + if args == 1 then + -- -- print("Bet raised") + TriggerEvent("doj:client:openBetMenu") + currentBetAmount = currentBetAmount + 10 + elseif args == 2 then + -- -- print("Bet lowered") + TriggerEvent("doj:client:openBetMenu") + if currentBetAmount >= 10 then + currentBetAmount = currentBetAmount - 10 + else + QBCore.Functions.Notify("Cannot bet below zero", "error", 3500) + end + elseif args == 3 then + -- -- print("submitting bet") + if tonumber(currentBetAmount) >= 0 then + TriggerServerEvent("Blackjack:setBlackjackBet",globalGameId,currentBetAmount,closestChair) + closestDealerPed = getClosestDealer() + PlayAmbientSpeech1(closestDealerPed,"MINIGAME_DEALER_PLACE_CHIPS","SPEECH_PARAMS_FORCE_NORMAL_CLEAR",1) --TODO check this is the right sound? + putBetOnTable() + Wait(1000) + else + QBCore.Functions.Notify("Invalid amount.", "error", 3500) + end + elseif args == 4 then + -- -- print("custom bet") + local tmpInput = getGenericTextInput("Bet Amount") + if tonumber(tmpInput) then + tmpInput = tonumber(tmpInput) + if tmpInput > 0 then + currentBetAmount = tmpInput + end + end + TriggerEvent("doj:client:openBetMenu") + else + -- print('exit') + shouldForceIdleCardGames = false + blackjackAnimDictToLoad = "anim_casino_b@amb@casino@games@shared@player@" + RequestAnimDict(blackjackAnimDictToLoad) + while not HasAnimDictLoaded(blackjackAnimDictToLoad) do + Wait(0) + end + NetworkStopSynchronisedScene(Local_198f_255) + TaskPlayAnim(PlayerPedId(), blackjackAnimDictToLoad, "sit_exit_left", 1.0, 1.0, 2500, 0) + sittingAtBlackjackTable = false + drawTimerBar = false + drawCurrentHand = false + exports['casinoUi']:HideCasinoUi('hide') + waitingForBetState = false + TriggerServerEvent("Blackjack:leaveBlackjackTable") + closestDealerPed, closestDealerPedDistance = getClosestDealer() + PlayAmbientSpeech1(closestDealerPed,"MINIGAME_DEALER_LEAVE_NEUTRAL_GAME","SPEECH_PARAMS_FORCE_NORMAL_CLEAR",1) + end + end +end) + +RegisterNetEvent('doj:client:hit&standMenu', function() + exports['qb-menu']:openMenu({ + { + header = "The Diamond Casino & Resort Blackjack", + isMenuHeader = true, + }, + { + header = "Hit", + txt = "Draw another card", + params = { + event = "doj:client:hit&standActions", + args = 1 + } + }, + { + header = "Stand", + txt = "Be a pussy", + params = { + event = "doj:client:hit&standActions", + args = 2 + } + }, + }) +end) + +RegisterNetEvent("doj:client:hit&standActions", function(args) + local args = tonumber(args) + if waitingForStandOrHitState and sittingAtBlackjackTable and blackjackGameInProgress then + if args == 1 then + -- print("hit") + waitingForStandOrHitState = false + TriggerServerEvent("Blackjack:hitBlackjack",globalGameId,globalNextCardCount) + drawTimerBar = false + standOrHitThisRound = true + requestCard() + else + -- print("stand") + waitingForStandOrHitState = false + TriggerServerEvent("Blackjack:standBlackjack",globalGameId,globalNextCardCount) + drawTimerBar = false + standOrHitThisRound = true + declineCard() + end + end +end) + +RegisterNetEvent("Blackjack:successBlackjackBet") +AddEventHandler("Blackjack:successBlackjackBet",function() + bettedThisRound = true + waitingForBetState = false + canExitBlackjack = false +end) + + + +RegisterNetEvent("Blackjack:sitAtBlackjackTable",function(chair) + goToBlackjackSeat(chair) +end) + +CreateThread(function() + while true do + local playerCoords = GetEntityCoords(PlayerPedId()) + closeToCasino = false + for k,v in pairs(cfg.blackjackTables) do + cfg.blackjackTables[k].distance = #(playerCoords-cfg.blackjackTables[k].tablePos) + if cfg.blackjackTables[k].distance < 100.0 then + closeToCasino = true + end + end + Wait(1000) + end +end) + + +CreateThread(function() + while true do + local sleep = 5 + local playerPed = PlayerPedId() + local inZone = false + if not sittingAtBlackjackTable then + if closestChair ~= nil and closestChairDist < 1.3 then + inZone = true + text = "The Diamond Casino & Resort

Blackjack RT98

Press E to sit" + if not timeoutHowToBlackjack then + if IsControlJustPressed(0, 38) then + if blackjackTableData[closestChair] == false then + print("calling goToBlackjackSeat with chairID: " .. tostring(closestChair)) + TriggerServerEvent("Blackjack:requestSitAtBlackjackTable",closestChair) + else + QBCore.Functions.Notify("This seat is taken.", "error", 3500) + end + end + end + end + if inZone and not alreadyEnteredZone then + alreadyEnteredZone = true + exports['qb-core']:DrawText(text, "top") + end + if not inZone and alreadyEnteredZone then + alreadyEnteredZone = false + exports["qb-core"]:HideText() + end + end + Wait(sleep) + end +end) + +CreateThread(function() + while true do + if closeToCasino then + closestChairDist = 1000 + closestChair = -1 + local playerCoords = GetEntityCoords(PlayerPedId()) + for i=0,((#cfg.blackjackTables+1)*4)-1,1 do + local vectorOfBlackjackSeat = blackjack_func_348(i) + local distToBlackjackSeat = #(playerCoords - vectorOfBlackjackSeat) + if distToBlackjackSeat < closestChairDist then + closestChairDist = distToBlackjackSeat + closestChair = i + end + end + -- -- print("closestChair = ",closestChair) + end + Wait(100) + end +end) + +CreateThread(function() + while true do + if drawTimerBar then + QBCore.Functions.TriggerCallback('BLACKJACKRT98:server:blackChipsAmount', function(result) + retval = result + exports['casinoUi']:DrawCasinoUi('show', "The Diamond Casino & Resort Blackjack

Time Left: 0:"..timeLeft.."

Availble chips: "..math.floor(result).."

Current Bet: "..math.floor(currentBetAmount)) + end) + end + if drawCurrentHand then + exports['qb-core']:DrawText("Dealers Hand: "..math.floor(dealersHand).."

Your hand: "..math.floor(currentHand), "top") + end + Wait(250) + end +end) + +RegisterNetEvent("Blackjack:syncChipsPropBlackjack") +AddEventHandler("Blackjack:syncChipsPropBlackjack",function(betAmount,chairId) + if closeToCasino then + betBlackjack(betAmount,chairId) + end +end) + +RegisterNetEvent("Blackjack:beginBetsBlackjack") +AddEventHandler("Blackjack:beginBetsBlackjack",function(gameID,tableId) + globalGameId = gameID + -- blackjackInstructional = setupBlackjackInstructionalScaleform("instructional_buttons") + -- exports["qb-core"]:HideText() + TriggerEvent("doj:client:openBetMenu") + -- QBCore.Functions.Notify("Place your bets", 'primary', 3500) + exports['qb-core']:DrawText("Place your bets...", "top") + + bettedThisRound = false + drawTimerBar = true + drawCurrentHand = false + standOrHitThisRound = false + canExitBlackjack = true + waitingForBetState = true + dealerPed = getDealerFromTableId(tableId) + PlayAmbientSpeech1(dealerPed,"MINIGAME_DEALER_PLACE_BET","SPEECH_PARAMS_FORCE_NORMAL_CLEAR",1) + currentBetAmount = 0 + dealersHand = 0 + currentHand = 0 + SetEntityCoordsNoOffset(dealerPed, cfg.blackjackTables[tableId].dealerPos.x,cfg.blackjackTables[tableId].dealerPos.y,cfg.blackjackTables[tableId].dealerPos.z, 0,0,1) + SetEntityHeading(dealerPed, cfg.blackjackTables[tableId].dealerHeading) + CreateThread(function() + drawTimerBar = true + while timeLeft > 0 do + timeLeft = timeLeft - 1 + Wait(1000) + end + timeLeft = 20 + drawTimerBar = false + -- if not bettedThisRound then + --QBCore.Functions.Notify("No bet placed, round skipped", 'primary', 3500) + + -- end + end) +end) + +RegisterNetEvent("Blackjack:beginCardGiveOut") +AddEventHandler("Blackjack:beginCardGiveOut",function(gameId,cardData,chairId,cardIndex,gotCurrentHand,tableId) + if closeToCasino then + blackjackGameInProgress = true + exports['casinoUi']:HideCasinoUi('hide') + blackjackAnimsToLoad = { + "anim_casino_b@amb@casino@games@blackjack@dealer", + "anim_casino_b@amb@casino@games@shared@dealer@", + "anim_casino_b@amb@casino@games@blackjack@player", + "anim_casino_b@amb@casino@games@shared@player@", + } + for k,v in pairs(blackjackAnimsToLoad) do + RequestAnimDict(v) + while not HasAnimDictLoaded(v) do + Wait(0) + end + end + if sittingAtBlackjackTable and bettedThisRound then + drawCurrentHand = true + end + dealerPed = getDealerFromTableId(tableId) + cardObj = startDealing(dealerPed,gameId,cardData,chairId,cardIndex+1,gotCurrentHand,((tableId+1)*4)-1) + if blackjack_func_368(closestChair) == tableId and gameId == chairId and cardIndex == 0 then + dealersHand = gotCurrentHand + -- blackjackInstructional = nil + end + dealerSecondCardFromGameId[gameId] = cardObj + if chairId == closestChair and gameId ~= chairId then + currentHand = gotCurrentHand + -- blackjackInstructional = nil + end + end +end) + +RegisterNetEvent("Blackjack:singleCard") +AddEventHandler("Blackjack:singleCard",function(gameId,cardData,chairID,nextCardCount,gotCurrentHand,tableId) + if closeToCasino then + dealerPed = getDealerFromTableId(tableId) + startSingleDealing(chairID,dealerPed,gameId,cardData,nextCardCount+1,gotCurrentHand) + end +end) + +RegisterNetEvent("Blackjack:singleDealerCard") +AddEventHandler("Blackjack:singleDealerCard",function(gameId,cardData,nextCardCount,gotCurrentHand,tableId) + if closeToCasino then + dealerPed = getDealerFromTableId(tableId) + startSingleDealerDealing(dealerPed,gameId,cardData,nextCardCount+1,gotCurrentHand,((tableId+1)*4)-1,tableId) + end +end) + +RegisterNetEvent("Blackjack:standOrHit") +AddEventHandler("Blackjack:standOrHit",function(gameId,chairId,nextCardCount,tableId) + if closeToCasino then + dealerPed = getDealerFromTableId(tableId) + standOrHitThisRound = false + if closestChair == chairId then + globalNextCardCount = nextCardCount + waitingForStandOrHitState = true + PlayAmbientSpeech1(dealerPed,"MINIGAME_BJACK_DEALER_ANOTHER_CARD","SPEECH_PARAMS_FORCE_NORMAL_CLEAR",1) + TriggerEvent("doj:client:hit&standMenu") + startStandOrHit(gameId,dealerPed,chairId,true) + CreateThread(function() + if sittingAtBlackjackTable then + drawTimerBar = true + timeLeft = 20 + while timeLeft > 0 do + timeLeft = timeLeft - 1 + if timeLeft == 6 then + PlayAmbientSpeech1(dealerPed,"MINIGAME_DEALER_COMMENT_SLOW","SPEECH_PARAMS_FORCE_NORMAL_CLEAR",1) --TODO check this is the right sound? + end + if standOrHitThisRound then + -- -- print("terminating standorhit timer thread") + timeLeft = 20 + drawTimerBar = false + return + -- -- print("failed it didnt terminate!") + end + Wait(1000) + end + end + if not standOrHitThisRound and sittingAtBlackjackTable then + -- print("you took too long fam standing shit") + waitingForStandOrHitState = false + TriggerServerEvent("Blackjack:standBlackjack",globalGameId,globalNextCardCount) + declineCard() + QBCore.Functions.Notify("Failed to stand/hit in time, standing.", 'error', 3500) + end + end) + else + startStandOrHit(gameId,dealerPed,chairId,false) + end + end +end) + +function getClosestDealer() + local tmpclosestDealerPed = nil + local tmpclosestDealerPedDistance = 100000 + local playerCoords = GetEntityCoords(PlayerPedId()) + for k,v in pairs(dealerPeds) do + local dealerPed = v + -- -- print("Entity ID of this dealer ped: " .. tostring(dealerPed)) + local distanceToDealer = #(playerCoords - GetEntityCoords(dealerPed)) + -- -- print("Distance to dealer ped: " .. tostring(distanceToDealer)) + if distanceToDealer < tmpclosestDealerPedDistance then + tmpclosestDealerPedDistance = distanceToDealer + tmpclosestDealerPed = dealerPed + end + end + -- -- print("Closest dealer ped is: " .. tostring(tmpclosestDealerPed)) + closestDealerPed = tmpclosestDealerPed + closestDealerPedDistance = tmpclosestDealerPedDistance + return closestDealerPed, closestDealerPedDistance +end + +function getDealerFromChairId(chairId) + tableId = blackjack_func_368(chairId) + closestDealerPed = dealerPeds[tableId+1] + return closestDealerPed +end + +function getDealerFromTableId(tableId) + closestDealerPed = dealerPeds[tableId+1] + return closestDealerPed +end + +function goToBlackjackSeat(blackjackSeatID) + + sittingAtBlackjackTable = true + waitingForSitDownState = true + canExitBlackjack = true + currentHand = 0 + dealersHand = 0 + + closestDealerPed, closestDealerPedDistance = getClosestDealer() + PlayAmbientSpeech1(closestDealerPed,"MINIGAME_DEALER_GREET","SPEECH_PARAMS_FORCE_NORMAL_CLEAR",1) + + + -- print("[CMG Casino] start sit at blackjack seat") + exports['qb-core']:DrawText("Waiting for game to start...", "top") + + blackjackAnimsToLoad = { + "anim_casino_b@amb@casino@games@blackjack@dealer", + "anim_casino_b@amb@casino@games@shared@dealer@", + "anim_casino_b@amb@casino@games@blackjack@player", + "anim_casino_b@amb@casino@games@shared@player@", + } + for k,v in pairs(blackjackAnimsToLoad) do + RequestAnimDict(v) + while not HasAnimDictLoaded(v) do + Wait(10) + end + end + -- print("[CMG Casino] blackjack anims loaded") + Local_198f_247 = blackjackSeatID + print("blackjackSeatID: " .. blackjackSeatID) + fVar3 = blackjack_func_217(PlayerPedId(),blackjack_func_218(Local_198f_247, 0), 1) + fVar4 = blackjack_func_217(PlayerPedId(),blackjack_func_218(Local_198f_247, 1), 1) + fVar5 = blackjack_func_217(PlayerPedId(),blackjack_func_218(Local_198f_247, 2), 1) + -- print("[CMG Casino] fVars passed") + if (fVar4 < fVar5 and fVar4 < fVar3) then + Local_198f_251 = 1 + elseif (fVar5 < fVar4 and fVar5 < fVar3) then + Local_198f_251 = 2 + else + Local_198f_251 = 0 + end + --blackjack_func_218 is get_anim_offset + --param0 is 0-3 && param1 is 0-15? (OF blackjack_func_218) + local walkToVector = blackjack_func_218(Local_198f_247, Local_198f_251) + local targetHeading = blackjack_func_216(Local_198f_247, Local_198f_251) + -- -- print("[CMG Casino] walking to seat, x: " .. tostring(walkToVector.x) .. " y: " .. tostring(walkToVector.y) .. " z: " .. tostring(walkToVector.z)) + TaskGoStraightToCoord(PlayerPedId(), walkToVector.x, walkToVector.y, walkToVector.z, 1.0, 5000, targetHeading, 0.01) + + local goToVector = blackjack_func_348(Local_198f_247) + local xRot,yRot,zRot = blackjack_func_215(Local_198f_247) + -- -- print("[CMG Casino] Blackjack sit at table net scene starting") + -- -- print("[CMG Casino] creating Scene at, x: " .. tostring(goToVector.x) .. " y: " .. tostring(goToVector.y) .. " z: " .. tostring(goToVector.z)) + Local_198f_255 = NetworkCreateSynchronisedScene(goToVector.x, goToVector.y, goToVector.z, xRot, yRot, zRot, 2, 1, 0, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(PlayerPedId(), Local_198f_255, "anim_casino_b@amb@casino@games@shared@player@", blackjack_func_213(Local_198f_251), 2.0, -2.0, 13, 16, 2.0, 0) -- 8.0, -1.5, 157, 16, 1148846080, 0) ? + NetworkStartSynchronisedScene(Local_198f_255) + -- -- print("[CMG Casino] Blackjack sit at table net scene started") + --Local_198.f_255 = NETWORK::NETWORK_CREATE_SYNCHRONISED_SCENE(func_348(Local_198.f_247), func_215(Local_198.f_247), 2, 1, 0, 1065353216, 0, 1065353216) + --NETWORK::NETWORK_ADD_PED_TO_SYNCHRONISED_SCENE(PLAYER::PLAYER_PED_ID(), Local_198.f_255, "anim_casino_b@amb@casino@games@shared@player@", blackjack_func_213(Local_198f_251), 2f, -2f, 13, 16, 2f, 0) + --NETWORK::NETWORK_START_SYNCHRONISED_SCENE(Local_198.f_255) + + --NEXT --> Line 5552 + Citizen.InvokeNative(0x79C0E43EB9B944E2, -2124244681) + Wait(6000) + -- -- print("STOP STITTING ") + --Wait for sit down anim to end + Locali98f_55 = NetworkCreateSynchronisedScene(goToVector.x, goToVector.y, goToVector.z, xRot, yRot, zRot, 2, 1, 1, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(PlayerPedId(), Locali98f_55, "anim_casino_b@amb@casino@games@shared@player@", "idle_cardgames", 2.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(Locali98f_55) + StartAudioScene("DLC_VW_Casino_Table_Games") --need to stream this + Citizen.InvokeNative(0x79C0E43EB9B944E2, -2124244681) + waitingForSitDownState = false + shouldForceIdleCardGames = true +end + +function betBlackjack(amount,chairId) + local chipsProp = getChipPropFromAmount(amount) + --betChipsForNextHand(100,chipsProp,pos,chairId,false,stack/100) --false or true no clue + -- for stack=1,10,1 do + -- for pos=0,1,1 do --can be 0 to 3, however last 2 chip x/y positions are for a split I think + + -- end + -- end + for i,v in ipairs(chipsProp) do + betChipsForNextHand(100,v,0,chairId,false,(i-1)/200) --false or true no clue + end +end + +function startSingleDealerDealing(dealerPed,gameId,cardData,nextCardCount,gotCurrentHand,chairId,tableId) + -- -- print("startSingleDealerDealing", chairId) + N_0x469f2ecdec046337(1) + StartAudioScene("DLC_VW_Casino_Cards_Focus_Hand") --need to stream this + ensureCardModelsLoaded() --request all 52 card models + --AUDIO::_0xF8AD2EED7C47E8FE(iVar1, false, 1); call sound on dealer + ----------------THIS CREATES A CARD AT THE MACHINE WHERE THE CARD COMES OUT OF----------------------- + -- -- print("dealerPed: " .. tostring(dealerPed)) + -- -- print("DoesEntityExist(dealerPed): " .. tostring(DoesEntityExist(dealerPed))) + -- -- print("NetworkHasControlOfEntity(dealerPed): " .. tostring(NetworkHasControlOfEntity(dealerPed))) + local gender = getDealerGenderFromPed(dealerPed) + -- -- print("getLocalChairIdFromGlobalChairId: " .. tostring(getLocalChairIdFromGlobalChairId)) + if DoesEntityExist(dealerPed) then + cardPosition = nextCardCount + -- -- print("getLocalChairIdFromGlobalChairId: " .. tostring(getLocalChairIdFromGlobalChairId)) + nextCard = getCardFromNumber(cardData,true) + local nextCardObj = getNewCardFromMachine(nextCard,chairId,gameId) + AttachEntityToEntity(nextCardObj, dealerPed, GetPedBoneIndex(dealerPed,28422), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 1, 2, 1) + if gender == "male" then + genderAnimString = "" + end + if gender == "female" then + genderAnimString = "female_" + end + dealerGiveSelfCard(genderAnimString,dealerPed,3,nextCardObj) + DetachEntity(nextCardObj,false,true) + -- -- print("blackjack_func_368(closestChair)",blackjack_func_368(closestChair)) + -- -- print("tableId",tableId) + if blackjack_func_368(closestChair) == tableId then + dealersHand = gotCurrentHand + end + local soundCardString = "MINIGAME_BJACK_DEALER_" .. tostring(gotCurrentHand) + PlayAmbientSpeech1(dealerPed,soundCardString,"SPEECH_PARAMS_FORCE_NORMAL_CLEAR",1) + vVar8 = vector3(0.0, 0.0, getTableHeading(blackjack_func_368(chairId))) + local tablePosX,tablePosY,tablePosZ = getTableCoords(blackjack_func_368(chairId)) + local cardQueue = cardPosition -- number of card + local iVar5 = cardQueue + cardOffsetX,cardOffsetY,cardOffsetZ = blackjack_func_377(iVar5, 4, 1) --iVar9 is seat number 0-3 + local cardPos = GetObjectOffsetFromCoords(tablePosX, tablePosY, tablePosZ, vVar8.z, cardOffsetX, cardOffsetY, cardOffsetZ) + SetEntityCoordsNoOffset(nextCardObj, cardPos.x, cardPos.y, cardPos.z, 0, 0, 1) + Wait(400) + else + -- -- print("Failed to deal cards, entity doesn't exist or we don't have control") + end +end + +function startSingleDealing(chairId,dealerPed,gameId,cardData,nextCardCount,gotCurrentHand) + N_0x469f2ecdec046337(1) + StartAudioScene("DLC_VW_Casino_Cards_Focus_Hand") --need to stream this + ensureCardModelsLoaded() + local gender = getDealerGenderFromPed(dealerPed) + if DoesEntityExist(dealerPed) then + local localChairId = getLocalChairIdFromGlobalChairId(chairId) + cardPosition = nextCardCount + nextCard = getCardFromNumber(cardData,true) + local nextCardObj = getNewCardFromMachine(nextCard,chairId) + AttachEntityToEntity(nextCardObj, dealerPed, GetPedBoneIndex(dealerPed,28422), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 1, 2, 1) + if gender == "male" then + genderAnimString = "" + end + if gender == "female" then + genderAnimString = "female_" + end + dealerGiveCards(chairId,genderAnimString,dealerPed,nextCardObj) + DetachEntity(nextCardObj,false,true) + if chairId == closestChair then + currentHand = gotCurrentHand + end + local soundCardString = "MINIGAME_BJACK_DEALER_" .. tostring(gotCurrentHand) + -- -- print("trying soundString: " .. tostring(soundCardString)) + PlayAmbientSpeech1(dealerPed,soundCardString,"SPEECH_PARAMS_FORCE_NORMAL_CLEAR",1) + vVar8 = vector3(0.0, 0.0, getTableHeading(blackjack_func_368(chairId))) + local tablePosX,tablePosY,tablePosZ = getTableCoords(blackjack_func_368(chairId)) + local cardQueue = cardPosition -- number of card + local iVar5 = cardQueue + local iVar9 = localChairId - 1-- <-ChairID 0-3 + if iVar9 <= 4 then + -- -- print("single card pos: " .. tostring(iVar5)) + cardOffsetX,cardOffsetY,cardOffsetZ = blackjack_func_377(iVar5, iVar9, 0) --iVar9 is seat number 0-3 + else + cardOffsetX,cardOffsetY,cardOffsetZ = 0.5737, 0.2376, 0.948025 + end + local cardPos = GetObjectOffsetFromCoords(tablePosX, tablePosY, tablePosZ, vVar8.z, cardOffsetX, cardOffsetY, cardOffsetZ) + SetEntityCoordsNoOffset(nextCardObj, cardPos.x, cardPos.y, cardPos.z, 0, 0, 1) + vVar8 = vector3(0.0, 0.0, getTableHeading(blackjack_func_368(chairId))) + cardObjectOffsetRotation = vVar8 + func_376(iVar5, iVar9, 0, false) + SetEntityRotation(nextCardObj, cardObjectOffsetRotation.x, cardObjectOffsetRotation.y, cardObjectOffsetRotation.z, 2, 1) + Wait(400) + else + -- print("Failed to deal cards, entity doesn't exist or we don't have control") + end +end + +function startDealing(dealerPed,gameId,cardData,chairId,cardIndex,gotCurrentHand,fakeChairIdForDealerTurn) + -- print("startDealing()") + --NEXT --> func_90 the FAT FUNCTION + N_0x469f2ecdec046337(1) + StartAudioScene("DLC_VW_Casino_Cards_Focus_Hand") --need to stream this + ensureCardModelsLoaded() --request all 52 card models + --AUDIO::_0xF8AD2EED7C47E8FE(iVar1, false, 1); call sound on dealer + ----------------THIS CREATES A CARD AT THE MACHINE WHERE THE CARD COMES OUT OF----------------------- + -- print("dealerPed: " .. tostring(dealerPed)) + -- print("DoesEntityExist(dealerPed): " .. tostring(DoesEntityExist(dealerPed))) + -- print("NetworkHasControlOfEntity(dealerPed): " .. tostring(NetworkHasControlOfEntity(dealerPed))) + local gender = getDealerGenderFromPed(dealerPed) + if DoesEntityExist(dealerPed) then + -- print("startDealing() - entityExists") + -- print("request cardId: " .. tostring(cardData[cardIndex])) + nextCard = getCardFromNumber(cardData[cardIndex],true) + local nextCardObj = getNewCardFromMachine(nextCard,chairId) + AttachEntityToEntity(nextCardObj, dealerPed, GetPedBoneIndex(dealerPed,28422), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 1, 2, 1) + if gender == "male" then + genderAnimString = "" + end + if gender == "female" then + genderAnimString = "female_" + end + if chairId <= 1000 then + -- print("[blackjack] giving player cards") + dealerGiveCards(chairId,genderAnimString,dealerPed,nextCardObj) + else + -- print("[blackjack] giving dealers cards") + dealerGiveSelfCard(genderAnimString,dealerPed,cardIndex,nextCardObj) + end + DetachEntity(nextCardObj,false,true) + if chairId ~= gameId or cardIndex ~= 2 then + local soundCardString = "MINIGAME_BJACK_DEALER_" .. tostring(gotCurrentHand) + -- print("trying soundString: " .. tostring(soundCardString)) + PlayAmbientSpeech1(dealerPed,soundCardString,"SPEECH_PARAMS_FORCE_NORMAL_CLEAR",1) + end + --ENTITY::SET_ENTITY_COORDS_NO_OFFSET(Local_198.f_648[iVar6], OBJECT::_GET_OBJECT_OFFSET_FROM_COORDS(func_70(iVar2), vVar8.z, func_377(iVar5, iVar9, 0)), 0, 0, 1); + --ENTITY::SET_ENTITY_ROTATION(Local_198.f_648[iVar6], vVar8 + func_376(iVar5, iVar9, 0, func_380(iVar6)), 2, 1); + cardQueue = cardIndex -- number of card + iVar5 = cardQueue + iVar9 = chairId -- <-localChairId 0-3 + if chairId <= 1000 then + vVar8 = vector3(0.0, 0.0, getTableHeading(blackjack_func_368(chairId))) + tablePosX,tablePosY,tablePosZ = getTableCoords(blackjack_func_368(chairId)) + cardOffsetX,cardOffsetY,cardOffsetZ = blackjack_func_377(iVar5, getLocalChairIndexFromGlobalChairId(chairId), 0) --iVar9 is the local seat number 0-3 + else + vVar8 = vector3(0.0, 0.0, getTableHeading(blackjack_func_368(fakeChairIdForDealerTurn))) + tablePosX,tablePosY,tablePosZ = getTableCoords(blackjack_func_368(fakeChairIdForDealerTurn)) + cardOffsetX,cardOffsetY,cardOffsetZ = blackjack_func_377(iVar5, 4, 1) + end + local cardPos = GetObjectOffsetFromCoords(tablePosX, tablePosY, tablePosZ, vVar8.z, cardOffsetX, cardOffsetY, cardOffsetZ) + SetEntityCoordsNoOffset(nextCardObj, cardPos.x, cardPos.y, cardPos.z, 0, 0, 1) + -- print("fakeChairIdForDealerTurn",fakeChairIdForDealerTurn) + if chairId <= 1000 then + vVar8 = vector3(0.0, 0.0, getTableHeading(blackjack_func_368(chairId))) + cardObjectOffsetRotation = vVar8 + func_376(iVar5, getLocalChairIndexFromGlobalChairId(chairId), 0, false) + SetEntityRotation(nextCardObj, cardObjectOffsetRotation.x, cardObjectOffsetRotation.y, cardObjectOffsetRotation.z, 2, 1) + else + cardObjectOffsetRotation = blackjack_func_398(blackjack_func_368(fakeChairIdForDealerTurn)) + end + -- print("checking betttingInstructional",closestChair,chairId) + if closestChair == chairId and sittingAtBlackjackTable then + bettingInstructional = setupBlackjackMidBetScaleform("instructional_buttons") + end + --soundID = GetSoundId() + --PlaySoundFromEntity(soundID,"DLC_VW_CHIP_BET_SML_MEDIUM",nextCardObj,"dlc_vw_table_games_sounds", 0, 0) + return nextCardObj + else + -- print("Failed to deal cards, entity doesn't exist or we don't have control") + end +end + +function startStandOrHit(gameId,dealerPed,chairId,actuallyPlaying) + chairAnimId = getLocalChairIdFromGlobalChairId(chairId) + gender = getDealerGenderFromPed(dealerPed) + if gender == "male" then + genderAnimString = "" + end + if gender == "female" then + genderAnimString = "female_" + end + -- -- print("dealerPed: " .. tostring(dealerPed)) + -- -- print("chairAnimId: " .. tostring(chairAnimId)) + -- -- print("genderAnimString: " .. tostring(genderAnimString)) + RequestAnimDict("anim_casino_b@amb@casino@games@blackjack@dealer") + while not HasAnimDictLoaded("anim_casino_b@amb@casino@games@blackjack@dealer") do + Wait(0) + end + TaskPlayAnim(dealerPed, "anim_casino_b@amb@casino@games@blackjack@dealer", genderAnimString .. "dealer_focus_player_0" .. chairAnimId .. "_idle_intro", 3.0, 1.0, -1, 2, 0, 0, 0, 0 ) + PlayFacialAnim(dealerPed, genderAnimString .. "dealer_focus_player_0" .. chairAnimId .. "_idle_facial", "anim_casino_b@amb@casino@games@blackjack@dealer") + Wait(0) + while IsEntityPlayingAnim(dealerPed, "anim_casino_b@amb@casino@games@blackjack@dealer", genderAnimString .. "dealer_focus_player_0" .. chairAnimId .. "_idle_intro") do + Wait(10) + -- -- print("waiting for anim to end #1") + end + TaskPlayAnim(dealerPed, "anim_casino_b@amb@casino@games@blackjack@dealer", genderAnimString .. "dealer_focus_player_0" .. chairAnimId .. "_idle", 3.0, 1.0, -1, 2, 0, 0, 0, 0 ) + if actuallyPlaying then + waitingForPlayerToHitOrStand = true + end +end + +function flipDealerCard(dealerPed,gotCurrentHand,tableId,gameId) + cardObj = dealerSecondCardFromGameId[gameId] + local cardX,cardY,cardZ = GetEntityCoords(cardObj) + local gender = getDealerGenderFromPed(dealerPed) + if gender == "male" then + genderAnimString = "" + end + if gender == "female" then + genderAnimString = "female_" + end + TaskPlayAnim(dealerPed, "anim_casino_b@amb@casino@games@blackjack@dealer", genderAnimString .. "check_and_turn_card", 3.0, 1.0, -1, 2, 0, 0, 0, 0 ) + --PlayFacialAnim(dealerPed, genderAnimString .. "check_and_turn_card_facial", "anim_casino_b@amb@casino@games@blackjack@dealer") + while not HasAnimEventFired(dealerPed,-1345695206) do + -- print("waiting for -1345695206 to fire") + Wait(0) + end + AttachEntityToEntity(cardObj, dealerPed, GetPedBoneIndex(dealerPed,28422), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 1, 2, 1) + while not HasAnimEventFired(dealerPed,585557868) do + Wait(0) + end + DetachEntity(cardObj,false,true) + if blackjack_func_368(closestChair) == tableId then + dealersHand = gotCurrentHand + end + local soundCardString = "MINIGAME_BJACK_DEALER_" .. tostring(gotCurrentHand) + PlayAmbientSpeech1(dealerPed,soundCardString,"SPEECH_PARAMS_FORCE_NORMAL_CLEAR",1) + SetEntityCoordsNoOffset(cardObj, cardX,cardY,cardZ) +end + +function checkCard(dealerPed,cardObj) + local cardX,cardY,cardZ = GetEntityCoords(cardObj) + AttachEntityToEntity(cardObj, dealerPed, GetPedBoneIndex(dealerPed,28422), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 1, 2, 1) + local gender = getDealerGenderFromPed(dealerPed) + if gender == "male" then + genderAnimString = "" + end + if gender == "female" then + genderAnimString = "female_" + end + TaskPlayAnim(dealerPed, "anim_casino_b@amb@casino@games@blackjack@dealer", genderAnimString .. "check_card", 3.0, 1.0, -1, 2, 0, 0, 0, 0 ) + PlayFacialAnim(dealerPed, genderAnimString .. "check_card_facial", "anim_casino_b@amb@casino@games@blackjack@dealer") + while not HasAnimEventFired(dealerPed,585557868) do + Wait(0) + end + Wait(100) + DetachEntity(cardObj,false,true) + SetEntityCoordsNoOffset(cardObj, cardX,cardY,cardZ) +end + +RegisterNetEvent("Blackjack:endStandOrHitPhase") +AddEventHandler("Blackjack:endStandOrHitPhase",function(chairId,tableId) + if closeToCasino then + dealerPed = getDealerFromTableId(tableId) + waitingForPlayerToHitOrStand = false + chairAnimId = getLocalChairIdFromGlobalChairId(chairId) + gender = getDealerGenderFromPed(dealerPed) + if gender == "male" then + genderAnimString = "" + end + if gender == "female" then + genderAnimString = "female_" + end + -- print("dealer ending anim: " .. "anim_casino_b@amb@casino@games@blackjack@dealer", genderAnimString .. "dealer_focus_player_0" .. chairAnimId .. "_idle_outro") + TaskPlayAnim(dealerPed, "anim_casino_b@amb@casino@games@blackjack@dealer", genderAnimString .. "dealer_focus_player_0" .. chairAnimId .. "_idle_outro", 3.0, 1.0, -1, 2, 0, 0, 0, 0 ) + PlayFacialAnim(dealerPed, genderAnimString .. "dealer_focus_player_0" .. chairAnimId .. "_idle_outro_facial", "anim_casino_b@amb@casino@games@blackjack@dealer") + end +end) + +RegisterNetEvent("Blackjack:bustBlackjack") +AddEventHandler("Blackjack:bustBlackjack",function(chairID,tableId) + if closeToCasino then + dealerPed = getDealerFromTableId(tableId) + PlayAmbientSpeech1(dealerPed,"MINIGAME_BJACK_DEALER_PLAYER_BUST","SPEECH_PARAMS_FORCE_NORMAL_CLEAR",1) + TaskPlayAnim(dealerPed, "anim_casino_b@amb@casino@games@blackjack@dealer", "reaction_bad", 3.0, 1.0, -1, 2, 0, 0, 0, 0 ) + -- print("closestChair:",closestChair) + -- print("getLocalChairIdFromGlobalChairId(closestChair):",getLocalChairIdFromGlobalChairId(closestChair)) + -- print("chairID+1:",chairID) + -- print("sittingAtBlackjackTable:",sittingAtBlackjackTable) + if chairID == closestChair and sittingAtBlackjackTable then + angryIBust() + drawCurrentHand = false + currentHand = 0 + dealersHand = 0 + end + end +end) + +RegisterNetEvent("Blackjack:flipDealerCard") +AddEventHandler("Blackjack:flipDealerCard",function(gotCurrentHand,tableId,gameId) + if closeToCasino then + dealerPed = getDealerFromTableId(tableId) + flipDealerCard(dealerPed,gotCurrentHand,tableId,gameId) + end +end) + +RegisterNetEvent("Blackjack:dealerBusts") +AddEventHandler("Blackjack:dealerBusts",function(tableId) + if closeToCasino then + dealerPed = getDealerFromTableId(tableId) + PlayAmbientSpeech1(dealerPed,"MINIGAME_DEALER_BUSTS","SPEECH_PARAMS_FORCE_NORMAL_CLEAR",1) + end +end) + +RegisterNetEvent("Blackjack:blackjackLose") +AddEventHandler("Blackjack:blackjackLose",function(tableId) + if closeToCasino then + blackjackGameInProgress = false + dealerPed = getDealerFromTableId(tableId) + PlayAmbientSpeech1(dealerPed,"MINIGAME_DEALER_WINS","SPEECH_PARAMS_FORCE_NORMAL_CLEAR",1) + TaskPlayAnim(dealerPed, "anim_casino_b@amb@casino@games@blackjack@dealer", "reaction_bad", 3.0, 1.0, -1, 2, 0, 0, 0, 0 ) + angryILost() + canExitBlackjack = true + PlaySoundFrontend(-1, "CHALLENGE_UNLOCKED", "HUD_AWARDS", 1) + drawCurrentHand = false + currentHand = 0 + dealersHand = 0 + end +end) + +RegisterNetEvent("Blackjack:blackjackPush") +AddEventHandler("Blackjack:blackjackPush",function(tableId) + if closeToCasino then + blackjackGameInProgress = false + dealerPed = getDealerFromTableId(tableId) + TaskPlayAnim(dealerPed, "anim_casino_b@amb@casino@games@blackjack@dealer", "reaction_impartial", 3.0, 1.0, -1, 2, 0, 0, 0, 0 ) + annoyedIPushed() + canExitBlackjack = true + PlaySoundFrontend(-1, "ERROR", "HUD_AMMO_SHOP_SOUNDSET", 1) + drawCurrentHand = false + currentHand = 0 + dealersHand = 0 + end +end) + +RegisterNetEvent("Blackjack:blackjackWin") +AddEventHandler("Blackjack:blackjackWin",function(tableId) + if closeToCasino then + blackjackGameInProgress = false + dealerPed = getDealerFromTableId(tableId) + TaskPlayAnim(dealerPed, "anim_casino_b@amb@casino@games@blackjack@dealer", "reaction_good", 3.0, 1.0, -1, 2, 0, 0, 0, 0 ) + happyIWon() + canExitBlackjack = true + PlaySoundFrontend(-1, "TENNIS_MATCH_POINT", "HUD_AWARDS", 1) + drawCurrentHand = false + currentHand = 0 + dealersHand = 0 + end +end) + +RegisterNetEvent("Blackjack:chipsCleanup") +AddEventHandler("Blackjack:chipsCleanup",function(chairId,tableId) + if closeToCasino then + if string.sub(chairId, -5) ~= "chips" then + dealerPed = getDealerFromTableId(tableId) + local gender = getDealerGenderFromPed(dealerPed) + if gender == "male" then + genderAnimString = "" + end + if gender == "female" then + genderAnimString = "female_" + end + localChairId = getLocalChairIdFromGlobalChairId(chairId) + if chairId > 99 then --if "chairId" is above 99 its not a chair Id, its the gameId so its the dealers turn + TaskPlayAnim(dealerPed, "anim_casino_b@amb@casino@games@blackjack@dealer", genderAnimString .. "retrieve_own_cards_and_remove", 3.0, 1.0, -1, 2, 0, 0, 0, 0) + PlayFacialAnim(dealerPed, genderAnimString .. "retrieve_own_cards_and_remove_facial", "anim_casino_b@amb@casino@games@blackjack@dealer") + else + TaskPlayAnim(dealerPed, "anim_casino_b@amb@casino@games@blackjack@dealer", genderAnimString .. "retrieve_cards_player_0" .. tostring(localChairId), 3.0, 1.0, -1, 2, 0, 0, 0, 0) + PlayFacialAnim(dealerPed, genderAnimString .. "retrieve_cards_player_0" .. tostring(localChairId).."_facial", "anim_casino_b@amb@casino@games@blackjack@dealer") + end + while not HasAnimEventFired(dealerPed,-1345695206) do + -- print("waiting for -1345695206 to fire") + Wait(0) + end + for k,v in pairs(cardObjects) do + if k == chairId then + for k2,v2 in pairs(v) do + -- print("attach entity chairId",k,"objkey",k2," objvalue",v2) + AttachEntityToEntity(v2, dealerPed, GetPedBoneIndex(dealerPed,28422), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 1, 2, 1) + end + end + end + while not HasAnimEventFired(dealerPed,585557868) do + -- print("waiting for 585557868 to fire") + Wait(0) + end + for k,v in pairs(cardObjects) do + if k == chairId then + for k2,v2 in pairs(v) do + DeleteEntity(v2) + --v[k2] = nil + end + end + end + else + for k,v in pairs(cardObjects) do + if k == chairId then + for k2,v2 in pairs(v) do + DeleteEntity(v2) + --v[k2] = nil + end + end + end + end + end +end) + +RegisterNetEvent("Blackjack:chipsCleanupNoAnim") +AddEventHandler("Blackjack:chipsCleanupNoAnim",function(chairId,tableId) + for k,v in pairs(cardObjects) do + if k == chairId then + for k2,v2 in pairs(v) do + DeleteEntity(v2) + end + end + end +end) + +function betChipsForNextHand(chipsAmount,chipsProp,something,chairID,someBool,zOffset) + -- Local_198.f_538[func_379(iVar2, iVar9, 0)] = OBJECT::CREATE_OBJECT_NO_OFFSET(func_375(iVar14, bVar4), OBJECT::_GET_OBJECT_OFFSET_FROM_COORDS(func_70(iVar2), vVar8.z, func_374(iVar14, 0, iVar9, bVar4)), 0, false, 1); + -- ENTITY::SET_ENTITY_COORDS_NO_OFFSET(Local_198.f_538[func_379(iVar2, iVar9, 0)], + --^-> OBJECT::_GET_OBJECT_OFFSET_FROM_COORDS(func_70(iVar2), vVar8.z, func_374(iVar14, 0, iVar9, bVar4)), 0, 0, 1); + -- ENTITY::SET_ENTITY_ROTATION(Local_198.f_538[func_379(iVar2, iVar9, 0)], vVar8 + func_373(iVar14, 0, iVar9, bVar4), 2, 1); + -- if (!MISC::IS_STRING_NULL_OR_EMPTY(func_372(iVar14))) + -- { + -- AUDIO::PLAY_SOUND_FROM_ENTITY(-1, func_372(iVar14), Local_198.f_538[func_379(iVar2, iVar9, 0)], "dlc_vw_table_games_sounds", 0, 0); + -- } + -- print("betChipsForNextHand",chairID) + -- print("betChipsForNextHand_local",getLocalChairIndexFromGlobalChairId(chairID)) + RequestModel(chipsProp) + while not HasModelLoaded(chipsProp) do + Wait(0) + -- print("[CMG Casino] Stuck requesting model: " .. tostring(chipsProp)) + RequestModel(chipsProp) + end + vVar8 = vector3(0.0, 0.0, getTableHeading(blackjack_func_368(chairID))) + local tablePosX,tablePosY,tablePosZ = getTableCoords(blackjack_func_368(chairID)) + local chipsVector = blackjack_func_374(chipsAmount,something,getLocalChairIndexFromGlobalChairId(chairID),someBool) + local chipsOffset = GetObjectOffsetFromCoords(tablePosX,tablePosY,tablePosZ, vVar8.z, chipsVector.x, chipsVector.y, chipsVector.z) + + local chipsObj = CreateObjectNoOffset(GetHashKey(chipsProp), chipsOffset.x,chipsOffset.y,chipsOffset.z, false, false, 1) + if cardObjects[tostring(chairID) .. "chips"] ~= nil then + table.insert(cardObjects[tostring(chairID) .. "chips"],chipsObj) + else + cardObjects[tostring(chairID) .. "chips"] = {} + table.insert(cardObjects[tostring(chairID) .. "chips"],chipsObj) + end + SetEntityCoordsNoOffset(chipsObj, chipsOffset.x, chipsOffset.y, chipsOffset.z+zOffset, 0, 0, 1) + local chipOffsetRotation = blackjack_func_373(chipsAmount,0,getLocalChairIndexFromGlobalChairId(chairID),someBool) + SetEntityRotation(chipsObj,vVar8 + chipOffsetRotation, 2, 1) + + -- print("betChips DEBUG") + -- print("==============") + -- print("zOffset: " .. tostring(zOffset)) + -- print("vVar8: " .. tostring(vVar8)) + -- print("tablePosX: " .. tostring(tablePosX)) + -- print("tablePosY: " .. tostring(tablePosY)) + -- print("tablePosZ: " .. tostring(tablePosZ)) + -- print("chipsVector: " .. tostring(chipsVector)) + -- print("chipsOffset: " .. tostring(chipsOffset)) + -- print("chipsObj: " .. tostring(chipsObj)) + -- print("chipOffsetRotation: " .. tostring(chipOffsetRotation)) +end + +function getDealerGenderFromPed(dealerPed) + maleCasinoDealer = GetHashKey("S_M_Y_Casino_01") + femaleCasinoDealer = GetHashKey("S_F_Y_Casino_01") + + if GetEntityModel(dealerPed) == maleCasinoDealer then + return "male" + end + return "female" +end + +function getNewCardFromMachine(nextCard,chairId,gameId) + -- print("getNewCardFromMachine:",chairId) + RequestModel(nextCard) + while not HasModelLoaded(nextCard) do + Wait(0) + RequestModel(nextCard) + end + nextCardHash = GetHashKey(nextCard) + local cardObjectOffset = blackjack_func_399(blackjack_func_368(chairId)) + local nextCardObj = CreateObjectNoOffset(nextCardHash, cardObjectOffset.x, cardObjectOffset.y, cardObjectOffset.z, false, false, 1) + if cardObjects[chairId] ~= nil then + if gameId then + -- print("inserting chipsobjects with key: " .. tostring(gameId)) + table.insert(cardObjects[gameId],nextCardObj) + else + -- print("inserting chipsobjects with key: " .. tostring(chairId)) + table.insert(cardObjects[chairId],nextCardObj) + end + else + cardObjects[chairId] = {} + if gameId then + -- print("inserting chipsobjects with key: " .. tostring(gameId)) + table.insert(cardObjects[gameId],nextCardObj) + else + -- print("inserting chipsobjects with key: " .. tostring(chairId)) + table.insert(cardObjects[chairId],nextCardObj) + end + end + SetEntityVisible(nextCardObj,false) + SetModelAsNoLongerNeeded(nextCardHash) + local cardObjectOffsetRotation = blackjack_func_398(blackjack_func_368(chairId)) + SetEntityCoordsNoOffset(nextCardObj, cardObjectOffset.x, cardObjectOffset.y, cardObjectOffset.z, 0, 0, 1) + --vVar8 = vector3(0.0, 0.0, getTableHeading(blackjack_func_368(chairId))) + + --if chairId > 99 then + -- cardObjectOffsetRotation = vVar8 + func_376(iVar5, iVar9, 0, false) + --else + -- cardObjectOffsetRotation = blackjack_func_398(blackjack_func_368(chairId)) + --end + -- print("cardObjectOffsetRotation.x: " .. tostring(cardObjectOffsetRotation.x)) + -- print("cardObjectOffsetRotation.y: " .. tostring(cardObjectOffsetRotation.y)) + -- print("cardObjectOffsetRotation.z: " .. tostring(cardObjectOffsetRotation.z)) + SetEntityRotation(nextCardObj, cardObjectOffsetRotation.x, cardObjectOffsetRotation.y, cardObjectOffsetRotation.z, 2, 1) + --FreezeEntityPosition(nextCardObj, true) + return nextCardObj +end + +function dealerGiveCards(chairId,gender,dealerPed,cardObj) --func_36 + local seatNumber = tostring(getLocalChairIdFromGlobalChairId(chairId)) + --local currentScene = NetworkCreateSynchronisedScene(x, y, z, 0.0, 0.0, zRot, 2, 1, 0, 1065353216, 0, 1065353216) + TaskPlayAnim(dealerPed, "anim_casino_b@amb@casino@games@blackjack@dealer", gender .. "deal_card_player_0" .. seatNumber, 3.0, 1.0, -1, 2, 0, 0, 0, 0 ) + PlayFacialAnim(dealerPed,"deal_card_player_0"..seatNumber.."_facial") + --NetworkStartSynchronisedScene(currentScene) + --func_15(func_21(iParam0, Local_188.f_899[iVar2 /*9*/].f_8, 0, 0), Local_188.f_1[iParam0 /*211*/][Local_188.f_1[iParam0 /*211*/].f_209], 0, 0); + Wait(300) + SetEntityVisible(cardObj,true) + while not HasAnimEventFired(dealerPed, 585557868) do + Wait(0) + -- print("waiting for anim event to fire.. for dealergivecards") + end +end + +function dealerGiveSelfCard(gender,dealerPed,cardIndex,cardObj) --func_36 + if cardIndex == 1 then + cardAnim = "deal_card_self_second_card" + elseif cardIndex == 2 then + cardAnim = "deal_card_self" + else + cardAnim = "deal_card_self_card_10" + end + TaskPlayAnim(dealerPed, "anim_casino_b@amb@casino@games@blackjack@dealer", gender .. cardAnim, 3.0, 1.0, -1, 2, 0, 0, 0, 0 ) + PlayFacialAnim(dealerPed, gender .. cardAnim.."_facial", "anim_casino_b@amb@casino@games@blackjack@dealer") + Wait(300) + SetEntityVisible(cardObj,true) + while not HasAnimEventFired(dealerPed, 585557868) do + Wait(0) + -- print("waiting for anim event to fire.. for dealerGiveSelfCard") + end + Wait(100) +end + +local chipsProps = { + "vw_prop_chip_10dollar_x1", + "vw_prop_chip_50dollar_x1", + "vw_prop_chip_100dollar_x1", + "vw_prop_chip_50dollar_st", + "vw_prop_chip_100dollar_st", + "vw_prop_chip_500dollar_x1", + "vw_prop_chip_1kdollar_x1", + "vw_prop_chip_500dollar_st", + "vw_prop_chip_5kdollar_x1", + "vw_prop_chip_1kdollar_st", + "vw_prop_chip_10kdollar_x1", + "vw_prop_chip_5kdollar_st", + "vw_prop_chip_10kdollar_st", + "vw_prop_plaq_5kdollar_x1", + "vw_prop_plaq_5kdollar_st", + "vw_prop_plaq_10kdollar_x1", + "vw_prop_plaq_10kdollar_st", + "vw_prop_vw_chips_pile_01a", + "vw_prop_vw_chips_pile_02a", + "vw_prop_vw_chips_pile_03a", + "vw_prop_vw_coin_01a", +} + +function declineCard() + shouldForceIdleCardGames = false + local chairPos = blackjack_func_348(closestChair) + local chairRot = blackjack_func_215(closestChair) + local currentScene = NetworkCreateSynchronisedScene(chairPos.x, chairPos.y, chairPos.z, chairRot.x, chairRot.y, chairRot.z, 2, 1, 0, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(PlayerPedId(), currentScene, "anim_casino_b@amb@casino@games@blackjack@player", "decline_card_001", 4.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(currentScene) + SetTimeout(2000,function() + shouldForceIdleCardGames = true + end) +end + +function requestCard() + shouldForceIdleCardGames = false + local chairPos = blackjack_func_348(closestChair) + local chairRot = blackjack_func_215(closestChair) + local currentScene = NetworkCreateSynchronisedScene(chairPos.x, chairPos.y, chairPos.z, chairRot.x, chairRot.y, chairRot.z, 2, 1, 0, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(PlayerPedId(), currentScene, "anim_casino_b@amb@casino@games@blackjack@player", "request_card", 4.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(currentScene) + SetTimeout(2000,function() + shouldForceIdleCardGames = true + end) +end + +function putBetOnTable() + shouldForceIdleCardGames = false + local chairPos = blackjack_func_348(closestChair) + local chairRot = blackjack_func_215(closestChair) + local currentScene = NetworkCreateSynchronisedScene(chairPos.x, chairPos.y, chairPos.z, chairRot.x, chairRot.y, chairRot.z, 2, 1, 0, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(PlayerPedId(), currentScene, "anim_casino_b@amb@casino@games@blackjack@player", getAnimNameFromBet(100), 4.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(currentScene) + SetTimeout(5000,function() + shouldForceIdleCardGames = true + end) +end + +function angryIBust() + shouldForceIdleCardGames = false + local chairPos = blackjack_func_348(closestChair) + local chairRot = blackjack_func_215(closestChair) + local currentScene = NetworkCreateSynchronisedScene(chairPos.x, chairPos.y, chairPos.z, chairRot.x, chairRot.y, chairRot.z, 2, 1, 0, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(PlayerPedId(), currentScene, "anim_casino_b@amb@casino@games@shared@player@", "reaction_terrible_var_01", 4.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(currentScene) + SetTimeout(5000,function() + shouldForceIdleCardGames = true + end) +end + +function angryILost() + shouldForceIdleCardGames = false + local chairPos = blackjack_func_348(closestChair) + local chairRot = blackjack_func_215(closestChair) + local currentScene = NetworkCreateSynchronisedScene(chairPos.x, chairPos.y, chairPos.z, chairRot.x, chairRot.y, chairRot.z, 2, 1, 0, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(PlayerPedId(), currentScene, "anim_casino_b@amb@casino@games@shared@player@", "reaction_bad_var_01", 4.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(currentScene) + SetTimeout(5000,function() + shouldForceIdleCardGames = true + end) +end + +function annoyedIPushed() + shouldForceIdleCardGames = false + local chairPos = blackjack_func_348(closestChair) + local chairRot = blackjack_func_215(closestChair) + local currentScene = NetworkCreateSynchronisedScene(chairPos.x, chairPos.y, chairPos.z, chairRot.x, chairRot.y, chairRot.z, 2, 1, 0, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(PlayerPedId(), currentScene, "anim_casino_b@amb@casino@games@shared@player@", "reaction_impartial_var_01", 4.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(currentScene) + SetTimeout(5000,function() + shouldForceIdleCardGames = true + end) +end + +function happyIWon() + shouldForceIdleCardGames = false + local chairPos = blackjack_func_348(closestChair) + local chairRot = blackjack_func_215(closestChair) + local currentScene = NetworkCreateSynchronisedScene(chairPos.x, chairPos.y, chairPos.z, chairRot.x, chairRot.y, chairRot.z, 2, 1, 0, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(PlayerPedId(), currentScene, "anim_casino_b@amb@casino@games@shared@player@", "reaction_good_var_01", 4.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(currentScene) + SetTimeout(5000,function() + shouldForceIdleCardGames = true + end) +end + +function blackjack_func_398(iParam0) + local vVar0 = vector3(0.0, 164.52, 11.5) + return vector3(getTableHeading(iParam0), 0.0, 0.0) + vVar0; +end + +function blackjack_func_399(iParam0) --iParam0 is table ID? + local vVar0 = vector3(0.526, 0.571, 0.963) + -- print("func_399 iParam0",iParam0) + local x,y,z = getTableCoords(iParam0) + return GetObjectOffsetFromCoords(x, y, z, getTableHeading(iParam0), vVar0.x, vVar0.y, vVar0.z) +end + + +function ensureCardModelsLoaded() + cardNum = 0; + while cardNum < 52 do + iVar1 = cardNum + 1 + local Local_198f_236 = 1 --assuming 1 cant find it equal anything else :/ + iVar2 = getCardFromNumber(iVar1, Local_198f_236) + if not HasModelLoaded(iVar2) then + RequestModel(iVar2) + while not HasModelLoaded(iVar2) do + Wait(0) + end + end + cardNum = cardNum + 1 + end +end + + +function blackjack_func_204(iParam0, iParam1, bParam2) --returns vector + if bParam2 then + return vector3(getTableHeading(iParam1), 0.0, 0.0) + vector3(0, 0.061, -59.1316); + else + vVar0 = blackjack_func_215(iParam0) + return vector3(vVar0.z, 0.0, 0.0) + vector3(-87.48, 0, -60.84); + end + return 0.0, 0.0, 0.0 +end + +function blackjack_func_205(iParam0, iParam1, bParam2) --returns Vector + if bParam2 then + --return OBJECT::_GET_OBJECT_OFFSET_FROM_COORDS(func_70(iParam1), func_69(iParam1), -0.0094f, -0.0611f, 1.5098f); + return GetObjectOffsetFromCoords(getTableCoords(iParam1), getTableHeading(iParam1),-0.0094, -0.0611, 1.5098) + else + --vVar0 = { func_215(iParam0) }; + --return OBJECT::_GET_OBJECT_OFFSET_FROM_COORDS(func_348(iParam0), vVar0.z, 0.245f, 0f, 1.415f); + vVar0 = blackjack_func_215(iParam0) + return GetObjectOffsetFromCoords(blackjack_func_348(iParam0), vVar0.z,0.245, 0.0, 1.415) + end + return 0.0, 0.0, 0.0 +end + +function blackjack_func_216(iParam0, iParam1) + local goToVector = blackjack_func_348(iParam0) + local xRot,yRot,zRot = blackjack_func_215(iParam0) + vVar0 = GetAnimInitialOffsetRotation("anim_casino_b@amb@casino@games@shared@player@", blackjack_func_213(iParam1), goToVector.x, goToVector.y, goToVector.z, xRot, yRot, zRot, 0.01, 2) + return vVar0.z +end + +function blackjack_func_217(iParam0, vParam1, bParam2) + local vVar0 = {} + + if not IsEntityDead(iParam0,0) then + vVar0 = GetEntityCoords(iParam0,1) + else + vVar0 = GetEntityCoords(iParam0,0) + end + return #(vVar0-vParam1) +end + +function blackjack_func_218(iParam0, iParam1) --//param0 is 0-3 && param1 is 0-15? + local goToVector = blackjack_func_348(iParam0) + local xRot,yRot,zRot = blackjack_func_215(iParam0) + vVar0 = GetAnimInitialOffsetPosition("anim_casino_b@amb@casino@games@shared@player@", blackjack_func_213(iParam1), goToVector.x, goToVector.y, goToVector.z, xRot, yRot, zRot, 0.01, 2) + return vVar0 +end + +function blackjack_func_213(sitAnimID) + if sitAnimID == 0 then + return "sit_enter_left" + elseif sitAnimID == 1 then + return "sit_enter_left_side" + elseif sitAnimID == 2 then + return "sit_enter_right_side" + end + return "sit_enter_left" +end + +function getInverseChairId(chairId) + if chairId == 0 then return 3 end + if chairId == 1 then return 2 end + if chairId == 2 then return 1 end + if chairId == 3 then return 0 end +end + +function blackjack_func_348(iParam0) --GetVectorFromChairId + if iParam0 == -1 then + return vector3(0.0,0.0,0.0) + end + local blackjackTableObj + local tableId = blackjack_func_368(iParam0) + local x,y,z = getTableCoords(tableId) + blackjackTableObj = GetClosestObjectOfType(x, y, z, 1.0, cfg.blackjackTables[tableId].prop, 0, 0, 0) + + if DoesEntityExist(blackjackTableObj) and DoesEntityHaveDrawable(blackjackTableObj) then + local localChairId = getLocalChairIndexFromGlobalChairId(iParam0) + -- -- print("localchairId was",localChairId) + localChairId = getInverseChairId(localChairId) + 1 + -- -- print("localchairId is now",localChairId) + return GetWorldPositionOfEntityBone_2(blackjackTableObj,GetEntityBoneIndexByName(blackjackTableObj, "Chair_Base_0"..localChairId)) + end + return vector3(0.0,0.0,0.0) +end + +function blackjack_func_215(iParam0) + if iParam0 == -1 then + return vector3(0.0,0.0,0.0) + end + local blackjackTableObj + local tableId = blackjack_func_368(iParam0) + local x,y,z = getTableCoords(tableId) + blackjackTableObj = GetClosestObjectOfType(x, y, z, 1.0, cfg.blackjackTables[tableId].prop, 0, 0, 0) + if DoesEntityExist(blackjackTableObj) and DoesEntityHaveDrawable(blackjackTableObj) then + local localChairId = getLocalChairIndexFromGlobalChairId(iParam0) + -- -- print("localchairId was",localChairId) + localChairId = getInverseChairId(localChairId) + 1 + -- -- print("localchairId is now",localChairId) + return GetWorldRotationOfEntityBone(blackjackTableObj,GetEntityBoneIndexByName(blackjackTableObj, "Chair_Base_0"..localChairId)) + else + return vector3(0.0,0.0,0.0) + end +end + +function blackjack_func_368(chairId) --returns tableID based on chairID + local tableId = -1 + for i=0,chairId,4 do + tableId = tableId + 1 + end + return tableId +end + +function getLocalChairIdFromGlobalChairId(globalChairId) --returns tableID based on chairID + if globalChairId ~= -1 then + return (globalChairId % 4) + 1 + else + return 100 + end +end + +function getLocalChairIndexFromGlobalChairId(globalChairId) --returns tableID based on chairID + if globalChairId ~= -1 then + return (globalChairId % 4) + else + return 100 + end +end + +function getTableHeading(id) --previously blackjack_func_69 + if cfg.blackjackTables[id] ~= nil then + return cfg.blackjackTables[id].tableHeading + else + return 0.0 --for when tableId = gameId (i.e for dealer) + end +end + +function getTableCoords(id) --previously blackjack_func_70 + if cfg.blackjackTables[id] ~= nil then + return cfg.blackjackTables[id].tablePos.x,cfg.blackjackTables[id].tablePos.y,cfg.blackjackTables[id].tablePos.z + else + return 0.0,0.0,0.0 --for when tableId = gameId (i.e for dealer) + end +end + +function getCardFromNumber(iParam0, bParam1) + if bParam1 then + if iParam0 == 1 then + return "vw_prop_vw_club_char_a_a" + elseif iParam0 == 2 then + return "vw_prop_vw_club_char_02a" + elseif iParam0 == 3 then + return "vw_prop_vw_club_char_03a" + elseif iParam0 == 4 then + return "vw_prop_vw_club_char_04a" + elseif iParam0 == 5 then + return "vw_prop_vw_club_char_05a" + elseif iParam0 == 6 then + return "vw_prop_vw_club_char_06a" + elseif iParam0 == 7 then + return "vw_prop_vw_club_char_07a" + elseif iParam0 == 8 then + return "vw_prop_vw_club_char_08a" + elseif iParam0 == 9 then + return "vw_prop_vw_club_char_09a" + elseif iParam0 == 10 then + return "vw_prop_vw_club_char_10a" + elseif iParam0 == 11 then + return "vw_prop_vw_club_char_j_a" + elseif iParam0 == 12 then + return "vw_prop_vw_club_char_q_a" + elseif iParam0 == 13 then + return "vw_prop_vw_club_char_k_a" + elseif iParam0 == 14 then + return "vw_prop_vw_dia_char_a_a" + elseif iParam0 == 15 then + return "vw_prop_vw_dia_char_02a" + elseif iParam0 == 16 then + return "vw_prop_vw_dia_char_03a" + elseif iParam0 == 17 then + return "vw_prop_vw_dia_char_04a" + elseif iParam0 == 18 then + return "vw_prop_vw_dia_char_05a" + elseif iParam0 == 19 then + return "vw_prop_vw_dia_char_06a" + elseif iParam0 == 20 then + return "vw_prop_vw_dia_char_07a" + elseif iParam0 == 21 then + return "vw_prop_vw_dia_char_08a" + elseif iParam0 == 22 then + return "vw_prop_vw_dia_char_09a" + elseif iParam0 == 23 then + return "vw_prop_vw_dia_char_10a" + elseif iParam0 == 24 then + return "vw_prop_vw_dia_char_j_a" + elseif iParam0 == 25 then + return "vw_prop_vw_dia_char_q_a" + elseif iParam0 == 26 then + return "vw_prop_vw_dia_char_k_a" + elseif iParam0 == 27 then + return "vw_prop_vw_hrt_char_a_a" + elseif iParam0 == 28 then + return "vw_prop_vw_hrt_char_02a" + elseif iParam0 == 29 then + return "vw_prop_vw_hrt_char_03a" + elseif iParam0 == 30 then + return "vw_prop_vw_hrt_char_04a" + elseif iParam0 == 31 then + return "vw_prop_vw_hrt_char_05a" + elseif iParam0 == 32 then + return "vw_prop_vw_hrt_char_06a" + elseif iParam0 == 33 then + return "vw_prop_vw_hrt_char_07a" + elseif iParam0 == 34 then + return "vw_prop_vw_hrt_char_08a" + elseif iParam0 == 35 then + return "vw_prop_vw_hrt_char_09a" + elseif iParam0 == 36 then + return "vw_prop_vw_hrt_char_10a" + elseif iParam0 == 37 then + return "vw_prop_vw_hrt_char_j_a" + elseif iParam0 == 38 then + return "vw_prop_vw_hrt_char_q_a" + elseif iParam0 == 39 then + return "vw_prop_vw_hrt_char_k_a" + elseif iParam0 == 40 then + return "vw_prop_vw_spd_char_a_a" + elseif iParam0 == 41 then + return "vw_prop_vw_spd_char_02a" + elseif iParam0 == 42 then + return "vw_prop_vw_spd_char_03a" + elseif iParam0 == 43 then + return "vw_prop_vw_spd_char_04a" + elseif iParam0 == 44 then + return "vw_prop_vw_spd_char_05a" + elseif iParam0 == 45 then + return "vw_prop_vw_spd_char_06a" + elseif iParam0 == 46 then + return "vw_prop_vw_spd_char_07a" + elseif iParam0 == 47 then + return "vw_prop_vw_spd_char_08a" + elseif iParam0 == 48 then + return "vw_prop_vw_spd_char_09a" + elseif iParam0 == 49 then + return "vw_prop_vw_spd_char_10a" + elseif iParam0 == 50 then + return "vw_prop_vw_spd_char_j_a" + elseif iParam0 == 51 then + return "vw_prop_vw_spd_char_q_a" + elseif iParam0 == 52 then + return "vw_prop_vw_spd_char_k_a" + end + else + if iParam0 == 1 then + return "vw_prop_cas_card_club_ace" + elseif iParam0 == 2 then + return "vw_prop_cas_card_club_02" + elseif iParam0 == 3 then + return "vw_prop_cas_card_club_03" + elseif iParam0 == 4 then + return "vw_prop_cas_card_club_04" + elseif iParam0 == 5 then + return "vw_prop_cas_card_club_05" + elseif iParam0 == 6 then + return "vw_prop_cas_card_club_06" + elseif iParam0 == 7 then + return "vw_prop_cas_card_club_07" + elseif iParam0 == 8 then + return "vw_prop_cas_card_club_08" + elseif iParam0 == 9 then + return "vw_prop_cas_card_club_09" + elseif iParam0 == 10 then + return "vw_prop_cas_card_club_10" + elseif iParam0 == 11 then + return "vw_prop_cas_card_club_jack" + elseif iParam0 == 12 then + return "vw_prop_cas_card_club_queen" + elseif iParam0 == 13 then + return "vw_prop_cas_card_club_king" + elseif iParam0 == 14 then + return "vw_prop_cas_card_dia_ace" + elseif iParam0 == 15 then + return "vw_prop_cas_card_dia_02" + elseif iParam0 == 16 then + return "vw_prop_cas_card_dia_03" + elseif iParam0 == 17 then + return "vw_prop_cas_card_dia_04" + elseif iParam0 == 18 then + return "vw_prop_cas_card_dia_05" + elseif iParam0 == 19 then + return "vw_prop_cas_card_dia_06" + elseif iParam0 == 20 then + return "vw_prop_cas_card_dia_07" + elseif iParam0 == 21 then + return "vw_prop_cas_card_dia_08" + elseif iParam0 == 22 then + return "vw_prop_cas_card_dia_09" + elseif iParam0 == 23 then + return "vw_prop_cas_card_dia_10" + elseif iParam0 == 24 then + return "vw_prop_cas_card_dia_jack" + elseif iParam0 == 25 then + return "vw_prop_cas_card_dia_queen" + elseif iParam0 == 26 then + return "vw_prop_cas_card_dia_king" + elseif iParam0 == 27 then + return "vw_prop_cas_card_hrt_ace" + elseif iParam0 == 28 then + return "vw_prop_cas_card_hrt_02" + elseif iParam0 == 29 then + return "vw_prop_cas_card_hrt_03" + elseif iParam0 == 30 then + return "vw_prop_cas_card_hrt_04" + elseif iParam0 == 31 then + return "vw_prop_cas_card_hrt_05" + elseif iParam0 == 32 then + return "vw_prop_cas_card_hrt_06" + elseif iParam0 == 33 then + return "vw_prop_cas_card_hrt_07" + elseif iParam0 == 34 then + return "vw_prop_cas_card_hrt_08" + elseif iParam0 == 35 then + return "vw_prop_cas_card_hrt_09" + elseif iParam0 == 36 then + return "vw_prop_cas_card_hrt_10" + elseif iParam0 == 37 then + return "vw_prop_cas_card_hrt_jack" + elseif iParam0 == 38 then + return "vw_prop_cas_card_hrt_queen" + elseif iParam0 == 39 then + return "vw_prop_cas_card_hrt_king" + elseif iParam0 == 40 then + return "vw_prop_cas_card_spd_ace" + elseif iParam0 == 41 then + return "vw_prop_cas_card_spd_02" + elseif iParam0 == 42 then + return "vw_prop_cas_card_spd_03" + elseif iParam0 == 43 then + return "vw_prop_cas_card_spd_04" + elseif iParam0 == 44 then + return "vw_prop_cas_card_spd_05" + elseif iParam0 == 45 then + return "vw_prop_cas_card_spd_06" + elseif iParam0 == 46 then + return "vw_prop_cas_card_spd_07" + elseif iParam0 == 47 then + return "vw_prop_cas_card_spd_08" + elseif iParam0 == 48 then + return "vw_prop_cas_card_spd_09" + elseif iParam0 == 49 then + return "vw_prop_cas_card_spd_10" + elseif iParam0 == 50 then + return "vw_prop_cas_card_spd_jack" + elseif iParam0 == 51 then + return "vw_prop_cas_card_spd_queen" + elseif iParam0 == 52 then + return "vw_prop_cas_card_spd_king" + end + end + if bParam1 then + return "vw_prop_vw_jo_char_01a" + end + return "vw_prop_casino_cards_single" +end + +function getAnimNameFromBet(betAmount) + --TODO sort this out once bet amounts decided + -- return "place_bet_small"; + -- return "place_bet_small_alt1"; + -- return "place_bet_small_alt2"; + -- return "place_bet_small_alt3"; + -- return "place_bet_large"; + -- return "place_bet_double_down"; + -- return "place_bet_small_player_02"; + -- return "place_bet_large_player_02"; + -- return "place_bet_double_down_player_02"; + -- return "place_bet_small_split"; + -- return "place_bet_large_split"; + + --default for now + return "place_bet_small" +end + + +function blackjack_func_377(iParam0, iParam1, bParam2) --iVar5, iVar9, 0 + -- print("blackjack_func_377") + -- print("iParam0: " .. tostring(iParam0)) + -- print("iParam1: " .. tostring(iParam1)) + -- print("bParam2: " .. tostring(bParam2)) + if bParam2 == 0 then + -- print("first check [OK]") + -- print("iParam1: " .. tostring(iParam1)) + -- print("iParam0: " .. tostring(iParam0)) + if iParam1 == 0 then + if iParam0 == 0 then + return 0.5737, 0.2376, 0.948025 + elseif iParam0 == 1 then + return 0.562975, 0.2523, 0.94875 + elseif iParam0 == 2 then + return 0.553875, 0.266325, 0.94955 + elseif iParam0 == 3 then + return 0.5459, 0.282075, 0.9501 + elseif iParam0 == 4 then + return 0.536125, 0.29645, 0.95085 + elseif iParam0 == 5 then + return 0.524975, 0.30975, 0.9516 + elseif iParam0 == 6 then + return 0.515775, 0.325325, 0.95235 + end + elseif iParam1 == 1 then + if iParam0 == 0 then + return 0.2325, -0.1082, 0.94805 + elseif iParam0 == 1 then + return 0.23645, -0.0918, 0.949 + elseif iParam0 == 2 then + return 0.2401, -0.074475, 0.950225 + elseif iParam0 == 3 then + return 0.244625, -0.057675, 0.951125 + elseif iParam0 == 4 then + return 0.249675, -0.041475, 0.95205 + elseif iParam0 == 5 then + return 0.257575, -0.0256, 0.9532 + elseif iParam0 == 6 then + return 0.2601, -0.008175, 0.954375 + end + elseif iParam1 == 2 then + if iParam0 == 0 then + return -0.2359, -0.1091, 0.9483 + elseif iParam0 == 1 then + return -0.221025, -0.100675, 0.949 + elseif iParam0 == 2 then + return -0.20625, -0.092875, 0.949725 + elseif iParam0 == 3 then + return -0.193225, -0.07985, 0.950325 + elseif iParam0 == 4 then + return -0.1776, -0.072, 0.951025 + elseif iParam0 == 5 then + return -0.165, -0.060025, 0.951825 + elseif iParam0 == 6 then + return -0.14895, -0.05155, 0.95255 + end + elseif iParam1 == 3 then + if iParam0 == 0 then + return -0.5765, 0.2229, 0.9482 + elseif iParam0 == 1 then + return -0.558925, 0.2197, 0.949175 + elseif iParam0 == 2 then + return -0.5425, 0.213025, 0.9499 + elseif iParam0 == 3 then + return -0.525925, 0.21105, 0.95095 + elseif iParam0 == 4 then + return -0.509475, 0.20535, 0.9519 + elseif iParam0 == 5 then + return -0.491775, 0.204075, 0.952825 + elseif iParam0 == 6 then + return -0.4752, 0.197525, 0.9543 + end + end + else + if iParam1 == 0 then + if iParam0 == 0 then + return 0.6083, 0.3523, 0.94795 + elseif iParam0 == 1 then + return 0.598475, 0.366475, 0.948925 + elseif iParam0 == 2 then + return 0.589525, 0.3807, 0.94975 + elseif iParam0 == 3 then + return 0.58045, 0.39435, 0.950375 + elseif iParam0 == 4 then + return 0.571975, 0.4092, 0.951075 + elseif iParam0 == 5 then + return 0.5614, 0.4237, 0.951775 + elseif iParam0 == 6 then + return 0.554325, 0.4402, 0.952525 + end + elseif iParam1 == 1 then + if iParam0 == 0 then + return 0.3431, -0.0527, 0.94855 + elseif iParam0 == 1 then + return 0.348575, -0.0348, 0.949425 + elseif iParam0 == 2 then + return 0.35465, -0.018825, 0.9502 + elseif iParam0 == 3 then + return 0.3581, -0.001625, 0.95115 + elseif iParam0 == 4 then + return 0.36515, 0.015275, 0.952075 + elseif iParam0 == 5 then + return 0.368525, 0.032475, 0.95335 + elseif iParam0 == 6 then + return 0.373275, 0.0506, 0.9543 + end + elseif iParam1 == 2 then + if iParam0 == 0 then + return -0.116, -0.1501, 0.947875 + elseif iParam0 == 1 then + return -0.102725, -0.13795, 0.948525 + elseif iParam0 == 2 then + return -0.08975, -0.12665, 0.949175 + elseif iParam0 == 3 then + return -0.075025, -0.1159, 0.949875 + elseif iParam0 == 4 then + return -0.0614, -0.104775, 0.9507 + elseif iParam0 == 5 then + return -0.046275, -0.095025, 0.9516 + elseif iParam0 == 6 then + return -0.031425, -0.0846, 0.952675 + end + elseif iParam1 == 3 then + if iParam0 == 0 then + return -0.5205, 0.1122, 0.9478 + elseif iParam0 == 1 then + return -0.503175, 0.108525, 0.94865 + elseif iParam0 == 2 then + return -0.485125, 0.10475, 0.949175 + elseif iParam0 == 3 then + return -0.468275, 0.099175, 0.94995 + elseif iParam0 == 4 then + return -0.45155, 0.09435, 0.95085 + elseif iParam0 == 5 then + return -0.434475, 0.089725, 0.95145 + elseif iParam0 == 6 then + return -0.415875, 0.0846, 0.9523 + end + elseif iParam1 == 4 then --estimated + if iParam0 == 0 then + return -0.293,0.253,0.950025 + elseif iParam0 == 1 then + return -0.093,0.253,0.950025 + elseif iParam0 == 2 then + return 0.0293,0.253,0.950025 + elseif iParam0 == 3 then + return 0.1516,0.253,0.950025 + elseif iParam0 == 4 then + return 0.2739,0.253,0.950025 + elseif iParam0 == 5 then + return 0.3962,0.253,0.950025 + elseif iParam0 == 6 then + return 0.5185,0.253,0.950025 + end + end + end + return 0.0, 0.0, 0.947875 +end + +function func_376(iParam0, iParam1, bParam2, bParam3) + if not bParam2 then + if iParam1 == 0 then + if iParam0 == 0 then + return vector3(0.0, 0.0, 69.12) + elseif iParam0 == 1 then + return vector3(0.0, 0.0, 67.8) + elseif iParam0 == 2 then + return vector3(0.0, 0.0, 66.6) + elseif iParam0 == 3 then + return vector3(0.0, 0.0, 70.44) + elseif iParam0 == 4 then + return vector3(0.0, 0.0, 70.84) + elseif iParam0 == 5 then + return vector3(0.0, 0.0, 67.88) + elseif iParam0 == 6 then + return vector3(0.0, 0.0, 69.56) + end + elseif iParam0 == 1 then + if iParam0 == 0 then + return vector3(0.0, 0.0, 22.11) + elseif iParam0 == 1 then + return vector3(0.0, 0.0, 22.32) + elseif iParam0 == 2 then + return vector3(0.0, 0.0, 20.8) + elseif iParam0 == 3 then + return vector3(0.0, 0.0, 19.8) + elseif iParam0 == 4 then + return vector3(0.0, 0.0, 19.44) + elseif iParam0 == 5 then + return vector3(0.0, 0.0, 26.28) + elseif iParam0 == 6 then + return vector3(0.0, 0.0, 22.68) + end + elseif iParam0 == 2 then + if iParam0 == 0 then + return vector3(0.0, 0.0, -21.43) + elseif iParam0 == 1 then + return vector3(0.0, 0.0, -20.16) + elseif iParam0 == 2 then + return vector3(0.0, 0.0, -16.92) + elseif iParam0 == 3 then + return vector3(0.0, 0.0, -23.4) + elseif iParam0 == 4 then + return vector3(0.0, 0.0, -21.24) + elseif iParam0 == 5 then + return vector3(0.0, 0.0, -23.76) + elseif iParam0 == 6 then + return vector3(0.0, 0.0, -19.44) + end + elseif iParam0 == 3 then + if iParam0 == 0 then + return vector3(0.0, 0.0, -67.03) + elseif iParam0 == 1 then + return vector3(0.0, 0.0, -69.12) + elseif iParam0 == 2 then + return vector3(0.0, 0.0, -64.44) + elseif iParam0 == 3 then + return vector3(0.0, 0.0, -67.68) + elseif iParam0 == 4 then + return vector3(0.0, 0.0, -63.72) + elseif iParam0 == 5 then + return vector3(0.0, 0.0, -68.4) + elseif iParam0 == 6 then + return vector3(0.0, 0.0, -64.44) + end + end + else + if iParam1 == 0 then + if iParam0 == 0 then + return vector3(0.0, 0.0, 68.57) + elseif iParam0 == 1 then + return vector3(0.0, 0.0, 67.52) + elseif iParam0 == 2 then + return vector3(0.0, 0.0, 67.76) + elseif iParam0 == 3 then + return vector3(0.0, 0.0, 67.04) + elseif iParam0 == 4 then + return vector3(0.0, 0.0, 68.84) + elseif iParam0 == 5 then + return vector3(0.0, 0.0, 65.96) + elseif iParam0 == 6 then + return vector3(0.0, 0.0, 67.76) + end + elseif iParam1 == 1 then + if iParam0 == 0 then + return vector3(0.0, 0.0, 22.11) + elseif iParam0 == 1 then + return vector3(0.0, 0.0, 22) + elseif iParam0 == 2 then + return vector3(0.0, 0.0, 24.44) + elseif iParam0 == 3 then + return vector3(0.0, 0.0, 21.08) + elseif iParam0 == 4 then + return vector3(0.0, 0.0, 25.96) + elseif iParam0 == 5 then + return vector3(0.0, 0.0, 26.16) + elseif iParam0 == 6 then + return vector3(0.0, 0.0, 28.76) + end + elseif iParam1 == 2 then + if iParam0 == 0 then + return vector3(0.0, 0.0, -14.04) + elseif iParam0 == 1 then + return vector3(0.0, 0.0, -15.48) + elseif iParam0 == 2 then + return vector3(0.0, 0.0, -16.56) + elseif iParam0 == 3 then + return vector3(0.0, 0.0, -15.84) + elseif iParam0 == 4 then + return vector3(0.0, 0.0, -16.92) + elseif iParam0 == 5 then + return vector3(0.0, 0.0, -14.4) + elseif iParam0 == 6 then + return vector3(0.0, 0.0, -14.28) + end + elseif iParam1 == 3 then + if iParam0 == 0 then + return vector3(0.0, 0.0, -67.03) + elseif iParam0 == 1 then + return vector3(0.0, 0.0, -67.6) + elseif iParam0 == 2 then + return vector3(0.0, 0.0, -69.4) + elseif iParam0 == 3 then + return vector3(0.0, 0.0, -69.04) + elseif iParam0 == 4 then + return vector3(0.0, 0.0, -68.68) + elseif iParam0 == 5 then + return vector3(0.0, 0.0, -66.16) + elseif iParam0 == 6 then + return vector3(0.0, 0.0, -63.28) + end + end + end + if bParam3 then + vVar0.z = (vVar0.z + 90.0) + end + return vVar0 +end + +function getChipPropFromAmount(amount) + --? _x1 is 1 chip _st is stack of 10 chips + --vw_prop_chip_10dollar_x1 --! £10 + --vw_prop_chip_50dollar_x1 --! £50 + --vw_prop_chip_100dollar_x1 --! £100 + --vw_prop_chip_50dollar_st --! £50 stack + --vw_prop_chip_100dollar_st --! £100 stack + --vw_prop_chip_500dollar_x1 --! £500 + --vw_prop_chip_1kdollar_x1 --! £1,000 + --vw_prop_chip_500dollar_st --! £500 stack + --vw_prop_chip_5kdollar_x1 --! £5,000 + --vw_prop_chip_1kdollar_st --! £1,000 stack + --vw_prop_chip_10kdollar_x1 --! £10,000 + --vw_prop_chip_5kdollar_st --! £5,000 stack + --vw_prop_chip_10kdollar_st --! £10,000 stack + --vw_prop_plaq_5kdollar_x1 --! £5,000 + --vw_prop_plaq_5kdollar_st --! £5,000 stack + --vw_prop_plaq_10kdollar_x1 --! £10,0000 + --vw_prop_plaq_10kdollar_st --! £10,0000 stack + --* below not included in func in decompiled code + --vw_prop_vw_chips_pile_01a.ydr + --vw_prop_vw_chips_pile_02a.ydr + --vw_prop_vw_chips_pile_03a.ydr + --vw_prop_vw_coin_01a.ydr + amount = tonumber(amount) + if amount < 1000000 then + denominations = {10,50,100,500,1000,5000,10000} + chips = {} + local max = 7 + for k,v in ipairs(denominations) do + while amount >= denominations[max] do + table.insert(chips,denominations[max]) + amount = amount - denominations[max] + end + max = max - 1 + end + for k,v in ipairs(chips) do + chips[k] = getChipFromAmount(v) + end + return chips + elseif amount < 5000000 then + return {"vw_prop_vw_chips_pile_01a"} + elseif amount < 10000000 then + return {"vw_prop_vw_chips_pile_02a"} + else + return {"vw_prop_vw_chips_pile_03a"} + end + return {"vw_prop_chip_500dollar_st"} +end + +local chipsFromAmount = { + [1] = "vw_prop_vw_coin_01a", + [10] = "vw_prop_chip_10dollar_x1", + [50] = "vw_prop_chip_50dollar_x1", + [100] = "vw_prop_chip_100dollar_x1", + [500] = "vw_prop_chip_500dollar_x1", + [1000] = "vw_prop_chip_1kdollar_x1", + [5000] = "vw_prop_plaq_5kdollar_x1", + [10000] = "vw_prop_plaq_10kdollar_x1", +} + +function getChipFromAmount(amount) + return chipsFromAmount[amount] +end + +function blackjack_func_374(betAmount, iParam1, chairId, bParam3) --returns vector3 + --betAmount, 0, chairID, someBool + -- print("blackjack_func_374 params:") + -- print("betAmount: " .. tostring(betAmount)) + -- print("iParam1: " .. tostring(iParam1)) + -- print("chairId: " .. tostring(chairId)) + -- print("bParam3: " .. tostring(bParam3)) + fVar0 = 0.0 + vVar1 = vector3(0,0,0) + if not bParam3 then + -- print("now checking betAmount: " .. tostring(betAmount)) + if betAmount == 10 then + fVar0 = 0.95 + elseif betAmount == 20 then + fVar0 = 0.896 + elseif betAmount == 30 then + fVar0 = 0.901 + elseif betAmount == 40 then + fVar0 = 0.907 + elseif betAmount == 50 then + fVar0 = 0.95 + elseif betAmount == 60 then + fVar0 = 0.917 + elseif betAmount == 70 then + fVar0 = 0.922 + elseif betAmount == 80 then + fVar0 = 0.927 + elseif betAmount == 90 then + fVar0 = 0.932 + elseif betAmount == 100 then + fVar0 = 0.95 + elseif betAmount == 150 then + fVar0 = 0.904 + elseif betAmount == 200 then + fVar0 = 0.899 + elseif betAmount == 250 then + fVar0 = 0.914 + elseif betAmount == 300 then + fVar0 = 0.904 + elseif betAmount == 350 then + fVar0 = 0.924 + elseif betAmount == 400 then + fVar0 = 0.91 + elseif betAmount == 450 then + fVar0 = 0.935 + elseif betAmount == 500 then + fVar0 = 0.95 + elseif betAmount == 1000 then + fVar0 = 0.95 + elseif betAmount == 1500 then + fVar0 = 0.904 + elseif betAmount == 2000 then + fVar0 = 0.899 + elseif betAmount == 2500 then + fVar0 = 0.915 + elseif betAmount == 3000 then + fVar0 = 0.904 + elseif betAmount == 3500 then + fVar0 = 0.925 + elseif betAmount == 4000 then + fVar0 = 0.91 + elseif betAmount == 4500 then + fVar0 = 0.935 + elseif betAmount == 5000 then + fVar0 = 0.95 + elseif betAmount == 6000 then + fVar0 = 0.919 + elseif betAmount == 7000 then + fVar0 = 0.924 + elseif betAmount == 8000 then + fVar0 = 0.93 + elseif betAmount == 9000 then + fVar0 = 0.935 + elseif betAmount == 10000 then + fVar0 = 0.95 + elseif betAmount == 15000 then + fVar0 = 0.902 + elseif betAmount == 20000 then + fVar0 = 0.897 + elseif betAmount == 25000 then + fVar0 = 0.912 + elseif betAmount == 30000 then + fVar0 = 0.902 + elseif betAmount == 35000 then + fVar0 = 0.922 + elseif betAmount == 40000 then + fVar0 = 0.907 + elseif betAmount == 45000 then + fVar0 = 0.932 + elseif betAmount == 50000 then + fVar0 = 0.912 + end + if chairId == 0 then + if iParam1 == 0 then + vVar1 = vector3(0.712625, 0.170625, 0.0001) + elseif iParam1 == 1 then + vVar1 = vector3(0.6658, 0.218375, 0.0) + elseif iParam1 == 2 then + vVar1 = vector3(0.756775, 0.292775, 0.0) + elseif iParam1 == 3 then + vVar1 = vector3(0.701875, 0.3439, 0.0) + end + elseif chairId == 1 then + if iParam1 == 0 then + vVar1 = vector3(0.278125, -0.2571, 0.0) + elseif iParam1 == 1 then + vVar1 = vector3(0.280375, -0.190375, 0.0) + elseif iParam1 == 2 then + vVar1 = vector3(0.397775, -0.208525, 0.0) + elseif iParam1 == 3 then + vVar1 = vector3(0.39715, -0.1354, 0.0) + end + elseif chairId == 2 then + if iParam1 == 0 then + vVar1 = vector3(-0.30305, -0.2464, 0.0) + elseif iParam1 == 1 then + vVar1 = vector3(-0.257975, -0.19715, 0.0) + elseif iParam1 == 2 then + vVar1 = vector3(-0.186575, -0.2861, 0.0) + elseif iParam1 == 3 then + vVar1 = vector3(-0.141675, -0.237925, 0.0) + end + elseif chairId == 3 then + if iParam1 == 0 then + vVar1 = vector3(-0.72855, 0.17345, 0.0) + elseif iParam1 == 1 then + vVar1 = vector3(-0.652825, 0.177525, 0.0) + elseif iParam1 == 2 then + vVar1 = vector3(-0.6783, 0.0744, 0.0) + elseif iParam1 == 3 then + vVar1 = vector3(-0.604425, 0.082575, 0.0) + end + end + else + if betAmount == 10 then + fVar0 = 0.95 + elseif betAmount == 20 then + fVar0 = 0.896 + elseif betAmount == 30 then + fVar0 = 0.901 + elseif betAmount == 40 then + fVar0 = 0.907 + elseif betAmount == 50 then + fVar0 = 0.95 + elseif betAmount == 60 then + fVar0 = 0.917 + elseif betAmount == 70 then + fVar0 = 0.922 + elseif betAmount == 80 then + fVar0 = 0.927 + elseif betAmount == 90 then + fVar0 = 0.932 + elseif betAmount == 100 then + fVar0 = 0.95 + elseif betAmount == 150 then + fVar0 = 0.904 + elseif betAmount == 200 then + fVar0 = 0.899 + elseif betAmount == 250 then + fVar0 = 0.914 + elseif betAmount == 300 then + fVar0 = 0.904 + elseif betAmount == 350 then + fVar0 = 0.924 + elseif betAmount == 400 then + fVar0 = 0.91 + elseif betAmount == 450 then + fVar0 = 0.935 + elseif betAmount == 500 then + fVar0 = 0.95 + elseif betAmount == 1000 then + fVar0 = 0.95 + elseif betAmount == 1500 then + fVar0 = 0.904 + elseif betAmount == 2000 then + fVar0 = 0.899 + elseif betAmount == 2500 then + fVar0 = 0.915 + elseif betAmount == 3000 then + fVar0 = 0.904 + elseif betAmount == 3500 then + fVar0 = 0.925 + elseif betAmount == 4000 then + fVar0 = 0.91 + elseif betAmount == 4500 then + fVar0 = 0.935 + elseif betAmount == 5000 then + fVar0 = 0.953 + elseif betAmount == 6000 then + fVar0 = 0.919 + elseif betAmount == 7000 then + fVar0 = 0.924 + elseif betAmount == 8000 then + fVar0 = 0.93 + elseif betAmount == 9000 then + fVar0 = 0.935 + elseif betAmount == 10000 then + fVar0 = 0.95 + elseif betAmount == 15000 then + fVar0 = 0.902 + elseif betAmount == 20000 then + fVar0 = 0.897 + elseif betAmount == 25000 then + fVar0 = 0.912 + elseif betAmount == 30000 then + fVar0 = 0.902 + elseif betAmount == 35000 then + fVar0 = 0.922 + elseif betAmount == 40000 then + fVar0 = 0.907 + elseif betAmount == 45000 then + fVar0 = 0.932 + elseif betAmount == 50000 then + fVar0 = 0.912 + end + -- case 5000: + -- case 10000: + -- case 15000: + -- case 20000: + -- case 25000: + -- case 30000: + -- case 35000: + -- case 40000: + -- case 45000: + if betAmount == 50000 then + if chairId == 0 then + if iParam1 == 0 then + vVar1 = vector3(0.6931, 0.1952, 0.0) + elseif iParam1 == 1 then + vVar1 = vector3(0.724925, 0.26955, 0.0) + elseif iParam1 == 1 then + vVar1 = vector3(0.7374, 0.349625, 0.0) + elseif iParam1 == 1 then + vVar1 = vector3(0.76415, 0.419225, 0.0) + end + elseif chairId == 1 then + if iParam1 == 0 then + vVar1 = vector3(0.2827, -0.227825, 0.0) + elseif iParam1 == 1 then + vVar1 = vector3(0.3605, -0.1898, 0.0) + elseif iParam1 == 2 then + vVar1 = vector3(0.4309, -0.16365, 0.0) + elseif iParam1 == 3 then + vVar1 = vector3(0.49275, -0.111575, 0.0) + end + elseif chairId == 2 then + if iParam1 == 0 then + vVar1 = vector3(-0.279425, -0.2238, 0.0) + elseif iParam1 == 1 then + vVar1 = vector3(-0.200775, -0.25855, 0.0) + elseif iParam1 == 2 then + vVar1 = vector3(-0.125775, -0.26815, 0.0) + elseif iParam1 == 3 then + vVar1 = vector3(-0.05615, -0.29435, 0.0) + end + elseif chairId == 3 then + if iParam1 == 0 then + vVar1 = vector3(-0.685925, 0.173275, 0.0) + elseif iParam1 == 1 then + vVar1 = vector3(-0.6568, 0.092525, 0.0) + elseif iParam1 == 2 then + vVar1 = vector3(-0.612875, 0.033025, 0.0) + elseif iParam1 == 3 then + vVar1 = vector3(-0.58465, -0.0374, 0.0) + end + end + else + if chairId == 0 then + if iParam1 == 0 then + vVar1 = vector3(0.712625, 0.170625, 0.0) + elseif iParam1 == 1 then + vVar1 = vector3(0.6658, 0.218375, 0.0) + elseif iParam1 == 2 then + vVar1 = vector3(0.756775, 0.292775, 0.0) + elseif iParam1 == 3 then + vVar1 = vector3(0.701875, 0.3439, 0.0) + end + elseif chairId == 1 then + if iParam1 == 0 then + vVar1 = vector3(0.278125, -0.2571, 0.0) + elseif iParam1 == 1 then + vVar1 = vector3(0.280375, -0.190375, 0.0) + elseif iParam1 == 2 then + vVar1 = vector3(0.397775, -0.208525, 0.0) + elseif iParam1 == 3 then + vVar1 = vector3(0.39715, -0.1354, 0.0) + end + elseif chairId == 2 then + if iParam1 == 0 then + vVar1 = vector3(-0.30305, -0.2464, 0.0) + elseif iParam1 == 1 then + vVar1 = vector3(-0.257975, -0.19715, 0.0) + elseif iParam1 == 2 then + vVar1 = vector3(-0.186575, -0.2861, 0.0) + elseif iParam1 == 3 then + vVar1 = vector3(-0.141675, -0.237925, 0.0) + end + elseif chairId == 3 then + if iParam1 == 0 then + vVar1 = vector3(-0.72855, 0.17345, 0.0) + elseif iParam1 == 1 then + vVar1 = vector3(-0.652825, 0.177525, 0.0) + elseif iParam1 == 2 then + vVar1 = vector3(-0.6783, 0.0744, 0.0) + elseif iParam1 == 3 then + vVar1 = vector3(-0.604425, 0.082575, 0.0) + end + end + end + end + -- print(vVar1) + -- print(vVar1.z) + -- print(fVar0) + --vVar1.z = fVar0 + vVar1 = vVar1 + vector3(0.0,0.0,fVar0) + return vVar1 +end + +function blackjack_func_373(iParam0, iParam1, iParam2, bParam3) + if not bParam3 then + if iParam2 == 0 then + if iParam1 == 0 then + return vector3(0.0, 0.0, 72) + elseif iParam1 == 1 then + return vector3(0.0, 0.0, 64.8) + elseif iParam1 == 2 then + return vector3(0.0, 0.0, 74.52) + elseif iParam1 == 3 then + return vector3(0.0, 0.0, 72) + end + elseif iParam2 == 1 then + if iParam1 == 0 then + return vector3(0.0, 0.0, 12.96) + elseif iParam1 == 1 then + return vector3(0.0, 0.0, 29.16) + elseif iParam1 == 2 then + return vector3(0.0, 0.0, 32.04) + elseif iParam1 == 3 then + return vector3(0.0, 0.0, 32.04) + end + elseif iParam2 == 2 then + if iParam1 == 0 then + return vector3(0.0, 0.0, -18.36) + elseif iParam1 == 1 then + return vector3(0.0, 0.0, -18.72) + elseif iParam1 == 2 then + return vector3(0.0, 0.0, -15.48) + elseif iParam1 == 3 then + return vector3(0.0, 0.0, -18) + end + elseif iParam2 == 3 then + if iParam1 == 0 then + return vector3(0.0, 0.0, -79.2) + elseif iParam1 == 1 then + return vector3(0.0, 0.0, -68.76) + elseif iParam1 == 2 then + return vector3(0.0, 0.0, -57.6) + elseif iParam1 == 3 then + return vector3(0.0, 0.0, -64.8) + end + end + else + -- case 5000 then + -- case 10000 then + -- case 15000 then + -- case 20000 then + -- case 25000 then + -- case 30000 then + -- case 35000 then + -- case 40000 then + -- case 45000 then + if iParam0 == 50000 then + if iParam2 == 0 then + if iParam1 == 0 then + return vector3(0.0, 0.0, -16.56) + elseif iParam1 == 1 then + return vector3(0.0, 0.0, -22.32) + elseif iParam1 == 2 then + return vector3(0.0, 0.0, -10.8) + elseif iParam1 == 3 then + return vector3(0.0, 0.0, -9.72) + end + elseif iParam2 == 1 then + if iParam1 == 0 then + return vector3(0.0, 0.0, -69.12) + elseif iParam1 == 1 then + return vector3(0.0, 0.0, -64.8) + elseif iParam1 == 2 then + return vector3(0.0, 0.0, -58.68) + elseif iParam1 == 3 then + return vector3(0.0, 0.0, -51.12) + end + elseif iParam2 == 2 then + if iParam1 == 0 then + return vector3(0.0, 0.0, -112.32) + elseif iParam1 == 1 then + return vector3(0.0, 0.0, -108.36) + elseif iParam1 == 2 then + return vector3(0.0, 0.0, -99.72) + elseif iParam1 == 3 then + return vector3(0.0, 0.0, -102.6) + end + elseif iParam2 == 3 then + if iParam1 == 0 then + return vector3(0.0, 0.0, -155.88) + elseif iParam1 == 1 then + return vector3(0.0, 0.0, -151.92) + elseif iParam1 == 2 then + return vector3(0.0, 0.0, -147.24) + elseif iParam1 == 3 then + return vector3(0.0, 0.0, -146.52) + end + end + else + if iParam2 == 0 then + if iParam1 == 0 then + return vector3(0.0, 0.0, 72) + elseif iParam1 == 1 then + return vector3(0.0, 0.0, 64.8) + elseif iParam1 == 2 then + return vector3(0.0, 0.0, 74.52) + elseif iParam1 == 3 then + return vector3(0.0, 0.0, 72) + end + elseif iParam2 == 1 then + if iParam1 == 0 then + return vector3(0.0, 0.0, 12.96) + elseif iParam1 == 1 then + return vector3(0.0, 0.0, 29.16) + elseif iParam1 == 2 then + return vector3(0.0, 0.0, 32.04) + elseif iParam1 == 3 then + return vector3(0.0, 0.0, 32.04) + end + elseif iParam2 == 2 then + if iParam1 == 0 then + return vector3(0.0, 0.0, -18.36) + elseif iParam1 == 1 then + return vector3(0.0, 0.0, -18.72) + elseif iParam1 == 2 then + return vector3(0.0, 0.0, -15.48) + elseif iParam1 == 3 then + return vector3(0.0, 0.0, -18) + end + elseif iParam2 == 3 then + if iParam1 == 0 then + return vector3(0.0, 0.0, -79.2) + elseif iParam1 == 1 then + return vector3(0.0, 0.0, -68.76) + elseif iParam1 == 2 then + return vector3(0.0, 0.0, -57.6) + elseif iParam1 == 3 then + return vector3(0.0, 0.0, -64.8) + end + end + end + end + return vector3(0.0, 0.0, 0) +end + +function ButtonMessage(text) + BeginTextCommandScaleformString("STRING") + AddTextComponentScaleform(text) + EndTextCommandScaleformString() +end + +function Button(ControlButton) + N_0xe83a3e3557a56640(ControlButton) +end + +function setupBlackjackInstructionalScaleform(scaleform) + local scaleform = RequestScaleformMovie(scaleform) + while not HasScaleformMovieLoaded(scaleform) do + Wait(0) + end + PushScaleformMovieFunction(scaleform, "CLEAR_ALL") + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, "SET_CLEAR_SPACE") + PushScaleformMovieFunctionParameterInt(200) + PopScaleformMovieFunctionVoid() + + -- PushScaleformMovieFunction(scaleform, "SET_DATA_SLOT") + -- PushScaleformMovieFunctionParameterInt(1) + -- Button(GetControlInstructionalButton(2, 194, true)) -- The button to display + -- ButtonMessage("Leave table") --BACKSPACE + -- PopScaleformMovieFunctionVoid() + + -- PushScaleformMovieFunction(scaleform, "SET_DATA_SLOT") + -- PushScaleformMovieFunctionParameterInt(0) + -- Button(GetControlInstructionalButton(2, 191, true)) + -- ButtonMessage("Place bet") --ENTER + -- PopScaleformMovieFunctionVoid() + + -- PushScaleformMovieFunction(scaleform, "SET_DATA_SLOT") + -- PushScaleformMovieFunctionParameterInt(2) + -- Button(GetControlInstructionalButton(2, 11, true)) + -- ButtonMessage("Lower bet") --Page Down + -- PopScaleformMovieFunctionVoid() + + -- PushScaleformMovieFunction(scaleform, "SET_DATA_SLOT") + -- PushScaleformMovieFunctionParameterInt(3) + -- Button(GetControlInstructionalButton(2, 10, true)) + -- ButtonMessage("Increase bet") --Page Up + -- PopScaleformMovieFunctionVoid() + + -- PushScaleformMovieFunction(scaleform, "SET_DATA_SLOT") + -- PushScaleformMovieFunctionParameterInt(4) + -- Button(GetControlInstructionalButton(2, 22, true)) + -- ButtonMessage("Custom bet") --Space + -- PopScaleformMovieFunctionVoid() + + + + PushScaleformMovieFunction(scaleform, "DRAW_INSTRUCTIONAL_BUTTONS") + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, "SET_BACKGROUND_COLOUR") + PushScaleformMovieFunctionParameterInt(0) + PushScaleformMovieFunctionParameterInt(0) + PushScaleformMovieFunctionParameterInt(0) + PushScaleformMovieFunctionParameterInt(80) + PopScaleformMovieFunctionVoid() + + return scaleform +end + +function setupBlackjackMidBetScaleform(scaleform) + local scaleform = RequestScaleformMovie(scaleform) + while not HasScaleformMovieLoaded(scaleform) do + Wait(0) + end + PushScaleformMovieFunction(scaleform, "CLEAR_ALL") + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, "SET_CLEAR_SPACE") + PushScaleformMovieFunctionParameterInt(200) + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, "SET_DATA_SLOT") + PushScaleformMovieFunctionParameterInt(1) + Button(GetControlInstructionalButton(2, 194, true)) -- The button to display + ButtonMessage("Stand") --BACKSPACE + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, "SET_DATA_SLOT") + PushScaleformMovieFunctionParameterInt(4) + Button(GetControlInstructionalButton(2, 22, true)) + ButtonMessage("Hit") --Space + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, "DRAW_INSTRUCTIONAL_BUTTONS") + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, "SET_BACKGROUND_COLOUR") + PushScaleformMovieFunctionParameterInt(0) + PushScaleformMovieFunctionParameterInt(0) + PushScaleformMovieFunctionParameterInt(0) + PushScaleformMovieFunctionParameterInt(80) + PopScaleformMovieFunctionVoid() + + return scaleform +end + +function getDealerIdFromEntity(dealerEntity) + local closestID = nil + local closestDist = 10000 + local dealerCoords = GetEntityCoords(dealerEntity) + for k,v in pairs(cfg.blackjackTables) do + local actualDealerPos = v.dealerPos + if #(dealerCoords-dealerPos) < closestDist then + closestID = k + closestDist = #(dealerCoords-dealerPos) + end + end + return closestID +end + +function setBlackjackDealerPedVoiceGroup(randomNumber,dealerPed) + if randomNumber == 0 then + SetPedVoiceGroup(dealerPed,GetHashKey("S_M_Y_Casino_01_WHITE_01")) + elseif randomNumber == 1 then + SetPedVoiceGroup(dealerPed,GetHashKey("S_M_Y_Casino_01_ASIAN_01")) + elseif randomNumber == 2 then + SetPedVoiceGroup(dealerPed,GetHashKey("S_M_Y_Casino_01_ASIAN_02")) + elseif randomNumber == 3 then + SetPedVoiceGroup(dealerPed,GetHashKey("S_M_Y_Casino_01_ASIAN_01")) + elseif randomNumber == 4 then + SetPedVoiceGroup(dealerPed,GetHashKey("S_M_Y_Casino_01_WHITE_01")) + elseif randomNumber == 5 then + SetPedVoiceGroup(dealerPed,GetHashKey("S_M_Y_Casino_01_WHITE_02")) + elseif randomNumber == 6 then + SetPedVoiceGroup(dealerPed,GetHashKey("S_M_Y_Casino_01_WHITE_01")) + elseif randomNumber == 7 then + SetPedVoiceGroup(dealerPed,GetHashKey("S_F_Y_Casino_01_ASIAN_01")) + elseif randomNumber == 8 then + SetPedVoiceGroup(dealerPed,GetHashKey("S_F_Y_Casino_01_ASIAN_02")) + elseif randomNumber == 9 then + SetPedVoiceGroup(dealerPed,GetHashKey("S_F_Y_Casino_01_ASIAN_01")) + elseif randomNumber == 10 then + SetPedVoiceGroup(dealerPed,GetHashKey("S_F_Y_Casino_01_ASIAN_02")) + elseif randomNumber == 11 then + SetPedVoiceGroup(dealerPed,GetHashKey("S_F_Y_Casino_01_LATINA_01")) + elseif randomNumber == 12 then + SetPedVoiceGroup(dealerPed,GetHashKey("S_F_Y_Casino_01_LATINA_02")) + elseif randomNumber == 13 then + SetPedVoiceGroup(dealerPed,GetHashKey("S_F_Y_Casino_01_LATINA_01")) + end +end + +function setBlackjackDealerClothes(randomNumber,dealerPed) + if randomNumber == 0 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 3, 0, 0) + SetPedComponentVariation(dealerPed, 1, 1, 0, 0) + SetPedComponentVariation(dealerPed, 2, 3, 0, 0) + SetPedComponentVariation(dealerPed, 3, 1, 0, 0) + SetPedComponentVariation(dealerPed, 4, 0, 0, 0) + SetPedComponentVariation(dealerPed, 6, 1, 0, 0) + SetPedComponentVariation(dealerPed, 7, 2, 0, 0) + SetPedComponentVariation(dealerPed, 8, 3, 0, 0) + SetPedComponentVariation(dealerPed, 10, 1, 0, 0) + SetPedComponentVariation(dealerPed, 11, 1, 0, 0) + elseif randomNumber == 1 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 2, 2, 0) + SetPedComponentVariation(dealerPed, 1, 1, 0, 0) + SetPedComponentVariation(dealerPed, 2, 4, 0, 0) + SetPedComponentVariation(dealerPed, 3, 0, 3, 0) + SetPedComponentVariation(dealerPed, 4, 0, 0, 0) + SetPedComponentVariation(dealerPed, 6, 1, 0, 0) + SetPedComponentVariation(dealerPed, 7, 2, 0, 0) + SetPedComponentVariation(dealerPed, 8, 1, 0, 0) + SetPedComponentVariation(dealerPed, 10, 1, 0, 0) + SetPedComponentVariation(dealerPed, 11, 1, 0, 0) + elseif randomNumber == 2 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 2, 1, 0) + SetPedComponentVariation(dealerPed, 1, 1, 0, 0) + SetPedComponentVariation(dealerPed, 2, 2, 0, 0) + SetPedComponentVariation(dealerPed, 3, 0, 3, 0) + SetPedComponentVariation(dealerPed, 4, 0, 0, 0) + SetPedComponentVariation(dealerPed, 6, 1, 0, 0) + SetPedComponentVariation(dealerPed, 7, 2, 0, 0) + SetPedComponentVariation(dealerPed, 8, 1, 0, 0) + SetPedComponentVariation(dealerPed, 10, 1, 0, 0) + SetPedComponentVariation(dealerPed, 11, 1, 0, 0) + elseif randomNumber == 3 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 2, 0, 0) + SetPedComponentVariation(dealerPed, 1, 1, 0, 0) + SetPedComponentVariation(dealerPed, 2, 3, 0, 0) + SetPedComponentVariation(dealerPed, 3, 1, 3, 0) + SetPedComponentVariation(dealerPed, 4, 0, 0, 0) + SetPedComponentVariation(dealerPed, 6, 1, 0, 0) + SetPedComponentVariation(dealerPed, 7, 2, 0, 0) + SetPedComponentVariation(dealerPed, 8, 3, 0, 0) + SetPedComponentVariation(dealerPed, 10, 1, 0, 0) + SetPedComponentVariation(dealerPed, 11, 1, 0, 0) + elseif randomNumber == 4 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 4, 2, 0) + SetPedComponentVariation(dealerPed, 1, 1, 0, 0) + SetPedComponentVariation(dealerPed, 2, 3, 0, 0) + SetPedComponentVariation(dealerPed, 3, 0, 0, 0) + SetPedComponentVariation(dealerPed, 4, 0, 0, 0) + SetPedComponentVariation(dealerPed, 6, 1, 0, 0) + SetPedComponentVariation(dealerPed, 7, 2, 0, 0) + SetPedComponentVariation(dealerPed, 8, 1, 0, 0) + SetPedComponentVariation(dealerPed, 10, 1, 0, 0) + SetPedComponentVariation(dealerPed, 11, 1, 0, 0) + elseif randomNumber == 5 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 4, 0, 0) + SetPedComponentVariation(dealerPed, 1, 1, 0, 0) + SetPedComponentVariation(dealerPed, 2, 0, 0, 0) + SetPedComponentVariation(dealerPed, 3, 0, 0, 0) + SetPedComponentVariation(dealerPed, 4, 0, 0, 0) + SetPedComponentVariation(dealerPed, 6, 1, 0, 0) + SetPedComponentVariation(dealerPed, 7, 2, 0, 0) + SetPedComponentVariation(dealerPed, 8, 1, 0, 0) + SetPedComponentVariation(dealerPed, 10, 1, 0, 0) + SetPedComponentVariation(dealerPed, 11, 1, 0, 0) + elseif randomNumber == 6 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 4, 1, 0) + SetPedComponentVariation(dealerPed, 1, 1, 0, 0) + SetPedComponentVariation(dealerPed, 2, 4, 0, 0) + SetPedComponentVariation(dealerPed, 3, 1, 0, 0) + SetPedComponentVariation(dealerPed, 4, 0, 0, 0) + SetPedComponentVariation(dealerPed, 6, 1, 0, 0) + SetPedComponentVariation(dealerPed, 7, 2, 0, 0) + SetPedComponentVariation(dealerPed, 8, 3, 0, 0) + SetPedComponentVariation(dealerPed, 10, 1, 0, 0) + SetPedComponentVariation(dealerPed, 11, 1, 0, 0) + elseif randomNumber == 7 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 1, 1, 0) + SetPedComponentVariation(dealerPed, 1, 0, 0, 0) + SetPedComponentVariation(dealerPed, 2, 1, 0, 0) + SetPedComponentVariation(dealerPed, 3, 0, 3, 0) + SetPedComponentVariation(dealerPed, 4, 0, 0, 0) + SetPedComponentVariation(dealerPed, 6, 0, 0, 0) + SetPedComponentVariation(dealerPed, 7, 0, 0, 0) + SetPedComponentVariation(dealerPed, 8, 0, 0, 0) + SetPedComponentVariation(dealerPed, 10, 0, 0, 0) + SetPedComponentVariation(dealerPed, 11, 0, 0, 0) + elseif randomNumber == 8 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 1, 1, 0) + SetPedComponentVariation(dealerPed, 1, 0, 0, 0) + SetPedComponentVariation(dealerPed, 2, 1, 1, 0) + SetPedComponentVariation(dealerPed, 3, 1, 3, 0) + SetPedComponentVariation(dealerPed, 4, 0, 0, 0) + SetPedComponentVariation(dealerPed, 6, 0, 0, 0) + SetPedComponentVariation(dealerPed, 7, 2, 0, 0) + SetPedComponentVariation(dealerPed, 8, 1, 0, 0) + SetPedComponentVariation(dealerPed, 10, 0, 0, 0) + SetPedComponentVariation(dealerPed, 11, 0, 0, 0) + elseif randomNumber == 9 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 2, 0, 0) + SetPedComponentVariation(dealerPed, 1, 0, 0, 0) + SetPedComponentVariation(dealerPed, 2, 2, 0, 0) + SetPedComponentVariation(dealerPed, 3, 2, 3, 0) + SetPedComponentVariation(dealerPed, 4, 0, 0, 0) + SetPedComponentVariation(dealerPed, 6, 0, 0, 0) + SetPedComponentVariation(dealerPed, 7, 0, 0, 0) + SetPedComponentVariation(dealerPed, 8, 2, 0, 0) + SetPedComponentVariation(dealerPed, 10, 0, 0, 0) + SetPedComponentVariation(dealerPed, 11, 0, 0, 0) + elseif randomNumber == 10 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 2, 1, 0) + SetPedComponentVariation(dealerPed, 1, 0, 0, 0) + SetPedComponentVariation(dealerPed, 2, 2, 1, 0) + SetPedComponentVariation(dealerPed, 3, 3, 3, 0) + SetPedComponentVariation(dealerPed, 4, 1, 0, 0) + SetPedComponentVariation(dealerPed, 6, 1, 0, 0) + SetPedComponentVariation(dealerPed, 7, 2, 0, 0) + SetPedComponentVariation(dealerPed, 8, 3, 0, 0) + SetPedComponentVariation(dealerPed, 10, 0, 0, 0) + SetPedComponentVariation(dealerPed, 11, 0, 0, 0) + elseif randomNumber == 11 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 3, 0, 0) + SetPedComponentVariation(dealerPed, 1, 0, 0, 0) + SetPedComponentVariation(dealerPed, 2, 3, 0, 0) + SetPedComponentVariation(dealerPed, 3, 0, 1, 0) + SetPedComponentVariation(dealerPed, 4, 1, 0, 0) + SetPedComponentVariation(dealerPed, 6, 1, 0, 0) + SetPedComponentVariation(dealerPed, 7, 1, 0, 0) + SetPedComponentVariation(dealerPed, 8, 0, 0, 0) + SetPedComponentVariation(dealerPed, 10, 0, 0, 0) + SetPedComponentVariation(dealerPed, 11, 0, 0, 0) + SetPedPropIndex(dealerPed, 1, 0, 0, false) + elseif randomNumber == 12 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 3, 1, 0) + SetPedComponentVariation(dealerPed, 1, 0, 0, 0) + SetPedComponentVariation(dealerPed, 2, 3, 1, 0) + SetPedComponentVariation(dealerPed, 3, 1, 1, 0) + SetPedComponentVariation(dealerPed, 4, 1, 0, 0) + SetPedComponentVariation(dealerPed, 6, 1, 0, 0) + SetPedComponentVariation(dealerPed, 7, 2, 0, 0) + SetPedComponentVariation(dealerPed, 8, 1, 0, 0) + SetPedComponentVariation(dealerPed, 10, 0, 0, 0) + SetPedComponentVariation(dealerPed, 11, 0, 0, 0) + elseif randomNumber == 13 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 4, 0, 0) + SetPedComponentVariation(dealerPed, 1, 0, 0, 0) + SetPedComponentVariation(dealerPed, 2, 4, 0, 0) + SetPedComponentVariation(dealerPed, 3, 2, 1, 0) + SetPedComponentVariation(dealerPed, 4, 1, 0, 0) + SetPedComponentVariation(dealerPed, 6, 1, 0, 0) + SetPedComponentVariation(dealerPed, 7, 1, 0, 0) + SetPedComponentVariation(dealerPed, 8, 2, 0, 0) + SetPedComponentVariation(dealerPed, 10, 0, 0, 0) + SetPedComponentVariation(dealerPed, 11, 0, 0, 0) + SetPedPropIndex(dealerPed, 1, 0, 0, false) + end +end + +function DrawTimerBar2(title, text, barIndex) + local width = 0.13 + local hTextMargin = 0.003 + local rectHeight = 0.038 + local textMargin = 0.008 + + local rectX = GetSafeZoneSize() - width + width / 2 + local rectY = GetSafeZoneSize() - rectHeight + rectHeight / 2 - (barIndex - 1) * (rectHeight + 0.005) + + DrawSprite("timerbars", "all_black_bg", rectX, rectY, width, 0.038, 0, 0, 0, 0, 128) + + DrawTimerBarText(title, GetSafeZoneSize() - width + hTextMargin, rectY - textMargin, 0.32) + DrawTimerBarText(string.upper(text), GetSafeZoneSize() - hTextMargin, rectY - 0.0175, 0.5, true, width / 2) +end + +function DrawNoiseBar(noise, barIndex) + DrawTimerBar2("NOISE", math.floor(noise), barIndex) +end + +function DrawTimerBarText(text, x, y, scale, right, width) + SetTextFont(0) + SetTextScale(scale, scale) + SetTextColour(254, 254, 254, 255) + + if right then + SetTextWrap(x - width, x) + SetTextRightJustify(true) + end + + BeginTextCommandDisplayText("STRING") + AddTextComponentSubstringPlayerName(text) + EndTextCommandDisplayText(x, y) +end + +function DrawAdvancedNativeText(x,y,w,h,sc, text, r,g,b,a,font,jus) + SetTextFont(font) + SetTextScale(sc, sc) + N_0x4e096588b13ffeca(jus) + SetTextColour(254, 254, 254, 255) + SetTextEntry("STRING") + AddTextComponentString(text) + DrawText(x - 0.1+w, y - 0.02+h) +end + + +RegisterNetEvent("blackjack:notify") +AddEventHandler("blackjack:notify",function(msg) + QBCore.Functions.Notify(msg, 'success', 3500) +end) + +function getGenericTextInput(type) + if type == nil then type = "" end + AddTextEntry('FMMC_MPM_NA', "Enter " .. tostring(type)) + DisplayOnscreenKeyboard(1, "FMMC_MPM_NA", "Enter " .. tostring(type) .. " message", "", "", "", "", 30) + while (UpdateOnscreenKeyboard() == 0) do + DisableAllControlActions(0); + Wait(0); + end + if (GetOnscreenKeyboardResult()) then + local result = GetOnscreenKeyboardResult() + if result then + return result + end + end + return false +end + +--Debug Commands-- +RegisterCommand("cleantable",function() + for k,v in pairs(cardObjects) do + for _,obj in pairs(v) do + DeleteObject(obj) + end + end +end) + +-- RegisterCommand("debugtable",function() +-- print(dump(blackjackTableData)) +-- end) + +-- RegisterCommand("debugtable2",function() +-- TriggerServerEvent("Blackjack:requestBlackjackTableData") +-- end) + +-- RegisterCommand("testcasinosound", function() +-- closestDealerPed, closestDealerPedDistance = getClosestDealer() +-- PlayAmbientSpeech1(closestDealerPed,"MINIGAME_DEALER_BUSTS","SPEECH_PARAMS_FORCE_NORMAL_CLEAR",1) +-- end) + +-- RegisterCommand("testcasinoanim", function() +-- TaskPlayAnim(PlayerPedId(), "anim_casino_b@amb@casino@games@blackjack@dealer", "check_card", 3.0, 1.0, -1, 2, 0, 0, 0, 0 ) +-- -- print("animation started") +-- -- while not HasEntityAnimFinished(PlayerPedId(), "anim_casino_b@amb@casino@games@blackjack@dealer", "check_card",3) do +-- -- -- print("waiting for check card animation to end") +-- -- Wait(0) +-- -- end +-- while true do +-- -- print("hasAnimEventFired: " .. tostring(HasAnimEventFired(PlayerPedId(),585557868))) +-- Wait(0) +-- end +-- -- print("animation finished") +-- end) + +-- RegisterCommand("debugblackjack", function() +-- -- print("closestBlackjackChair: " .. tostring(closestChairDist)) +-- -- print("closestChair: " .. tostring(closestChair)) +-- -- print("Local_198f_247: " .. tostring(Local_198f_247)) +-- end) + +-- RegisterCommand("debugtablepeds",function() +-- print(dump(dealerPeds)) +-- end) + +-- RegisterCommand("testcleanup",function() +-- print(dump(cardObjects)) +-- end) + +-- RegisterCommand("hitme",function() +-- requestCard() +-- end) + +-- RegisterCommand("stand",function() +-- declineCard() +-- end) + +-- RegisterCommand("dealerhit",function() +-- closestDealerPed, closestDealerPedDistance = getClosestDealer() +-- startStandOrHit(gameId,closestDealerPed,1) +-- end) + +-- RegisterCommand("dealergive",function() +-- closestDealerPed, closestDealerPedDistance = getClosestDealer() +-- cardData = {20,19} +-- chairId = 7 +-- tableId = 1 +-- cardObj = startDealing(closestChair,closestDealerPed,gameId,cardData,chairId,0,10,tableId) +-- SetTimeout(5000,function() +-- DeleteObject(cardObj) +-- -- print("DELETED.") +-- end) +-- end) + +-- RegisterCommand("debugchips", function(source,args,rawCommand) +-- local chips = args[2] +-- -- print("getChipPropFromAmount(" .. tostring(chips) .. ")") +-- getChipPropFromAmount(chips) +-- end) diff --git a/resources/[qb]/[qb_casino]/casino-blackjackRT98/fxmanifest.lua b/resources/[qb]/[qb_casino]/casino-blackjackRT98/fxmanifest.lua new file mode 100644 index 0000000..510f23e --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-blackjackRT98/fxmanifest.lua @@ -0,0 +1,13 @@ +fx_version 'cerulean' + +game "gta5" + +description "DiamondBlackjack created by Robbster" + +client_scripts { + "cl_blackjack.lua", +} + +server_script "sv_blackjack.lua" + + diff --git a/resources/[qb]/[qb_casino]/casino-blackjackRT98/sv_blackjack.lua b/resources/[qb]/[qb_casino]/casino-blackjackRT98/sv_blackjack.lua new file mode 100644 index 0000000..e89439b --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-blackjackRT98/sv_blackjack.lua @@ -0,0 +1,700 @@ + +local QBCore = exports['qb-core']:GetCoreObject() + + +local ItemList = { + ["casino_redchip"] = 1 +} +QBCore.Functions.CreateCallback('BLACKJACKRT98:server:blackChipsAmount', function(source, cb) + local retval = 0 + local Player = QBCore.Functions.GetPlayer(source) + if Player.PlayerData.items ~= nil and next(Player.PlayerData.items) ~= nil then + for k, v in pairs(Player.PlayerData.items) do + if Player.PlayerData.items[k] ~= nil then + if ItemList[Player.PlayerData.items[k].name] ~= nil then + retval = retval + (ItemList[Player.PlayerData.items[k].name] * Player.PlayerData.items[k].amount) + end + end + end + end + cb(retval) +end) + + +local blackjackTables = { + --[chairId] == false or source if taken + [0] = false, + [1] = false, + [2] = false, + [3] = false, + [4] = false, + [5] = false, + [6] = false, + [7] = false, + [8] = false, + [9] = false, + [10] = false, + [11] = false, + [12] = false, + [13] = false, + [14] = false, + [15] = false, +} + +for i=0,127,1 do + blackjackTables[i] = false +end + +local blackjackGameInProgress = {} +local blackjackGameData = {} + +function tryTakeChips(source,amount) + local Player = QBCore.Functions.GetPlayer(source) + local ItemList = { + ["casino_redchip"] = 1, + } + local playerChips = Player.Functions.GetItemByName("casino_redchip") + local canBet = false + + if Player.PlayerData.items ~= nil and next(Player.PlayerData.items) ~= nil then + for k, v in pairs(Player.PlayerData.items) do + if Player.PlayerData.items[k] ~= nil then + if ItemList[Player.PlayerData.items[k].name] ~= nil then + if playerChips.amount >= amount then + Player.Functions.RemoveItem("casino_redchip", amount) + TriggerClientEvent('inventory:client:ItemBox', source, QBCore.Shared.Items["casino_redchip"], 'remove') + canBet = true + end + end + end + end + end + + return canBet +end + + +function giveChips(source,amount) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if Player ~= nil then + if Player.Functions.AddItem('casino_redchip', amount, nil, {["quality"] = 100}) then + TriggerClientEvent('inventory:client:ItemBox', src, QBCore.Shared.Items["casino_redchip"], "add", amount) + TriggerClientEvent('QBCore:Notify', src, "You Won "..math.floor(amount).." Casino Chips!") + else + TriggerClientEvent('QBCore:Notify', src, 'You have to much in your pockets', 'error') + end + end +end + +AddEventHandler('playerDropped', function (reason) + local source = source + for k,v in pairs(blackjackTables) do + if v == source then + blackjackTables[k] = false + end + end +end) + +RegisterNetEvent("Blackjack:requestBlackjackTableData") +AddEventHandler("Blackjack:requestBlackjackTableData", function() + local source = source + TriggerClientEvent("Blackjack:sendBlackjackTableData",source,blackjackTables) +end) + +RegisterNetEvent("Blackjack:requestSitAtBlackjackTable") +AddEventHandler("Blackjack:requestSitAtBlackjackTable", function(chairId) + local source = source + + if source ~= nil then + for k,v in pairs(blackjackTables) do + if v == source then + blackjackTables[k] = false + --print("[Error] Player tried to sit at a table, but he's already sitting there :?, proceeding...") + return + end + end + --print("setting blackjacktable chairID: " .. tostring(chairId)) + blackjackTables[chairId] = source + TriggerClientEvent("Blackjack:sendBlackjackTableData",-1,blackjackTables) + TriggerClientEvent("Blackjack:sitAtBlackjackTable",source,chairId) + else + TriggerClientEvent('QBCore:Notify', source, 'cant sit you down.', 'error') + end +end) + +RegisterNetEvent("Blackjack:leaveBlackjackTable") +AddEventHandler("Blackjack:leaveBlackjackTable", function(chairId) + local source = source + + if source ~= nil then + for k,v in pairs(blackjackTables) do + if v == source then + blackjackTables[k] = false + end + end + TriggerClientEvent("Blackjack:sendBlackjackTableData",-1,blackjackTables) + end +end) + +RegisterNetEvent("Blackjack:setBlackjackBet") +AddEventHandler("Blackjack:setBlackjackBet",function(gameId,betAmount,chairId) + local source = source + + if gameId ~= nil and betAmount ~= nil and chairId ~= nil then + if blackjackGameData[gameId] == nil then + blackjackGameData[gameId] = {} + end + if not blackjackGameInProgress[gameId] then + if tonumber(betAmount) then + betAmount = tonumber(betAmount) + if betAmount > 0 then + if tryTakeChips(source,betAmount) then + ----print("Taken",betAmount,"chips from id",source) + if blackjackGameData[gameId][source] == nil then + blackjackGameData[gameId][source] = {} + end + blackjackGameData[gameId][source][1] = betAmount + ----print("GameId: " .. tostring(gameId) .. " source: " .. tostring(source) .. " has placed a bet of " .. tostring(betAmount)) + TriggerClientEvent("Blackjack:successBlackjackBet",source) + TriggerClientEvent("Blackjack:syncChipsPropBlackjack",-1,betAmount,chairId) + -- TriggerClientEvent('QBCore:Notify', source, 'Bet placed: ' .. tostring(betAmount) .. ' chips.', 'success') + else + TriggerClientEvent('QBCore:Notify', source, 'You dont have enough chips', 'error') + TriggerClientEvent("doj:client:openBetMenu", source) + end + end + end + end + else + TriggerClientEvent('QBCore:Notify', source, 'Error betting!', 'error') + + end +end) + +RegisterNetEvent("Blackjack:hitBlackjack") +AddEventHandler("Blackjack:hitBlackjack",function(gameId,nextCardCount) + local source = source + blackjackGameData[gameId][source][2][nextCardCount] = true +end) + +RegisterNetEvent("Blackjack:standBlackjack") +AddEventHandler("Blackjack:standBlackjack",function(gameId,nextCardCount) + local source = source + blackjackGameData[gameId][source][2][nextCardCount] = false +end) + +for i=0,31,1 do + CreateThread(function() + math.randomseed(os.clock()*100000000000) + while true do --blackjack game management thread + math.random() + math.random() + math.random() + local game_ready = false + local players_ready = {} + local tableId = i + local chairIdInitial = i*4 --0-3,4-7,8-11,12-15 + local chairIdFinal = (i*4)+3 + for chairID=chairIdInitial,chairIdFinal do + ----print("checking chairID[" .. tostring(chairID) .. "] = " .. tostring(blackjackTables[chairID])) + if blackjackTables[chairID] ~= false then + table.insert(players_ready,blackjackTables[chairID]) + game_ready = true + end + end + if game_ready then + local gameId = math.random(1000,10000000) + --print("generated gameId",gameId) + blackjackGameData[gameId] = {} --init game data + blackjackGameInProgress[gameId] = false + for k,v in pairs(players_ready) do + local source = v + blackjackGameData[gameId][v] = {} + if source ~= nil then + TriggerClientEvent("Blackjack:beginBetsBlackjack",source,gameId,tableId) + end + end + Wait(21000) --Wait 20 seconds for everyone to put bets up + if blackjackGameData[gameId] ~= nil then + for k,v in pairs(blackjackGameData[gameId]) do + if v ~= nil then + local playerBetted = false + betAmount = v[1] + -- --print("betAmount: " .. tostring(betAmount)) + -- --print("v: " .. tostring(v)) + -- --print("vdump: " .. dump(blackjackGameData[gameId][k])) + if betAmount ~= nil and betAmount > 0 then + playerBetted = true + end + if not playerBetted then + blackjackGameData[gameId][k] = nil + end + end + end + if not isTableEmpty(blackjackGameData[gameId]) then + blackjackGameInProgress[gameId] = true + --generate random cards here to send? in round "1" + for cardIndex=0,1,1 do + for chairID=chairIdInitial,chairIdFinal do + if blackjackTables[chairID] ~= false then + local source = blackjackTables[chairID] + if blackjackGameData[gameId] == nil then + blackjackGameData[gameId] = {} + end + if blackjackGameData[gameId][source] == nil then + blackjackGameData[gameId][source] = {} + end + if blackjackGameData[gameId][source][1] ~= nil then + if blackjackGameData[gameId][source][1] > 0 then + if blackjackGameData[gameId][source]["cardData"] == nil then + blackjackGameData[gameId][source]["cardData"] = {} + end + local randomCard = math.random(1,52) + table.insert(blackjackGameData[gameId][source]["cardData"], randomCard) + TriggerClientEvent("Blackjack:beginCardGiveOut",-1,gameId,blackjackGameData[gameId][source]["cardData"],chairID,cardIndex,getCurrentHand(gameId,source),tableId) + Wait(3500) + else + blackjackGameData[gameId][source] = nil + end + else + blackjackGameData[gameId][source] = nil + end + end + end + if blackjackGameData[gameId]["dealer"] == nil then + blackjackGameData[gameId]["dealer"] = {} + end + if blackjackGameData[gameId]["dealer"]["cardData"] == nil then + blackjackGameData[gameId]["dealer"]["cardData"] = {} + end + if cardIndex == 0 then + local randomCard = math.random(1,52) + ----print("randomDealerCard: " .. tostring(randomCard)) + table.insert(blackjackGameData[gameId]["dealer"]["cardData"], randomCard) + TriggerClientEvent("Blackjack:beginCardGiveOut",-1,gameId,blackjackGameData[gameId]["dealer"]["cardData"],gameId,cardIndex,getCurrentHand(gameId,"dealer"),tableId) + end + Wait(1500) --Wait between each initial give out card + end + --Wait(6000) --Wait for dealer to check own card + for chairID=chairIdInitial,chairIdFinal do + if blackjackTables[chairID] ~= false then + local source = blackjackTables[chairID] + if blackjackGameData[gameId][source] ~= nil then + local nextCardCount = 1 + local currentHand = getCurrentHand(gameId,source) + if currentHand < 21 then + TriggerClientEvent("Blackjack:standOrHit",-1,gameId,chairID,nextCardCount,tableId) + blackjackGameData[gameId][source][2] = {} + ----print("initialize card count = 1") + while nextCardCount >= 1 do + ----print("while card count >= 1 waiting for a response... cardCount is: " .. tostring(nextCardCount)) + secondsWaited = 0 + ----print("error debug #1") + ----print("gameId",gameId) + --print(dump(blackjackGameData[gameId])) + ----print("=======") + while blackjackGameData[gameId][source][2][nextCardCount] == nil and secondsWaited < 20 do + Wait(100) + secondsWaited = secondsWaited + 0.1 + ------print("response to stand or hit is still false") + end + --print("response received! [ok]") + if blackjackGameData[gameId][source][2][nextCardCount] == true then --if hit + ----print("response was hit") + nextCardCount = nextCardCount + 1 + local randomCard = math.random(1,52) + table.insert(blackjackGameData[gameId][source]["cardData"], randomCard) + TriggerClientEvent("Blackjack:singleCard",-1,gameId,randomCard,chairID,nextCardCount,getCurrentHand(gameId,source),tableId) + Wait(2000) + local currentHand = getCurrentHand(gameId,source) + ----print("Checking for bust... currentHand: " .. tostring(currentHand)) + if currentHand > 21 then + ----print("currentHand > 21") + TriggerClientEvent("Blackjack:bustBlackjack",-1,chairID,tableId) + nextCardCount = 0 + blackjackGameData[gameId][source]["status"] = "bust" + local lostAmount = blackjackGameData[gameId][source][1] + TriggerClientEvent('QBCore:Notify', source, 'Lost -'..tostring(lostAmount).." chips", 'error') + if lostAmount > 10000000 then + TriggerClientEvent('chatMessage', -1, "Diamond Casino | " .. GetPlayerName(source) .. " has LOST " .. tostring(getMoneyStringFormatted(lostAmount)) .. " chips!") + end + elseif currentHand < 21 then + ----print("currentHand < 21") + TriggerClientEvent("Blackjack:standOrHit",-1,gameId,chairID,nextCardCount,tableId) + else + ----print("currentHand == 21") + ----print("got 21 auto-standing") + nextCardCount = 0 + blackjackGameData[gameId][source]["status"] = "stand" + end + elseif blackjackGameData[gameId][source][2][nextCardCount] == false then --if stand + ----print("response was false") + nextCardCount = 0 + blackjackGameData[gameId][source]["status"] = "stand" + else + ----print("response was false") + nextCardCount = 0 + blackjackGameData[gameId][source]["status"] = "stand" + end + end + else + ----print("got 21 auto-standing") + blackjackGameData[gameId][source]["status"] = "stand" + end + end + TriggerClientEvent("Blackjack:endStandOrHitPhase",-1,chairID,tableId) + end + end + local randomCard = math.random(1,52) + ----print("randomDealerCard: " .. tostring(randomCard)) + table.insert(blackjackGameData[gameId]["dealer"]["cardData"], randomCard) + TriggerClientEvent("Blackjack:beginCardGiveOut",-1,gameId,blackjackGameData[gameId]["dealer"]["cardData"],gameId,1,getCurrentHand(gameId,"dealer"),tableId) + Wait(2800) + dealerHand = getCurrentHand(gameId,"dealer") + TriggerClientEvent("Blackjack:flipDealerCard",-1,dealerHand,tableId,gameId) + Wait(2800) + --Dealer hit til 17 logic + local allPlayersHaveBusted = true + ----print("allPlayersHaveBusted loop") + for k,v in pairs(blackjackGameData[gameId]) do + local betStatus = v["status"] + ----print("betStatus: " .. tostring(betStatus)) + if betStatus ~= nil then + if betStatus == "stand" then + allPlayersHaveBusted = false + ----print("allPlayersHaveBusted!") + end + end + end + dealerHand = getCurrentHand(gameId,"dealer") + if not allPlayersHaveBusted then + ----print("dealing hand is: " .. tostring(dealerHand)) + if dealerHand >= 17 then + ----print("dealing hand is: " .. tostring(dealerHand) .. " so standing") + else + ----print("dealing hand is: " .. tostring(dealerHand) .. " so hitting") + local nextCardCount = 2 + local highestPlayerHand = 0 + ----print("highestPlayerHand",highestPlayerHand) + for k,v in pairs(blackjackGameData[gameId]) do + if k ~= "dealer" then + playerHand = getCurrentHand(gameId,k) + ----print("================") + ----print("playerHand",playerHand) + ----print("highestPlayerHand",highestPlayerHand) + ----print("================") + if playerHand ~= nil then + if playerHand > highestPlayerHand and playerHand <= 21 then + highestPlayerHand = playerHand + ----print("highestPlayerHand",highestPlayerHand,"= playerHand",playerHand) + end + end + end + end + while dealerHand < 17 do + local randomCard = math.random(1,52) + ----print("randomDealerCard: " .. tostring(randomCard)) + table.insert(blackjackGameData[gameId]["dealer"]["cardData"], randomCard) + TriggerClientEvent("Blackjack:singleDealerCard",-1,gameId,randomCard,nextCardCount,getCurrentHand(gameId,"dealer"),tableId) + Wait(2800) + nextCardCount = nextCardCount + 1 + dealerHand = getCurrentHand(gameId,"dealer") + end + end + end + for k,v in pairs(blackjackGameData[gameId]) do + if k ~= "dealer" then + local source = k + if blackjackGameData[gameId][source] ~= nil then + ----print("Checking source: " .. tostring(source) .. " for bust when final checks are doing") + ----print("result: " .. tostring(blackjackGameData[gameId][source]["status"])) + ----print("table dump:") + --print(dump(blackjackGameData[gameId][source])) + if blackjackGameData[gameId][source]["status"] ~= "bust" then + local currentHand = getCurrentHand(gameId,source) + ----print("Checking for bust... currentHand: " .. tostring(currentHand)) + ----print("dealerHand: " .. tostring(dealerHand)) + if currentHand ~= nil then + if currentHand <= 21 then + local potentialWinAmount = blackjackGameData[gameId][source][1] * 2 + local potentialPushAmount = blackjackGameData[gameId][source][1] + local playerPing = GetPlayerPing(source) + if dealerHand > 21 then + ----print("source: " .. tostring(source) .. " wins!") + giveChips(source,potentialWinAmount) + if playerPing ~= nil then + if playerPing > 0 then + TriggerClientEvent("Blackjack:blackjackWin",source,tableId) + TriggerClientEvent('QBCore:Notify', source, 'You have BLACKJACK! +'..tostring(potentialWinAmount)..' chips', 'success') + if potentialPushAmount > 10000000 then + TriggerClientEvent('chatMessage', -1, "Diamond Casino | " .. GetPlayerName(source) .. " has WON " .. tostring(getMoneyStringFormatted(potentialPushAmount)) .. " chips!") + end + end + end + TriggerClientEvent("Blackjack:dealerBusts",-1,tableId) + elseif currentHand > dealerHand and currentHand <= 21 then + ----print("source: " .. tostring(source) .. " wins!") + giveChips(source,potentialWinAmount) + if playerPing ~= nil then + if playerPing > 0 then + TriggerClientEvent("Blackjack:blackjackWin",source,tableId) + TriggerClientEvent('QBCore:Notify', source, 'You have BLACKJACK! +'..tostring(potentialWinAmount)..' chips', 'success') + if potentialPushAmount > 10000000 then + TriggerClientEvent('chatMessage', -1, "Diamond Casino | " .. GetPlayerName(source) .. " has WON " .. tostring(getMoneyStringFormatted(potentialPushAmount)) .. " chips!") + end + end + end + elseif currentHand == dealerHand then + ----print("source: " .. tostring(source) .. " pushes!") + giveChips(source,potentialPushAmount) + if playerPing ~= nil then + if playerPing > 0 then + TriggerClientEvent("Blackjack:blackjackPush",source,tableId) + TriggerClientEvent('QBCore:Notify', source, 'You pushed!', 'error') + end + end + else + ----print("source: " .. tostring(source) .. " loses!") + if playerPing ~= nil then + if playerPing > 0 then + TriggerClientEvent("Blackjack:blackjackLose",source,tableId) + TriggerClientEvent('QBCore:Notify', source, 'You lost! -'..tostring(potentialPushAmount)..' chips', 'error') + if potentialPushAmount > 10000000 then + TriggerClientEvent('chatMessage', -1, "Diamond Casino | " .. GetPlayerName(source) .. " has LOST " .. tostring(getMoneyStringFormatted(potentialPushAmount)) .. " chips!") + end + end + end + end + end + end + end + end + end + end + for chairID=chairIdInitial,chairIdFinal do + if blackjackTables[chairID] ~= false then + local source = blackjackTables[chairID] + if blackjackGameData[gameId][source] ~= nil then + TriggerClientEvent("Blackjack:chipsCleanup",-1,chairID,tableId) + TriggerClientEvent("Blackjack:chipsCleanup",-1,tostring(chairID).."chips",tableId) + ----print("chips cleanup for chairID, waiting 2 seconds....") + Wait(3500) + end + end + end + ----print("chips cleanup for dealer") + TriggerClientEvent("Blackjack:chipsCleanup",-1,gameId,tableId) + for chairID=chairIdInitial,chairIdFinal do + TriggerClientEvent("Blackjack:chipsCleanupNoAnim",-1,chairID,tableId) + TriggerClientEvent("Blackjack:chipsCleanupNoAnim",-1,tostring(chairID).."chips",tableId) + end + blackjackGameInProgress[gameId] = false + else + ----print("Game not started") + end + else + ----print("No one betted :(") + end + else + Wait(1000) + end + Wait(1000) --Check ever second if anyones sitting at the table + end + end) +end + +--1,1,3,6 +function getCurrentHand(gameId,userId) + ----print("error debug #2") + ----print("gameId",gameId) + ----print("userId",userId) + --print(dump(blackjackGameData[gameId])) + ----print("=======") + if blackjackGameData[gameId][userId]["cardData"] ~= nil then + local hand = 0 + local numberOfAces = 0 + for k,v in pairs(blackjackGameData[gameId][userId]["cardData"]) do + local nextCard = getCardNumberFromCardId(v) + if nextCard == 11 then + numberOfAces = numberOfAces + 1 + else + hand = hand + nextCard + end + end + for i=1,numberOfAces,1 do + if i == 1 then + if hand + 11 > 21 then + nextCard = 1 + else + nextCard = 11 + end + else + nextCard = 1 + end + hand = hand + nextCard + end + return hand + end +end + +function chairIdToTableId(chairId) + if chairId <= 3 then return 0 end + if chairId <= 7 then return 1 end + if chairId <= 11 then return 2 end + if chairId <= 15 then return 3 end +end + +function getCardNumberFromCardId(cardId) + if cardId == 1 then + return 11 + elseif cardId == 2 then + return 2 + elseif cardId == 3 then + return 3 + elseif cardId == 4 then + return 4 + elseif cardId == 5 then + return 5 + elseif cardId == 6 then + return 6 + elseif cardId == 7 then + return 7 + elseif cardId == 8 then + return 8 + elseif cardId == 9 then + return 9 + elseif cardId == 10 then + return 10 + elseif cardId == 11 then + return 10 + elseif cardId == 12 then + return 10 + elseif cardId == 13 then + return 10 + elseif cardId == 14 then + return 11 + elseif cardId == 15 then + return 2 + elseif cardId == 16 then + return 3 + elseif cardId == 17 then + return 4 + elseif cardId == 18 then + return 5 + elseif cardId == 19 then + return 6 + elseif cardId == 20 then + return 7 + elseif cardId == 21 then + return 8 + elseif cardId == 22 then + return 9 + elseif cardId == 23 then + return 10 + elseif cardId == 24 then + return 10 + elseif cardId == 25 then + return 10 + elseif cardId == 26 then + return 10 + elseif cardId == 27 then + return 11 + elseif cardId == 28 then + return 2 + elseif cardId == 29 then + return 3 + elseif cardId == 30 then + return 4 + elseif cardId == 31 then + return 5 + elseif cardId == 32 then + return 6 + elseif cardId == 33 then + return 7 + elseif cardId == 34 then + return 8 + elseif cardId == 35 then + return 9 + elseif cardId == 36 then + return 10 + elseif cardId == 37 then + return 10 + elseif cardId == 38 then + return 10 + elseif cardId == 39 then + return 10 + elseif cardId == 40 then + return 11 + elseif cardId == 41 then + return 2 + elseif cardId == 42 then + return 3 + elseif cardId == 43 then + return 4 + elseif cardId == 44 then + return 5 + elseif cardId == 45 then + return 6 + elseif cardId == 46 then + return 7 + elseif cardId == 47 then + return 8 + elseif cardId == 48 then + return 9 + elseif cardId == 49 then + return 10 + elseif cardId == 50 then + return 10 + elseif cardId == 51 then + return 10 + elseif cardId == 52 then + return 10 + end +end + +function isTableEmpty(self) + for _, _ in pairs(self) do + return false + end + return true +end + +function getMoneyStringFormatted(cashString) + local i, j, minus, int, fraction = tostring(cashString):find('([-]?)(%d+)([.]?%d*)') + + -- reverse the int-string and append a comma to all blocks of 3 digits + int = int:reverse():gsub("(%d%d%d)", "%1,") + + -- reverse the int-string back remove an optional comma and put the + -- optional minus and fractional part back + return minus .. int:reverse():gsub("^,", "") .. fraction +end + +function dump(o) + if type(o) == 'table' then + local s = '{ ' + for k,v in pairs(o) do + if type(k) ~= 'number' then k = '"'..k..'"' end + s = s .. '['..k..'] = ' .. dump(v) .. ',' + end + return s .. '} ' + else + return tostring(o) + end + end + +-- RegisterCommand("debugtableserver",function() +-- --print("blackjackTables") +-- --print("===============") +-- print(dump(blackjackTables)) +-- --print("blackjackGameData") +-- --print("===============") +-- print(dump(blackjackGameData)) +-- end) + +-- RegisterCommand("debugcarddata",function() +-- --print("carddata") +-- --print("===============") +-- print(dump(blackjackGameData[1024])) +-- end) diff --git a/resources/[qb]/[qb_casino]/casino-insidetrack/client/client.lua b/resources/[qb]/[qb_casino]/casino-insidetrack/client/client.lua new file mode 100644 index 0000000..fb73cf0 --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-insidetrack/client/client.lua @@ -0,0 +1,273 @@ + +local QBCore = exports['qb-core']:GetCoreObject() + +local cooldown = 60 +local tick = 0 +local checkRaceStatus = false +local insideTrackActive = false +local gameOpen = false + + + +CreateThread(function() + local insideTrackZone = CircleZone:Create(vector3(955.619, 70.179, 70.433), 2.5, { + name="insideTrack", + heading=328.0, + debugPoly=false, + useZ=true, + }) + insideTrackZone:onPlayerInOut(function(isPointInside) + if isPointInside then + if Config.HorseBetPrompt == 'walk-up' then + TriggerEvent('doj:casinoinsideTrackHeader') + elseif Config.HorseBetPrompt == 'peek' then + text = 'The Diamond Casino & Resort Inside Track' + exports['qb-core']:DrawText(text) + exports['qb-target']:AddCircleZone("Betting", vector3(956.121,70.185,70.433), 1.0, { + name="Betting", + heading=160, + debugPoly=false, + useZ=true, + }, { + options = { + { + event = "QBCore:client:openInsideTrack", + icon = "fas fa-coins", + label = "Start Betting", + }, + }, + distance = 3.0 + }) + end + else + exports['qb-menu']:closeMenu() + exports["qb-core"]:HideText() + end + end) +end) + +RegisterNetEvent('doj:casinoinsideTrackHeader', function() + exports['qb-menu']:showHeader({ + { + header = "The Diamond Casino & Resort Inside Track", + isMenuHeader = true, + }, + { + header = "Start Horse Betting", + txt = "100 casino chips", + params = { + event = "QBCore:client:openInsideTrack", + } + }, + { + header = "Cancel", + txt = "", + params = { + event = "doj:casinoinsideTrackHeader" + } + }, + }) +end) + +local function OpenInsideTrack() + QBCore.Functions.TriggerCallback("insidetrack:server:getbalance", function(balance) + Utils.PlayerBalance = balance + end) + + if insideTrackActive then + return + end + insideTrackActive = true + -- Scaleform + Utils.Scaleform = RequestScaleformMovie('HORSE_RACING_CONSOLE') + while not HasScaleformMovieLoaded(Utils.Scaleform) do + Wait(0) + end + DisplayHud(false) + SetPlayerControl(PlayerId(), false, 0) + while not RequestScriptAudioBank('DLC_VINEWOOD/CASINO_GENERAL') do + Wait(0) + end + Utils:ShowMainScreen() + Utils:SetMainScreenCooldown(cooldown) + -- Add horses + Utils:AddHorses() + Utils:DrawInsideTrack() + Utils:HandleControls() +end + +function closeHorseBets() + insideTrackActive = false + SetPlayerControl(PlayerId(), true, 0) + SetScaleformMovieAsNoLongerNeeded(Utils.Scaleform) + Utils.Scaleform = -1 + StopSound(0) +end + +local function LeaveInsideTrack() + insideTrackActive = false + SetPlayerControl(PlayerId(), true, 0) + SetScaleformMovieAsNoLongerNeeded(Utils.Scaleform) + Utils.Scaleform = -1 + StopSound(0) +end + +RegisterNetEvent('QBCore:client:closeBetsNotEnough') +AddEventHandler('QBCore:client:closeBetsNotEnough', function() + closeHorseBets() + QBCore.Functions.Notify("Bets Closed! You dont have enough Casino Chips...", "error", 3500) +end) + +RegisterNetEvent('QBCore:client:closeBetsZeroChips') +AddEventHandler('QBCore:client:closeBetsZeroChips', function() + closeHorseBets() + QBCore.Functions.Notify("Bets Closed! You dont have any Casino Chips...", "error", 3500) +end) + + + +RegisterNetEvent('QBCore:client:openInsideTrack') +AddEventHandler('QBCore:client:openInsideTrack', function() + -- QBCore.Functions.TriggerCallback('QBCore:HasItem', function(HasItem) + -- if HasItem then + OpenInsideTrack() + -- else + -- QBCore.Functions.Notify('You are not a member of the casino', 'error', 3500) + -- end + -- end, "casino_member") +end) + + + + +function Utils:DrawInsideTrack() + CreateThread(function() + while insideTrackActive do + Wait(0) + local xMouse, yMouse = GetDisabledControlNormal(2, 239), GetDisabledControlNormal(2, 240) + -- Fake cooldown + tick = (tick + 10) + if (tick == 1000) then + if (cooldown == 1) then + cooldown = 60 + end + cooldown = (cooldown - 1) + tick = 0 + Utils:SetMainScreenCooldown(cooldown) + end + -- Mouse control + BeginScaleformMovieMethod(Utils.Scaleform, 'SET_MOUSE_INPUT') + ScaleformMovieMethodAddParamFloat(xMouse) + ScaleformMovieMethodAddParamFloat(yMouse) + EndScaleformMovieMethod() + -- Draw + DrawScaleformMovieFullscreen(Utils.Scaleform, 255, 255, 255, 255) + end + end) +end + +function Utils:HandleControls() + CreateThread(function() + while insideTrackActive do + Wait(0) + + + if IsControlJustPressed(2, 194) then + LeaveInsideTrack() + end + + if IsControlJustPressed(2, 202) then + LeaveInsideTrack() + end + + -- Left click + if IsControlJustPressed(2, 237) then + local clickedButton = Utils:GetMouseClickedButton() + + if Utils.ChooseHorseVisible then + if (clickedButton ~= 12) and (clickedButton ~= -1) then + Utils.CurrentHorse = (clickedButton - 1) + Utils:ShowBetScreen(Utils.CurrentHorse) + Utils.ChooseHorseVisible = false + end + end + + -- Rules button + if (clickedButton == 15) then + Utils:ShowRules() + end + + -- Close buttons + if (clickedButton == 12) then + if Utils.ChooseHorseVisible then + Utils.ChooseHorseVisible = false + end + + if Utils.BetVisible then + Utils:ShowHorseSelection() + Utils.BetVisible = false + Utils.CurrentHorse = -1 + else + Utils:ShowMainScreen() + end + end + + -- Start bet + if (clickedButton == 1) then + Utils:ShowHorseSelection() + end + + -- Start race + if (clickedButton == 10) then + PlaySoundFrontend(-1, 'race_loop', 'dlc_vw_casino_inside_track_betting_single_event_sounds') + TriggerServerEvent("insidetrack:server:placebet", Utils.CurrentBet) + Utils:StartRace() + checkRaceStatus = true + end + + -- Change bet + if (clickedButton == 8) then + if (Utils.CurrentBet < Utils.PlayerBalance) then + Utils.CurrentBet = (Utils.CurrentBet + 100) + Utils.CurrentGain = (Utils.CurrentBet * 2) + Utils:UpdateBetValues(Utils.CurrentHorse, Utils.CurrentBet, Utils.PlayerBalance, Utils.CurrentGain) + end + end + + if (clickedButton == 9) then + if (Utils.CurrentBet > 100) then + Utils.CurrentBet = (Utils.CurrentBet - 100) + Utils.CurrentGain = (Utils.CurrentBet * 2) + Utils:UpdateBetValues(Utils.CurrentHorse, Utils.CurrentBet, Utils.PlayerBalance, Utils.CurrentGain) + end + end + + if (clickedButton == 13) then + Utils:ShowMainScreen() + end + + -- Check race + while checkRaceStatus do + Wait(0) + local raceFinished = Utils:IsRaceFinished() + if (raceFinished) then + StopSound(0) + if (Utils.CurrentHorse == Utils.CurrentWinner) then + TriggerServerEvent("insidetrack:server:winnings", Utils.CurrentGain) + end + QBCore.Functions.TriggerCallback("insidetrack:server:getbalance", function(balance) + Utils.PlayerBalance = balance + end) + Utils:UpdateBetValues(Utils.CurrentHorse, Utils.CurrentBet, Utils.PlayerBalance, Utils.CurrentGain) + Utils:ShowResults() + Utils.CurrentHorse = -1 + Utils.CurrentWinner = -1 + Utils.HorsesPositions = {} + checkRaceStatus = false + end + end + end + end + end) +end + diff --git a/resources/[qb]/[qb_casino]/casino-insidetrack/client/utils.lua b/resources/[qb]/[qb_casino]/casino-insidetrack/client/utils.lua new file mode 100644 index 0000000..24f5a9e --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-insidetrack/client/utils.lua @@ -0,0 +1,377 @@ + + +Utils = { + Scaleform = -1, + ChooseHorseVisible = false, + BetVisible = false, + HorseStyles = { + {15553363,5474797,9858144,4671302}, + {16724530,3684408,14807026,16777215}, + {13560920,15582764,16770746,7500402}, + {16558591,5090807,10446437,7493977}, + {5090807,16558591,3815994,9393493}, + {16269415,16767010,10329501,16777215}, + {2263807,16777215,9086907,3815994}, + {4879871,16715535,3815994,16777215}, + {16777215,2263807,16769737,15197642}, + {16338779,16777215,11166563,6974058}, + {16777215,16559849,5716493,3815994}, + {16760644,3387257,16701597,16777215}, + {6538729,2249420,16777215,3815994}, + {15913534,15913534,16304787,15985375}, + {15655629,16240452,16760474,13664854}, + {16320263,16777215,14920312,16773316}, + {7176404,15138618,6308658,13664854}, + {4879871,8453903,11382189,15724527}, + {16777215,16777215,16754809,16777215}, + {16732497,16732497,3815994,16777215}, + {5739220,5739220,11382189,15724527}, + {16712909,6935639,8742735,3877137}, + {2136867,16777215,16761488,3877137}, + {3118422,10019244,14932209,6121086}, + {2136867,10241979,8081664,3815994}, + {16769271,13724403,9852728,14138263}, + {13724403,16769271,6444881,14138263}, + {10017279,4291288,16304787,15985375}, + {1071491,4315247,14935011,6121086}, + {3861944,16627705,14932209,6121086}, + {15583546,4671303,11836798,3090459}, + {15567418,4671303,9985296,3815994}, + {5701417,16711680,16771760,6970713}, + {16760303,5986951,12353664,15395562}, + {8907670,2709022,9475214,4278081}, + {5429688,6400829,16777215,16773316}, + {15138618,5272210,14920312,16773316}, + {10241979,12396337,14920312,15395562}, + {16777215,13481261,13667152,3815994}, + {5077874,16777215,15444592,7820105}, + {10408040,2960685,7424036,10129549}, + {7754308,16777215,12944259,3815994}, + {16736955,16106560,16771760,6970713}, + {16106560,16770224,16767659,15843765}, + {9573241,14703194,9789279,3815994}, + {44799,14703194,10968156,16777215}, + {7143224,16753956,10975076,4210752}, + {7895160,4013373,5855577,11645361}, + {16075595,6869196,13530742,7105644}, + {16090955,6272992,16777215,16777215}, + {13313356,13313356,5849409,11623516}, + {13911070,5583427,14935011,6121086}, + {8604661,10408040,12944259,3815994}, + {9716612,2960685,16767659,6708313}, + {7806040,16777215,16765601,14144436}, + {15632075,11221989,16777215,16770037}, + {1936722,14654697,16763851,3815994}, + {10377543,3815994,14807026,16777215}, + {16775067,11067903,16770746,7500402}, + {16741712,8669718,16777215,16777215}, + {16515280,6318459,3815994,9393493}, + {65526,16515280,10329501,16777215}, + {16711680,4783925,3815994,3815994}, + {65532,4783925,16766671,15197642}, + {16760303,16760303,3815994,14207663}, + {16770048,16770048,3815994,3815994}, + {16737792,16737792,11166563,6974058}, + {12773119,12773119,5716493,3815994}, + {16777215,16763043,16701597,16777215}, + {6587161,6587161,16777215,3815994}, + {6329328,16749602,3815994,3815994}, + {15793920,16519679,14920312,15395562}, + {15466636,10724259,16760474,13664854}, + {11563263,327629,6308658,13664854}, + {58867,16777215,16754809,8082236}, + {4909311,16777215,5849409,11623516}, + {3700643,7602233,9852728,14138263}, + {16777215,1017599,8742735,3877137}, + {16772022,16772022,16761488,3877137}, + {7849983,5067443,8081664,3815994}, + {15913534,7602233,6444881,14138263}, + {12320733,16775618,11836798,3090459}, + {15240846,16777215,9985296,3815994}, + {14967137,3702939,3815994,14207663}, + {6343571,3702939,12353664,15395562}, + {16761374,15018024,9475214,4278081}, + {16743936,3756172,16777215,16773316}, + {2899345,5393472,16777215,4210752}, + {11645361,16777215,16771542,10123632}, + {3421236,5958825,16771542,3815994}, + {15851871,5395026,15444592,7820105}, + {16777215,9463517,7424036,10129549}, + {16760556,16733184,16767659,15843765}, + {4781311,15771930,16765601,14144436}, + {16760556,10287103,16767659,6708313}, + {13083490,16777215,9789279,3815994}, + {13810226,9115524,5855577,11645361}, + {14176336,9115524,13530742,7105644}, + {16770310,16751169,16772294,16777215} + }, + PlayerBalance = 500, + CurrentHorse = -1, + CurrentBet = 100, + CurrentGain = 1000, + HorsesPositions = {}, + CurrentWiner = -1 +} + + + + +function Utils:GetMouseClickedButton() + local returnValue = -1 + + CallScaleformMovieMethodWithNumber(self.Scaleform, 'SET_INPUT_EVENT', 237.0, -1082130432, -1082130432, -1082130432, -1082130432) + BeginScaleformMovieMethod(self.Scaleform, 'GET_CURRENT_SELECTION') + + returnValue = EndScaleformMovieMethodReturnValue() + + while not IsScaleformMovieMethodReturnValueReady(returnValue) do + Wait(0) + end + + return GetScaleformMovieMethodReturnValueInt(returnValue) +end + +function Utils.GetRandomHorseName() + local random = math.random(0, 99) + local randomName = (random < 10) and ('ITH_NAME_00'..random) or ('ITH_NAME_0'..random) + + return randomName +end + +-- int param : +-- 0 = main +-- 1 = choose a horse +-- 2 = choose a horse (2) +-- 3 = select a bet +-- 4 = select a bet (2) +-- 5 = race screen (frozen) +-- 6 = photo finish (frozen) +-- 7 = results +-- 8 = same as main but a bit different +-- 9 = rules +function Utils:ShowMainScreen() + BeginScaleformMovieMethod(self.Scaleform, 'SHOW_SCREEN') + ScaleformMovieMethodAddParamInt(0) + EndScaleformMovieMethod() + + BeginScaleformMovieMethod(Utils.Scaleform, 'SET_MAIN_EVENT_IN_PROGRESS') + ScaleformMovieMethodAddParamBool(true) + EndScaleformMovieMethod() + + BeginScaleformMovieMethod(Utils.Scaleform, 'CLEAR_ALL') + EndScaleformMovieMethod() +end + +---@param cooldown int +---(in seconds). +function Utils:SetMainScreenCooldown(cooldown) + BeginScaleformMovieMethod(self.Scaleform, 'SET_COUNTDOWN') + ScaleformMovieMethodAddParamInt(cooldown) + EndScaleformMovieMethod() +end + +function Utils:SetNotAvailable() + BeginScaleformMovieMethod(self.Scaleform, 'SHOW_ERROR') + + BeginTextCommandScaleformString('IT_ERROR_TITLE') + EndTextCommandScaleformString() + + BeginTextCommandScaleformString('IT_ERROR_MSG') + EndTextCommandScaleformString() + + EndScaleformMovieMethod() +end + +local function IsPositionAvailable(position) + for i = 1, #Utils.HorsesPositions do + if (Utils.HorsesPositions[i] == position) then + return false + end + end + + return true +end + +local function GenerateHorsesOrder() + while (#Utils.HorsesPositions < 6) do + Wait(0) + + for i = 1, 6 do + local randomPos = math.random(6) + + if IsPositionAvailable(randomPos) then + table.insert(Utils.HorsesPositions, randomPos) + end + end + end +end + +function Utils:StartRace() + GenerateHorsesOrder() + + self.CurrentWinner = self.HorsesPositions[1] + + BeginScaleformMovieMethod(self.Scaleform, 'START_RACE') + ScaleformMovieMethodAddParamFloat(15000.0) -- Race duration (in MS) + ScaleformMovieMethodAddParamInt(4) + + -- Add each horses by their index (win order) + ScaleformMovieMethodAddParamInt(self.HorsesPositions[1]) + ScaleformMovieMethodAddParamInt(self.HorsesPositions[2]) + ScaleformMovieMethodAddParamInt(self.HorsesPositions[3]) + ScaleformMovieMethodAddParamInt(self.HorsesPositions[4]) + ScaleformMovieMethodAddParamInt(self.HorsesPositions[5]) + ScaleformMovieMethodAddParamInt(self.HorsesPositions[6]) + + ScaleformMovieMethodAddParamFloat(0.0) -- Unk + ScaleformMovieMethodAddParamBool(false) + EndScaleformMovieMethod() +end + +function Utils:IsRaceFinished() + BeginScaleformMovieMethod(Utils.Scaleform, 'GET_RACE_IS_COMPLETE') + + local raceReturnValue = EndScaleformMovieMethodReturnValue() + + while not IsScaleformMovieMethodReturnValueReady(raceReturnValue) do + Wait(0) + end + + return GetScaleformMovieMethodReturnValueBool(raceReturnValue) +end + +function Utils:ShowResults() + BeginScaleformMovieMethod(self.Scaleform, 'SHOW_SCREEN') + ScaleformMovieMethodAddParamInt(7) + EndScaleformMovieMethod() +end + +function Utils:ShowRules() + BeginScaleformMovieMethod(self.Scaleform, 'SHOW_SCREEN') + ScaleformMovieMethodAddParamInt(9) + EndScaleformMovieMethod() +end + +function Utils:ShowHorseSelection() + self.ChooseHorseVisible = true + + BeginScaleformMovieMethod(self.Scaleform, 'SHOW_SCREEN') + ScaleformMovieMethodAddParamInt(1) + EndScaleformMovieMethod() +end + +function Utils:AddHorses() + for i = 1, 6 do + local name = self.GetRandomHorseName() + + BeginScaleformMovieMethod(self.Scaleform, 'SET_HORSE') + ScaleformMovieMethodAddParamInt(i) -- Horse index + + -- Horse name + BeginTextCommandScaleformString(name) + EndTextCommandScaleformString() + + ScaleformMovieMethodAddParamPlayerNameString('Cool Horse') + + -- Horse style + ScaleformMovieMethodAddParamInt(self.HorseStyles[i][1]) + ScaleformMovieMethodAddParamInt(self.HorseStyles[i][2]) + ScaleformMovieMethodAddParamInt(self.HorseStyles[i][3]) + ScaleformMovieMethodAddParamInt(self.HorseStyles[i][4]) + EndScaleformMovieMethod() + end +end + +local function IsPositionAvailable(position) + for i = 1, #Utils.HorsesPositions do + if (Utils.HorsesPositions[i] == position) then + return false + end + end + + return true +end + +local function GenerateHorsesOrder() + while (#Utils.HorsesPositions < 6) do + Wait(0) + + for i = 1, 6 do + local randomPos = math.random(6) + + if IsPositionAvailable(randomPos) then + table.insert(Utils.HorsesPositions, randomPos) + end + end + end +end + +function Utils:StartRace() + GenerateHorsesOrder() + + self.CurrentWinner = self.HorsesPositions[1] + + BeginScaleformMovieMethod(self.Scaleform, 'START_RACE') + ScaleformMovieMethodAddParamFloat(15000.0) -- Race duration (in MS) + ScaleformMovieMethodAddParamInt(4) + + -- Add each horses by their index (win order) + ScaleformMovieMethodAddParamInt(self.HorsesPositions[1]) + ScaleformMovieMethodAddParamInt(self.HorsesPositions[2]) + ScaleformMovieMethodAddParamInt(self.HorsesPositions[3]) + ScaleformMovieMethodAddParamInt(self.HorsesPositions[4]) + ScaleformMovieMethodAddParamInt(self.HorsesPositions[5]) + ScaleformMovieMethodAddParamInt(self.HorsesPositions[6]) + + ScaleformMovieMethodAddParamFloat(0.0) -- Unk + ScaleformMovieMethodAddParamBool(false) + EndScaleformMovieMethod() +end + +function Utils:IsRaceFinished() + BeginScaleformMovieMethod(Utils.Scaleform, 'GET_RACE_IS_COMPLETE') + + local raceReturnValue = EndScaleformMovieMethodReturnValue() + + while not IsScaleformMovieMethodReturnValueReady(raceReturnValue) do + Wait(0) + end + + return GetScaleformMovieMethodReturnValueBool(raceReturnValue) +end + +function Utils:ShowResults() + BeginScaleformMovieMethod(self.Scaleform, 'SHOW_SCREEN') + ScaleformMovieMethodAddParamInt(7) + EndScaleformMovieMethod() +end + +function Utils:ShowRules() + BeginScaleformMovieMethod(self.Scaleform, 'SHOW_SCREEN') + ScaleformMovieMethodAddParamInt(9) + EndScaleformMovieMethod() +end + +function Utils:ShowBetScreen(horse) + self:UpdateBetValues(horse, self.CurrentBet, self.PlayerBalance, self.CurrentGain) + + BeginScaleformMovieMethod(self.Scaleform, 'SHOW_SCREEN') + ScaleformMovieMethodAddParamInt(3) + EndScaleformMovieMethod() + + BeginScaleformMovieMethod(self.Scaleform, 'SET_BETTING_ENABLED') + ScaleformMovieMethodAddParamBool(true) + EndScaleformMovieMethod() + + self.BetVisible = true +end + +function Utils:UpdateBetValues(horse, bet, balance, gain) + BeginScaleformMovieMethod(self.Scaleform, 'SET_BETTING_VALUES') + ScaleformMovieMethodAddParamInt(horse) -- Horse index + + ScaleformMovieMethodAddParamInt(bet) -- Bet + ScaleformMovieMethodAddParamInt(balance) -- Current balance + ScaleformMovieMethodAddParamInt(gain) -- Gain + EndScaleformMovieMethod() +end \ No newline at end of file diff --git a/resources/[qb]/[qb_casino]/casino-insidetrack/config.lua b/resources/[qb]/[qb_casino]/casino-insidetrack/config.lua new file mode 100644 index 0000000..b7f920f --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-insidetrack/config.lua @@ -0,0 +1,4 @@ +Config = {} + + +Config.HorseBetPrompt = 'walk-up' -- 'peek' or 'walk-up' ['walk-up' = walk up to open menu] ['peek' = uses qb-target to start bets] \ No newline at end of file diff --git a/resources/[qb]/[qb_casino]/casino-insidetrack/fxmanifest.lua b/resources/[qb]/[qb_casino]/casino-insidetrack/fxmanifest.lua new file mode 100644 index 0000000..948440f --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-insidetrack/fxmanifest.lua @@ -0,0 +1,20 @@ +fx_version 'cerulean' + +game 'gta5' + +description 'Rexhack Gaming : Inside-Track' + +version '1.0.1' + +client_scripts { + '@PolyZone/client.lua', + '@PolyZone/BoxZone.lua', + '@PolyZone/EntityZone.lua', + '@PolyZone/CircleZone.lua', + '@PolyZone/ComboZone.lua', + 'config.lua', + 'client/utils.lua', + 'client/client.lua', +} + +server_script 'server/server.lua' \ No newline at end of file diff --git a/resources/[qb]/[qb_casino]/casino-insidetrack/server/server.lua b/resources/[qb]/[qb_casino]/casino-insidetrack/server/server.lua new file mode 100644 index 0000000..d5a86cf --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-insidetrack/server/server.lua @@ -0,0 +1,49 @@ + +local QBCore = exports['qb-core']:GetCoreObject() + +QBCore.Functions.CreateCallback("insidetrack:server:getbalance", function(source, cb) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local Chips = Player.Functions.GetItemByName("casino_redchip") + local minAmount = 100 + if Chips ~= nil then + if Chips.amount >= minAmount then + Chips = Chips + else + return TriggerClientEvent('QBCore:client:closeBetsNotEnough', src) + end + else + return TriggerClientEvent('QBCore:client:closeBetsZeroChips', src) + end +end) + +RegisterServerEvent("insidetrack:server:placebet", function(bet) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local Chips = Player.Functions.GetItemByName("casino_redchip") + if Chips ~= nil then + if Chips.amount >= bet then + Player.Functions.RemoveItem("casino_redchip", bet) + TriggerClientEvent('inventory:client:ItemBox', src, QBCore.Shared.Items['casino_redchip'], "remove", bet) + TriggerClientEvent('QBCore:Notify', src, "You placed a "..bet.." casino chips bet") + else + return TriggerClientEvent('QBCore:client:closeBetsNotEnough', src) + end + else + return TriggerClientEvent('QBCore:client:closeBetsZeroChips', src) + end +end) + +RegisterServerEvent("insidetrack:server:winnings", function(amount) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if Player ~= nil then + if Player.Functions.AddItem('casino_redchip', amount, nil, {["quality"] = 100}) then + TriggerClientEvent('inventory:client:ItemBox', src, QBCore.Shared.Items["casino_redchip"], "add", amount) + TriggerClientEvent('QBCore:Notify', src, "You Won "..amount.." casino chips!") + else + TriggerClientEvent('QBCore:Notify', src, 'You have to much in your pockets', 'error') + end + end +end) + diff --git a/resources/[qb]/[qb_casino]/casino-luckywheel/client.lua b/resources/[qb]/[qb_casino]/casino-luckywheel/client.lua new file mode 100644 index 0000000..60d5c8a --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-luckywheel/client.lua @@ -0,0 +1,418 @@ + +local QBCore = exports['qb-core']:GetCoreObject() + +local car, h +local _wheel, _base, _lights1, _lights2, _arrow1, _arrow2 = nil, nil, nil, nil, nil, nil + + +local m1a = GetHashKey('vw_prop_vw_luckylight_off') +local m1b = GetHashKey('vw_prop_vw_luckylight_on') +local m2a = GetHashKey('vw_prop_vw_jackpot_off') +local m2b = GetHashKey('vw_prop_vw_jackpot_on') + +local _wheelPos = Config.WheelPos +local _isRolling = false + + +-- Car on podium, Start +local ped +Citizen.CreateThread(function() + while 1 do + ped = PlayerPedId() + Wait(5000) + end +end) +spawned = nil + +Citizen.CreateThread(function() + while 1 do + local pCoords = GetEntityCoords(PlayerPedId()) + for i=1, #Config.Cars do + if #(pCoords - Config.Cars[i].pos) < Config.ShowRange then + if Config.Cars[i].spawned == nil then + SpawnLocalCar(i) + end + else + DeleteEntity(Config.Cars[i].spawned) + Config.Cars[i].spawned = nil + end + Wait(500) + end + end +end) + +Citizen.CreateThread(function() + local ped = ped + while true do + Citizen.Wait(0) + local pl = GetEntityCoords(ped, true) + for k, v in pairs(Config.Cars) do + if GetDistanceBetweenCoords(pl.x, pl.y, pl.z, v.pos.x, v.pos.y, v.pos.z, true) < Config.ShowRange then + Draw3DText(v.pos.x, v.pos.y, v.pos.z - 0.5, v.text, 0, 0.1, 0.1) + end + end + end +end) + +Citizen.CreateThread(function() + while 1 do + for i=1, #Config.Cars do + if Config.Cars[i].spawned ~= nil and Config.Cars[i].spin then + SetEntityHeading(Config.Cars[i].spawned, GetEntityHeading(Config.Cars[i].spawned) + 0.069) + end + end + Wait(5) + end +end) + +function SpawnLocalCar(i) + Citizen.CreateThread(function() + local hash = GetHashKey(Config.Cars[i].model) + RequestModel(hash) + local attempt = 0 + while not HasModelLoaded(hash) do + attempt = attempt + 1 + if attempt > 2000 then return end + Wait(0) + end + local veh = CreateVehicle(hash, Config.Cars[i].pos.x, Config.Cars[i].pos.y, Config.Cars[i].pos.z-1, Config.Cars[i].heading, false, false) + SetModelAsNoLongerNeeded(hash) + SetVehicleEngineOn(veh, false, true, true) + SetVehicleBrakeLights(veh, false) + SetVehicleLights(veh, 0) + SetVehicleLightsMode(veh, 0) + SetVehicleInteriorlight(veh, true) + SetVehicleOnGroundProperly(veh) + FreezeEntityPosition(veh, true) + SetVehicleCanBreak(veh, false) + SetVehicleFullbeam(veh, false) + SetVehicleReceivesRampDamage(veh, true) + RemoveDecalsFromVehicle(veh) + SetVehicleCanBeVisiblyDamaged(veh, false) + SetVehicleLightsCanBeVisiblyDamaged(veh, false) + SetVehicleWheelsCanBreakOffWhenBlowUp(veh, false) + SetDisableVehicleWindowCollisions(veh, true) + SetEntityInvincible(veh, true) + SetVehicleDoorsLocked(veh, 2) + SetVehicleNumberPlateText(veh, Config.Cars[i].plate) + Config.Cars[i].spawned = veh + end) +end + +AddEventHandler('onResourceStop', function(res) + if res == GetCurrentResourceName() then + for i=1, #Cars do + if Config.Cars[i].spawned ~= nil then + DeleteEntity(Config.Cars[i].spawned) + end + end + end +end) + +function Draw3DText(x, y, z, textInput, fontId, scaleX, scaleY) + local px, py, pz = table.unpack(GetGameplayCamCoords()) + local dist = GetDistanceBetweenCoords(px, py, pz, x, y, z, true) + local scale = (1 / dist) * 20 + local fov = (1 / GetGameplayCamFov()) * 100 + local scale = scale * fov + SetTextScale(scaleX * scale, scaleY * scale) + SetTextFont(fontId) + SetTextProportional(1) + SetTextColour(250, 250, 250, 255) + SetTextDropshadow(1, 1, 1, 1, 255) + SetTextEdge(2, 0, 0, 0, 150) + SetTextOutline() + SetTextEntry("STRING") + SetTextCentre(1) + AddTextComponentString(textInput) + SetDrawOrigin(x, y, z + 2, 0) + DrawText(0.0, 0.0) + ClearDrawOrigin() +end + +-- Car on podium, End + +CreateThread(function() + RequestScriptAudioBank("DLC_VINEWOOD\\CASINO_GENERAL", false) + local model1 = GetHashKey('vw_prop_vw_luckywheel_02a') + local model2 = GetHashKey('vw_prop_vw_luckywheel_01a') + local podiumModel = GetHashKey('vw_prop_vw_casino_podium_01a') + CreateThread(function() + RequestModel(model1) while not HasModelLoaded(model1) do Wait(0) end + RequestModel(model2) while not HasModelLoaded(model2) do Wait(0) end + RequestModel(m1a) while not HasModelLoaded(m1a) do Wait(0) end + RequestModel(m1b) while not HasModelLoaded(m1b) do Wait(0) end + RequestModel(m2a) while not HasModelLoaded(m2a) do Wait(0) end + RequestModel(m2b) while not HasModelLoaded(m2b) do Wait(0) end + ClearArea(Config.WheelPos.x, Config.WheelPos.y, Config.WheelPos.z, 5.0, true, false, false, false) + _wheel = CreateObject(model1, Config.WheelPos.x, Config.WheelPos.y, Config.WheelPos.z, false, false, true) + SetEntityHeading(_wheel, Config.WheelPos.h) + SetModelAsNoLongerNeeded(model1) + _base = CreateObject(model2, Config.WheelPos.x, Config.WheelPos.y, Config.WheelPos.z-0.26, false, false, true) + SetEntityHeading(_base, Config.WheelPos.h) + SetModelAsNoLongerNeeded(_base) + _lights1 = CreateObject(m1a, Config.WheelPos.x, Config.WheelPos.y, Config.WheelPos.z+0.35, false, false, true) + SetEntityHeading(_lights1, Config.WheelPos.h) + SetModelAsNoLongerNeeded(_lights1) + _lights2 = CreateObject(m1b, Config.WheelPos.x, Config.WheelPos.y, Config.WheelPos.z+0.35, false, false, true) + SetEntityVisible(_lights2, false, 0) + SetEntityHeading(_lights2, Config.WheelPos.h) + SetModelAsNoLongerNeeded(_lights2) + _arrow1 = CreateObject(m2a, Config.WheelPos.x, Config.WheelPos.y, Config.WheelPos.z+2.5, false, false, true) + SetEntityHeading(_arrow1, Config.WheelPos.h) + SetModelAsNoLongerNeeded(_arrow1) + _arrow2 = CreateObject(m2b, Config.WheelPos.x, Config.WheelPos.y, Config.WheelPos.z+2.5, false, false, true) + SetEntityVisible(_arrow2, false, 0) + SetEntityHeading(_arrow2, Config.WheelPos.h) + SetModelAsNoLongerNeeded(_arrow2) + h = GetEntityRotation(_wheel) + end) +end) + +CreateThread(function() + local LuckyWheelZone = CircleZone:Create(vector3(949.71, 45.1, 70.9), 2.5, { + name="LuckyWheelZone", + heading=328.0, + debugPoly=false, + useZ=true, + }) + LuckyWheelZone:onPlayerInOut(function(isPointInside) + if isPointInside then + text = "The Diamond Casino & Resort

Lucky Wheel

"..Config.startingPrice..",- a spin" + exports['qb-core']:DrawText(text, "top") + exports['qb-target']:AddCircleZone("LuckyWheel", vector3(949.391, 44.72, 71.638), 2.0, { + name="LuckyWheel", + heading=160, + debugPoly=false, + useZ=true, + }, { + options = { + { + event = "luckywheel:client:startWheel", + icon = "fas fa-sync-alt", + label = "Try Your Luck", + }, + }, + distance = 2.0 + }) + else + exports['qb-menu']:closeMenu() + exports["qb-core"]:HideText() + end + end) +end) + +RegisterNetEvent('doj:casinoLuckyWheelHeader', function() + exports['qb-menu']:showHeader({ + { + header = "The Diamond Casino & Resort Lucky Wheel", + isMenuHeader = true, + }, + { + header = "Try Your Luck", + txt = "$"..Config.startingPrice.." a spin", + params = { + event = "luckywheel:client:startWheel", + } + }, + { + header = "Cancel", + txt = "", + params = { + event = "doj:casinoLuckyWheelHeader" + } + }, + }) +end) + +-- RegisterNetEvent("luckywheel:client:startWheel", function() +-- QBCore.Functions.TriggerCallback('QBCore.Functions.HasItem', function(HasItem) +-- if HasItem then +-- TriggerServerEvent("luckywheel:getwheel") +-- else +-- QBCore.Functions.Notify('You are not a V.I.P of the casino', 'error', 3500) +-- end +-- end, "casino_vip") +-- end) + + +RegisterNetEvent("luckywheel:client:startWheel", function() + QBCore.Functions.TriggerCallback('doj:server:HasVIPMembership', function(HasItem) + if HasItem then + TriggerServerEvent("luckywheel:getwheel") + else + QBCore.Functions.Notify('You dont have a V.I.P Membership!', 'error', 3500) + text = 'The Diamond Casino & Resort

Please visit the front desk!
' + exports['qb-core']:DrawText(text, "top") + end + end) +end) + +RegisterNetEvent("luckywheel:syncanim", function() + doRoll(0) +end) + +RegisterNetEvent("luckywheel:startroll", function(s, index, p) + Wait(1000) + SetEntityVisible(_lights1, false, 0) + SetEntityVisible(_lights2, true, 0) + win = (index - 1) * 18 + 0.0 + local j = 360 + if s == GetPlayerServerId(PlayerId()) then + PlaySoundFromEntity(-1, "Spin_Start", _wheel, 'dlc_vw_casino_lucky_wheel_sounds', 1, 1) + end + for i=1,1100,1 do + SetEntityRotation(_wheel, h.x, j+0.0, h.z, 0, false) + if i < 50 then + j = j - 1.5 + elseif i < 100 then + j = j - 2.0 + elseif i < 150 then + j = j - 2.5 + elseif i > 1060 then + j = j - 0.3 + elseif i > 1030 then + j = j - 0.6 + elseif i > 1000 then + j = j - 0.9 + elseif i > 970 then + j = j - 1.2 + elseif i > 940 then + j = j - 1.5 + elseif i > 910 then + j = j - 1.8 + elseif i > 880 then + j = j - 2.1 + elseif i > 850 then + j = j - 2.4 + elseif i > 820 then + j = j - 2.7 + else + j = j - 3.0 + end + if i == 850 then j = math.random(win-4, win+10) + 0.0 end + if j > 360 then j = j + 0 end + if j < 0 then j = j + 360 end + if i == 900 then + end + Wait(0) + end + Wait(300) + SetEntityVisible(_arrow1, false, 0) + SetEntityVisible(_arrow2, true, 0) + local t = true + if s == GetPlayerServerId(PlayerId()) then + if p.sound == 'car' then + PlaySoundFromEntity(-1, "Win_Car", _wheel, 'dlc_vw_casino_lucky_wheel_sounds', 1, 1) + elseif p.sound == 'cash' then + PlaySoundFromEntity(-1, "Win_Cash", _wheel, 'dlc_vw_casino_lucky_wheel_sounds', 1, 1) + elseif p.sound == 'chips' then + PlaySoundFromEntity(-1, "Win_Chips", _wheel, 'dlc_vw_casino_lucky_wheel_sounds', 1, 1) + elseif p.sound == 'clothes' then + PlaySoundFromEntity(-1, "Win_Clothes", _wheel, 'dlc_vw_casino_lucky_wheel_sounds', 1, 1) + elseif p.sound == 'mystery' then + PlaySoundFromEntity(-1, "Win_Mystery", _wheel, 'dlc_vw_casino_lucky_wheel_sounds', 1, 1) + else + PlaySoundFromEntity(-1, "Win", _wheel, 'dlc_vw_casino_lucky_wheel_sounds', 1, 1) + end + end + for i=1,15,1 do + Wait(200) + SetEntityVisible(_lights1, t, 0) + SetEntityVisible(_arrow2, t, 0) + t = not t + SetEntityVisible(_lights2, t, 0) + SetEntityVisible(_arrow1, t, 0) + if i == 5 then + if s == GetPlayerServerId(PlayerId()) then + TriggerServerEvent('luckywheel:give', s, p) + end + end + end + Wait(1000) + SetEntityVisible(_lights1, true, 0) + SetEntityVisible(_lights2, false, 0) + SetEntityVisible(_arrow1, true, 0) + SetEntityVisible(_arrow2, false, 0) + TriggerServerEvent('luckywheel:stoproll') +end) + +RegisterNetEvent("luckywheel:rollFinished", function() + _isRolling = false +end) + +function QBCoreRequestAnimDict(animDict, cb) + if not HasAnimDictLoaded(animDict) then + RequestAnimDict(animDict) + + while not HasAnimDictLoaded(animDict) do + Wait(1) + end + end + + if cb ~= nil then + cb() + end +end + +function doRoll(index) + if not _isRolling then + exports["qb-core"]:HideText() + _isRolling = true + local playerPed = PlayerPedId() + local _lib = 'anim_casino_a@amb@casino@games@lucky7wheel@female' + if IsPedMale(playerPed) then + _lib = 'anim_casino_a@amb@casino@games@lucky7wheel@male' + end + local lib, anim = _lib, 'enter_right_to_baseidle' + + QBCoreRequestAnimDict(lib, function() + local _movePos = GetObjectOffsetFromCoords(GetEntityCoords(_base), GetEntityHeading(_base),-0.9, -0.8, -1.0) + TaskGoStraightToCoord(playerPed, _movePos.x, _movePos.y, _movePos.z, 1.0, 3000, GetEntityHeading(_base), 0.0) + local _isMoved = false + while not _isMoved do + local coords = GetEntityCoords(PlayerPedId()) + if coords.x >= (_movePos.x - 0.01) and coords.x <= (_movePos.x + 0.01) and coords.y >= (_movePos.y - 0.01) and coords.y <= (_movePos.y + 0.01) then + _isMoved = true + end + Wait(0) + end + SetEntityHeading(playerPed, GetEntityHeading(_base)) + TaskPlayAnim(playerPed, lib, anim, 8.0, -8.0, -1, 0, 0, false, false, false) + while IsEntityPlayingAnim(playerPed, lib, anim, 3) do + Wait(0) + DisableAllControlActions(0) + end + TaskPlayAnim(playerPed, lib, 'enter_to_armraisedidle', 8.0, -8.0, -1, 0, 0, false, false, false) + while IsEntityPlayingAnim(playerPed, lib, 'enter_to_armraisedidle', 3) do + Wait(0) + DisableAllControlActions(0) + end + TaskPlayAnim(playerPed, lib, 'armraisedidle_to_spinningidle_high', 8.0, -8.0, -1, 0, 0, false, false, false) + Wait(4800) + TaskStartScenarioInPlace(playerPed, "WORLD_HUMAN_STRIP_WATCH_STAND", 0, true) + Wait(4800) + ClearPedTasks(playerPed) + end) + end +end + + + +RegisterNetEvent('dojLuckywheel:winCar', function(plate) + local ped = PlayerPedId() + local coords = Config.VehicleSpawnCoords + QBCore.Functions.TriggerCallback('QBCore:Server:SpawnVehicle', function(netId) + local veh = NetToVeh(netId) + SetVehicleNumberPlateText(veh, Config.VehiclePlateText) + TaskWarpPedIntoVehicle(PlayerPedId(), veh, -1) + SetVehicleFuelLevel(veh, 100) + TriggerEvent("vehiclekeys:client:SetOwner", Config.VehiclePrize) + TriggerServerEvent('luckywheel:server:setVehicleOwner') + QBCore.Functions.SetVehicleProperties(veh, vehmods) + end, Config.VehiclePrize, coords, true) +end) + + + + diff --git a/resources/[qb]/[qb_casino]/casino-luckywheel/config.lua b/resources/[qb]/[qb_casino]/casino-luckywheel/config.lua new file mode 100644 index 0000000..19b76ef --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-luckywheel/config.lua @@ -0,0 +1,56 @@ +Config = {} + + +Config.WheelPos = {x = 949.71, y = 45.1, z = 70.9, h =328.0} -- Where the wheel prop will spawn OR where wheel prop is +Config.startingPrice = 5000 +Config.VehiclePrize = 'p90d' +Config.VehicleSpawnCoords = vector4(933.29, -2.82, 78.76, 144.6) +Config.VehiclePlateText = 'CASINO' + + +Config.LimitedSpins = false -- [true = players spin once per day for free] [false= $5000 a spin] +Config.LimitedSpinResetTime = 3600000 -- 1 hour real time + + +-- Config for car on podium +Config.ShowRange = 200 +Config.Cars = { + { + pos = vector3(935.3839, 42.5959, 72.5251), + heading = 192.56, + model = 'p90d', + spin = true, + text = "", + plate = "CASINO" + } +} + + +-- type = [weapon, money, item, car] (for money it will give only in bank) +-- name = item in the database +-- count = amount to receive +-- sound = [car, cash, clothes, chips, mystery, win] + + +Config.Prices = { + [1] = {type = 'car', name = 'car', count = 1, sound = 'car', probability = {a = 0, b = 1}}, -- 0.1 % 0.1 -- VEHICLE + [2] = {type = 'money', name = 'money', count = 15000, sound = 'cash', probability = {a = 1, b = 5}}, -- 0.4 % 0.5 -- 15.000 RP + [3] = {type = 'item', name = 'casino_vip', count = 1, sound = 'clothes', probability = {a = 5, b = 10}}, -- 0.5 % 1.0 -- CLOTHING + [4] = {type = 'item', name = 'casino_redchip', count = 25000, sound = 'chips', probability = {a = 10, b = 20}}, -- 1.0 % 2.0 -- 25.000 chips + [5] = {type = 'money', name = 'money', count = 40000, sound = 'cash', probability = {a = 20, b = 40}}, -- 2.0 % 4.0 -- 40.000 $ + [6] = {type = 'money', name = 'money', count = 10000, sound = 'cash', probability = {a = 40, b = 60}}, -- 2.0 % 6.0 -- 10.000 RP + [7] = {type = 'item', name = 'bread', count = 1, sound = 'clothes', probability = {a = 60, b = 80}}, -- 4.0 % 8.0 -- CLOTHING + [8] = {type = 'item', name = 'casino_vip', count = 1, sound = 'mystery', probability = {a = 80, b = 120}}, -- 4.0 % 12.0 -- MYSTERY + [9] = {type = 'item', name = 'casino_redchip', count = 20000, sound = 'chips', probability = {a = 120, b = 170}}, -- 5.0 % 17.0 -- 20.000 chips + [10] = {type = 'money', name = 'money', count = 7500, sound = 'cash', probability = {a = 170, b = 250}}, -- 5.0 % 22.0 -- 7.500 RP + [11] = {type = 'item', name = 'bread', count = 1, sound = 'clothes', probability = {a = 250, b = 300}}, -- 6.0 % 28.0 -- CLOTHING + [12] = {type = 'item', name = 'casino_redchip', count = 15000, sound = 'chips', probability = {a = 300, b = 340}}, -- 6.0 % 34.0 -- 15.000 chips + [13] = {type = 'money', name = 'money', count = 30000, sound = 'cash', probability = {a = 340, b = 380}}, -- 7.0 % 41.0 -- 30.000 $ + [14] = {type = 'money', name = 'money', count = 5000, sound = 'cash', probability = {a = 380, b = 540}}, -- 7.0 % 48.0 -- 5.000 RP + [15] = {type = 'weapon', name = 'weapon_pistol', count = 1, sound = 'mystery', probability = {a = 540, b = 610}}, -- 8.0 % 56.0 -- DISCOUNT + [16] = {type = 'item', name = 'casino_redchip', count = 10000, sound = 'chips', probability = {a = 610, b = 640}}, -- 8.0 % 64.0 -- 10.000 chips + [17] = {type = 'money', name = 'money', count = 20000, sound = 'cash', probability = {a = 640, b = 700}}, -- 8.0 % 72.0 -- 20.000 $ + [18] = {type = 'money', name = 'money', count = 2500, sound = 'cash', probability = {a = 700, b = 810}}, -- 9.0 % 81.0 -- 2.500 RP + [19] = {type = 'item', name = 'bread', count = 1, sound = 'clothes', probability = {a = 810, b = 990}}, -- 9.0 % 90.0 -- CLOTHING + [20] = {type = 'money', name = 'money', count = 50000, sound = 'cash', probability = {a = 990, b = 1000}}, -- 1.0 % -- 50.000 $ +} diff --git a/resources/[qb]/[qb_casino]/casino-luckywheel/fxmanifest.lua b/resources/[qb]/[qb_casino]/casino-luckywheel/fxmanifest.lua new file mode 100644 index 0000000..271c3a9 --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-luckywheel/fxmanifest.lua @@ -0,0 +1,49 @@ + + +fx_version 'cerulean' +game 'gta5' +description 'Lucky Wheel' + + +server_script 'server.lua' + +client_scripts { + '@PolyZone/client.lua', + '@PolyZone/BoxZone.lua', + '@PolyZone/EntityZone.lua', + '@PolyZone/CircleZone.lua', + '@PolyZone/ComboZone.lua', + 'client.lua', +} + +shared_script 'config.lua' + +-- files { +-- 'audio/dlcvinewood_amp.dat10', +-- 'audio/dlcvinewood_amp.dat10.nametable', +-- 'audio/dlcvinewood_amp.dat10.rel', +-- 'audio/dlcvinewood_game.dat151', +-- 'audio/dlcvinewood_game.dat151.nametable', +-- 'audio/dlcvinewood_game.dat151.rel', +-- 'audio/dlcvinewood_mix.dat15', +-- 'audio/dlcvinewood_mix.dat15.nametable', +-- 'audio/dlcvinewood_mix.dat15.rel', +-- 'audio/dlcvinewood_sounds.dat54', +-- 'audio/dlcvinewood_sounds.dat54.nametable', +-- 'audio/dlcvinewood_sounds.dat54.rel', +-- 'audio/dlcvinewood_speech.dat4', +-- 'audio/dlcvinewood_speech.dat4.nametable', +-- 'audio/dlcvinewood_speech.dat4.rel', +-- 'audio/sfx/dlc_vinewood/casino_general.awc', +-- 'audio/sfx/dlc_vinewood/casino_interior_stems.awc', +-- 'audio/sfx/dlc_vinewood/casino_slot_machines_01.awc', +-- 'audio/sfx/dlc_vinewood/casino_slot_machines_02.awc', +-- 'audio/sfx/dlc_vinewood/casino_slot_machines_03.awc' +-- } + +-- data_file 'AUDIO_GAMEDATA' 'audio/dlcvinewood_game.dat' +-- data_file 'AUDIO_SOUNDDATA' 'audio/dlcvinewood_sounds.dat' +-- data_file 'AUDIO_DYNAMIXDATA' 'audio/dlcvinewood_mix.dat' +-- data_file 'AUDIO_SYNTHDATA' 'audio/dlcVinewood_amp.dat' +-- data_file 'AUDIO_SPEECHDATA' 'audio/dlcvinewood_speech.dat' +-- data_file 'AUDIO_WAVEPACK' 'audio/sfx/dlc_vinewood' \ No newline at end of file diff --git a/resources/[qb]/[qb_casino]/casino-luckywheel/luckywheel.sql b/resources/[qb]/[qb_casino]/casino-luckywheel/luckywheel.sql new file mode 100644 index 0000000..baa30c9 --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-luckywheel/luckywheel.sql @@ -0,0 +1,2 @@ + +ALTER TABLE `players` ADD COLUMN `luckywheel_spins` VARCHAR(1) NULL DEFAULT '0' diff --git a/resources/[qb]/[qb_casino]/casino-luckywheel/server.lua b/resources/[qb]/[qb_casino]/casino-luckywheel/server.lua new file mode 100644 index 0000000..1b0d347 --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-luckywheel/server.lua @@ -0,0 +1,120 @@ +math.randomseed(os.time()) + +local QBCore = exports['qb-core']:GetCoreObject() +isRoll = false +-- local car = Config.Cars[math.random(#Config.Cars)] + +if Config.LimitedSpins then + Citizen.CreateThread(function() + while true do + Wait(1000*60) + if os.date('%H:%M') == Config.LimitedSpins then + exports.oxmysql:execute('UPDATE players SET luckywheel_spins = 0') + end + end + end) +end + +RegisterNetEvent('luckywheel:getwheel', function() + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if Config.LimitedSpins == true then + local result = exports.oxmysql:scalarSync('SELECT luckywheel_spins FROM players where citizenid= ?', {Player.PlayerData.citizenid}) + if result == '0' then + TriggerEvent("luckywheel:startwheel", Player, src) + else + TriggerClientEvent('QBCore:Notify', src, "You have already had a spin on the wheel today", "error") + end + elseif Config.LimitedSpins == false then + if Player.PlayerData.money["bank"] >= Config.startingPrice then + Player.Functions.RemoveMoney("bank", tonumber(Config.startingPrice), "lucky-wheel") + TriggerEvent("luckywheel:startwheel", Player, src) + else + -- return TriggerClientEvent('QBCore:Notify', src, "You have enough in the bank to spin", "error") + TriggerClientEvent('QBCore:Notify', src, "You have enough in the bank to spin", "error") + end + end +end) + +RegisterNetEvent('luckywheel:startwheel', function(Player, source) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if not isRoll then + if Player ~= nil then + exports.oxmysql:execute('UPDATE players SET luckywheel_spins = 1 where citizenid= ?', {Player.PlayerData.citizenid}) + isRoll = true + local rnd = math.random(1, 1000) + local price = 0 + local priceIndex = 0 + for k,v in pairs(Config.Prices) do + if (rnd > v.probability.a) and (rnd <= v.probability.b) then + price = v + priceIndex = k + break + end + end + TriggerClientEvent("luckywheel:syncanim", src, priceIndex) + TriggerClientEvent("luckywheel:startroll", -1, src, priceIndex, price) + end + end +end) + +RegisterNetEvent('luckywheel:give', function(source, price) + local Player = QBCore.Functions.GetPlayer(source) + isRoll = false + if price.type == 'car' then + TriggerClientEvent("dojLuckywheel:winCar", source) + TriggerClientEvent("chCasinoWall:bigWin", source) + elseif price.type == 'item' then + TriggerClientEvent("chCasinoWall:bigWin", source) + Player.Functions.AddItem(price.name, price.count, slot) + TriggerClientEvent('QBCore:Notify', source, "Congratulations! You won "..price.count.." "..price.name.."!", 'success') + TriggerClientEvent('inventory:client:ItemBox', source, QBCore.Shared.Items[price.name], "add",price.count ) + elseif price.type == 'money' then + TriggerClientEvent("chCasinoWall:bigWin", source) + Player.Functions.AddMoney('bank', tonumber(price.count), 'banking-quick-depo') + TriggerClientEvent('QBCore:Notify', source, "Congratulations! You won $"..price.count, 'success') + elseif price.type == 'weapon' then + TriggerClientEvent("chCasinoWall:bigWin", source) + Player.Functions.AddItem(price.name, 1, slot) + TriggerClientEvent('QBCore:Notify', source, "Congratulations! You won a Pistol!", 'success') + TriggerClientEvent('inventory:client:ItemBox', source, QBCore.Shared.Items[price.name], "add",1) + end + TriggerClientEvent("luckywheel:rollFinished", -1) +end) + +RegisterNetEvent('luckywheel:stoproll', function() + isRoll = false +end) + +RegisterNetEvent('luckywheel:server:setVehicleOwner', function() + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local cid = Player.PlayerData.citizenid + local vehicle = Config.VehiclePrize + local plate = GeneratePlate() + exports.oxmysql:insert('INSERT INTO player_vehicles (license, citizenid, vehicle, hash, mods, plate, state) VALUES (?, ?, ?, ?, ?, ?, ?)', { + Player.PlayerData.license, + cid, + vehicle, + GetHashKey(vehicle), + '{}', + -- plate, + Config.VehiclePlateText, + 0 + }) + TriggerClientEvent('QBCore:Notify', src, "YOU WON THE SHOW CAR! congratulations!", 'success') +end) + + +function GeneratePlate() + local plate = QBCore.Shared.RandomInt(1) .. QBCore.Shared.RandomStr(2) .. QBCore.Shared.RandomInt(3) .. QBCore.Shared.RandomStr(2) + local result = exports.oxmysql:scalarSync('SELECT plate FROM player_vehicles WHERE plate = ?', {plate}) + if result then + return GeneratePlate() + else + return plate:upper() + end +end + + diff --git a/resources/[qb]/[qb_casino]/casino-luckywheel/stream/vw_prop_vw_luckywheel.ytd b/resources/[qb]/[qb_casino]/casino-luckywheel/stream/vw_prop_vw_luckywheel.ytd new file mode 100644 index 0000000..b6a7d06 Binary files /dev/null and b/resources/[qb]/[qb_casino]/casino-luckywheel/stream/vw_prop_vw_luckywheel.ytd differ diff --git a/resources/[qb]/[qb_casino]/casino-poker/client/cl_main.lua b/resources/[qb]/[qb_casino]/casino-poker/client/cl_main.lua new file mode 100644 index 0000000..91206fe --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-poker/client/cl_main.lua @@ -0,0 +1,2091 @@ +QBCore = exports['qb-core']:GetCoreObject() + +SharedPokers = {} +closeToPokers = false + +function ShowHelpNotification(msg) + exports["qb-core"]:DrawText(msg) + -- exports["qb-core"]:DrawText("Diamond Casino Poker

"..msg) + -- exports['casinoUi']:DrawCasinoUi('show', "Diamond Casino Poker

"..reactiveText) +end + +local mainScene = nil -- the main sitting scene, we need it globally, for the exit +local activePokerTable = nil -- current table Id where we are sitting +local activeChairData = nil -- chair data, it is a table with rotation and coords +local currentBetInput = 0 -- currently bet input + +local playerBetted = nil -- important, because when it changes to TRUE, we are disabling the standup, etc +local playerPairPlus = nil -- pair plus bet amount +local watchingCards = false -- for the notification and other inputs +local playerDecidedChoice = false + +local clientTimer = nil +local currentHelpText = nil + +local mainCamera = nil + +local buttonScaleform = nil + +local networkedChips = {} + +local PlayerOwnedChips = 0 + +local InformationPlaying = false + +local playedHudSound = false + +local frm_showed = false + +-- EVENTS +RegisterNetEvent('aquiverPoker:updateCards') +RegisterNetEvent('aquiverPoker:updateState') +RegisterNetEvent('aquiverPoker:playerBetAnim') +RegisterNetEvent('aquiverPoker:Stage:1') +RegisterNetEvent('aquiverPoker:Stage:2') +RegisterNetEvent('aquiverPoker:Stage:3') +RegisterNetEvent('aquiverPoker:Stage:4') +RegisterNetEvent('aquiverPoker:playerPlayCards') +RegisterNetEvent('aquiverPoker:playerFoldCards') +RegisterNetEvent('aquiverPoker:Stage:5') +RegisterNetEvent('aquiverPoker:Stage:6') +RegisterNetEvent('aquiverPoker:Stage:7') +RegisterNetEvent('aquiverPoker:resetTable') +RegisterNetEvent('aquiverPoker:playerWin') +RegisterNetEvent('aquiverPoker:playerLost') +RegisterNetEvent('aquiverPoker:playerDraw') +RegisterNetEvent('aquiverPoker:updatePlayerChips') +RegisterNetEvent('aquiverPoker:playerPairPlusAnim') +---------------------- + +AddEventHandler( + 'aquiverPoker:playerPairPlusAnim', + function(amount) + if SharedPokers[activePokerTable] ~= nil then + SharedPokers[activePokerTable].playerPairPlusAnim(amount) + end + end +) + +AddEventHandler( + 'aquiverPoker:updateCards', + function(tableId, Cards) + if SharedPokers[tableId] ~= nil then + SharedPokers[tableId].updateCards(Cards) + end + end +) + +AddEventHandler( + 'aquiverPoker:updateState', + function(tableId, Active, TimeLeft) + if SharedPokers[tableId] ~= nil then + SharedPokers[tableId].updateState(Active, TimeLeft) + end + end +) + +AddEventHandler( + 'aquiverPoker:playerBetAnim', + function(amount) + if SharedPokers[activePokerTable] ~= nil then + SharedPokers[activePokerTable].playerBetAnim(amount) + end + end +) + +AddEventHandler( + 'aquiverPoker:updatePlayerChips', + function(amount) + PlayerOwnedChips = math.floor(amount) + end +) + +AddEventHandler( + 'aquiverPoker:playerDraw', + function(tableId) + if SharedPokers[tableId] ~= nil then + SharedPokers[tableId].playerDraw() + end + end +) + +AddEventHandler( + 'aquiverPoker:playerLost', + function(tableId) + if SharedPokers[tableId] ~= nil then + SharedPokers[tableId].playerLost() + end + end +) + +AddEventHandler( + 'aquiverPoker:playerWin', + function(tableId) + if SharedPokers[tableId] ~= nil then + SharedPokers[tableId].playerWin() + end + end +) + +AddEventHandler('aquiverPoker:resetTable',function(tableId) + if SharedPokers[tableId] ~= nil then + SharedPokers[tableId].resetTable() + exports["qb-core"]:DrawText("Place Bet:

Adjust Bet: ↑/↓

Exit: ←") + end +end) + +AddEventHandler( + 'aquiverPoker:Stage:7', + function(tableId) + if SharedPokers[tableId] ~= nil then + currentHelpText = _U('clearing_table') + SharedPokers[tableId].clearTable() + end + end +) +AddEventHandler( + 'aquiverPoker:Stage:6', + function(tableId) + if SharedPokers[tableId] ~= nil then + currentHelpText = _U('dealer_showing_hand') + SharedPokers[tableId].revealSelfCards() + end + end +) +AddEventHandler( + 'aquiverPoker:Stage:5', + function(tableId) + if SharedPokers[tableId] ~= nil then + currentHelpText = _U('players_showing_hands') + SharedPokers[tableId].revealPlayerCards() + end + end +) + +AddEventHandler( + 'aquiverPoker:Stage:1', + function(tableId) + if SharedPokers[tableId] ~= nil then + SharedPokers[tableId].FirstAction() + end + end +) + +AddEventHandler( + 'aquiverPoker:Stage:2', + function(tableId) + if SharedPokers[tableId] ~= nil then + currentHelpText = _U('dealing_cards') + SharedPokers[tableId].dealToPlayers() + end + end +) +AddEventHandler( + 'aquiverPoker:Stage:3', + function(tableId) + if SharedPokers[tableId] ~= nil then + currentHelpText = nil + SharedPokers[tableId].dealToSelf() + SharedPokers[tableId].putDownDeck() + SharedPokers[tableId].dealerStandingIdle() + end + end +) + +AddEventHandler( + 'aquiverPoker:Stage:4', + function(tableId) + if SharedPokers[tableId] ~= nil then + currentHelpText = nil + SharedPokers[tableId].watchCards() + end + end +) + +AddEventHandler( + 'aquiverPoker:playerPlayCards', + function(mainSrc, tableId) + if SharedPokers[tableId] ~= nil then + SharedPokers[tableId].playCards(mainSrc) + end + end +) + +AddEventHandler( + 'aquiverPoker:playerFoldCards', + function(mainSrc, tableId) + if SharedPokers[tableId] ~= nil then + SharedPokers[tableId].foldCards(mainSrc) + end + end +) + +AquiverPoker = function(index, data) + local self = {} + + self.index = index + self.data = data + + self.cards = {} + + self.playersFolded = {} + + self.updateCards = function(Cards) + self.ServerCards = Cards + end + + self.updateState = function(Active, TimeLeft) + self.Active = Active + self.TimeLeft = TimeLeft + end + + self.playerDraw = function() + local pedReaction = CreateSynchronizedScene(self.data.Position, 0.0, 0.0, self.data.Heading, 2) + if self.isPedFemale() then + local pedr = ({'female_dealer_reaction_impartial_var01', 'female_dealer_reaction_impartial_var02', 'female_dealer_reaction_impartial_var03'})[math.random(1, 3)] + TaskSynchronizedScene(self.ped, pedReaction, Config.DealerAnimDictShared, pedr, 2.0, -2.0, 13, 16, 1000.0, 0) + else + local pedr = ({'reaction_impartial_var_01', 'reaction_impartial_var_02', 'reaction_impartial_var_03', 'reaction_impartial_var_04'})[math.random(1, 4)] + TaskSynchronizedScene(self.ped, pedReaction, Config.DealerAnimDictShared, pedr, 2.0, -2.0, 13, 16, 1000.0, 0) + end + end + + self.playerWin = function() + local reaction = nil + if GetEntityModel(PlayerPedId()) == GetHashKey('mp_f_freemode_01') then -- female + reaction = + ({ + 'female_reaction_great_var_01', + 'female_reaction_great_var_02', + 'female_reaction_great_var_03', + 'female_reaction_great_var_04', + 'female_reaction_great_var_05' + })[math.random(1, 5)] + else + reaction = ({'reaction_great_var_01', 'reaction_great_var_02', 'reaction_great_var_03', 'reaction_great_var_04'})[math.random(1, 4)] + end + + if reaction then + local reactionScene = NetworkCreateSynchronisedScene(activeChairData.chairCoords, activeChairData.chairRotation, 2, true, false, 1.0, 0.0, 1.0) + NetworkAddPedToSynchronisedScene(PlayerPedId(), reactionScene, Config.PlayerAnimDictShared, reaction, 2.0, -2.0, 13, 16, 2.0, 0) + NetworkStartSynchronisedScene(reactionScene) + end + + local pedReaction = CreateSynchronizedScene(self.data.Position, 0.0, 0.0, self.data.Heading, 2) + if self.isPedFemale() then + local pedr = ({'female_dealer_reaction_bad_var01', 'female_dealer_reaction_bad_var02', 'female_dealer_reaction_bad_var03'})[math.random(1, 3)] + TaskSynchronizedScene(self.ped, pedReaction, Config.DealerAnimDictShared, pedr, 2.0, -2.0, 13, 16, 1000.0, 0) + else + local pedr = ({'reaction_bad_var_01', 'reaction_bad_var_02', 'reaction_bad_var_03', 'reaction_bad_var_04'})[math.random(1, 4)] + TaskSynchronizedScene(self.ped, pedReaction, Config.DealerAnimDictShared, pedr, 2.0, -2.0, 13, 16, 1000.0, 0) + end + end + + self.playerLost = function() + local reaction = nil + if GetEntityModel(PlayerPedId()) == GetHashKey('mp_f_freemode_01') then -- female + reaction = + ({ + 'female_reaction_terrible_var_01', + 'female_reaction_terrible_var_02', + 'female_reaction_terrible_var_03', + 'female_reaction_terrible_var_04', + 'female_reaction_terrible_var_05' + })[math.random(1, 5)] + else + reaction = ({'reaction_terrible_var_01', 'reaction_terrible_var_02', 'reaction_terrible_var_03', 'reaction_terrible_var_04'})[math.random(1, 4)] + end + + if reaction then + local reactionScene = NetworkCreateSynchronisedScene(activeChairData.chairCoords, activeChairData.chairRotation, 2, true, false, 1.0, 0.0, 1.0) + NetworkAddPedToSynchronisedScene(PlayerPedId(), reactionScene, Config.PlayerAnimDictShared, reaction, 2.0, -2.0, 13, 16, 2.0, 0) + NetworkStartSynchronisedScene(reactionScene) + end + + local pedReaction = CreateSynchronizedScene(self.data.Position, 0.0, 0.0, self.data.Heading, 2) + if self.isPedFemale() then + local pedr = ({'female_dealer_reaction_good_var01', 'female_dealer_reaction_good_var02', 'female_dealer_reaction_good_var03'})[math.random(1, 3)] + TaskSynchronizedScene(self.ped, pedReaction, Config.DealerAnimDictShared, pedr, 2.0, -2.0, 13, 16, 1000.0, 0) + else + local pedr = ({'reaction_good_var_01', 'reaction_good_var_02', 'reaction_good_var_03'})[math.random(1, 3)] + TaskSynchronizedScene(self.ped, pedReaction, Config.DealerAnimDictShared, pedr, 2.0, -2.0, 13, 16, 1000.0, 0) + end + end + + self.speakPed = function(duma) + Citizen.CreateThread(function() + PlayPedAmbientSpeechNative(self.ped, duma, 'SPEECH_PARAMS_FORCE_NORMAL_CLEAR', 1) + end) + end + + self.createDefaultPakli = function() + Citizen.CreateThread( + function() + local cardModel = GetHashKey('vw_prop_casino_cards_01') + RequestModel(cardModel) + while not HasModelLoaded(cardModel) do + Citizen.Wait(1) + end + + RequestAnimDict(Config.DealerAnimDictPoker) + while not HasAnimDictLoaded(Config.DealerAnimDictPoker) do + Citizen.Wait(1) + end + + local offset = GetAnimInitialOffsetPosition(Config.DealerAnimDictPoker, 'deck_pick_up_deck', self.data.Position, 0.0, 0.0, self.data.Heading, 0.01, 2) + self.pakli = CreateObject(cardModel, offset, false, false, true) + SetEntityCoordsNoOffset(self.pakli, offset, false, false, true) + SetEntityRotation(self.pakli, 0.0, 0.0, self.data.Rotation, 2, true) + FreezeEntityPosition(self.pakli, true) + end + ) + end + + self.isPedFemale = function() + if GetEntityModel(self.ped) == GetHashKey('S_M_Y_Casino_01') then + return false + else + return true + end + end + + self.createPed = function() + Citizen.CreateThread( + function() + local maleCasinoDealer = GetHashKey('S_M_Y_Casino_01') + local femaleCasinoDealer = GetHashKey('S_F_Y_Casino_01') + + local frmVar_1 = math.random(1, 13) + if frmVar_1 < 7 then + dealerModel = maleCasinoDealer + else + dealerModel = femaleCasinoDealer + end + + RequestModel(dealerModel) + while not HasModelLoaded(dealerModel) do + Citizen.Wait(1) + end + + self.ped = CreatePed(26, dealerModel, self.data.Position, self.data.Heading, false, true) + SetModelAsNoLongerNeeded(dealerModel) + SetEntityCanBeDamaged(self.ped, false) + SetPedAsEnemy(self.ped, false) + SetBlockingOfNonTemporaryEvents(self.ped, true) + SetPedResetFlag(self.ped, 249, 1) + SetPedConfigFlag(self.ped, 185, true) + SetPedConfigFlag(self.ped, 108, true) + SetPedCanEvasiveDive(self.ped, 0) + SetPedCanRagdollFromPlayerImpact(self.ped, 0) + SetPedConfigFlag(self.ped, 208, true) + SetPedCanRagdoll(self.ped, false) + -- N_0x352e2b5cf420bf3b(self.ped, 1) -- no idea what are these + -- N_0x2f3c3d9f50681de4(self.ped, true) -- no idea what are these + -- N_0xf8ad2eed7c47e8fe(self.ped, true, false) -- no idea what are these + + frm_setPedClothes(frmVar_1, self.ped) + frm_setPedVoiceGroup(frmVar_1, self.ped) + + SetEntityCoordsNoOffset(self.ped, self.data.Position + vector3(0.0, 0.0, 1.0), false, false, true) + SetEntityHeading(self.ped, self.data.Heading) + + RequestAnimDict(Config.DealerAnimDictShared) + while not HasAnimDictLoaded(Config.DealerAnimDictShared) do + Citizen.Wait(1) + end + + self.dealerStandingIdle() + + Config.DebugMsg('Poker ped created.') + end + ) + end + + self.sitDown = function(chairId, chairCoords, chairRotation) + StartAudioScene('DLC_VW_Casino_Table_Games') + + if not IsEntityDead(PlayerPedId()) then + QBCore.Functions.TriggerCallback( + 'aquiverPoker:sitDown', + function(canSit) + if canSit then + activeChairData = { + chairId = chairId, + chairCoords = chairCoords, + chairRotation = chairRotation + } + + exports["qb-core"]:HideText() + -- exports['casinoUi']:HideCasinoUi('hide') + + if GetEntityModel(PlayerPedId()) == GetHashKey('mp_m_freemode_01') then + local rspeech = math.random(1, 2) + if rspeech == 1 then + self.speakPed('MINIGAME_DEALER_GREET') + else + self.speakPed('MINIGAME_DEALER_GREET_MALE') + end + else + local rspeech = math.random(1, 2) + if rspeech == 1 then + self.speakPed('MINIGAME_DEALER_GREET') + else + self.speakPed('MINIGAME_DEALER_GREET_FEMALE') + end + end + + buttonScaleform = setupFirstButtons('instructional_buttons') + + RequestAnimDict(Config.PlayerAnimDictShared) + while not HasAnimDictLoaded(Config.PlayerAnimDictShared) do + Citizen.Wait(1) + end + SetPlayerControl(PlayerPedId(), 0, 0) + local sitScene = NetworkCreateSynchronisedScene(chairCoords, chairRotation, 2, true, false, 1.0, 0.0, 1.0) + local sitAnim = ({'sit_enter_left_side', 'sit_enter_right_side'})[math.random(1, 2)] + NetworkAddPedToSynchronisedScene(PlayerPedId(), sitScene, Config.PlayerAnimDictShared, sitAnim, 2.0, -2.0, 13, 16, 2.0, 0) + NetworkStartSynchronisedScene(sitScene) + + Citizen.Wait(4000) + mainScene = NetworkCreateSynchronisedScene(chairCoords, chairRotation, 2, true, false, 1.0, 0.0, 1.0) + NetworkAddPedToSynchronisedScene(PlayerPedId(), mainScene, Config.PlayerAnimDictShared, 'idle_cardgames', 2.0, -2.0, 13, 16, 1000.0, 0) + NetworkStartSynchronisedScene(mainScene) + + self.EnableRender(true) + SetPlayerControl(PlayerPedId(), 1, 0) + exports["qb-core"]:DrawText("Place Bet:

Adjust Bet: ↑/↓

Exit: ←") + + Citizen.Wait(500) + else + QBCore.Functions.Notify('This seat is occupied.') + end + end, + self.index, + chairId + ) + end + end + + self.createCard = function(cardName) + local cardModel = GetHashKey(cardName) + RequestModel(cardModel) + while not HasModelLoaded(cardModel) do + Citizen.Wait(1) + end + + return CreateObject(cardModel, self.data.Position + vector3(0.0, 0.0, -0.1), false, true, true) + end + + self.FirstAction = function() + self.speakPed('MINIGAME_DEALER_CLOSED_BETS') + + -- FIRST ACTION TO DO WHEN STARTING GAME + RequestAnimDict(Config.DealerAnimDictPoker) + while not HasAnimDictLoaded(Config.DealerAnimDictPoker) do + Citizen.Wait(1) + end + + local firstScene = CreateSynchronizedScene(self.data.Position, 0.0, 0.0, self.data.Heading, 2) + + if self.isPedFemale() then + TaskSynchronizedScene(self.ped, firstScene, Config.DealerAnimDictPoker, 'female_deck_pick_up', 2.0, -2.0, 13, 16, 1000.0, 0) + else + TaskSynchronizedScene(self.ped, firstScene, Config.DealerAnimDictPoker, 'deck_pick_up', 2.0, -2.0, 13, 16, 1000.0, 0) + end + + while GetSynchronizedScenePhase(firstScene) < 0.99 do + if HasAnimEventFired(self.ped, 1691374422) then + if not IsEntityAttachedToAnyPed(self.pakli) then + FreezeEntityPosition(self.pakli, false) + AttachEntityToEntity(self.pakli, self.ped, GetPedBoneIndex(self.ped, 60309), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false, false, false, true, 2, true) + end + end + + Citizen.Wait(1) + end + + if self.ServerCards['dealer'] ~= nil then + self.cards['dealer'] = {} + + if not DoesEntityExist(self.cards['dealer'][1]) then + self.cards['dealer'][1] = self.createCard(Config.Cards[self.ServerCards['dealer'].Hand[1]]) + end + if not DoesEntityExist(self.cards['dealer'][2]) then + self.cards['dealer'][2] = self.createCard(Config.Cards[self.ServerCards['dealer'].Hand[2]]) + end + if not DoesEntityExist(self.cards['dealer'][3]) then + self.cards['dealer'][3] = self.createCard(Config.Cards[self.ServerCards['dealer'].Hand[3]]) + end + end + + local secondScene = CreateSynchronizedScene(self.data.Position, 0.0, 0.0, self.data.Heading, 2) + if self.isPedFemale() then + TaskSynchronizedScene(self.ped, secondScene, Config.DealerAnimDictPoker, 'female_deck_shuffle', 2.0, -2.0, 13, 16, 1000.0, 0) + else + TaskSynchronizedScene(self.ped, secondScene, Config.DealerAnimDictPoker, 'deck_shuffle', 2.0, -2.0, 13, 16, 1000.0, 0) + end + PlaySynchronizedEntityAnim(self.cards['dealer'][1], secondScene, 'deck_shuffle_card_a', Config.DealerAnimDictPoker, 1000.0, 0, 0, 1000.0) + PlaySynchronizedEntityAnim(self.cards['dealer'][2], secondScene, 'deck_shuffle_card_b', Config.DealerAnimDictPoker, 1000.0, 0, 0, 1000.0) + PlaySynchronizedEntityAnim(self.cards['dealer'][3], secondScene, 'deck_shuffle_card_c', Config.DealerAnimDictPoker, 1000.0, 0, 0, 1000.0) + + while GetSynchronizedScenePhase(secondScene) < 0.99 do + Citizen.Wait(1) + end + + SetEntityVisible(self.cards['dealer'][1], false, false) + SetEntityVisible(self.cards['dealer'][2], false, false) + SetEntityVisible(self.cards['dealer'][3], false, false) + + local thirdScene = CreateSynchronizedScene(self.data.Position, 0.0, 0.0, self.data.Heading, 2) + if self.isPedFemale() then + TaskSynchronizedScene(self.ped, thirdScene, Config.DealerAnimDictPoker, 'female_deck_idle', 2.0, -2.0, 13, 16, 1000.0, 0) + else + TaskSynchronizedScene(self.ped, thirdScene, Config.DealerAnimDictPoker, 'deck_idle', 2.0, -2.0, 13, 16, 1000.0, 0) + end + while GetSynchronizedScenePhase(thirdScene) < 0.99 do + Citizen.Wait(1) + end + end + + self.dealToPlayers = function() + StartAudioScene('DLC_VW_Casino_Cards_Focus_Hand') + StartAudioScene('DLC_VW_Casino_Table_Games') + + buttonScaleform = nil + Config.DebugMsg('dealing to players') + -- SECOND ACTIONS TO DO, THERE CAN BE MORE PLAYERS! + for targetSrc, data in pairs(self.ServerCards) do + if targetSrc ~= 'dealer' then + self.cards[targetSrc] = {} + self.cards[targetSrc][1] = self.createCard(Config.Cards[data.Hand[1]]) + self.cards[targetSrc][2] = self.createCard(Config.Cards[data.Hand[2]]) + self.cards[targetSrc][3] = self.createCard(Config.Cards[data.Hand[3]]) + + RequestAnimDict(Config.DealerAnimDictPoker) + while not HasAnimDictLoaded(Config.DealerAnimDictPoker) do + Citizen.Wait(1) + end + + local playerAnimId = nil + + if data.chairData.chairId == 4 then -- this is reverse because rockstar think differently no idea why + playerAnimId = 'p01' + elseif data.chairData.chairId == 3 then + playerAnimId = 'p02' + elseif data.chairData.chairId == 2 then + playerAnimId = 'p03' + elseif data.chairData.chairId == 1 then + playerAnimId = 'p04' + end + + if playerAnimId ~= nil then + local dealScene = CreateSynchronizedScene(self.data.Position, 0.0, 0.0, self.data.Heading, 2) + + SetEntityVisible(self.cards[targetSrc][1], false, false) + SetEntityVisible(self.cards[targetSrc][2], false, false) + SetEntityVisible(self.cards[targetSrc][3], false, false) + + if self.isPedFemale() then + TaskSynchronizedScene(self.ped, dealScene, Config.DealerAnimDictPoker, string.format('female_deck_deal_%s', playerAnimId), 2.0, -2.0, 13, 16, 1000.0, 0) + else + TaskSynchronizedScene(self.ped, dealScene, Config.DealerAnimDictPoker, string.format('deck_deal_%s', playerAnimId), 2.0, -2.0, 13, 16, 1000.0, 0) + end + + PlaySynchronizedEntityAnim( + self.cards[targetSrc][1], + dealScene, + string.format('deck_deal_%s_card_a', playerAnimId), + Config.DealerAnimDictPoker, + 1000.0, + 0, + 0, + 1000.0 + ) + PlaySynchronizedEntityAnim( + self.cards[targetSrc][2], + dealScene, + string.format('deck_deal_%s_card_b', playerAnimId), + Config.DealerAnimDictPoker, + 1000.0, + 0, + 0, + 1000.0 + ) + PlaySynchronizedEntityAnim( + self.cards[targetSrc][3], + dealScene, + string.format('deck_deal_%s_card_c', playerAnimId), + Config.DealerAnimDictPoker, + 1000.0, + 0, + 0, + 1000.0 + ) + + while GetSynchronizedScenePhase(dealScene) < 0.05 do + Citizen.Wait(1) + end + + SetEntityVisible(self.cards[targetSrc][1], true, false) + SetEntityVisible(self.cards[targetSrc][2], true, false) + SetEntityVisible(self.cards[targetSrc][3], true, false) + + while GetSynchronizedScenePhase(dealScene) < 0.99 do + Citizen.Wait(1) + end + end + end + end + Config.DebugMsg('dealing ended') + end + + self.watchCards = function() + self.speakPed('MINIGAME_DEALER_COMMENT_SLOW') + + if self.index == activePokerTable and playerBetted ~= nil then + clientTimer = Config.PlayerDecideTime + Citizen.CreateThread( + function() + while clientTimer ~= nil do + Citizen.Wait(1000) + if clientTimer ~= nil then + clientTimer = clientTimer - 1 + + if clientTimer < 1 then + clientTimer = nil + QBCore.Functions.Notify('You did not respond for the dealer ask in time, you have folded your hand.') + TriggerServerEvent('aquiverPoker:foldCards', self.index) + end + end + end + end + ) + end + + RequestAnimDict(Config.PlayerAnimDictPoker) + while not HasAnimDictLoaded(Config.PlayerAnimDictPoker) do + Citizen.Wait(1) + end + + for targetSrc, data in pairs(self.ServerCards) do + if targetSrc ~= 'dealer' then + -- if we are the player, we call it once + if GetPlayerServerId(PlayerId()) == targetSrc and self.index == activePokerTable then + local scene = NetworkCreateSynchronisedScene(data.chairData.chairCoords, data.chairData.chairRotation, 2, true, false, 1.0, 0.0, 1.0) + NetworkAddPedToSynchronisedScene(PlayerPedId(), scene, Config.PlayerAnimDictPoker, 'cards_pickup', 2.0, -2.0, 13, 16, 1000.0, 0) + NetworkStartSynchronisedScene(scene) + Citizen.CreateThread( + function() + Citizen.Wait(1500) + watchingCards = true + -- ShakeGameplayCam('HAND_SHAKE', 0.15) + -- buttonScaleform = setupThirdButtons('instructional_buttons') + exports["qb-core"]:DrawText("Play Hand: E

Fold Hand: ←") + + local playerHandValue = Config.getHandAllValues(data.Hand) + if playerHandValue ~= nil then + Config.DebugMsg(string.format('Player hand value: %s', playerHandValue)) + local form = Config.formatHandValue(playerHandValue) + if form ~= nil then + Citizen.CreateThread( + function() + while watchingCards do + Citizen.Wait(0) + exports['casinoUi']:DrawCasinoUi('show', "The Diamond Casino & Resort Poker

Player hand: "..form) + + -- drawText2d(0.5, 0.9, 0.45, form) + + end + end + ) + -- else + -- exports['casinoUi']:HideCasinoUi('hide') + end + end + end + ) + end + + local cardsScene = CreateSynchronizedScene(data.chairData.chairCoords, data.chairData.chairRotation, 2) + + PlaySynchronizedEntityAnim(self.cards[targetSrc][1], cardsScene, 'cards_pickup_card_a', Config.PlayerAnimDictPoker, 1000.0, 0, 0, 1000.0) + PlaySynchronizedEntityAnim(self.cards[targetSrc][2], cardsScene, 'cards_pickup_card_b', Config.PlayerAnimDictPoker, 1000.0, 0, 0, 1000.0) + PlaySynchronizedEntityAnim(self.cards[targetSrc][3], cardsScene, 'cards_pickup_card_c', Config.PlayerAnimDictPoker, 1000.0, 0, 0, 1000.0) + end + end + end + + self.foldCards = function(mainSrc) + self.playersFolded[mainSrc] = true + + if GetPlayerServerId(PlayerId()) == mainSrc then + local scene = NetworkCreateSynchronisedScene(activeChairData.chairCoords, activeChairData.chairRotation, 2, true, false, 1.0, 0.0, 1.0) + NetworkAddPedToSynchronisedScene(PlayerPedId(), scene, Config.PlayerAnimDictPoker, 'cards_fold', 2.0, -2.0, 13, 16, 1000.0, 0) + NetworkStartSynchronisedScene(scene) + playerDecidedChoice = true + watchingCards = false + buttonScaleform = nil + StopGameplayCamShaking(true) + end + + if self.cards[mainSrc] ~= nil then + local chairData = self.ServerCards[mainSrc].chairData + local cardsScene = CreateSynchronizedScene(chairData.chairCoords, chairData.chairRotation, 2) + PlaySynchronizedEntityAnim(self.cards[mainSrc][1], cardsScene, 'cards_fold_card_a', Config.PlayerAnimDictPoker, 1000.0, 0, 0, 1000.0) + PlaySynchronizedEntityAnim(self.cards[mainSrc][2], cardsScene, 'cards_fold_card_b', Config.PlayerAnimDictPoker, 1000.0, 0, 0, 1000.0) + PlaySynchronizedEntityAnim(self.cards[mainSrc][3], cardsScene, 'cards_fold_card_c', Config.PlayerAnimDictPoker, 1000.0, 0, 0, 1000.0) + end + end + + self.playCards = function(mainSrc) + if GetPlayerServerId(PlayerId()) == mainSrc then + playerDecidedChoice = true + watchingCards = false + buttonScaleform = nil + StopGameplayCamShaking(true) + + Citizen.CreateThread( + function() + local scene = NetworkCreateSynchronisedScene(activeChairData.chairCoords, activeChairData.chairRotation, 2, true, false, 1.0, 0.0, 1.0) + NetworkAddPedToSynchronisedScene(PlayerPedId(), scene, Config.PlayerAnimDictPoker, 'cards_play', 2.0, -2.0, 13, 16, 1000.0, 0) + NetworkStartSynchronisedScene(scene) + + while not HasAnimEventFired(PlayerPedId(), -1424880317) do + Citizen.Wait(1) + end + + local nextScene = NetworkCreateSynchronisedScene(activeChairData.chairCoords, activeChairData.chairRotation, 2, true, false, 1.0, 0.0, 1.0) + NetworkAddPedToSynchronisedScene(PlayerPedId(), nextScene, Config.PlayerAnimDictPoker, 'cards_bet', 2.0, -2.0, 13, 16, 1000.0, 0) + NetworkStartSynchronisedScene(nextScene) + + Citizen.Wait(500) + + local offsetAlign = nil + if activeChairData.chairId == 4 then + offsetAlign = vector3(0.689125, 0.171575, 0.954) + elseif activeChairData.chairId == 3 then + offsetAlign = vector3(0.2869, -0.211925, 0.954) + elseif activeChairData.chairId == 2 then + offsetAlign = vector3(-0.30935, -0.205675, 0.954) + elseif activeChairData.chairId == 1 then + offsetAlign = vector3(-0.69795, 0.211525, 0.954) + end + + if offsetAlign == nil then + Config.DebugMsg('Something error happened during the playCards function.') + return + end + + local offset = GetObjectOffsetFromCoords(self.data.Position, self.data.Heading, offsetAlign) + local chipModel = getChipModelByAmount(playerBetted) + RequestModel(chipModel) + while not HasModelLoaded(chipModel) do + Citizen.Wait(1) + end + + local chipObj = CreateObjectNoOffset(chipModel, offset, true, false, true) + SetEntityCoordsNoOffset(chipObj, offset, false, false, true) + SetEntityHeading(chipObj, GetEntityHeading(PlayerPedId())) + table.insert(networkedChips, chipObj) + + while not HasAnimEventFired(PlayerPedId(), -1424880317) do + Citizen.Wait(1) + end + + self.playerRandomIdleAnim() + end + ) + end + + if self.cards[mainSrc] ~= nil and self.ServerCards[mainSrc] ~= nil then + local chairData = self.ServerCards[mainSrc].chairData + local cardsScene = CreateSynchronizedScene(chairData.chairCoords, chairData.chairRotation, 2) + PlaySynchronizedEntityAnim(self.cards[mainSrc][1], cardsScene, 'cards_play_card_a', Config.PlayerAnimDictPoker, 1000.0, 0, 0, 1000.0) + PlaySynchronizedEntityAnim(self.cards[mainSrc][2], cardsScene, 'cards_play_card_b', Config.PlayerAnimDictPoker, 1000.0, 0, 0, 1000.0) + PlaySynchronizedEntityAnim(self.cards[mainSrc][3], cardsScene, 'cards_play_card_c', Config.PlayerAnimDictPoker, 1000.0, 0, 0, 1000.0) + end + end + + self.playerRandomIdleAnim = function() + local selectedIdleAnim = nil + + if GetEntityModel(PlayerPedId()) == GetHashKey('mp_f_freemode_01') then -- female + local fmlIdles = { + 'female_idle_cardgames_var_01', + 'female_idle_cardgames_var_02', + 'female_idle_cardgames_var_03', + 'female_idle_cardgames_var_04', + 'female_idle_cardgames_var_05', + 'female_idle_cardgames_var_06', + 'female_idle_cardgames_var_07', + 'female_idle_cardgames_var_08' + } + selectedIdleAnim = fmlIdles[math.random(1, 8)] + else -- male or UFO + local mlIdles = { + 'idle_cardgames_var_01', + 'idle_cardgames_var_02', + 'idle_cardgames_var_03', + 'idle_cardgames_var_04', + 'idle_cardgames_var_05', + 'idle_cardgames_var_06', + 'idle_cardgames_var_07', + 'idle_cardgames_var_08', + 'idle_cardgames_var_09', + 'idle_cardgames_var_10', + 'idle_cardgames_var_11', + 'idle_cardgames_var_12', + 'idle_cardgames_var_13' + } + selectedIdleAnim = mlIdles[math.random(1, 13)] + end + + if selectedIdleAnim ~= nil then + local playerIdleScene = NetworkCreateSynchronisedScene(activeChairData.chairCoords, activeChairData.chairRotation, 2, true, false, 1.0, 0.0, 1.0) + NetworkAddPedToSynchronisedScene(PlayerPedId(), playerIdleScene, Config.PlayerAnimDictShared, selectedIdleAnim, 2.0, -2.0, 13, 16, 1000.0, 0) + NetworkStartSynchronisedScene(playerIdleScene) + + while not HasAnimEventFired(PlayerPedId(), -1424880317) do + Citizen.Wait(1) + end + + local playerIdleScene2 = NetworkCreateSynchronisedScene(activeChairData.chairCoords, activeChairData.chairRotation, 2, true, false, 1.0, 0.0, 1.0) + NetworkAddPedToSynchronisedScene(PlayerPedId(), playerIdleScene2, Config.PlayerAnimDictShared, 'idle_cardgames', 2.0, -2.0, 13, 16, 1000.0, 0) + NetworkStartSynchronisedScene(playerIdleScene2) + end + end + + self.dealToSelf = function() + Config.DebugMsg('dealing dealer cards.') + local dealSelfScene = CreateSynchronizedScene(self.data.Position, 0.0, 0.0, self.data.Heading, 2) + if self.isPedFemale() then + TaskSynchronizedScene(self.ped, dealSelfScene, Config.DealerAnimDictPoker, 'female_deck_deal_self', 2.0, -2.0, 13, 16, 1000.0, 0) + else + TaskSynchronizedScene(self.ped, dealSelfScene, Config.DealerAnimDictPoker, 'deck_deal_self', 2.0, -2.0, 13, 16, 1000.0, 0) + end + PlaySynchronizedEntityAnim(self.cards['dealer'][1], dealSelfScene, 'deck_deal_self_card_a', Config.DealerAnimDictPoker, 1000.0, 0, 0, 1000.0) + PlaySynchronizedEntityAnim(self.cards['dealer'][2], dealSelfScene, 'deck_deal_self_card_b', Config.DealerAnimDictPoker, 1000.0, 0, 0, 1000.0) + PlaySynchronizedEntityAnim(self.cards['dealer'][3], dealSelfScene, 'deck_deal_self_card_c', Config.DealerAnimDictPoker, 1000.0, 0, 0, 1000.0) + + while GetSynchronizedScenePhase(dealSelfScene) < 0.05 do + Citizen.Wait(1) + end + + SetEntityVisible(self.cards['dealer'][1], true, false) + SetEntityVisible(self.cards['dealer'][2], true, false) + SetEntityVisible(self.cards['dealer'][3], true, false) + + while GetSynchronizedScenePhase(dealSelfScene) < 0.99 do + Citizen.Wait(1) + end + end + + self.dealerStandingIdle = function() + local scene = CreateSynchronizedScene(self.data.Position, 0.0, 0.0, self.data.Heading, 2) + if self.isPedFemale() then + TaskSynchronizedScene(self.ped, scene, Config.DealerAnimDictShared, 'female_idle', 1000.0, -2.0, -1.0, 33, 1000.0, 0) + else + TaskSynchronizedScene(self.ped, scene, Config.DealerAnimDictShared, 'idle', 1000.0, -2.0, -1.0, 33, 1000.0, 0) + end + end + + self.putDownDeck = function() + local scene = CreateSynchronizedScene(self.data.Position, 0.0, 0.0, self.data.Heading, 2) + if self.isPedFemale() then + TaskSynchronizedScene(self.ped, scene, Config.DealerAnimDictPoker, 'female_deck_put_down', 2.0, -2.0, 13, 16, 1000.0, 0) + else + TaskSynchronizedScene(self.ped, scene, Config.DealerAnimDictPoker, 'deck_put_down', 2.0, -2.0, 13, 16, 1000.0, 0) + end + while GetSynchronizedScenePhase(scene) < 0.99 do + Citizen.Wait(1) + end + + if IsEntityAttachedToAnyPed(self.pakli) then + DetachEntity(self.pakli, true, true) + FreezeEntityPosition(self.pakli, true) + Config.DebugMsg('pakli detached') + end + + self.dealerStandingIdle() + end + + self.EnableRender = function(state) + if state then + activePokerTable = self.index + TriggerEvent('ShowPlayerHud', false) + + Citizen.CreateThread( + function() + while activePokerTable do + Citizen.Wait(0) + DisableAllControlActions(0) + + if buttonScaleform ~= nil then + -- DrawScaleformMovieFullscreen(buttonScaleform, 255, 255, 255, 255, 0) + exports['casinoUi']:DrawCasinoUi('show', "The Diamond Casino & Resort Poker

Current Bet: "..currentBetInput.."

Availble chips: "..PlayerOwnedChips) + end + + EnableControlAction(0, 0, true) -- changing camera + EnableControlAction(0, 1, true) -- mouse cam + EnableControlAction(0, 2, true) -- mouse cam + EnableControlAction(0, 24, true) + EnableControlAction(0, 249, true) + + -- if player betted then + if playerBetted and QBCore then + local reactiveText = '' + + if currentHelpText then + reactiveText = reactiveText .. currentHelpText + end + + if self.TimeLeft > 0 then + reactiveText = reactiveText .. _U('waiting_for_players') + end + + if watchingCards then + if IsDisabledControlJustPressed(0, 38) then + clientTimer = nil + watchingCards = false + buttonScaleform = nil + StopGameplayCamShaking(true) + TriggerServerEvent('aquiverPoker:playCards', self.index, playerBetted) + end + + if IsDisabledControlJustPressed(0, 177) then + clientTimer = nil + watchingCards = false + buttonScaleform = nil + StopGameplayCamShaking(true) + TriggerServerEvent('aquiverPoker:foldCards', self.index) + end + end + + if string.len(reactiveText) > 0 then + ShowHelpNotification(reactiveText) + end + end + + -- only enable standup if he did not bet + if playerBetted == nil then + if IsDisabledControlJustPressed(0, 177) then + self.EnableRender(false) + -- PlaySoundFrontend(-1, 'FocusOut', 'HintCamSounds', false) + end + end + + if playerBetted == nil or playerPairPlus == nil then + if self.TimeLeft == nil or self.TimeLeft > 0 then + -- -- bet input + -- if IsDisabledControlJustPressed(0, 22) then --Custom Bet [space] + -- local tmpInput = getGenericTextInput('Tét') + -- if tonumber(tmpInput) then + -- tmpInput = tonumber(tmpInput) + -- if tmpInput > 0 then + -- if tmpInput > self.data.MaximumBet then + -- PlaySoundFrontend(-1, 'DLC_VW_ERROR_MAX', 'dlc_vw_table_games_frontend_sounds', true) + -- else + -- currentBetInput = tmpInput + -- PlaySoundFrontend(-1, 'DLC_VW_BET_HIGHLIGHT', 'dlc_vw_table_games_frontend_sounds', true) + -- end + -- end + -- end + -- end + + if IsDisabledControlJustPressed(0, 176) then + if currentBetInput > 0 then + if currentBetInput >= self.data.MinimumBet and currentBetInput <= self.data.MaximumBet then + if playerBetted == nil then + TriggerServerEvent('aquiverPoker:betPlayer', self.index, activeChairData, currentBetInput) + else + if playerPairPlus == nil then + TriggerServerEvent('aquiverPoker:betPairPlusPlayer', self.index, currentBetInput) + end + end + else + PlaySoundFrontend(-1, 'DLC_VW_ERROR_MAX', 'dlc_vw_table_games_frontend_sounds', true) + end + else + QBCore.Functions.Notify("You did not set up a bet value.") + end + end + + if IsDisabledControlJustPressed(0, 172) then -- up + local increase = Config.IncreaseAmounts(currentBetInput) + currentBetInput = currentBetInput + increase + if currentBetInput > self.data.MaximumBet then + PlaySoundFrontend(-1, 'DLC_VW_ERROR_MAX', 'dlc_vw_table_games_frontend_sounds', true) + currentBetInput = self.data.MaximumBet + else + PlaySoundFrontend(-1, 'DLC_VW_BET_UP', 'dlc_vw_table_games_frontend_sounds', true) + end + elseif IsDisabledControlJustPressed(0, 173) then -- down + if currentBetInput > 0 then + local increase = Config.IncreaseAmounts(currentBetInput) + currentBetInput = currentBetInput - increase + PlaySoundFrontend(-1, 'DLC_VW_BET_DOWN', 'dlc_vw_table_games_frontend_sounds', true) + if currentBetInput < 0 then + currentBetInput = 0 + PlaySoundFrontend(-1, 'DLC_VW_ERROR_MAX', 'dlc_vw_table_games_frontend_sounds', true) + end + else + PlaySoundFrontend(-1, 'DLC_VW_ERROR_MAX', 'dlc_vw_table_games_frontend_sounds', true) + end + end + end + end + -- if self.Active then + -- if self.TimeLeft >= 10 then + -- -- DrawRect(0.944, 0.799, 0.081, 0.032, 0, 0, 0, 200) + -- -- DrawAdvancedNativeText(1.013, 0.806, 0.005, 0.0028, 0.29, _U('remaining_time'), 255, 255, 255, 255, 0, 0) + -- -- DrawAdvancedNativeText(1.05, 0.799, 0.005, 0.0028, 0.464, string.format('00:%s', self.TimeLeft), 255, 255, 255, 255, 0, 0) + -- else + -- if self.TimeLeft > 0 then + -- -- DrawAdvancedNativeText(1.013, 0.806, 0.005, 0.0028, 0.29, _U('remaining_time'), 255, 255, 255, 255, 0, 0) + -- -- DrawRect(0.944, 0.799, 0.081, 0.032, 0, 0, 0, 200) + -- -- DrawAdvancedNativeText(1.05, 0.799, 0.005, 0.0028, 0.464, string.format('00:0%s', self.TimeLeft), 255, 255, 255, 255, 0, 0) + + -- else + -- if clientTimer ~= nil then + -- -- DrawAdvancedNativeText(1.013, 0.806, 0.005, 0.0028, 0.29, _U('remaining_time'), 255, 255, 255, 255, 0, 0) + -- -- DrawRect(0.944, 0.799, 0.081, 0.032, 0, 0, 0, 200) + + -- if clientTimer >= 10 then + -- -- DrawAdvancedNativeText(1.05, 0.799, 0.005, 0.0028, 0.464, string.format('00:%s', clientTimer), 255, 255, 255, 255, 0, 0) + + -- else + -- -- DrawAdvancedNativeText(1.05, 0.799, 0.005, 0.0028, 0.464, string.format('00:0%s', clientTimer), 255, 255, 255, 255, 0, 0) + -- end + -- end + -- end + -- end + -- end + -- exports['casinoUi']:DrawCasinoUi('show', "BET:"..currentBetInput.."
CHIPS:"..PlayerOwnedChips.."
MIN/MAX:"..self.data.MinimumBet.."/"..self.data.MaximumBet.."
TIME:") + end + end + ) + else + exports['casinoUi']:HideCasinoUi('hide') + exports["qb-core"]:HideText() + + + self.speakPed('MINIGAME_DEALER_LEAVE_NEUTRAL_GAME') + local sitExitScene = NetworkCreateSynchronisedScene(activeChairData.chairCoords, activeChairData.chairRotation, 2, true, false, 1.0, 0.0, 1.0) + NetworkAddPedToSynchronisedScene(PlayerPedId(), sitExitScene, Config.PlayerAnimDictShared, 'sit_exit_left', 2.0, -2.0, 13, 16, 2.0, 0) + NetworkStartSynchronisedScene(sitExitScene) + Citizen.Wait(4000) + TriggerServerEvent('aquiverPoker:standUp', self.index, activeChairData.chairId) + TriggerEvent('ShowPlayerHud', true) + NetworkStopSynchronisedScene(mainScene) + NetworkStopSynchronisedScene(sitExitScene) + activePokerTable = nil + activeChairData = nil + end + end + + self.revealSelfCards = function() + Citizen.CreateThread( + function() + if self.index == activePokerTable then + local offset = GetObjectOffsetFromCoords(self.data.Position, self.data.Heading, 0.0, -0.04, 1.35) + + mainCamera = CreateCamWithParams('DEFAULT_SCRIPTED_CAMERA', offset, -78.0, 0.0, self.data.Heading, 80.0, true, 2) + SetCamActive(mainCamera, true) + RenderScriptCams(true, 900, 900, true, false) + ShakeCam(mainCamera, 'HAND_SHAKE', 0.25) + + Citizen.Wait(2500) + + local dealerHandValue = Config.getHandAllValues(self.ServerCards['dealer'].Hand) + if dealerHandValue ~= nil then + Config.DebugMsg(string.format('Dealer hand value: %s', dealerHandValue)) + local form = Config.formatHandValue(dealerHandValue) + if form ~= nil then + Citizen.CreateThread( + function() + while DoesCamExist(mainCamera) do + Citizen.Wait(0) + + exports['casinoUi']:DrawCasinoUi('show', "The Diamond Casino & Resort Poker

Dealer hand: "..form) + + -- drawText2d(0.5, 0.9, 0.45, form) + -- elseif + -- exports['casinoUi']:HideCasinoUi('hide') + end + end + ) + -- else + -- exports['casinoUi']:HideCasinoUi('hide') + end + end + + Citizen.Wait(7500) + + if DoesCamExist(mainCamera) then + DestroyCam(mainCamera, false) + end + RenderScriptCams(false, 900, 900, true, false) + end + end + ) + if self.ServerCards['dealer'] ~= nil then + local revealScene = CreateSynchronizedScene(self.data.Position, 0.0, 0.0, self.data.Heading, 2) + if self.isPedFemale() then + TaskSynchronizedScene(self.ped, revealScene, Config.DealerAnimDictPoker, 'female_reveal_self', 2.0, -2.0, 13, 16, 1000.0, 0) + else + TaskSynchronizedScene(self.ped, revealScene, Config.DealerAnimDictPoker, 'reveal_self', 2.0, -2.0, 13, 16, 1000.0, 0) + end + PlaySynchronizedEntityAnim(self.cards['dealer'][1], revealScene, 'reveal_self_card_a', Config.DealerAnimDictPoker, 1000.0, 0, 0, 1000.0) + PlaySynchronizedEntityAnim(self.cards['dealer'][2], revealScene, 'reveal_self_card_b', Config.DealerAnimDictPoker, 1000.0, 0, 0, 1000.0) + PlaySynchronizedEntityAnim(self.cards['dealer'][3], revealScene, 'reveal_self_card_c', Config.DealerAnimDictPoker, 1000.0, 0, 0, 1000.0) + end + end + + self.revealPlayerCards = function() + for targetSrc, data in pairs(self.ServerCards) do + if targetSrc ~= 'dealer' then + local playerAnimId = nil + + if data.chairData.chairId == 4 then -- this is reverse because rockstar think differently no idea why + playerAnimId = 'p01' + elseif data.chairData.chairId == 3 then + playerAnimId = 'p02' + elseif data.chairData.chairId == 2 then + playerAnimId = 'p03' + elseif data.chairData.chairId == 1 then + playerAnimId = 'p04' + end + + local mainAnimFormat = nil + local entityAnimFormatA = nil + local entityAnimFormatB = nil + local entityAnimFormatC = nil + + if self.playersFolded[targetSrc] then -- if he or she folded the hand + if self.isPedFemale() then + mainAnimFormat = string.format('female_reveal_folded_%s', playerAnimId) + else + mainAnimFormat = string.format('reveal_folded_%s', playerAnimId) + end + entityAnimFormatA = string.format('reveal_folded_%s_card_a', playerAnimId) + entityAnimFormatB = string.format('reveal_folded_%s_card_b', playerAnimId) + entityAnimFormatC = string.format('reveal_folded_%s_card_c', playerAnimId) + else + if self.isPedFemale() then + mainAnimFormat = string.format('female_reveal_played_%s', playerAnimId) + else + mainAnimFormat = string.format('reveal_played_%s', playerAnimId) + end + entityAnimFormatA = string.format('reveal_played_%s_card_a', playerAnimId) + entityAnimFormatB = string.format('reveal_played_%s_card_b', playerAnimId) + entityAnimFormatC = string.format('reveal_played_%s_card_c', playerAnimId) + end + + if mainAnimFormat ~= nil then + if activePokerTable == self.index then -- only show camera if he/she is sitting at the table. + if Config.ShowCardsAfterReveal then + local offset = + GetAnimInitialOffsetPosition(Config.PlayerAnimDictPoker, 'cards_play_card_b', data.chairData.chairCoords, data.chairData.chairRotation, 0.0, 2) + + if DoesCamExist(mainCamera) then + DestroyCam(mainCamera, false) + end + + mainCamera = + CreateCamWithParams('DEFAULT_SCRIPTED_CAMERA', offset + vector3(0.0, 0.0, 0.45), -85.0, 0.0, data.chairData.chairRotation.z - 90.0, 80.0, true, 2) + SetCamActive(mainCamera, true) + RenderScriptCams(true, 900, 900, true, false) + ShakeCam(mainCamera, 'HAND_SHAKE', 0.25) + end + end + + SetEntityVisible(self.cards[targetSrc][1], false, false) + SetEntityVisible(self.cards[targetSrc][2], false, false) + SetEntityVisible(self.cards[targetSrc][3], false, false) + + local revealScene = CreateSynchronizedScene(self.data.Position, 0.0, 0.0, self.data.Heading, 2) + TaskSynchronizedScene(self.ped, revealScene, Config.DealerAnimDictPoker, mainAnimFormat, 2.0, -2.0, 13, 16, 1000.0, 0) + PlaySynchronizedEntityAnim(self.cards[targetSrc][1], revealScene, entityAnimFormatA, Config.DealerAnimDictPoker, 1000.0, 0, 0, 1000.0) + PlaySynchronizedEntityAnim(self.cards[targetSrc][2], revealScene, entityAnimFormatB, Config.DealerAnimDictPoker, 1000.0, 0, 0, 1000.0) + PlaySynchronizedEntityAnim(self.cards[targetSrc][3], revealScene, entityAnimFormatC, Config.DealerAnimDictPoker, 1000.0, 0, 0, 1000.0) + + while GetSynchronizedScenePhase(revealScene) < 0.025 do + Citizen.Wait(1) + end + + SetEntityVisible(self.cards[targetSrc][1], true, false) + SetEntityVisible(self.cards[targetSrc][2], true, false) + SetEntityVisible(self.cards[targetSrc][3], true, false) + + while GetSynchronizedScenePhase(revealScene) < 0.99 do + Citizen.Wait(1) + end + + local ggScene = CreateSynchronizedScene(self.data.Position, 0.0, 0.0, self.data.Heading, 2) + if self.isPedFemale() then + TaskSynchronizedScene(self.ped, ggScene, Config.DealerAnimDictShared, string.format('female_acknowledge_%s', playerAnimId), 2.0, -2.0, 13, 16, 1000.0, 0) + else + TaskSynchronizedScene(self.ped, ggScene, Config.DealerAnimDictShared, string.format('acknowledge_%s', playerAnimId), 2.0, -2.0, 13, 16, 1000.0, 0) + end + end + end + end + end + + self.resetTable = function() + + -- chips clearing + if #networkedChips > 0 then + for i = 1, #networkedChips, 1 do + if NetworkGetEntityOwner(networkedChips[i]) == PlayerId() then + DeleteObject(networkedChips[i]) + end + end + end + + Citizen.Wait(200) -- because i like timeouts -_- + + for k, v in pairs(self.cards) do + for i = 1, #v, 1 do + DeleteObject(v[i]) + end + end + + Citizen.Wait(200) -- because i like timeouts -_- + + self.cards = {} + + if self.index == activePokerTable then + playerBetted = nil + playerPairPlus = nil + watchingCards = false + StopGameplayCamShaking(true) + playerDecidedChoice = false + clientTimer = nil + currentHelpText = nil + networkedChips = {} + currentBetInput = 0 + buttonScaleform = setupFirstButtons('instructional_buttons') + end + + self.ServerCards = {} + self.Active = false + self.TimeLeft = nil + self.playersPlaying = {} + self.playersFolded = {} + + self.dealerStandingIdle() + end + + self.clearTable = function() + self.speakPed('MINIGAME_DEALER_ANOTHER_GO') + -- exports["qb-core"]:HideText() + -- exports['casinoUi']:HideCasinoUi('hide') + -- deck picking up anim + local firstScene = CreateSynchronizedScene(self.data.Position, 0.0, 0.0, self.data.Heading, 2) + if self.isPedFemale() then + TaskSynchronizedScene(self.ped, firstScene, Config.DealerAnimDictPoker, 'female_deck_pick_up', 2.0, -2.0, 13, 16, 1000.0, 0) + else + TaskSynchronizedScene(self.ped, firstScene, Config.DealerAnimDictPoker, 'deck_pick_up', 2.0, -2.0, 13, 16, 1000.0, 0) + end + while GetSynchronizedScenePhase(firstScene) < 0.99 do + if HasAnimEventFired(self.ped, 1691374422) then + if not IsEntityAttachedToAnyPed(self.pakli) then + FreezeEntityPosition(self.pakli, false) + AttachEntityToEntity(self.pakli, self.ped, GetPedBoneIndex(self.ped, 60309), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false, false, false, true, 2, true) + end + end + Citizen.Wait(1) + end + + -- collect player cards + for targetSrc, data in pairs(self.ServerCards) do + if targetSrc ~= 'dealer' then + local playerAnimId = nil + + if data.chairData.chairId == 4 then -- this is reverse because rockstar think differently no idea why + playerAnimId = 'p01' + elseif data.chairData.chairId == 3 then + playerAnimId = 'p02' + elseif data.chairData.chairId == 2 then + playerAnimId = 'p03' + elseif data.chairData.chairId == 1 then + playerAnimId = 'p04' + end + + local collectScene = CreateSynchronizedScene(self.data.Position, 0.0, 0.0, self.data.Heading, 2) + if self.isPedFemale() then + TaskSynchronizedScene(self.ped, collectScene, Config.DealerAnimDictPoker, string.format('female_cards_collect_%s', playerAnimId), 2.0, -2.0, 13, 16, 1000.0, 0) + else + TaskSynchronizedScene(self.ped, collectScene, Config.DealerAnimDictPoker, string.format('cards_collect_%s', playerAnimId), 2.0, -2.0, 13, 16, 1000.0, 0) + end + PlaySynchronizedEntityAnim( + self.cards[targetSrc][1], + collectScene, + string.format('cards_collect_%s_card_a', playerAnimId), + Config.DealerAnimDictPoker, + 1000.0, + 0, + 0, + 1000.0 + ) + PlaySynchronizedEntityAnim( + self.cards[targetSrc][2], + collectScene, + string.format('cards_collect_%s_card_b', playerAnimId), + Config.DealerAnimDictPoker, + 1000.0, + 0, + 0, + 1000.0 + ) + PlaySynchronizedEntityAnim( + self.cards[targetSrc][3], + collectScene, + string.format('cards_collect_%s_card_c', playerAnimId), + Config.DealerAnimDictPoker, + 1000.0, + 0, + 0, + 1000.0 + ) + while GetSynchronizedScenePhase(collectScene) < 0.99 do + Citizen.Wait(1) + end + + DeleteObject(self.cards[targetSrc][1]) + DeleteObject(self.cards[targetSrc][2]) + DeleteObject(self.cards[targetSrc][3]) + end + end + + -- collect own dealer cards + if self.ServerCards['dealer'] then + local collectScene = CreateSynchronizedScene(self.data.Position, 0.0, 0.0, self.data.Heading, 2) + if self.isPedFemale() then + TaskSynchronizedScene(self.ped, collectScene, Config.DealerAnimDictPoker, 'female_cards_collect_self', 2.0, -2.0, 13, 16, 1000.0, 0) + else + TaskSynchronizedScene(self.ped, collectScene, Config.DealerAnimDictPoker, 'cards_collect_self', 2.0, -2.0, 13, 16, 1000.0, 0) + end + PlaySynchronizedEntityAnim(self.cards['dealer'][1], collectScene, 'cards_collect_self_card_a', Config.DealerAnimDictPoker, 1000.0, 0, 0, 1000.0) + PlaySynchronizedEntityAnim(self.cards['dealer'][2], collectScene, 'cards_collect_self_card_b', Config.DealerAnimDictPoker, 1000.0, 0, 0, 1000.0) + PlaySynchronizedEntityAnim(self.cards['dealer'][3], collectScene, 'cards_collect_self_card_c', Config.DealerAnimDictPoker, 1000.0, 0, 0, 1000.0) + SetBit(0) + while GetSynchronizedScenePhase(collectScene) < 0.99 do + Citizen.Wait(1) + end + + DeleteObject(self.cards['dealer'][1]) + DeleteObject(self.cards['dealer'][2]) + DeleteObject(self.cards['dealer'][3]) + end + + self.putDownDeck() + end + + self.playerPairPlusAnim = function(amount) + playerPairPlus = amount + buttonScaleform = nil + + RequestAnimDict(Config.PlayerAnimDictPoker) + while not HasAnimDictLoaded(Config.PlayerAnimDictPoker) do + Citizen.Wait(1) + end + + local offsetAlign = nil + if activeChairData.chairId == 4 then + offsetAlign = vector3(0.51655, 0.2268, 0.95) + elseif activeChairData.chairId == 3 then + offsetAlign = vector3(0.2163, -0.04745, 0.95) + elseif activeChairData.chairId == 2 then + offsetAlign = vector3(-0.2552, -0.031225, 0.95) + elseif activeChairData.chairId == 1 then + offsetAlign = vector3(-0.529875, 0.281425, 0.95) + end + + if offsetAlign == nil then + Config.DebugMsg('Something error happened during the playerBetAnim function.') + return + end + + local animName = 'bet_plus' + if amount >= 10000 then + animName = 'bet_plus_large' + end + + local scene = NetworkCreateSynchronisedScene(activeChairData.chairCoords, activeChairData.chairRotation, 2, true, false, 1.0, 0.0, 1.0) + NetworkAddPedToSynchronisedScene(PlayerPedId(), scene, Config.PlayerAnimDictPoker, animName, 2.0, -2.0, 13, 16, 1000.0, 0) + NetworkStartSynchronisedScene(scene) + + while not HasAnimEventFired(PlayerPedId(), -1424880317) do + Citizen.Wait(1) + end + + local offset = GetObjectOffsetFromCoords(self.data.Position, self.data.Heading, offsetAlign) + local chipModel = getChipModelByAmount(amount) + RequestModel(chipModel) + while not HasModelLoaded(chipModel) do + Citizen.Wait(1) + end + + local chipObj = CreateObjectNoOffset(chipModel, offset, true, false, true) + SetEntityCoordsNoOffset(chipObj, offset, false, false, true) + SetEntityHeading(chipObj, GetEntityHeading(PlayerPedId())) + table.insert(networkedChips, chipObj) + + self.playerRandomIdleAnim() + end + + self.playerBetAnim = function(amount) + playerBetted = amount + buttonScaleform = setupSecondButtons('instructional_buttons') + + RequestAnimDict(Config.PlayerAnimDictPoker) + while not HasAnimDictLoaded(Config.PlayerAnimDictPoker) do + Citizen.Wait(1) + end + + local offsetAlign = nil + if activeChairData.chairId == 4 then + offsetAlign = vector3(0.59535, 0.200875, 0.95) + elseif activeChairData.chairId == 3 then + offsetAlign = vector3(0.247825, -0.123625, 0.95) + elseif activeChairData.chairId == 2 then + offsetAlign = vector3(-0.2804, -0.109775, 0.95) + elseif activeChairData.chairId == 1 then + offsetAlign = vector3(-0.606975, 0.249675, 0.95) + end + + if offsetAlign == nil then + Config.DebugMsg('Something error happened during the playerBetAnim function.') + return + end + + local animName = 'bet_ante' + if amount >= 10000 then + animName = 'bet_ante_large' + end + + local scene = NetworkCreateSynchronisedScene(activeChairData.chairCoords, activeChairData.chairRotation, 2, false, true, 1.0, 0.0, 1.0) + NetworkAddPedToSynchronisedScene(PlayerPedId(), scene, Config.PlayerAnimDictPoker, animName, 2.0, -2.0, 13, 16, 1000.0, 0) + NetworkStartSynchronisedScene(scene) + + while not HasAnimEventFired(PlayerPedId(), -1424880317) do + Citizen.Wait(1) + end + + local offset = GetObjectOffsetFromCoords(self.data.Position, self.data.Heading, offsetAlign) + local chipModel = getChipModelByAmount(amount) + RequestModel(chipModel) + while not HasModelLoaded(chipModel) do + Citizen.Wait(1) + end + + local chipObj = CreateObjectNoOffset(chipModel, offset, true, false, true) + SetEntityCoordsNoOffset(chipObj, offset, false, false, true) + SetEntityHeading(chipObj, GetEntityHeading(PlayerPedId())) + table.insert(networkedChips, chipObj) + + self.playerRandomIdleAnim() + end + + self.createDefaultPakli() + self.createPed() + + SharedPokers[index] = self +end + +Citizen.CreateThread(function() + while true do + local playerpos = GetEntityCoords(PlayerPedId()) + closeToPokers = false + for k, v in pairs(Config.Pokers) do + if #(playerpos - v.Position) < 100.0 then + closeToPokers = true + end + end + Citizen.Wait(1000) + end +end) + +Citizen.CreateThread(function() + while QBCore == nil do + Citizen.Wait(1) + end + while not closeToPokers do + Citizen.Wait(500) + end + RequestAnimDict(Config.DealerAnimDictShared) + RequestAnimDict(Config.DealerAnimDictPoker) + RequestAnimDict(Config.PlayerAnimDictShared) + RequestAnimDict(Config.PlayerAnimDictPoker) + Config.DebugMsg('Poker loading started..') + for index, data in pairs(Config.Pokers) do + AquiverPoker(index, data) + end +end) + +Citizen.CreateThread(function() + local alreadyEnteredZone = false + while true do + local sleep = 5 + local inZone = false + if QBCore and not InformationPlaying and activePokerTable == nil and activeChairData == nil then + local playerpos = GetEntityCoords(PlayerPedId()) + for k, v in pairs(SharedPokers) do + local dist = #(playerpos - v.data.Position) + if dist < 3.0 then + for i = 1, #Config.Tables, 1 do + local tableObj = GetClosestObjectOfType(playerpos, 3.0, GetHashKey(Config.Tables[i]), false) + if DoesEntityExist(tableObj) then + for chairBone, chairId in pairs(Config.PokerChairs) do + local chaircoords = GetWorldPositionOfEntityBone(tableObj, GetEntityBoneIndexByName(tableObj, chairBone)) + if chaircoords then + if #(playerpos - chaircoords) < 1.5 then + wait = 5 + inZone = true + text = "The Diamond Casino & Resort

Poker

Press E to sit" + + local chairrotation = GetWorldRotationOfEntityBone(tableObj, GetEntityBoneIndexByName(tableObj, chairBone)) + -- drawfreameeMarker(chaircoords + vector3(0.0, 0.0, 1.0)) + + if IsControlJustPressed(1, 51) then + v.sitDown(chairId, chaircoords, chairrotation) + end + + break + end + end + end + break + end + end + end + end + end + + if inZone and not alreadyEnteredZone then + alreadyEnteredZone = true + exports["qb-core"]:DrawText(text) + end + if not inZone and alreadyEnteredZone then + alreadyEnteredZone = false + exports["qb-core"]:HideText() + end + Wait(sleep) + end +end) + +-- OTHERS + +function frm_setPedVoiceGroup(randomNumber, dealerPed) + if randomNumber == 0 then + SetPedVoiceGroup(dealerPed, GetHashKey('S_M_Y_Casino_01_WHITE_01')) + elseif randomNumber == 1 then + SetPedVoiceGroup(dealerPed, GetHashKey('S_M_Y_Casino_01_ASIAN_01')) + elseif randomNumber == 2 then + SetPedVoiceGroup(dealerPed, GetHashKey('S_M_Y_Casino_01_ASIAN_02')) + elseif randomNumber == 3 then + SetPedVoiceGroup(dealerPed, GetHashKey('S_M_Y_Casino_01_ASIAN_01')) + elseif randomNumber == 4 then + SetPedVoiceGroup(dealerPed, GetHashKey('S_M_Y_Casino_01_WHITE_01')) + elseif randomNumber == 5 then + SetPedVoiceGroup(dealerPed, GetHashKey('S_M_Y_Casino_01_WHITE_02')) + elseif randomNumber == 6 then + SetPedVoiceGroup(dealerPed, GetHashKey('S_M_Y_Casino_01_WHITE_01')) + elseif randomNumber == 7 then + SetPedVoiceGroup(dealerPed, GetHashKey('S_F_Y_Casino_01_ASIAN_01')) + elseif randomNumber == 8 then + SetPedVoiceGroup(dealerPed, GetHashKey('S_F_Y_Casino_01_ASIAN_02')) + elseif randomNumber == 9 then + SetPedVoiceGroup(dealerPed, GetHashKey('S_F_Y_Casino_01_ASIAN_01')) + elseif randomNumber == 10 then + SetPedVoiceGroup(dealerPed, GetHashKey('S_F_Y_Casino_01_ASIAN_02')) + elseif randomNumber == 11 then + SetPedVoiceGroup(dealerPed, GetHashKey('S_F_Y_Casino_01_LATINA_01')) + elseif randomNumber == 12 then + SetPedVoiceGroup(dealerPed, GetHashKey('S_F_Y_Casino_01_LATINA_02')) + elseif randomNumber == 13 then + SetPedVoiceGroup(dealerPed, GetHashKey('S_F_Y_Casino_01_LATINA_01')) + end +end + +function frm_setPedClothes(randomNumber, dealerPed) + if randomNumber == 0 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 3, 0, 0) + SetPedComponentVariation(dealerPed, 1, 1, 0, 0) + SetPedComponentVariation(dealerPed, 2, 3, 0, 0) + SetPedComponentVariation(dealerPed, 3, 1, 0, 0) + SetPedComponentVariation(dealerPed, 4, 0, 0, 0) + SetPedComponentVariation(dealerPed, 6, 1, 0, 0) + SetPedComponentVariation(dealerPed, 7, 2, 0, 0) + SetPedComponentVariation(dealerPed, 8, 3, 0, 0) + SetPedComponentVariation(dealerPed, 10, 1, 0, 0) + SetPedComponentVariation(dealerPed, 11, 1, 0, 0) + elseif randomNumber == 1 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 2, 2, 0) + SetPedComponentVariation(dealerPed, 1, 1, 0, 0) + SetPedComponentVariation(dealerPed, 2, 4, 0, 0) + SetPedComponentVariation(dealerPed, 3, 0, 3, 0) + SetPedComponentVariation(dealerPed, 4, 0, 0, 0) + SetPedComponentVariation(dealerPed, 6, 1, 0, 0) + SetPedComponentVariation(dealerPed, 7, 2, 0, 0) + SetPedComponentVariation(dealerPed, 8, 1, 0, 0) + SetPedComponentVariation(dealerPed, 10, 1, 0, 0) + SetPedComponentVariation(dealerPed, 11, 1, 0, 0) + elseif randomNumber == 2 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 2, 1, 0) + SetPedComponentVariation(dealerPed, 1, 1, 0, 0) + SetPedComponentVariation(dealerPed, 2, 2, 0, 0) + SetPedComponentVariation(dealerPed, 3, 0, 3, 0) + SetPedComponentVariation(dealerPed, 4, 0, 0, 0) + SetPedComponentVariation(dealerPed, 6, 1, 0, 0) + SetPedComponentVariation(dealerPed, 7, 2, 0, 0) + SetPedComponentVariation(dealerPed, 8, 1, 0, 0) + SetPedComponentVariation(dealerPed, 10, 1, 0, 0) + SetPedComponentVariation(dealerPed, 11, 1, 0, 0) + elseif randomNumber == 3 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 2, 0, 0) + SetPedComponentVariation(dealerPed, 1, 1, 0, 0) + SetPedComponentVariation(dealerPed, 2, 3, 0, 0) + SetPedComponentVariation(dealerPed, 3, 1, 3, 0) + SetPedComponentVariation(dealerPed, 4, 0, 0, 0) + SetPedComponentVariation(dealerPed, 6, 1, 0, 0) + SetPedComponentVariation(dealerPed, 7, 2, 0, 0) + SetPedComponentVariation(dealerPed, 8, 3, 0, 0) + SetPedComponentVariation(dealerPed, 10, 1, 0, 0) + SetPedComponentVariation(dealerPed, 11, 1, 0, 0) + elseif randomNumber == 4 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 4, 2, 0) + SetPedComponentVariation(dealerPed, 1, 1, 0, 0) + SetPedComponentVariation(dealerPed, 2, 3, 0, 0) + SetPedComponentVariation(dealerPed, 3, 0, 0, 0) + SetPedComponentVariation(dealerPed, 4, 0, 0, 0) + SetPedComponentVariation(dealerPed, 6, 1, 0, 0) + SetPedComponentVariation(dealerPed, 7, 2, 0, 0) + SetPedComponentVariation(dealerPed, 8, 1, 0, 0) + SetPedComponentVariation(dealerPed, 10, 1, 0, 0) + SetPedComponentVariation(dealerPed, 11, 1, 0, 0) + elseif randomNumber == 5 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 4, 0, 0) + SetPedComponentVariation(dealerPed, 1, 1, 0, 0) + SetPedComponentVariation(dealerPed, 2, 0, 0, 0) + SetPedComponentVariation(dealerPed, 3, 0, 0, 0) + SetPedComponentVariation(dealerPed, 4, 0, 0, 0) + SetPedComponentVariation(dealerPed, 6, 1, 0, 0) + SetPedComponentVariation(dealerPed, 7, 2, 0, 0) + SetPedComponentVariation(dealerPed, 8, 1, 0, 0) + SetPedComponentVariation(dealerPed, 10, 1, 0, 0) + SetPedComponentVariation(dealerPed, 11, 1, 0, 0) + elseif randomNumber == 6 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 4, 1, 0) + SetPedComponentVariation(dealerPed, 1, 1, 0, 0) + SetPedComponentVariation(dealerPed, 2, 4, 0, 0) + SetPedComponentVariation(dealerPed, 3, 1, 0, 0) + SetPedComponentVariation(dealerPed, 4, 0, 0, 0) + SetPedComponentVariation(dealerPed, 6, 1, 0, 0) + SetPedComponentVariation(dealerPed, 7, 2, 0, 0) + SetPedComponentVariation(dealerPed, 8, 3, 0, 0) + SetPedComponentVariation(dealerPed, 10, 1, 0, 0) + SetPedComponentVariation(dealerPed, 11, 1, 0, 0) + elseif randomNumber == 7 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 1, 1, 0) + SetPedComponentVariation(dealerPed, 1, 0, 0, 0) + SetPedComponentVariation(dealerPed, 2, 1, 0, 0) + SetPedComponentVariation(dealerPed, 3, 0, 3, 0) + SetPedComponentVariation(dealerPed, 4, 0, 0, 0) + SetPedComponentVariation(dealerPed, 6, 0, 0, 0) + SetPedComponentVariation(dealerPed, 7, 0, 0, 0) + SetPedComponentVariation(dealerPed, 8, 0, 0, 0) + SetPedComponentVariation(dealerPed, 10, 0, 0, 0) + SetPedComponentVariation(dealerPed, 11, 0, 0, 0) + elseif randomNumber == 8 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 1, 1, 0) + SetPedComponentVariation(dealerPed, 1, 0, 0, 0) + SetPedComponentVariation(dealerPed, 2, 1, 1, 0) + SetPedComponentVariation(dealerPed, 3, 1, 3, 0) + SetPedComponentVariation(dealerPed, 4, 0, 0, 0) + SetPedComponentVariation(dealerPed, 6, 0, 0, 0) + SetPedComponentVariation(dealerPed, 7, 2, 0, 0) + SetPedComponentVariation(dealerPed, 8, 1, 0, 0) + SetPedComponentVariation(dealerPed, 10, 0, 0, 0) + SetPedComponentVariation(dealerPed, 11, 0, 0, 0) + elseif randomNumber == 9 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 2, 0, 0) + SetPedComponentVariation(dealerPed, 1, 0, 0, 0) + SetPedComponentVariation(dealerPed, 2, 2, 0, 0) + SetPedComponentVariation(dealerPed, 3, 2, 3, 0) + SetPedComponentVariation(dealerPed, 4, 0, 0, 0) + SetPedComponentVariation(dealerPed, 6, 0, 0, 0) + SetPedComponentVariation(dealerPed, 7, 0, 0, 0) + SetPedComponentVariation(dealerPed, 8, 2, 0, 0) + SetPedComponentVariation(dealerPed, 10, 0, 0, 0) + SetPedComponentVariation(dealerPed, 11, 0, 0, 0) + elseif randomNumber == 10 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 2, 1, 0) + SetPedComponentVariation(dealerPed, 1, 0, 0, 0) + SetPedComponentVariation(dealerPed, 2, 2, 1, 0) + SetPedComponentVariation(dealerPed, 3, 3, 3, 0) + SetPedComponentVariation(dealerPed, 4, 1, 0, 0) + SetPedComponentVariation(dealerPed, 6, 1, 0, 0) + SetPedComponentVariation(dealerPed, 7, 2, 0, 0) + SetPedComponentVariation(dealerPed, 8, 3, 0, 0) + SetPedComponentVariation(dealerPed, 10, 0, 0, 0) + SetPedComponentVariation(dealerPed, 11, 0, 0, 0) + elseif randomNumber == 11 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 3, 0, 0) + SetPedComponentVariation(dealerPed, 1, 0, 0, 0) + SetPedComponentVariation(dealerPed, 2, 3, 0, 0) + SetPedComponentVariation(dealerPed, 3, 0, 1, 0) + SetPedComponentVariation(dealerPed, 4, 1, 0, 0) + SetPedComponentVariation(dealerPed, 6, 1, 0, 0) + SetPedComponentVariation(dealerPed, 7, 1, 0, 0) + SetPedComponentVariation(dealerPed, 8, 0, 0, 0) + SetPedComponentVariation(dealerPed, 10, 0, 0, 0) + SetPedComponentVariation(dealerPed, 11, 0, 0, 0) + SetPedPropIndex(dealerPed, 1, 0, 0, false) + elseif randomNumber == 12 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 3, 1, 0) + SetPedComponentVariation(dealerPed, 1, 0, 0, 0) + SetPedComponentVariation(dealerPed, 2, 3, 1, 0) + SetPedComponentVariation(dealerPed, 3, 1, 1, 0) + SetPedComponentVariation(dealerPed, 4, 1, 0, 0) + SetPedComponentVariation(dealerPed, 6, 1, 0, 0) + SetPedComponentVariation(dealerPed, 7, 2, 0, 0) + SetPedComponentVariation(dealerPed, 8, 1, 0, 0) + SetPedComponentVariation(dealerPed, 10, 0, 0, 0) + SetPedComponentVariation(dealerPed, 11, 0, 0, 0) + elseif randomNumber == 13 then + SetPedDefaultComponentVariation(dealerPed) + SetPedComponentVariation(dealerPed, 0, 4, 0, 0) + SetPedComponentVariation(dealerPed, 1, 0, 0, 0) + SetPedComponentVariation(dealerPed, 2, 4, 0, 0) + SetPedComponentVariation(dealerPed, 3, 2, 1, 0) + SetPedComponentVariation(dealerPed, 4, 1, 0, 0) + SetPedComponentVariation(dealerPed, 6, 1, 0, 0) + SetPedComponentVariation(dealerPed, 7, 1, 0, 0) + SetPedComponentVariation(dealerPed, 8, 2, 0, 0) + SetPedComponentVariation(dealerPed, 10, 0, 0, 0) + SetPedComponentVariation(dealerPed, 11, 0, 0, 0) + SetPedPropIndex(dealerPed, 1, 0, 0, false) + end +end + +function getChipModelByAmount(amount) + if amount <= 10 then + return GetHashKey('vw_prop_chip_10dollar_x1') + elseif amount > 10 and amount < 50 then + return GetHashKey('vw_prop_chip_10dollar_st') + elseif amount >= 50 and amount < 100 then + return GetHashKey('vw_prop_chip_50dollar_x1') + elseif amount >= 100 and amount < 200 then + return GetHashKey('vw_prop_chip_100dollar_x1') + elseif amount >= 200 and amount < 500 then + return GetHashKey('vw_prop_chip_100dollar_st') + elseif amount == 500 then + return GetHashKey('vw_prop_chip_500dollar_x1') + elseif amount > 500 and amount < 1000 then + return GetHashKey('vw_prop_chip_500dollar_st') + elseif amount == 1000 then + return GetHashKey('vw_prop_chip_1kdollar_x1') + elseif amount > 1000 and amount < 5000 then + return GetHashKey('vw_prop_chip_1kdollar_st') + elseif amount == 5000 then + return GetHashKey('vw_prop_plaq_5kdollar_x1') + elseif amount > 5000 and amount < 10000 then + return GetHashKey('vw_prop_plaq_5kdollar_st') + elseif amount == 10000 then + return GetHashKey('vw_prop_plaq_10kdollar_x1') + elseif amount > 10000 then + return GetHashKey('vw_prop_plaq_10kdollar_st') + end +end + +function setupSecondButtons(scaleform) + -- to have the 'hint' sound effect + PlaySoundFrontend(-1, 'FocusIn', 'HintCamSounds', true) + --- + local scaleform = RequestScaleformMovie(scaleform) + while not HasScaleformMovieLoaded(scaleform) do + Citizen.Wait(0) + end + PushScaleformMovieFunction(scaleform, 'CLEAR_ALL') + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, 'SET_CLEAR_SPACE') + PushScaleformMovieFunctionParameterInt(200) + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, 'SET_DATA_SLOT') + PushScaleformMovieFunctionParameterInt(1) + Button(GetControlInstructionalButton(0, 177, true)) -- The button to display + ButtonMessage(_U('leave_game')) + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, 'SET_DATA_SLOT') + PushScaleformMovieFunctionParameterInt(2) + Button(GetControlInstructionalButton(0, 172, true)) + ButtonMessage(_U('raise_bet')) + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, 'SET_DATA_SLOT') + PushScaleformMovieFunctionParameterInt(3) + Button(GetControlInstructionalButton(0, 173, true)) + ButtonMessage(_U('reduce_bet')) + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, 'SET_DATA_SLOT') + PushScaleformMovieFunctionParameterInt(4) + Button(GetControlInstructionalButton(0, 22, true)) + ButtonMessage(_U('custom_bet')) + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, 'SET_DATA_SLOT') + PushScaleformMovieFunctionParameterInt(5) + Button(GetControlInstructionalButton(0, 176, true)) + ButtonMessage(_U('place_pair_bet')) + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, 'DRAW_INSTRUCTIONAL_BUTTONS') + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, 'SET_BACKGROUND_COLOUR') + PushScaleformMovieFunctionParameterInt(0) + PushScaleformMovieFunctionParameterInt(0) + PushScaleformMovieFunctionParameterInt(0) + PushScaleformMovieFunctionParameterInt(80) + PopScaleformMovieFunctionVoid() + + return scaleform +end + +function setupFirstButtons(scaleform) + local scaleform = RequestScaleformMovie(scaleform) + while not HasScaleformMovieLoaded(scaleform) do + Citizen.Wait(0) + end + PushScaleformMovieFunction(scaleform, 'CLEAR_ALL') + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, 'SET_CLEAR_SPACE') + PushScaleformMovieFunctionParameterInt(200) + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, 'SET_DATA_SLOT') + PushScaleformMovieFunctionParameterInt(1) + Button(GetControlInstructionalButton(0, 177, true)) -- The button to display + ButtonMessage(_U('leave_game')) + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, 'SET_DATA_SLOT') + PushScaleformMovieFunctionParameterInt(2) + Button(GetControlInstructionalButton(0, 172, true)) + ButtonMessage(_U('raise_bet')) + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, 'SET_DATA_SLOT') + PushScaleformMovieFunctionParameterInt(3) + Button(GetControlInstructionalButton(0, 173, true)) + ButtonMessage(_U('reduce_bet')) + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, 'SET_DATA_SLOT') + PushScaleformMovieFunctionParameterInt(4) + Button(GetControlInstructionalButton(0, 22, true)) + ButtonMessage(_U('custom_bet')) + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, 'SET_DATA_SLOT') + PushScaleformMovieFunctionParameterInt(5) + Button(GetControlInstructionalButton(0, 176, true)) + ButtonMessage(_U('place_bet')) + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, 'DRAW_INSTRUCTIONAL_BUTTONS') + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, 'SET_BACKGROUND_COLOUR') + PushScaleformMovieFunctionParameterInt(0) + PushScaleformMovieFunctionParameterInt(0) + PushScaleformMovieFunctionParameterInt(0) + PushScaleformMovieFunctionParameterInt(80) + PopScaleformMovieFunctionVoid() + + return scaleform +end + +function Button(ControlButton) + N_0xe83a3e3557a56640(ControlButton) +end + +function ButtonMessage(text) + BeginTextCommandScaleformString('STRING') + AddTextComponentScaleform(text) + EndTextCommandScaleformString() +end + +-- function setupThirdButtons(scaleform) +-- -- to have the 'hint' sound effect +-- PlaySoundFrontend(-1, 'FocusIn', 'HintCamSounds', true) +-- --- + +-- local scaleform = RequestScaleformMovie(scaleform) +-- while not HasScaleformMovieLoaded(scaleform) do +-- Citizen.Wait(0) +-- end +-- PushScaleformMovieFunction(scaleform, 'CLEAR_ALL') +-- PopScaleformMovieFunctionVoid() + +-- PushScaleformMovieFunction(scaleform, 'SET_CLEAR_SPACE') +-- PushScaleformMovieFunctionParameterInt(200) +-- PopScaleformMovieFunctionVoid() + +-- PushScaleformMovieFunction(scaleform, 'SET_DATA_SLOT') +-- PushScaleformMovieFunctionParameterInt(1) +-- Button(GetControlInstructionalButton(0, 177, true)) -- The button to display +-- ButtonMessage(_U('fold_cards')) +-- PopScaleformMovieFunctionVoid() + +-- PushScaleformMovieFunction(scaleform, 'SET_DATA_SLOT') +-- PushScaleformMovieFunctionParameterInt(2) +-- Button(GetControlInstructionalButton(0, 38, true)) +-- ButtonMessage(_U('play_cards')) +-- PopScaleformMovieFunctionVoid() + +-- PushScaleformMovieFunction(scaleform, 'DRAW_INSTRUCTIONAL_BUTTONS') +-- PopScaleformMovieFunctionVoid() + +-- PushScaleformMovieFunction(scaleform, 'SET_BACKGROUND_COLOUR') +-- PushScaleformMovieFunctionParameterInt(0) +-- PushScaleformMovieFunctionParameterInt(0) +-- PushScaleformMovieFunctionParameterInt(0) +-- PushScaleformMovieFunctionParameterInt(80) +-- PopScaleformMovieFunctionVoid() + +-- return scaleform +-- end + +-- function drawText2d(x, y, sc, text) +-- SetTextFont(0) +-- SetTextScale(sc, sc) +-- SetTextCentre(true) +-- SetTextOutline() +-- SetTextColour(254, 254, 254, 255) +-- SetTextEntry('STRING') +-- AddTextComponentString(text) +-- DrawText(x, y) +-- end + +-- function DrawAdvancedNativeText(x, y, w, h, sc, text, r, g, b, a, font, jus) +-- SetTextFont(font) +-- SetTextScale(sc, sc) +-- N_0x4e096588b13ffeca(jus) +-- SetTextColour(254, 254, 254, 255) +-- SetTextEntry('STRING') +-- AddTextComponentString(text) +-- DrawText(x - 0.1 + w, y - 0.02 + h) +-- end + +-- function getGenericTextInput(type) +-- if type == nil then +-- type = '' +-- end +-- AddTextEntry('FMMC_MPM_NA', tostring(type)) +-- DisplayOnscreenKeyboard(1, 'FMMC_MPM_NA', tostring(type), '', '', '', '', 30) +-- while (UpdateOnscreenKeyboard() == 0) do +-- DisableAllControlActions(0) +-- Wait(0) +-- end +-- if (GetOnscreenKeyboardResult()) then +-- local result = GetOnscreenKeyboardResult() +-- if result then +-- return result +-- end +-- end +-- return false +-- end + +-- function drawfreameeMarker(position) +-- DrawMarker(20, position, 0.0, 0.0, 0.0, 180.0, 0.0, 0.0, 0.3, 0.3, 0.3, 255, 255, 255, 255, true, true, 2, true, nil, nil, false) +-- end + +-- RMenu.Add('aquiver_poker', 'instructions', RageUI.CreateMenu('', 'test', 0, 100, 'CasinoUI_Cards_Three_High', 'CasinoUI_Cards_Three_High')) +-- RMenu:Get('aquiver_poker', 'instructions'):SetSubtitle(_U('tcp')) + +-- function showHowTo() +-- Citizen.CreateThread( +-- function() +-- local helps = {_U('desc_1'), _U('desc_2'), _U('desc_3')} + +-- InformationPlaying = true + +-- for i = 1, #helps, 1 do +-- PlaySoundFrontend(-1, 'DLC_VW_CONTINUE', 'dlc_vw_table_games_frontend_sounds', true) +-- BeginTextCommandDisplayHelp(helps[i]) +-- EndTextCommandDisplayHelp(0, false, false, Config.HowToSpeed) + +-- Citizen.Wait(Config.HowToSpeed) +-- if helps[i + 1] == nil then +-- InformationPlaying = false +-- end +-- end +-- end +-- ) +-- end + +-- function showRules() +-- Citizen.CreateThread( +-- function() +-- local helps = {_U('rule_1'), _U('rule_2'), _U('rule_3'), _U('rule_4'), _U('rule_5')} +-- local helpsHeader = {_U('rule_header_1'), _U('rule_header_2'), _U('rule_header_3'), _U('rule_header_4'), _U('rule_header_5')} + +-- InformationPlaying = true + +-- for i = 1, #helps, 1 do +-- PlaySoundFrontend(-1, 'DLC_VW_CONTINUE', 'dlc_vw_table_games_frontend_sounds', true) +-- BeginTextCommandDisplayHelp(helpsHeader[i]) +-- AddTextComponentSubstringTextLabel(helps[i]) +-- EndTextCommandDisplayHelp(0, false, false, Config.HowToSpeed) + +-- Citizen.Wait(Config.HowToSpeed) +-- if helps[i + 1] == nil then +-- InformationPlaying = false +-- end +-- end +-- end +-- ) +-- end + +-- You can use this command to implement your POKER TABLES +-- You get the results out in the console, after you can register in the c onfig. +--[[RegisterCommand( + 'getpokertable', + function() + local found = false + + local playercoords = GetEntityCoords(PlayerPedId()) + for i = 1, #Config.Tables, 1 do + local obj = GetClosestObjectOfType(playercoords, 3.0, GetHashKey(Config.Tables[i]), false, false, false) + if DoesEntityExist(obj) then + found = true + end + end + + if not found then + print('none table found.') + end + end +)--]] diff --git a/resources/[qb]/[qb_casino]/casino-poker/config.lua b/resources/[qb]/[qb_casino]/casino-poker/config.lua new file mode 100644 index 0000000..a751f30 --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-poker/config.lua @@ -0,0 +1,531 @@ +Config = {} +Config.Locale = 'en' +QBCore = exports['qb-core']:GetCoreObject() + +-- YOUR MAIN SETUPS +Config.Debug = false -- enable debug messages in sv/cl console +Config.TimeLeftAfter = 15 -- time remaining after one player betted (dealer actions start timeout) +Config.PlayerDecideTime = 15 -- player decide time (when watching our cards) +Config.HowToSpeed = 5000 -- x seconds to change to next +Config.ShowCardsAfterReveal = true -- showing cameras on the players cards when revealing them + +Config.Pokers = { + [1] = { + Position = vector3(996.17, 51.69, 68.45), + Heading = 318.91, + MaximumBet = 2500, + MinimumBet = 50 + }, + [2] = { + Position = vector3(1000.66, 50.88, 68.45), + Heading = 6.73, + MaximumBet = 2500, + MinimumBet = 50 + }, + [3] = { + Position = vector3(984.97, 66.64, 69.25), + Heading = 3.39, + MaximumBet = 2000, + MinimumBet = 50 + }, + [4] = { + Position = vector3(998.44, 60.99, 68.45), + Heading = 191.77, + MaximumBet = 2000, + MinimumBet = 50 + }, + [5] = { + Position = vector3(994.89, 58.29, 68.45), + Heading = 237.13, + MaximumBet = 2000, + MinimumBet = 50 + }, + [6] = { + Position = vector3(988.45, 64.38, 69.25), + Heading = 278.52, + MaximumBet = 2000, + MinimumBet = 50 + }, + [7] = { + Position = vector3(991.56, 40.10, 69.25), + Heading = 200.84, + MaximumBet = 2000, + MinimumBet = 50 + }, + [8] = { + Position = vector3(993.20, 43.70, 69.25), + Heading = 279.25, + MaximumBet = 2000, + MinimumBet = 50 + } +} + +-- DO NOT WORRY ABOUT DOWNSIDE + +Config.Tables = { + 'h4_prop_casino_3cardpoker_01a', + 'h4_prop_casino_3cardpoker_01b', + 'h4_prop_casino_3cardpoker_01c', + 'h4_prop_casino_3cardpoker_01e', + 'vw_prop_casino_3cardpoker_01b', + 'vw_prop_casino_3cardpoker_01' +} + +Config.DealerAnimDictShared = 'anim_casino_b@amb@casino@games@shared@dealer@' +Config.DealerAnimDictPoker = 'anim_casino_b@amb@casino@games@threecardpoker@dealer' +Config.PlayerAnimDictShared = 'anim_casino_b@amb@casino@games@shared@player@' +Config.PlayerAnimDictPoker = 'anim_casino_b@amb@casino@games@threecardpoker@player' + +Config.IncreaseAmounts = function(currentAmount) + if currentAmount < 500 then + return 50 + elseif currentAmount >= 500 and currentAmount < 2000 then + return 100 + elseif currentAmount >= 2000 and currentAmount < 5000 then + return 200 + elseif currentAmount >= 5000 and currentAmount < 10000 then + return 500 + elseif currentAmount >= 10000 then + return 1000 + else + return 50 + end +end + +Config.PokerChairs = { + ['Chair_Base_01'] = 1, + ['Chair_Base_02'] = 2, + ['Chair_Base_03'] = 3, + ['Chair_Base_04'] = 4 +} + +Config.GetCardType = function(cardArrayId) + if cardArrayId >= 1 and cardArrayId <= 13 then -- CLUBS + return 0 + elseif cardArrayId >= 14 and cardArrayId <= 26 then -- DIAMOND + return 1 + elseif cardArrayId >= 26 and cardArrayId <= 39 then -- HEARTS + return 2 + elseif cardArrayId >= 39 and cardArrayId <= 52 then -- SPADES + return 3 + end +end + +Config.GetCardValue = function(cardArrayId) + local vals = { + -- 2 + [2] = 2, + [15] = 2, + [28] = 2, + [41] = 2, + -- 3 + [3] = 3, + [16] = 3, + [29] = 3, + [42] = 3, + -- 4 + [4] = 4, + [17] = 4, + [30] = 4, + [43] = 4, + -- 5 + [5] = 5, + [18] = 5, + [31] = 5, + [44] = 5, + -- 6 + [6] = 6, + [19] = 6, + [32] = 6, + [45] = 6, + -- 7 + [7] = 7, + [20] = 7, + [33] = 7, + [46] = 7, + -- 8 + [8] = 8, + [21] = 8, + [34] = 8, + [47] = 8, + -- 9 + [9] = 9, + [22] = 9, + [35] = 9, + [48] = 9, + -- 10 + [10] = 10, + [23] = 10, + [36] = 10, + [49] = 10, + -- JACK + [11] = 11, + [24] = 11, + [37] = 11, + [50] = 11, + -- QUEEN + [12] = 12, + [25] = 12, + [38] = 12, + [51] = 12, + -- KING + [13] = 13, + [26] = 13, + [39] = 13, + [52] = 13, + -- ACE + [1] = 14, + [14] = 14, + [27] = 14, + [40] = 14 + } + + if vals[cardArrayId] then + return vals[cardArrayId] + else + return 0 + end +end + +Config.Cards = { + -- i kwow its a default array, not an object key array, i know what i am doing, its easier to see the numbers like this + [1] = 'vw_prop_vw_club_char_a_a', + [2] = 'vw_prop_vw_club_char_02a', + [3] = 'vw_prop_vw_club_char_03a', + [4] = 'vw_prop_vw_club_char_04a', + [5] = 'vw_prop_vw_club_char_05a', + [6] = 'vw_prop_vw_club_char_06a', + [7] = 'vw_prop_vw_club_char_07a', + [8] = 'vw_prop_vw_club_char_08a', + [9] = 'vw_prop_vw_club_char_09a', + [10] = 'vw_prop_vw_club_char_10a', + [11] = 'vw_prop_vw_club_char_j_a', + [12] = 'vw_prop_vw_club_char_q_a', + [13] = 'vw_prop_vw_club_char_k_a', + [14] = 'vw_prop_vw_dia_char_a_a', + [15] = 'vw_prop_vw_dia_char_02a', + [16] = 'vw_prop_vw_dia_char_03a', + [17] = 'vw_prop_vw_dia_char_04a', + [18] = 'vw_prop_vw_dia_char_05a', + [19] = 'vw_prop_vw_dia_char_06a', + [20] = 'vw_prop_vw_dia_char_07a', + [21] = 'vw_prop_vw_dia_char_08a', + [22] = 'vw_prop_vw_dia_char_09a', + [23] = 'vw_prop_vw_dia_char_10a', + [24] = 'vw_prop_vw_dia_char_j_a', + [25] = 'vw_prop_vw_dia_char_q_a', + [26] = 'vw_prop_vw_dia_char_k_a', + [27] = 'vw_prop_vw_hrt_char_a_a', + [28] = 'vw_prop_vw_hrt_char_02a', + [29] = 'vw_prop_vw_hrt_char_03a', + [30] = 'vw_prop_vw_hrt_char_04a', + [31] = 'vw_prop_vw_hrt_char_05a', + [32] = 'vw_prop_vw_hrt_char_06a', + [33] = 'vw_prop_vw_hrt_char_07a', + [34] = 'vw_prop_vw_hrt_char_08a', + [35] = 'vw_prop_vw_hrt_char_09a', + [36] = 'vw_prop_vw_hrt_char_10a', + [37] = 'vw_prop_vw_hrt_char_j_a', + [38] = 'vw_prop_vw_hrt_char_q_a', + [39] = 'vw_prop_vw_hrt_char_k_a', + [40] = 'vw_prop_vw_spd_char_a_a', + [41] = 'vw_prop_vw_spd_char_02a', + [42] = 'vw_prop_vw_spd_char_03a', + [43] = 'vw_prop_vw_spd_char_04a', + [44] = 'vw_prop_vw_spd_char_05a', + [45] = 'vw_prop_vw_spd_char_06a', + [46] = 'vw_prop_vw_spd_char_07a', + [47] = 'vw_prop_vw_spd_char_08a', + [48] = 'vw_prop_vw_spd_char_09a', + [49] = 'vw_prop_vw_spd_char_10a', + [50] = 'vw_prop_vw_spd_char_j_a', + [51] = 'vw_prop_vw_spd_char_q_a', + [52] = 'vw_prop_vw_spd_char_k_a' +} + +Config.getHandAllValues = function(handTable, bool_1, bool_2) + if type(handTable) == 'table' then + local c1, c2, c3 = Config.GetCardValue(handTable[1]), Config.GetCardValue(handTable[2]), Config.GetCardValue(handTable[3]) + + local handValue = 0 + + -- FIRST CHECK + if (c1 ~= c2 and c1 ~= c3) and c2 ~= c3 then + local Flush = false + + handValue = c1 + c2 + c3 + + if handValue == 19 then + if (c1 == 14 or c1 == 2 or c1 == 3) and (c2 == 14 or c2 == 2 or c2 == 3) and (c3 == 14 or c3 == 2 or c3 == 3) then + Flush = true + end + elseif handValue == 9 then + if (c1 == 2 or c1 == 3 or c1 == 4) and (c2 == 2 or c2 == 3 or c2 == 4) and (c3 == 2 or c3 == 3 or c3 == 4) then + Flush = true + end + elseif handValue == 12 then + if (c1 == 3 or c1 == 4 or c1 == 5) and (c2 == 3 or c2 == 4 or c2 == 5) and (c3 == 3 or c3 == 4 or c3 == 5) then + Flush = true + end + elseif handValue == 15 then + if (c1 == 4 or c1 == 5 or c1 == 6) and (c2 == 4 or c2 == 5 or c2 == 6) and (c3 == 4 or c3 == 5 or c3 == 6) then + Flush = true + end + elseif handValue == 18 then + if (c1 == 5 or c1 == 6 or c1 == 7) and (c2 == 5 or c2 == 6 or c2 == 7) and (c3 == 5 or c3 == 6 or c3 == 7) then + Flush = true + end + elseif handValue == 21 then + if (c1 == 6 or c1 == 7 or c1 == 8) and (c2 == 6 or c2 == 7 or c2 == 8) and (c3 == 6 or c3 == 7 or c3 == 8) then + Flush = true + end + elseif handValue == 24 then + if (c1 == 7 or c1 == 8 or c1 == 9) and (c2 == 7 or c2 == 8 or c2 == 9) and (c3 == 7 or c3 == 8 or c3 == 9) then + Flush = true + end + elseif handValue == 27 then + if (c1 == 8 or c1 == 9 or c1 == 10) and (c2 == 8 or c2 == 9 or c2 == 10) and (c3 == 8 or c3 == 9 or c3 == 10) then + Flush = true + end + elseif handValue == 30 then + if (c1 == 9 or c1 == 10 or c1 == 11) and (c2 == 9 or c2 == 10 or c2 == 11) and (c3 == 9 or c3 == 10 or c3 == 11) then + Flush = true + end + elseif handValue == 33 then + if (c1 == 10 or c1 == 11 or c1 == 12) and (c2 == 10 or c2 == 11 or c2 == 12) and (c3 == 10 or c3 == 11 or c3 == 12) then + Flush = true + end + elseif handValue == 36 then + if (c1 == 11 or c1 == 12 or c1 == 13) and (c2 == 11 or c2 == 12 or c3 == 13) and (c3 == 11 or c3 == 12 or c3 == 13) then + --something true + Flush = true + end + elseif handValue == 39 then + if (c1 == 12 or c1 == 13 or c1 == 14) and (c2 == 12 or c2 == 13 or c2 == 14) and (c3 == 12 or c3 == 13 or c3 == 14) then + --something true + Flush = true + end + end + + if Flush then + if handValue == 19 then + handValue = 6 + end + + if Config.GetCardType(handTable[1]) == Config.GetCardType(handTable[2]) and Config.GetCardType(handTable[1]) == Config.GetCardType(handTable[3]) then + return handValue + 500 + end + + return handValue + 300 + end + end + + handValue = 0 + + -- SECOND CHECK + if (c1 == c2) and c1 ~= c3 then -- pairs + if not bool_1 and not bool_2 then + return (c1 + c2) + 100 + else + return c3 + end + elseif (c2 == c3) and c2 ~= c1 then -- pairs + if not bool_1 and not bool_2 then + return (c2 + c3) + 100 + else + return c1 + end + elseif (c3 == c1) and c3 ~= c2 then -- pairs + if not bool_1 and not bool_2 then + return (c1 + c3) + 100 + else + return c2 + end + elseif c1 == c2 and c1 == c3 then -- 3 of a kind + return c1 + c2 + c3 + 400 + elseif Config.GetCardType(handTable[1]) == Config.GetCardType(handTable[2]) and Config.GetCardType(handTable[1]) == Config.GetCardType(handTable[3]) then + handValue = 200 + end + + -- third check if it runs here + + if c1 > c2 and c1 > c3 then + if bool_1 then + if c2 > c3 then + return handValue + c2 + else + return handValue + c3 + end + elseif bool_2 then + if c2 > c3 then + return handValue + c3 + else + return handValue + c2 + end + end + + return handValue + c1 + elseif c2 > c1 and c2 > c3 then + if bool_1 then + if c1 > c3 then + return handValue + c1 + else + return handValue + c3 + end + elseif bool_2 then + if c1 > c3 then + return handValue + c3 + else + return handValue + c1 + end + end + + return handValue + c2 + elseif c3 > c1 and c3 > c2 then + if bool_1 then + if c1 > c2 then + return handValue + c1 + else + return handValue + c2 + end + elseif bool_2 then + if c1 > c2 then + return handValue + c2 + else + return handValue + c1 + end + end + + return handValue + c3 + end + + return handValue + else + return 0 + end +end + +Config.GetPairMultiplier = function(handValue) + if handValue > 500 then + return 40 + elseif handValue > 400 then + return 30 + elseif handValue > 300 then + return 6 + elseif handValue > 200 then + return 4 + elseif handValue > 100 then + return 1 + end + + return 0 +end + +Config.GetAnteMultiplier = function(handValue) + if handValue > 500 then + return 5 + elseif handValue > 400 then + return 4 + elseif handValue > 300 then + return 1 + end + + return 0 +end + +Config.formatHandValue = function(handValue) + if handValue > 500 then + return 'Straight flush' + elseif handValue > 400 then + return '3 of a kind' + elseif handValue > 300 then + return 'Straight' + elseif handValue > 200 then + return 'Flush' + elseif handValue > 100 then + if handValue == 128 then + return 'Pair Ace' + elseif handValue == 104 then + return 'Pair 2' + elseif handValue == 106 then + return 'Pair 3' + elseif handValue == 108 then + return 'Pair 4' + elseif handValue == 110 then + return 'Pair 5' + elseif handValue == 112 then + return 'Pair 6' + elseif handValue == 114 then + return 'Pair 7' + elseif handValue == 116 then + return 'Pair 8' + elseif handValue == 118 then + return 'Pair 9' + elseif handValue == 120 then + return 'Pair 10' + elseif handValue == 122 then + return 'Pair Jack' + elseif handValue == 124 then + return 'Pair Queen' + elseif handValue == 126 then + return 'Pair King' + end + elseif handValue == 5 then + return 'High Card 5' + elseif handValue == 6 then + return 'High Card 6' + elseif handValue == 7 then + return 'High Card 7' + elseif handValue == 8 then + return 'High Card 8' + elseif handValue == 9 then + return 'High Card 9' + elseif handValue == 10 then + return 'High Card 10' + elseif handValue == 11 then + return 'High Card Jack' + elseif handValue == 12 then + return 'High Card Queen' + elseif handValue == 13 then + return 'High Card King' + else + return 'High Card Ace' + end + + return '' +end + +Config.canDealerPlay = function(handValue) + if handValue >= 12 then + return true + else + return false + end +end + +Config.DebugMsg = function(msg) + if Config.Debug then + print(msg) + end +end + +Locales = {} +function _(str, ...) -- Translate string + + if Locales[Config.Locale] ~= nil then + + if Locales[Config.Locale][str] ~= nil then + return string.format(Locales[Config.Locale][str], ...) + else + return 'Translation [' .. Config.Locale .. '][' .. str .. '] does not exist' + end + + else + return 'Locale [' .. Config.Locale .. '] does not exist' + end + +end + +function _U(str, ...) -- Translate string first char uppercase + return tostring(_(str, ...):gsub("^%l", string.upper)) +end diff --git a/resources/[qb]/[qb_casino]/casino-poker/fxmanifest.lua b/resources/[qb]/[qb_casino]/casino-poker/fxmanifest.lua new file mode 100644 index 0000000..6bbcb63 --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-poker/fxmanifest.lua @@ -0,0 +1,19 @@ +shared_scripts { + 'config.lua' +} + +client_scripts { + 'locales/*.lua', + 'client/*.lua', +} + +server_scripts { + -- 'config.lua', + 'locales/*.lua', + 'server/sv_main.lua' +} + + + +game 'gta5' +fx_version 'cerulean' diff --git a/resources/[qb]/[qb_casino]/casino-poker/locales/en.lua b/resources/[qb]/[qb_casino]/casino-poker/locales/en.lua new file mode 100644 index 0000000..e19d5d0 --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-poker/locales/en.lua @@ -0,0 +1,58 @@ +Locales['en'] = { + -- notifications + ['chair_occupied'] = 'This seat is occupied.', + ['no_react'] = 'You did not respond for the dealer ask in time, you have folded your hand.', + ['no_bet_input'] = 'You did not set up a bet value.', + ['not_enough_chips'] = 'You do not have enough chips.', + ['not_enough_chips_next'] = 'You do not have enough chips to bet on the Pair Plus, because you would not have enough chips for the playing.', + ['not_enough_chips_third'] = 'You can not put that amount of chips because you would not have enough for playing your hand.', + ['not_enough_chips_toplay'] = 'You do not have enough chips to play!', + ['already_betted']= 'You already betted.', + ['lose'] = 'You lose!', + -- formatted notif + ['dealer_not_qual'] = 'Draw.\nThe Dealer did not qualify for the game.\nYou got %s chips back.', + ['dealer_not_qual_ante'] = 'Draw.\nThe Dealer did not qualify for the game.\nYou got %s chips back. (Ante multiplier: x%s)', + ['player_won_ante'] = 'Your hand won!\nYou got %s chips. (Ante multiplier: x%s)', + ['player_won'] = 'Your hand won!\nYou got %s chips.', + ['pair_won'] = 'You won %s chips with your Pair Plus bet! (Pair multiplier: x%s)', + -- hud + -- ['current_bet_input'] = 'BET:', + -- ['current_player_chips'] = 'CHIPS:', + -- ['table_min_max'] = 'MIN/MAX:', + -- ['remaining_time'] = 'TIME:', + -- top left + ['waiting_for_players'] = 'Waiting for players...', + ['clearing_table'] = 'Clearing the table...
Next game starting soon.', + ['dealer_showing_hand'] = 'The Dealer is showing their hand.', + ['players_showing_hands'] = 'Revealing player hands...', + ['dealing_cards'] = 'Dealing cards to players...', + -- inputs + ['fold_cards'] = 'Fold', + ['play_cards'] = 'Play', + -- ['leave_game'] = 'Leave game', + -- ['raise_bet'] = 'Raise bet', + -- ['reduce_bet'] = 'Reduce bet', + -- ['custom_bet'] = 'Custom bet', + -- ['place_bet'] = 'Place bet', + ['place_pair_bet'] = 'Place Pair Plus bet', + -- gtao ui + -- ['tcp'] = '[Aquiver] ~b~Three Card Poker', + -- ['sit_down_table'] = '~h~Play ~b~Three Card Poker', + -- ['description'] = 'Game Description', + -- ['desc_1'] = 'TCP_DIS1', -- this is Rockstar Setuped default, this will automaticly change if you are using german language etc. + -- ['desc_2'] = 'TCP_DIS2', + -- ['desc_3'] = 'TCP_DIS3', + -- ['description_info'] = 'How is the game working?', + -- ['rule_1'] = 'TCP_RULE_1', + -- ['rule_2'] = 'TCP_RULE_2', + -- ['rule_3'] = 'TCP_RULE_3', + -- ['rule_4'] = 'TCP_RULE_4', + -- ['rule_5'] = 'TCP_RULE_5', + -- ['rule_header_1'] = 'TCP_RULE_1T', + -- ['rule_header_2'] = 'TCP_RULE_2T', + -- ['rule_header_3'] = 'TCP_RULE_3T', + -- ['rule_header_4'] = 'TCP_RULE_4T', + -- ['rule_header_5'] = 'TCP_RULE_5T', + -- ['rules'] = 'Game Rules', + -- ['rules_info'] = 'Game rules or anything you should know.' +} diff --git a/resources/[qb]/[qb_casino]/casino-poker/server/sv_main.lua b/resources/[qb]/[qb_casino]/casino-poker/server/sv_main.lua new file mode 100644 index 0000000..0996d21 --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-poker/server/sv_main.lua @@ -0,0 +1,484 @@ +QBCore = exports['qb-core']:GetCoreObject() + + +function getPlayerChips(source) + local Player = QBCore.Functions.GetPlayer(source) + if Player then + if Player.Functions.GetItemByName('casino_redchip') ~= nil then + return Player.Functions.GetItemByName('casino_redchip').amount + else + return 0 + end + else + return 0 + end +end + +function giveChips(source, amount) + local Player = QBCore.Functions.GetPlayer(source) + if Player then + Player.Functions.AddItem("casino_redchip", amount, nil, nil, false, GetCurrentResourceName(), "", "", "") + TriggerClientEvent('inventory:client:ItemBox', source, QBCore.Shared.Items['casino_redchip'], "add", amount) + updatePlayerChips(source) + end +end + +function removeChips(source, amount) + local Player = QBCore.Functions.GetPlayer(source) + if Player then + Player.Functions.RemoveItem("casino_redchip", amount) + TriggerClientEvent('inventory:client:ItemBox', source, QBCore.Shared.Items['casino_redchip'], "remove", amount) + updatePlayerChips(source) + end +end + +--//////////////////////////--//////////////////////////--////////////////////////// + +ServerPokers = {} + +RegisterNetEvent('aquiverPoker:standUp') +RegisterNetEvent('aquiverPoker:betPlayer') +RegisterNetEvent('aquiverPoker:playCards') +RegisterNetEvent('aquiverPoker:foldCards') +RegisterNetEvent('aquiverPoker:betPairPlusPlayer') + +AddEventHandler( + 'aquiverPoker:foldCards', + function(tableId) + local source = source + if ServerPokers[tableId] ~= nil then + ServerPokers[tableId].PlayersFolded[source] = true + TriggerClientEvent('aquiverPoker:playerFoldCards', -1, source, tableId) + end + end +) +AddEventHandler( + 'aquiverPoker:playCards', + function(tableId, bettedAmount) + local source = source + local Player = QBCore.Functions.GetPlayer(source) + if Player then + if ServerPokers[tableId] ~= nil then + if getPlayerChips(source) >= bettedAmount then + TriggerClientEvent('aquiverPoker:playerPlayCards', -1, source, tableId) + removeChips(source, bettedAmount) + else + TriggerClientEvent('QBCore:Notify', source, _U('not_enough_chips_toplay'), 'error') + end + end + end + end +) + +AddEventHandler( + 'aquiverPoker:standUp', + function(tableId, chairId) + local source = source + if ServerPokers[tableId] ~= nil and ServerPokers[tableId].ChairsUsed[chairId] ~= nil then + ServerPokers[tableId].ChairsUsed[chairId] = nil + Config.DebugMsg('player standup') + end + end +) + +AddEventHandler( + 'aquiverPoker:betPairPlusPlayer', + function(tableId, betAmount) + local source = source + local Player = QBCore.Functions.GetPlayer(source) + if Player then + if ServerPokers[tableId] ~= nil then + if ServerPokers[tableId].PairPlusBets[source] == nil then + if getPlayerChips(source) < betAmount then + TriggerClientEvent('QBCore:Notify', source, _U('not_enough_chips'), 'error') + return + end + + -- checking if he is able to bet the pair plus without lowering the bets < 0 + local currentAnteBetAmount = getPlayerBetAmount(source, tableId) + if getPlayerChips(source) < (currentAnteBetAmount + betAmount) then + TriggerClientEvent('QBCore:Notify', source, _U('not_enough_chips_next'), 'error') + return + end + + ---------------- + + if ServerPokers[tableId].TimeLeft ~= nil and ServerPokers[tableId].TimeLeft > 0 then + ServerPokers[tableId].PairPlusBets[source] = betAmount + TriggerClientEvent('aquiverPoker:playerPairPlusAnim', Player.PlayerData.source, betAmount) + removeChips(source, betAmount) + end + else + TriggerClientEvent('QBCore:Notify', source, _U('already_betted'), 'error') + end + end + end + end +) + +AddEventHandler( + 'aquiverPoker:betPlayer', + function(tableId, chairData, betAmount) + local source = source + local Player = QBCore.Functions.GetPlayer(source) + if Player then + if ServerPokers[tableId] ~= nil then + if ServerPokers[tableId].PlayerBets[source] == nil then + if getPlayerChips(source) < betAmount then + TriggerClientEvent('QBCore:Notify', source, _U('not_enough_chips'), 'error') + + return + end + + -- check the doubled value of the bet for the play deny + if getPlayerChips(source) < betAmount * 2 then + TriggerClientEvent('QBCore:Notify', source, _U('not_enough_chips_third'), 'error') + return + end + + --------------------- + if ServerPokers[tableId].Active == false then -- really important here + -- important to have it after + ServerPokers[tableId].TimeLeft = Config.TimeLeftAfter + ServerPokers[tableId].Active = true + TriggerClientEvent('aquiverPoker:updateState', -1, tableId, ServerPokers[tableId].Active, ServerPokers[tableId].TimeLeft) + end + + ------------------------------------------ + + if ServerPokers[tableId].TimeLeft ~= nil and ServerPokers[tableId].TimeLeft > 0 then + ServerPokers[tableId].PlayerBets[source] = betAmount + TriggerClientEvent('aquiverPoker:playerBetAnim', Player.PlayerData.source, betAmount) + removeChips(source, betAmount) + + if ServerPokers[tableId].Cards['dealer'] == nil then -- generating dealer hands if not exist + ServerPokers[tableId].Cards['dealer'] = { + Hand = generateHand(tableId) + } + end + + if ServerPokers[tableId].Cards[source] == nil then -- generating player hands if not exist + ServerPokers[tableId].Cards[source] = { + Hand = generateHand(tableId), + chairData = chairData + } + end + + TriggerClientEvent('aquiverPoker:updateCards', -1, tableId, ServerPokers[tableId].Cards) + end + else + TriggerClientEvent('QBCore:Notify', source, _U('already_betted'), 'error') + end + end + end + end +) + +-- generating hand function +function generateHand(tableId) + local handTable = {} + + if ServerPokers[tableId] ~= nil then + for i = 1, 3, 1 do + local randomCard = math.random(1, #Config.Cards) + + while ServerPokers[tableId].UsedCards[randomCard] ~= nil do + randomCard = math.random(1, #Config.Cards) + end + + ServerPokers[tableId].UsedCards[randomCard] = true + handTable[i] = randomCard + end + + return handTable + end +end + +Citizen.CreateThread( + function() + while true do + Citizen.Wait(1000) + + for tableId, _ in pairs(ServerPokers) do + if ServerPokers[tableId].Active then + if ServerPokers[tableId].TimeLeft > 0 then + ServerPokers[tableId].TimeLeft = ServerPokers[tableId].TimeLeft - 1 + TriggerClientEvent('aquiverPoker:updateState', -1, tableId, ServerPokers[tableId].Active, ServerPokers[tableId].TimeLeft) + + if ServerPokers[tableId].TimeLeft < 1 then + if ServerPokers[tableId].Stage == 0 then + Citizen.CreateThread( + function() + TriggerClientEvent('aquiverPoker:Stage:1', -1, tableId) -- first ACTION + + Citizen.Wait(9000) + + TriggerClientEvent('aquiverPoker:Stage:2', -1, tableId) -- dealing PLAYER cards + + local activePlayers = getTablePlayersCount(tableId) + Config.DebugMsg(string.format('Active poker players: %s', activePlayers)) + + Citizen.Wait(4000 * activePlayers) + + TriggerClientEvent('aquiverPoker:Stage:3', -1, tableId) -- dealing DEALER cards + + Citizen.Wait(8000) + + TriggerClientEvent('aquiverPoker:Stage:4', -1, tableId) -- PLAYERS watching cards + + Citizen.Wait((Config.PlayerDecideTime * 1000) + 5000) + + TriggerClientEvent('aquiverPoker:Stage:5', -1, tableId) -- reveal PLAYER cards + + local activePlayers = getTablePlayersCount(tableId) + Citizen.Wait(2000 + (5000 * activePlayers)) + + TriggerClientEvent('aquiverPoker:Stage:6', -1, tableId) -- reveal DEALER cards + Citizen.Wait(10000) + CheckWinners(tableId) -- checking winners on server side + Citizen.Wait(1500) + + TriggerClientEvent('aquiverPoker:Stage:7', -1, tableId) -- clearing table + + Citizen.Wait(8000 + (4000 * activePlayers)) + + TriggerClientEvent('aquiverPoker:resetTable', -1, tableId) + ServerPokers[tableId].PlayerBets = {} + ServerPokers[tableId].Active = false + ServerPokers[tableId].Cards = {} + ServerPokers[tableId].UsedCards = {} + ServerPokers[tableId].Stage = 0 + ServerPokers[tableId].TimeLeft = nil + ServerPokers[tableId].PlayersFolded = {} + ServerPokers[tableId].PairPlusBets = {} + end + ) + end + end + end + end + end + end + end +) + +function CheckWinners(tableId) + if ServerPokers[tableId] ~= nil then + local dealerHand = 0 + local dealerHand_second = 0 + local dealerHand_third = 0 + + -- dh means dealerhand + local dH = ServerPokers[tableId].Cards['dealer'] + if dH then + dealerHand = Config.getHandAllValues(dH.Hand) + dealerHand_second = Config.getHandAllValues(dH.Hand, true, false) + dealerHand_third = Config.getHandAllValues(dH.Hand, false, true) + end + + Config.DebugMsg(string.format('Dealer hand value: %s', dealerHand)) + + for targetSrc, data in pairs(ServerPokers[tableId].Cards) do + if targetSrc ~= 'dealer' and playerStillExist(targetSrc) then + -- check that the player folded their hand or not + if ServerPokers[tableId].PlayersFolded[targetSrc] == nil then + local playerHand = Config.getHandAllValues(data.Hand) + local playerHand_second = Config.getHandAllValues(data.Hand, true, false) + local playerHand_third = Config.getHandAllValues(data.Hand, false, true) + + if Config.canDealerPlay(dealerHand) then + if playerHand > dealerHand then -- win + playerWon(targetSrc, tableId, playerHand) + elseif playerHand < dealerHand then -- lose + playerLost(targetSrc, tableId, playerHand) + elseif playerHand == dealerHand then + if playerHand_second == dealerHand_second then -- if equals going more + if playerHand_third > dealerHand_third then + playerWon(targetSrc, tableId, playerHand) + elseif playerHand_third == dealerHand_third then + playerDraw(targetSrc, tableId, playerHand) + else + playerLost(targetSrc, tableId, playerHand) + end + elseif playerHand_second > dealerHand_second then -- if bigger then win + playerWon(targetSrc, tableId, playerHand) + else + playerLost(targetSrc, tableId, playerHand) + end + end + else + playerDraw(targetSrc, tableId, playerHand) + Config.DebugMsg('Díler kártyái nem játszottak.') + end + + local pairMultiplier = Config.GetPairMultiplier(playerHand) + if pairMultiplier > 0 then + playerPairPlusWon(targetSrc, tableId, pairMultiplier) + end + end + end + end + end +end + +function playerPairPlusWon(targetSrc, tableId, pairMultiplier) + local betAmount = getPlayerPairPlusBetAmount(targetSrc, tableId) + if betAmount > 0 then + local Player = QBCore.Functions.GetPlayer(targetSrc) + if Player then + local plusChips = math.floor(betAmount * pairMultiplier) + if plusChips > 0 then + TriggerClientEvent('QBCore:Notify', targetSrc, _('pair_won', plusChips, pairMultiplier), 'error') + giveChips(targetSrc, plusChips) + end + end + end +end + + + + +function playerWon(targetSrc, tableId, handValue) + local betAmount = getPlayerBetAmount(targetSrc, tableId) + if betAmount > 0 then + local Player = QBCore.Functions.GetPlayer(targetSrc) + if Player then + local plusChips = math.floor((betAmount * 2) * 2) + + local AnteMultiplier = Config.GetAnteMultiplier(handValue) + if AnteMultiplier > 0 then + plusChips = math.floor(plusChips + (AnteMultiplier * betAmount)) + TriggerClientEvent('QBCore:Notify', targetSrc, _('player_won_ante', plusChips, AnteMultiplier), 'error') + else + TriggerClientEvent('QBCore:Notify', targetSrc, _('player_won', plusChips), 'error') + end + + giveChips(targetSrc, plusChips) + + TriggerClientEvent('aquiverPoker:playerWin', targetSrc, tableId) + end + end +end + +function playerDraw(targetSrc, tableId, handValue) + local betAmount = getPlayerBetAmount(targetSrc, tableId) + if betAmount > 0 then + local plusChips = math.floor(betAmount * 2) + + local Player = QBCore.Functions.GetPlayer(targetSrc) + if Player then + -- you will get your ante bet bonus even if you loss or draw + local AnteMultiplier = Config.GetAnteMultiplier(handValue) + if AnteMultiplier > 0 then + plusChips = math.floor(plusChips + ((betAmount / 2) * AnteMultiplier)) + TriggerClientEvent('QBCore:Notify', targetSrc, _('dealer_not_qual_ante', plusChips, AnteMultiplier), 'error') + else + TriggerClientEvent('QBCore:Notify', targetSrc, _('dealer_not_qual', plusChips), 'error') + end + end + + giveChips(targetSrc, plusChips) + TriggerClientEvent('aquiverPoker:playerDraw', targetSrc, tableId) + end +end + +function playerLost(targetSrc, tableId, handValue) + local betAmount = getPlayerBetAmount(targetSrc, tableId) + if betAmount > 0 then + local Player = QBCore.Functions.GetPlayer(targetSrc) + if Player then + TriggerClientEvent('QBCore:Notify', targetSrc, _U('lose'), 'error') + end + TriggerClientEvent('aquiverPoker:playerLost', targetSrc, tableId) + end +end + +function updatePlayerChips(targetSrc) + TriggerClientEvent('aquiverPoker:updatePlayerChips', targetSrc, getPlayerChips(targetSrc)) +end + +function getPlayerPairPlusBetAmount(targetSrc, tableId) + if ServerPokers[tableId] ~= nil then + if ServerPokers[tableId].PairPlusBets ~= nil and ServerPokers[tableId].PairPlusBets[targetSrc] ~= nil then + return ServerPokers[tableId].PairPlusBets[targetSrc] + end + end + + return 0 +end + +function getPlayerBetAmount(targetSrc, tableId) + if ServerPokers[tableId] ~= nil then + if ServerPokers[tableId].PlayerBets ~= nil and ServerPokers[tableId].PlayerBets[targetSrc] ~= nil then + return ServerPokers[tableId].PlayerBets[targetSrc] + end + end + + return 0 +end + +function playerStillExist(source) + if GetPlayerName(source) == nil then + return false + else + return true + end +end + +function getTablePlayersCount(tableId) + local playersCount = 0 + if ServerPokers[tableId] ~= nil then + for targetSrc, _ in pairs(ServerPokers[tableId].Cards) do + if playerStillExist(targetSrc) then + playersCount = playersCount + 1 + end + end + end + + return playersCount +end + +QBCore.Functions.CreateCallback( + 'aquiverPoker:sitDown', + function(source, cb, tableId, chairId) + if ServerPokers[tableId] == nil then + ServerPokers[tableId] = { + ChairsUsed = {}, -- chairs used, for disable sitting + PlayerBets = {}, -- player bets ofc. + Active = false, + Cards = {}, -- player / dealer cards, etc. + UsedCards = {}, -- which card was used, so we can not pick the same + PlayersFolded = {}, -- following who folded their cards + PairPlusBets = {}, + Stage = 0, -- following the stages + TimeLeft = nil + } + end + + if ServerPokers[tableId].ChairsUsed[chairId] == nil then + ServerPokers[tableId].ChairsUsed[chairId] = source + Config.DebugMsg('player sit down') + updatePlayerChips(source) + cb(true) + else + cb(false) + end + end +) + +AddEventHandler( + 'playerDropped', + function(reason) + local source = source + + for k, v in pairs(ServerPokers) do + if v.ChairsUsed ~= nil then + for chairId, chairOwner in pairs(v.ChairsUsed) do + if chairOwner == source then + ServerPokers[k].ChairsUsed[chairId] = nil + end + end + end + end + end +) diff --git a/resources/[qb]/[qb_casino]/casino-roulette/client/cl_main.lua b/resources/[qb]/[qb_casino]/casino-roulette/client/cl_main.lua new file mode 100644 index 0000000..66ff0ff --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-roulette/client/cl_main.lua @@ -0,0 +1,1362 @@ + +local QBCore = exports['qb-core']:GetCoreObject() + +SITTING_SCENE = nil +CURRENT_CHAIR_DATA = nil +SELECTED_CHAIR_ID = nil +selectedRulett = nil +Rulettek = {} +closetoRulett = false +currentBetAmount = 0 +idleTimer = 0 +aimingAtBet = -1 +lastAimedBet = -1 + +--------------------------------------------------------- +-- local rouletteTables = { +-- 'vw_prop_casino_roulette_01', +-- 'vw_prop_casino_roulette_01b' + +-- } + +-- Citizen.CreateThread(function() +-- while true do +-- for i=1, #rouletteTables do +-- local coords = GetEntityCoords(PlayerPedId(), false) +-- local gate = GetClosestObjectOfType(coords.x, coords.y, coords.z, 250.0, GetHashKey(rouletteTables[i]), 0, 0, 0) +-- if gate ~= 0 then +-- SetEntityAsMissionEntity(gate, 1, 1) +-- DeleteObject(gate) +-- SetEntityAsNoLongerNeeded(gate) +-- end +-- end +-- Citizen.Wait(2500) +-- end +-- end) + + +-- CreateThread(function() +-- local removeOldRouletteTables = CircleZone:Create(vector3(993.005, 55.232, 69.435), 12.0, { +-- name="OldRouletteTables", +-- heading=160, +-- debugPoly=true, +-- useZ=true, +-- }) +-- removeOldRouletteTables:onPlayerInOut(function(isPointInside) +-- if isPointInside then + +-- -- local table = `vw_prop_casino_roulette_01` +-- local table = GetHashKey('vw_prop_casino_roulette_01b') +-- RequestModel(table) +-- SetEntityAsMissionEntity(table, 1, 1) +-- DeleteObject(table) +-- SetEntityAsNoLongerNeeded(table) +-- QBCore.Functions.Notify('Remove: '..table,'success') +-- else +-- end +-- end) +-- end) +--------------------------------------------------------- + +createRulettAsztal = function(index, data) + local self = {} + + self.index = index + self.data = data + + Config.DebugMsg(string.format('Rulett table creating.. %s', self.index)) + + RequestModel(GetHashKey('vw_prop_casino_roulette_01')) + while not HasModelLoaded(GetHashKey('vw_prop_casino_roulette_01')) do + Wait(1) + end + + self.tableObject = CreateObject(GetHashKey('vw_prop_casino_roulette_01'), data.position, false) + SetEntityHeading(self.tableObject, data.rot) + + RequestModel(GetHashKey('S_F_Y_Casino_01')) + while not HasModelLoaded(GetHashKey('S_F_Y_Casino_01')) do + Wait(1) + end + + local pedOffset = GetObjectOffsetFromCoords(data.position.x, data.position.y, data.position.z, data.rot, 0.0, 0.7, 1.0) + self.ped = CreatePed(2, GetHashKey('S_F_Y_Casino_01'), pedOffset, data.rot + 180.0, false, true) + + SetEntityCanBeDamaged(self.ped, 0) + SetPedAsEnemy(self.ped, 0) + SetBlockingOfNonTemporaryEvents(self.ped, 1) + SetPedResetFlag(self.ped, 249, 1) + SetPedConfigFlag(self.ped, 185, true) + SetPedConfigFlag(self.ped, 108, true) + SetPedCanEvasiveDive(self.ped, 0) + SetPedCanRagdollFromPlayerImpact(self.ped, 0) + SetPedConfigFlag(self.ped, 208, true) + + -- 1.0.1 + SetPedVoiceGroup(self.ped, 'S_M_Y_Casino_01_WHITE_01') + addRandomClothes(self.ped) + + TaskPlayAnim(self.ped, 'anim_casino_b@amb@casino@games@roulette@dealer_female', 'idle', 3.0, 3.0, -1, 2, 0, true, true, true) + + self.numbersData = {} + self.betData = {} + self.hoverObjects = {} + self.betObjects = {} + self.ballObject = nil + + self.rulettCam = nil + self.cameraMode = 1 + + self.enableCamera = function(state) + if state then + self.speakPed('MINIGAME_DEALER_GREET') + TriggerEvent('ShowPlayerHud', false) + -- SendNUIMessage( + -- { + -- action = 'showRulettNui', + -- state = true + -- } + -- ) + -- SendNUIMessage( + -- { + -- action = 'setBetAmount', + -- amount = currentBetAmount + -- } + -- ) + casinoNuiUpdateGame(self.index, self.ido, self.statusz) + + Config.DebugMsg('creating camera..') + local rot = vector3(270.0, -90.0, self.data.rot + 270.0) + self.rulettCam = + CreateCamWithParams('DEFAULT_SCRIPTED_CAMERA', self.data.position.x, self.data.position.y, self.data.position.z + 2.0, rot.x, rot.y, rot.z, 80.0, true, 2) + SetCamActive(self.rulettCam, true) + RenderScriptCams(true, 900, 900, true, false) + Config.DebugMsg('camera setted active.') + + selectedRulett = self.index + self.betRenderState(true) + + playRulettIdle() + + CreateThread(function() + while selectedRulett ~= nil do + Wait(1000) + if idleTimer ~= nil then + idleTimer = idleTimer - 1 + if idleTimer < 1 then + Config.DebugMsg('start idle') + idleTimer = nil + playRulettIdle() + end + end + end + end) + + CreateThread(function() + while selectedRulett ~= nil do + Wait(1) + if self.betObjects then + for i = 1, #self.betObjects, 1 do + local bet = self.betObjects[i] + if DoesEntityExist(bet.obj) then + local coords = GetEntityCoords(bet.obj) + if bet.playerSrc == GetPlayerServerId(PlayerId()) then + Draw3DText(coords, string.format('~w~%s', bet.betAmount), 0.10, 0) + end + end + end + end + end + end) + + CreateThread(function() + while selectedRulett ~= nil do + Wait(125) + + if IsDisabledControlPressed(0, 172) then + currentBetAmount = currentBetAmount + 10 + changeBetAmount(currentBetAmount) + -- QBCore.Functions.Notify('+'..currentBetAmount.." bet [raised]",'success') + + elseif IsDisabledControlPressed(0, 173) then + if currentBetAmount > 0 then + currentBetAmount = currentBetAmount - 10 + + if currentBetAmount < 0 then + currentBetAmount = 0 + end + changeBetAmount(currentBetAmount) + -- QBCore.Functions.Notify('-'..currentBetAmount.." bet [lowered]",'primary') + + end + end + end + end) + + CreateThread(function() + while selectedRulett ~= nil do + Wait(0) + DisableAllControlActions(0) + if IsDisabledControlJustPressed(0, 202) then + self.enableCamera(false) + end + if IsDisabledControlJustPressed(0, 38) then + self.changeKameraMode() + end + + if Config.allowCustomBet then + if IsDisabledControlJustPressed(0, 22) then --Custom Bet [space] + local tmpInput = getGenericTextInput('How many chips you would like to bet?') + if tonumber(tmpInput) then + tmpInput = tonumber(tmpInput) + if tmpInput > 0 then + changeBetAmount(tmpInput) + QBCore.Functions.Notify('Custom Bet: '..currentBetAmount..' chips','success') + end + end + end + else + + end + end + end) + + Wait(1000) + else + TriggerServerEvent('casino:rulett:notUsing', selectedRulett) + if DoesCamExist(self.rulettCam) then + DestroyCam(self.rulettCam, false) + end + RenderScriptCams(false, 900, 900, true, false) + self.betRenderState(false) + Config.DebugMsg('camera deleted.') + selectedRulett = nil + self.speakPed('MINIGAME_DEALER_LEAVE_NEUTRAL_GAME') + + NetworkStopSynchronisedScene(SITTING_SCENE) + + local endingDict = 'anim_casino_b@amb@casino@games@shared@player@' + RequestAnimDict(endingDict) + while not HasAnimDictLoaded(endingDict) do + Wait(1) + end + + local whichAnim = nil + if SELECTED_CHAIR_ID == 1 then + whichAnim = 'sit_exit_left' + elseif SELECTED_CHAIR_ID == 2 then + whichAnim = 'sit_exit_right' + elseif SELECTED_CHAIR_ID == 3 then + whichAnim = ({'sit_exit_left', 'sit_exit_right'})[math.random(1, 2)] + elseif SELECTED_CHAIR_ID == 4 then + whichAnim = 'sit_exit_left' + end + + TaskPlayAnim(PlayerPedId(), endingDict, whichAnim, 1.0, 1.0, 2500, 0) + SetPlayerControl(PlayerId(), 0, 0) + hideUi() + Wait(3600) + SetPlayerControl(PlayerId(), 1, 0) + end + end + + self.changeKameraMode = function() + if DoesCamExist(self.rulettCam) then + if self.cameraMode == 1 then + DoScreenFadeOut(200) + while not IsScreenFadedOut() do + Wait(1) + end + self.cameraMode = 2 + local camOffset = GetOffsetFromEntityInWorldCoords(self.tableObject, -1.45, -0.15, 1.45) + SetCamCoord(self.rulettCam, camOffset) + SetCamRot(self.rulettCam, -25.0, 0.0, self.data.rot + 270.0, 2) + SetCamFov(self.rulettCam, 40.0) + ShakeCam(self.rulettCam, 'HAND_SHAKE', 0.3) + DoScreenFadeIn(200) + elseif self.cameraMode == 2 then + DoScreenFadeOut(200) + while not IsScreenFadedOut() do + Wait(1) + end + self.cameraMode = 3 + local camOffset = GetOffsetFromEntityInWorldCoords(self.tableObject, 1.45, -0.15, 2.15) + SetCamCoord(self.rulettCam, camOffset) + SetCamRot(self.rulettCam, -58.0, 0.0, self.data.rot + 90.0, 2) + ShakeCam(self.rulettCam, 'HAND_SHAKE', 0.3) + SetCamFov(self.rulettCam, 80.0) + DoScreenFadeIn(200) + elseif self.cameraMode == 3 then + DoScreenFadeOut(200) + while not IsScreenFadedOut() do + Wait(1) + end + self.cameraMode = 4 + local camOffset = GetWorldPositionOfEntityBone(self.tableObject, GetEntityBoneIndexByName(self.tableObject, 'Roulette_Wheel')) + local rot = vector3(270.0, -90.0, self.data.rot + 270.0) + SetCamCoord(self.rulettCam, camOffset + vector3(0.0, 0.0, 0.5)) + SetCamRot(self.rulettCam, rot, 2) + StopCamShaking(self.rulettCam, false) + SetCamFov(self.rulettCam, 80.0) + DoScreenFadeIn(200) + elseif self.cameraMode == 4 then + DoScreenFadeOut(200) + while not IsScreenFadedOut() do + Wait(1) + end + self.cameraMode = 1 + local rot = vector3(270.0, -90.0, self.data.rot + 270.0) + SetCamCoord(self.rulettCam, self.data.position + vector3(0.0, 0.0, 2.0)) + SetCamRot(self.rulettCam, rot, 2) + SetCamFov(self.rulettCam, 80.0) + StopCamShaking(self.rulettCam, false) + DoScreenFadeIn(200) + end + end + end + + self.loadTableData = function() + Config.DebugMsg('Table data creating, loading..') + self.numbersData = {} + self.betData = {} + local e = 1 + for i = 0, 11, 1 do + for j = 0, 2, 1 do + table.insert( + self.numbersData, + { + name = e + 1, + hoverPos = GetOffsetFromEntityInWorldCoords(self.tableObject, (0.081 * i) - 0.057, (0.167 * j) - 0.192, 0.9448), + hoverObject = 'vw_prop_vw_marker_02a' + } + ) + local offset = nil + if j == 0 then + offset = 0.155 + elseif j == 1 then + offset = 0.171 + elseif j == 2 then + offset = 0.192 + end + + table.insert( + self.betData, + { + betId = e, + name = e + 1, + pos = GetOffsetFromEntityInWorldCoords(self.tableObject, (0.081 * i) - 0.057, (0.167 * j) - 0.192, 0.9448), + objectPos = GetOffsetFromEntityInWorldCoords(self.tableObject, 0.081 * i - 0.057, 0.167 * j - 0.192, 0.9448), + hoverNumbers = {e} + } + ) + + e = e + 1 + end + end + table.insert( + self.numbersData, + { + name = 'Zero', + hoverPos = GetOffsetFromEntityInWorldCoords(self.tableObject, -0.137, -0.148, 0.9448), + hoverObject = 'vw_prop_vw_marker_01a' + } + ) + table.insert( + self.betData, + { + betId = #self.betData, + name = 'Zero', + pos = GetOffsetFromEntityInWorldCoords(self.tableObject, -0.137, -0.148, 0.9448), + objectPos = GetOffsetFromEntityInWorldCoords(self.tableObject, -0.137, -0.148, 0.9448), + hoverNumbers = {#self.numbersData} + } + ) + table.insert( + self.numbersData, + { + name = 'Double Zero', + hoverPos = GetOffsetFromEntityInWorldCoords(self.tableObject, -0.133, 0.107, 0.9448), + hoverObject = 'vw_prop_vw_marker_01a' + } + ) + table.insert( + self.betData, + { + betId = #self.betData, + name = 'Double Zero', + pos = GetOffsetFromEntityInWorldCoords(self.tableObject, -0.133, 0.107, 0.9448), + objectPos = GetOffsetFromEntityInWorldCoords(self.tableObject, -0.133, 0.107, 0.9448), + hoverNumbers = {#self.numbersData} + } + ) + table.insert( + self.betData, + { + betId = #self.betData, + name = 'RED', + pos = GetOffsetFromEntityInWorldCoords(self.tableObject, 0.3, -0.4, 0.9448), + objectPos = GetOffsetFromEntityInWorldCoords(self.tableObject, 0.3, -0.4, 0.9448), + hoverNumbers = {1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36} + } + ) + table.insert( + self.betData, + { + betId = #self.betData, + name = 'BLACK', + pos = GetOffsetFromEntityInWorldCoords(self.tableObject, 0.5, -0.4, 0.9448), + objectPos = GetOffsetFromEntityInWorldCoords(self.tableObject, 0.5, -0.4, 0.9448), + hoverNumbers = {0, 2, 4, 6, 8, 9, 11, 13, 15, 18, 20, 22, 24, 26, 27, 29, 31, 33, 35} + } + ) + table.insert( + self.betData, + { + betId = #self.betData, + name = 'EVEN', + pos = GetOffsetFromEntityInWorldCoords(self.tableObject, 0.15, -0.4, 0.9448), + objectPos = GetOffsetFromEntityInWorldCoords(self.tableObject, 0.15, -0.4, 0.9448), + hoverNumbers = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36} + } + ) + table.insert( + self.betData, + { + betId = #self.betData, + name = 'ODD', + pos = GetOffsetFromEntityInWorldCoords(self.tableObject, 0.65, -0.4, 0.9448), + objectPos = GetOffsetFromEntityInWorldCoords(self.tableObject, 0.65, -0.4, 0.9448), + hoverNumbers = {1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35} + } + ) + table.insert( + self.betData, + { + betId = #self.betData, + name = '1to18', + pos = GetOffsetFromEntityInWorldCoords(self.tableObject, -0.02, -0.4, 0.9448), + objectPos = GetOffsetFromEntityInWorldCoords(self.tableObject, -0.02, -0.4, 0.9448), + hoverNumbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18} + } + ) + table.insert( + self.betData, + { + betId = #self.betData, + name = '19to36', + pos = GetOffsetFromEntityInWorldCoords(self.tableObject, 0.78, -0.4, 0.9448), + objectPos = GetOffsetFromEntityInWorldCoords(self.tableObject, 0.78, -0.4, 0.9448), + hoverNumbers = {19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36} + } + ) + table.insert( + self.betData, + { + betId = #self.betData, + name = '1st 12', + pos = GetOffsetFromEntityInWorldCoords(self.tableObject, 0.05, -0.3, 0.9448), + objectPos = GetOffsetFromEntityInWorldCoords(self.tableObject, 0.05, -0.3, 0.9448), + hoverNumbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12} + } + ) + table.insert( + self.betData, + { + betId = #self.betData, + name = '2nd 12', + pos = GetOffsetFromEntityInWorldCoords(self.tableObject, 0.4, -0.3, 0.9448), + objectPos = GetOffsetFromEntityInWorldCoords(self.tableObject, 0.4, -0.3, 0.9448), + hoverNumbers = {13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24} + } + ) + table.insert( + self.betData, + { + betId = #self.betData, + name = '3rd 12', + pos = GetOffsetFromEntityInWorldCoords(self.tableObject, 0.75, -0.3, 0.9448), + objectPos = GetOffsetFromEntityInWorldCoords(self.tableObject, 0.75, -0.3, 0.9448), + hoverNumbers = {25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36} + } + ) + table.insert( + self.betData, + { + betId = #self.betData, + name = '2to1', + pos = GetOffsetFromEntityInWorldCoords(self.tableObject, 0.91, -0.15, 0.9448), + objectPos = GetOffsetFromEntityInWorldCoords(self.tableObject, 0.91, -0.15, 0.9448), + hoverNumbers = {1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34} + } + ) + table.insert( + self.betData, + { + betId = #self.betData, + name = '2to1', + pos = GetOffsetFromEntityInWorldCoords(self.tableObject, 0.91, 0.0, 0.9448), + objectPos = GetOffsetFromEntityInWorldCoords(self.tableObject, 0.91, 0.0, 0.9448), + hoverNumbers = {2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35} + } + ) + table.insert( + self.betData, + { + betId = #self.betData, + name = '2to1', + pos = GetOffsetFromEntityInWorldCoords(self.tableObject, 0.91, 0.15, 0.9448), + objectPos = GetOffsetFromEntityInWorldCoords(self.tableObject, 0.91, 0.15, 0.9448), + hoverNumbers = {3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36} + } + ) + + Config.DebugMsg('Table data successfully created..') + end + + self.speakPed = function(speakName) + PlayPedAmbientSpeechNative(self.ped, speakName, 'SPEECH_PARAMS_FORCE_NORMAL_CLEAR', 1) + end + + self.createBetObjects = function(bets) + for i = 1, #self.betObjects, 1 do + if DoesEntityExist(self.betObjects[i].obj) then + DeleteObject(self.betObjects[i].obj) + end + end + + self.betObjects = {} + + local existBetId = {} + + for i = 1, #bets, 1 do + local t = self.betData[bets[i].betId] + + if existBetId[bets[i].betId] == nil then + existBetId[bets[i].betId] = 0 + else + existBetId[bets[i].betId] = existBetId[bets[i].betId] + 1 + end + + if t ~= nil then + local betModelObject = getBetObjectType(bets[i].betAmount) + + if betModelObject ~= nil then + RequestModel(betModelObject) + while not HasModelLoaded(betModelObject) do + Wait(0) + end + + local obj = CreateObject(betModelObject, t.objectPos.x, t.objectPos.y, t.objectPos.z + (existBetId[bets[i].betId] * 0.0081), false) + SetEntityHeading(obj, self.data.rot) + table.insert( + self.betObjects, + { + obj = obj, + betAmount = bets[i].betAmount, + playerSrc = bets[i].playerSrc + } + ) + end + end + end + end + + self.hoverNumbers = function(hoveredNumbers) + for i = 1, #self.hoverObjects, 1 do + if DoesEntityExist(self.hoverObjects[i]) then + DeleteObject(self.hoverObjects[i]) + end + end + + self.hoverObjects = {} + + for i = 1, #hoveredNumbers, 1 do + local t = self.numbersData[hoveredNumbers[i]] + if t ~= nil then + RequestModel(GetHashKey(t.hoverObject)) + while not HasModelLoaded(GetHashKey(t.hoverObject)) do + Wait(1) + end + + local obj = CreateObject(GetHashKey(t.hoverObject), t.hoverPos, false) + SetEntityHeading(obj, self.data.rot) + + table.insert(self.hoverObjects, obj) + end + end + end + + self.betRenderState = function(state) + enabledBetRender = state + + Config.DebugMsg('Bet rendering turned: %s', enabledBetRender) + + if state then + CreateThread( + function() + while enabledBetRender do + Wait(8) + + if aimingAtBet ~= -1 and lastAimedBet ~= aimingAtBet then + Config.DebugMsg('aimed at different bet.') + lastAimedBet = aimingAtBet + local bettingData = self.betData[aimingAtBet] + if bettingData ~= nil then + self.hoverNumbers(bettingData.hoverNumbers) + else + self.hoverNumbers({}) + end + end + + if aimingAtBet == -1 and lastAimedBet ~= -1 then + self.hoverNumbers({}) + end + end + end + ) + + CreateThread( + function() + while enabledBetRender do + Wait(0) + + ShowCursorThisFrame() + + local e = Rulettek[selectedRulett] + if e ~= nil then + local cx, cy = GetNuiCursorPosition() + local rx, ry = GetActiveScreenResolution() + + local n = 30 -- this is for the cursor point, how much to tolerate in range, increasing it you will find it easier to click on the bets. + + local foundBet = false + + for i = 1, #self.betData, 1 do + local bettingData = self.betData[i] + local onScreen, screenX, screenY = World3dToScreen2d(bettingData.pos.x, bettingData.pos.y, bettingData.pos.z) + local l = math.sqrt(math.pow(screenX * rx - cx, 2) + math.pow(screenY * ry - cy, 2)) + if l < n then + aimingAtBet = i + foundBet = true + + if IsDisabledControlJustPressed(0, 24) then + if currentBetAmount > 0 then + if Config.RulettTables[selectedRulett] ~= nil then + if currentBetAmount >= Config.RulettTables[selectedRulett].minBet and currentBetAmount <= Config.RulettTables[selectedRulett].maxBet then + PlaySoundFrontend(-1, 'DLC_VW_BET_DOWN', 'dlc_vw_table_games_frontend_sounds', true) + TriggerServerEvent('casino:taskBetRulett', selectedRulett, aimingAtBet, currentBetAmount) + else + QBCore.Functions.Notify('Your bet it too low or too high for this table.','error') + + end + end + else + QBCore.Functions.Notify('Bet needs to be raised','error') + end + end + end + end + + if not foundBet then + aimingAtBet = -1 + end + end + end + end + ) + end + end + + self.spinRulett = function(tickRate) + Config.DebugMsg(self.index) + if DoesEntityExist(self.tableObject) and DoesEntityExist(self.ped) then + Config.DebugMsg('spinRulett event 1') + + self.speakPed('MINIGAME_DEALER_CLOSED_BETS') + TaskPlayAnim(self.ped, 'anim_casino_b@amb@casino@games@roulette@dealer_female', 'no_more_bets', 3.0, 3.0, -1, 0, 0, true, true, true) + + Wait(1500) + + if DoesEntityExist(self.ballObject) then + DeleteObject(self.ballObject) + end + + TaskPlayAnim(self.ped, 'anim_casino_b@amb@casino@games@roulette@dealer_female', 'spin_wheel', 3.0, 3.0, -1, 0, 0, true, true, true) + + RequestModel(GetHashKey('vw_prop_roulette_ball')) + while not HasModelLoaded(GetHashKey('vw_prop_roulette_ball')) do + Wait(1) + end + + local ballOffset = GetWorldPositionOfEntityBone(self.tableObject, GetEntityBoneIndexByName(self.tableObject, 'Roulette_Wheel')) + + Config.DebugMsg('spinRulett event 2') + + local LIB = 'anim_casino_b@amb@casino@games@roulette@table' + RequestAnimDict(LIB) + while not HasAnimDictLoaded(LIB) do + Wait(1) + end + + Wait(3000) + + self.ballObject = CreateObject(GetHashKey('vw_prop_roulette_ball'), ballOffset, false) + SetEntityHeading(self.ballObject, self.data.rot) + SetEntityCoordsNoOffset(self.ballObject, ballOffset, false, false, false) + local h = GetEntityRotation(self.ballObject) + SetEntityRotation(self.ballObject, h.x, h.y, h.z + 90.0, 2, false) + + if DoesEntityExist(self.tableObject) and DoesEntityExist(self.ped) then + Config.DebugMsg('spinRulett event 3') + + PlayEntityAnim(self.ballObject, 'intro_ball', LIB, 1000.0, false, true, true, 0, 136704) + PlayEntityAnim(self.ballObject, 'loop_ball', LIB, 1000.0, false, true, false, 0, 136704) + + PlayEntityAnim(self.tableObject, 'intro_wheel', LIB, 1000.0, false, true, true, 0, 136704) + PlayEntityAnim(self.tableObject, 'loop_wheel', LIB, 1000.0, false, true, false, 0, 136704) + + PlayEntityAnim(self.ballObject, string.format('exit_%s_ball', tickRate), LIB, 1000.0, false, true, false, 0, 136704) + PlayEntityAnim(self.tableObject, string.format('exit_%s_wheel', tickRate), LIB, 1000.0, false, true, false, 0, 136704) + + Wait(11e3) + + if DoesEntityExist(self.tableObject) and DoesEntityExist(self.ped) then + TaskPlayAnim(self.ped, 'anim_casino_b@amb@casino@games@roulette@dealer_female', 'clear_chips_zone1', 3.0, 3.0, -1, 0, 0, true, true, true) + Wait(1500) + TaskPlayAnim(self.ped, 'anim_casino_b@amb@casino@games@roulette@dealer_female', 'clear_chips_zone2', 3.0, 3.0, -1, 0, 0, true, true, true) + Wait(1500) + TaskPlayAnim(self.ped, 'anim_casino_b@amb@casino@games@roulette@dealer_female', 'clear_chips_zone3', 3.0, 3.0, -1, 0, 0, true, true, true) + + Wait(2000) + if DoesEntityExist(self.tableObject) and DoesEntityExist(self.ped) then + TaskPlayAnim(self.ped, 'anim_casino_b@amb@casino@games@roulette@dealer_female', 'idle', 3.0, 3.0, -1, 0, 0, true, true, true) + end + + Config.DebugMsg('spinRulett event ending') + + if DoesEntityExist(self.ballObject) then + DeleteObject(self.ballObject) + end + end + end + end + end + + self.loadTableData() + Config.DebugMsg(string.format('Rulett table created %s id', self.index)) + Rulettek[self.index] = self +end + +function Draw3DText(coords, text, size, font) + coords = vector3(coords.x, coords.y, coords.z) + + local camCoords = GetGameplayCamCoords() + local distance = #(coords - camCoords) + + if not size then + size = 1 + end + if not font then + font = 0 + end + + local scale = (size / distance) * 2 + local fov = (1 / GetGameplayCamFov()) * 100 + scale = scale * fov + + SetTextScale(0.0 * scale, 0.55 * scale) + SetTextFont(font) + SetTextColour(255, 255, 255, 255) + SetTextDropshadow(0, 0, 0, 0, 255) + SetTextDropShadow() + SetTextOutline() + SetTextCentre(true) + + SetDrawOrigin(coords, 0) + BeginTextCommandDisplayText('STRING') + AddTextComponentSubstringPlayerName(text) + EndTextCommandDisplayText(0.0, 0.0) + ClearDrawOrigin() +end + +function hideUi() + exports["qb-core"]:HideText() + exports['casinoUi']:HideCasinoUi('hide') +end + +function changeBetAmount(amount) + currentBetAmount = amount + PlaySoundFrontend(-1, 'DLC_VW_BET_HIGHLIGHT', 'dlc_vw_table_games_frontend_sounds', true) +end + + +function getGenericTextInput(type) + if type == nil then + type = '' + end + AddTextEntry('FMMC_MPM_NA', tostring(type)) + DisplayOnscreenKeyboard(1, 'FMMC_MPM_NA', tostring(type), '', '', '', '', 30) + while (UpdateOnscreenKeyboard() == 0) do + DisableAllControlActions(0) + Wait(0) + end + if (GetOnscreenKeyboardResult()) then + local result = GetOnscreenKeyboardResult() + if result then + return result + end + end + return false +end + +CreateThread( + function() + while true do + local playerCoords = GetEntityCoords(GetPlayerPed(-1)) + closetoRulett = false + for k, v in pairs(Config.RulettTables) do + if #(playerCoords - Config.RulettTables[k].position) < 100.0 then + closetoRulett = true + end + end + Wait(1000) + end + end +) + +CreateThread( + function() + while not closetoRulett do + Wait(0) + end + + for rulettIndex, data in pairs(Config.RulettTables) do + createRulettAsztal(rulettIndex, data) + + RequestAnimDict('anim_casino_b@amb@casino@games@roulette@table') + RequestAnimDict('anim_casino_b@amb@casino@games@roulette@dealer_female') + RequestAnimDict('anim_casino_b@amb@casino@games@shared@player@') + RequestAnimDict('anim_casino_b@amb@casino@games@roulette@player') + end + Config.DebugMsg('Casino rulett loaded.') + end +) + +-- CreateThread(function() +-- while true do +-- local sleep = 5 +-- local playerpos = GetEntityCoords(PlayerPedId()) + +-- if closetoRulett and selectedRulett == nil then +-- for k, v in pairs(Rulettek) do +-- if DoesEntityExist(v.tableObject) then +-- local objcoords = GetEntityCoords(v.tableObject) +-- local dist = Vdist(playerpos, objcoords) +-- if dist < 2.4 then +-- if dist < 2.3 then +-- -- exports['textUi']:DrawTextUi('show',"Diamond Casino Roulette

Press [E] to sit down") +-- exports["qb-core"]:DrawText("The Diamond Casino & Resort

Roulette

Press E to sit") +-- local closestChairData = getClosestChairData(v.tableObject) + +-- if closestChairData == nil then +-- break +-- end +-- if IsControlJustPressed(0, 38) then +-- -- QBCore.Functions.TriggerCallback('doj:server:HasCasinoMembership', function(HasItem) +-- -- if HasItem then +-- TriggerServerEvent('server_remote:rulett:taskSitDown', k, closestChairData) +-- -- else +-- -- QBCore.Functions.Notify('You are not a member of the casino', 'error', 3500) +-- -- end +-- -- end) +-- end +-- break +-- end +-- hideUi() +-- end +-- end +-- end +-- end +-- Wait(sleep) +-- end +-- end) + + + + + +Citizen.CreateThread(function() + local alreadyEnteredZone = false + local text = nil + while true do + if closetoRulett and selectedRulett == nil then + wait = 5 + local inZone = false + + local playerpos = GetEntityCoords(PlayerPedId()) + for k, v in pairs(Rulettek) do + if DoesEntityExist(v.tableObject) then + local objcoords = GetEntityCoords(v.tableObject) + local dist = Vdist(playerpos, objcoords) + if dist <= 2.0 then + wait = 5 + inZone = true + text = "The Diamond Casino & Resort

Roulette

Press E to sit" + local closestChairData = getClosestChairData(v.tableObject) + if closestChairData == nil then + break + end + if IsControlJustPressed(0, 38) then + TriggerServerEvent('server_remote:rulett:taskSitDown', k, closestChairData) + end + -- break + -- else + -- wait = 2000 + end + if inZone and not alreadyEnteredZone then + alreadyEnteredZone = true + exports["qb-core"]:DrawText(text) + end + if not inZone and alreadyEnteredZone then + alreadyEnteredZone = false + exports["qb-core"]:HideText() + end + end + end + end + Wait(wait) + end +end) + + + + +RegisterNetEvent('client_callback:rulett:taskSitDown',function(rulettIndex, chairData) + -- exports['progressBars']:drawBar(4000, 'Sitting...') + -- QBCore.Functions.Notify("Sitting...", "primary", 3200) + exports["qb-core"]:HideText() + SELECTED_CHAIR_ID = chairData.chairId + CURRENT_CHAIR_DATA = chairData + SITTING_SCENE = NetworkCreateSynchronisedScene(chairData.position, chairData.rotation, 2, 1, 0, 1065353216, 0, 1065353216) + RequestAnimDict('anim_casino_b@amb@casino@games@shared@player@') + while not HasAnimDictLoaded('anim_casino_b@amb@casino@games@shared@player@') do + Wait(1) + end + local randomSit = ({'sit_enter_left', 'sit_enter_right'})[math.random(1, 2)] + NetworkAddPedToSynchronisedScene(PlayerPedId(), SITTING_SCENE, 'anim_casino_b@amb@casino@games@shared@player@', randomSit, 2.0, -2.0, 13, 16, 2.0, 0) + NetworkStartSynchronisedScene(SITTING_SCENE) + SetPlayerControl(PlayerId(), 0, 0) + startRulett(rulettIndex, chairData.chairId) + Wait(4000) + SetPlayerControl(PlayerId(), 1, 0) +end) + +function startRulett(index, chairId) + if Rulettek[index] then + TriggerServerEvent('casino:taskStartRoulette', index, chairId) + end +end + +RegisterNetEvent('client:casino:openRulett') +AddEventHandler('client:casino:openRulett',function(rulettIndex) + if Rulettek[rulettIndex] ~= nil then + Wait(4000) + Rulettek[rulettIndex].enableCamera(true) + end +end) + +RegisterNetEvent('casino:rulett:startSpin') +AddEventHandler( + 'casino:rulett:startSpin', + function(rulettIndex, tickRate) + + if Rulettek[rulettIndex] ~= nil then + + Config.DebugMsg(string.format('rulett table index: %s, tickrate: %s', rulettIndex, tickRate)) + Rulettek[rulettIndex].spinRulett(tickRate) + + if selectedRulett == rulettIndex then + Config.DebugMsg('impartial anim play') + playImpartial() + end + end + end +) + +RegisterNetEvent('client:rulett:updateStatusz') +AddEventHandler( + 'client:rulett:updateStatusz', + function(rulettIndex, ido, statusz) + + if Rulettek[rulettIndex] ~= nil then + Rulettek[rulettIndex].ido = ido + Rulettek[rulettIndex].statusz = statusz + casinoNuiUpdateGame(rulettIndex, ido, statusz) + end + end +) + +RegisterNetEvent('client:rulett:updateTableBets') +AddEventHandler( + 'client:rulett:updateTableBets', + function(rulettIndex, bets) + if Rulettek[rulettIndex] ~= nil then + Rulettek[rulettIndex].createBetObjects(bets) + end + end +) + +function casinoNuiUpdateGame(rulettIndex, ido, statusz) + QBCore.Functions.TriggerCallback('roulette:server:ChipsAmount', function(result) + retval = result + if selectedRulett == rulettIndex then + if not statusz then + exports['casinoUi']:DrawCasinoUi('show', "Diamond Casino Roulette

Time Left: "..ido.."

Current Bet: "..currentBetAmount.."

Availble chips: "..math.floor(retval)) + if Config.allowCustomBet then + exports["qb-core"]:DrawText("Place Bet: LEFT CLICK

Adjust Bet: ↑/↓

Exit:

Custom Amount: SPACEBAR") + else + exports["qb-core"]:DrawText("Place Bet: LEFT CLICK

Adjust Bet: ↑/↓

Exit: ←") + end + else + exports["qb-core"]:DrawText("The game is starting..") + hideUi() + end + end + end) +end + + +function getClosestChairData(tableObject) + local localPlayer = PlayerPedId() + local playerpos = GetEntityCoords(localPlayer) + if DoesEntityExist(tableObject) then + local chairs = {'Chair_Base_01', 'Chair_Base_02', 'Chair_Base_03', 'Chair_Base_04'} + for i = 1, #chairs, 1 do + local objcoords = GetWorldPositionOfEntityBone(tableObject, GetEntityBoneIndexByName(tableObject, chairs[i])) + local dist = Vdist(playerpos, objcoords) + if dist < 1.7 then + return { + position = objcoords, + rotation = GetWorldRotationOfEntityBone(tableObject, GetEntityBoneIndexByName(tableObject, chairs[i])), + chairId = Config.ChairIds[chairs[i]] + } + end + end + end +end + +function getBetObjectType(betAmount) + if betAmount < 10 then + return GetHashKey('vw_prop_vw_coin_01a') + elseif betAmount >= 10 and betAmount < 50 then + return GetHashKey('vw_prop_chip_10dollar_x1') + elseif betAmount >= 50 and betAmount < 100 then + return GetHashKey('vw_prop_chip_50dollar_x1') + elseif betAmount >= 100 and betAmount < 500 then + return GetHashKey('vw_prop_chip_100dollar_x1') + elseif betAmount >= 500 and betAmount < 1000 then + return GetHashKey('vw_prop_chip_500dollar_x1') + elseif betAmount >= 1000 and betAmount < 5000 then + return GetHashKey('vw_prop_chip_1kdollar_x1') + elseif betAmount >= 5000 then + return GetHashKey('vw_prop_plaq_10kdollar_x1') + else -- this should never happen, but yeah. + return GetHashKey('vw_prop_plaq_10kdollar_x1') + end + + -- these are deprecated, it looks cool, but it hides some data, you can put it in you like the big chip piles. + + -- elseif bets[i].betAmount >= 10000 and bets[i].betAmount < 25000 then + -- return GetHashKey('vw_prop_vw_chips_pile_01a') + -- elseif bets[i].betAmount >= 25000 and bets[i].betAmount < 50000 then + -- return GetHashKey('vw_prop_vw_chips_pile_02a') + -- elseif bets[i].betAmount >= 50000 then + -- return GetHashKey('vw_prop_vw_chips_pile_03a') + -- end +end + +RegisterNetEvent('client:rulett:playBetAnim') +AddEventHandler( + 'client:rulett:playBetAnim', + function(chairId) + local sex = 0 + + if GetEntityModel(PlayerPedId()) == GetHashKey('mp_f_freemode_01') then + sex = 1 + end + + local rot = CURRENT_CHAIR_DATA.rotation + + if chairId == 4 then + rot = rot + vector3(0.0, 0.0, 90.0) + elseif chairId == 3 then + rot = rot + vector3(0.0, 0.0, -180.0) + elseif chairId == 2 then + rot = rot + vector3(0.0, 0.0, -90.0) + elseif chairId == 1 then + chairId = 1 + rot = rot + vector3(0.0, 0.0, -90.0) + end + + local L = string.format('anim_casino_b@amb@casino@games@roulette@ped_male@seat_%s@regular@0%sa@play@v01', chairId, chairId) + if sex == 1 then + L = string.format('anim_casino_b@amb@casino@games@roulette@ped_female@seat_%s@regular@0%sa@play@v01', chairId, chairId) + end + + RequestAnimDict(L) + while not HasAnimDictLoaded(L) do + Wait(1) + end + + if CURRENT_CHAIR_DATA ~= nil then + local currentScene = NetworkCreateSynchronisedScene(CURRENT_CHAIR_DATA.position, rot, 2, 1, 0, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene( + PlayerPedId(), + currentScene, + L, + ({'place_bet_zone1', 'place_bet_zone2', 'place_bet_zone3'})[math.random(1, 3)], + 4.0, + -2.0, + 13, + 16, + 1148846080, + 0 + ) + NetworkStartSynchronisedScene(currentScene) + + idleTimer = 8 + end + end +) + +RegisterNetEvent('client:rulett:playWinAnim') +AddEventHandler( + 'client:rulett:playWinAnim', + function(chairId) + local rot = CURRENT_CHAIR_DATA.rotation + + if chairId == 4 then + rot = rot + vector3(0.0, 0.0, 90.0) + elseif chairId == 3 then + rot = rot + vector3(0.0, 0.0, -180.0) + elseif chairId == 2 then + rot = rot + vector3(0.0, 0.0, -90.0) + elseif chairId == 1 then + chairId = 1 + rot = rot + vector3(0.0, 0.0, -90.0) + end + + local sex = 0 + local L = string.format('anim_casino_b@amb@casino@games@roulette@ped_male@seat_%s@regular@0%sa@reacts@v01', chairId, chairId) + + if GetEntityModel(PlayerPedId()) == GetHashKey('mp_f_freemode_01') then + sex = 1 + end + + if sex == 1 then + local L = string.format('anim_casino_b@amb@casino@games@roulette@ped_female@seat_%s@regular@0%sa@reacts@v01', chairId, chairId) + end + + RequestAnimDict(L) + while not HasAnimDictLoaded(L) do + Wait(1) + end + + if CURRENT_CHAIR_DATA ~= nil then + local currentScene = NetworkCreateSynchronisedScene(CURRENT_CHAIR_DATA.position, rot, 2, 1, 0, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(PlayerPedId(), currentScene, L, 'reaction_great', 4.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(currentScene) + + idleTimer = 8 + end + end +) + +RegisterNetEvent('client:rulett:playLossAnim',function(chairId) + local rot = CURRENT_CHAIR_DATA.rotation + + if chairId == 4 then + rot = rot + vector3(0.0, 0.0, 90.0) + elseif chairId == 3 then + rot = rot + vector3(0.0, 0.0, -180.0) + elseif chairId == 2 then + rot = rot + vector3(0.0, 0.0, -90.0) + elseif chairId == 1 then + chairId = 1 + rot = rot + vector3(0.0, 0.0, -90.0) + end + + local sex = 0 + local L = string.format('anim_casino_b@amb@casino@games@roulette@ped_male@seat_%s@regular@0%sa@reacts@v01', chairId, chairId) + + if GetEntityModel(PlayerPedId()) == GetHashKey('mp_f_freemode_01') then + sex = 1 + end + + if sex == 1 then + local L = string.format('anim_casino_b@amb@casino@games@roulette@ped_female@seat_%s@regular@0%sa@reacts@v01', chairId, chairId) + end + + RequestAnimDict(L) + while not HasAnimDictLoaded(L) do + Wait(1) + end + + if CURRENT_CHAIR_DATA ~= nil then + local currentScene = NetworkCreateSynchronisedScene(CURRENT_CHAIR_DATA.position, rot, 2, 1, 0, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene( + PlayerPedId(), + currentScene, + L, + ({'reaction_bad_var01', 'reaction_bad_var02', 'reaction_terrible'})[math.random(1, 3)], + 4.0, + -2.0, + 13, + 16, + 1148846080, + 0 + ) + NetworkStartSynchronisedScene(currentScene) + + idleTimer = 8 + end +end) + +function playImpartial() + local rot = CURRENT_CHAIR_DATA.rotation + + if SELECTED_CHAIR_ID == 4 then + rot = rot + vector3(0.0, 0.0, 90.0) + elseif SELECTED_CHAIR_ID == 3 then + rot = rot + vector3(0.0, 0.0, -180.0) + elseif SELECTED_CHAIR_ID == 2 then + rot = rot + vector3(0.0, 0.0, -90.0) + elseif SELECTED_CHAIR_ID == 1 then + SELECTED_CHAIR_ID = 1 + rot = rot + vector3(0.0, 0.0, -90.0) + end + + local sex = 0 + local L = string.format('anim_casino_b@amb@casino@games@roulette@ped_male@seat_%s@regular@0%sa@reacts@v01', SELECTED_CHAIR_ID, SELECTED_CHAIR_ID) + + if GetEntityModel(PlayerPedId()) == GetHashKey('mp_f_freemode_01') then + sex = 1 + end + + if sex == 1 then + local L = string.format('anim_casino_b@amb@casino@games@roulette@ped_female@seat_%s@regular@0%sa@reacts@v01', SELECTED_CHAIR_ID, SELECTED_CHAIR_ID) + end + + RequestAnimDict(L) + while not HasAnimDictLoaded(L) do + Wait(1) + end + + if CURRENT_CHAIR_DATA ~= nil then + local currentScene = NetworkCreateSynchronisedScene(CURRENT_CHAIR_DATA.position, rot, 2, 1, 0, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene( + PlayerPedId(), + currentScene, + L, + ({'reaction_impartial_var01', 'reaction_impartial_var02', 'reaction_impartial_var03'})[math.random(1, 3)], + 4.0, + -2.0, + 13, + 16, + 1148846080, + 0 + ) + NetworkStartSynchronisedScene(currentScene) + + idleTimer = 8 + end +end + +function playRulettIdle() + local rot = CURRENT_CHAIR_DATA.rotation + + if SELECTED_CHAIR_ID == 4 then + rot = rot + vector3(0.0, 0.0, 90.0) + elseif SELECTED_CHAIR_ID == 3 then + rot = rot + vector3(0.0, 0.0, -180.0) + elseif SELECTED_CHAIR_ID == 2 then + rot = rot + vector3(0.0, 0.0, -90.0) + elseif SELECTED_CHAIR_ID == 1 then + SELECTED_CHAIR_ID = 1 + rot = rot + vector3(0.0, 0.0, -90.0) + end + + local sex = 0 + local L = string.format('anim_casino_b@amb@casino@games@roulette@ped_male@seat_%s@regular@0%sa@idles', SELECTED_CHAIR_ID, SELECTED_CHAIR_ID) + + if GetEntityModel(PlayerPedId()) == GetHashKey('mp_f_freemode_01') then + sex = 1 + end + + if sex == 1 then + local L = string.format('anim_casino_b@amb@casino@games@roulette@ped_female@seat_%s@regular@0%sa@idles', SELECTED_CHAIR_ID, SELECTED_CHAIR_ID) + end + + RequestAnimDict(L) + while not HasAnimDictLoaded(L) do + Wait(1) + end + + if CURRENT_CHAIR_DATA ~= nil then + local currentScene = NetworkCreateSynchronisedScene(CURRENT_CHAIR_DATA.position, rot, 2, 1, 0, 1065353216, 0, 1065353216) + NetworkAddPedToSynchronisedScene(PlayerPedId(), currentScene, L, ({'idle_a', 'idle_b', 'idle_c', 'idle_d'})[math.random(1, 4)], 1.0, -2.0, 13, 16, 1148846080, 0) + NetworkStartSynchronisedScene(currentScene) + end +end + +function addRandomClothes(ped) + local r = math.random(1, 5) + + if r == 1 then + SetPedComponentVariation(ped, 0, 4, 0, 0) + SetPedComponentVariation(ped, 1, 0, 0, 0) + SetPedComponentVariation(ped, 2, 4, 0, 0) + SetPedComponentVariation(ped, 3, 2, 1, 0) + SetPedComponentVariation(ped, 4, 1, 0, 0) + SetPedComponentVariation(ped, 6, 1, 0, 0) + SetPedComponentVariation(ped, 7, 1, 0, 0) + SetPedComponentVariation(ped, 8, 2, 0, 0) + SetPedComponentVariation(ped, 10, 0, 0, 0) + SetPedComponentVariation(ped, 11, 0, 0, 0) + SetPedPropIndex(ped, 1, 0, 0, false) + elseif r == 2 then + SetPedComponentVariation(ped, 0, 3, 1, 0) + SetPedComponentVariation(ped, 1, 0, 0, 0) + SetPedComponentVariation(ped, 2, 3, 1, 0) + SetPedComponentVariation(ped, 3, 1, 1, 0) + SetPedComponentVariation(ped, 4, 1, 0, 0) + SetPedComponentVariation(ped, 6, 1, 0, 0) + SetPedComponentVariation(ped, 7, 2, 0, 0) + SetPedComponentVariation(ped, 8, 1, 0, 0) + SetPedComponentVariation(ped, 10, 0, 0, 0) + SetPedComponentVariation(ped, 11, 0, 0, 0) + elseif r == 3 then + SetPedComponentVariation(ped, 0, 3, 0, 0) + SetPedComponentVariation(ped, 1, 0, 0, 0) + SetPedComponentVariation(ped, 2, 3, 0, 0) + SetPedComponentVariation(ped, 3, 0, 1, 0) + SetPedComponentVariation(ped, 4, 1, 0, 0) + SetPedComponentVariation(ped, 6, 1, 0, 0) + SetPedComponentVariation(ped, 7, 1, 0, 0) + SetPedComponentVariation(ped, 8, 0, 0, 0) + SetPedComponentVariation(ped, 10, 0, 0, 0) + SetPedComponentVariation(ped, 11, 0, 0, 0) + SetPedPropIndex(ped, 1, 0, 0, false) + elseif r == 4 then + SetPedComponentVariation(ped, 0, 2, 1, 0) + SetPedComponentVariation(ped, 1, 0, 0, 0) + SetPedComponentVariation(ped, 2, 2, 1, 0) + SetPedComponentVariation(ped, 3, 3, 3, 0) + SetPedComponentVariation(ped, 4, 1, 0, 0) + SetPedComponentVariation(ped, 6, 1, 0, 0) + SetPedComponentVariation(ped, 7, 2, 0, 0) + SetPedComponentVariation(ped, 8, 3, 0, 0) + SetPedComponentVariation(ped, 10, 0, 0, 0) + SetPedComponentVariation(ped, 11, 0, 0, 0) + end +end diff --git a/resources/[qb]/[qb_casino]/casino-roulette/config.lua b/resources/[qb]/[qb_casino]/casino-roulette/config.lua new file mode 100644 index 0000000..541ec9d --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-roulette/config.lua @@ -0,0 +1,304 @@ +Config = {} +Config.TranslationSelected = 'en' + +Config.Debug = false -- enable debug messages in sv/cl console +Config.TestTicker = nil -- need for testing the numbers, you can change the fix roulette tick by /rultick [1-38] + +Config.allowCustomBet = false -- allow playes to enter a custom bet + +Config.RouletteStart = 40 -- how many seconds to start the rulett after you sit down + + +Config.RulettTables = { + -- you can implement tables easily or delete them. + [0] = { + position = vector3(1001.53, 56.479, 68.433), --Test Loc + rot = 102.133, + minBet = 10, + maxBet = 1000 + }, + [1] = { + position = vector3(997.595, 55.573, 68.433), + rot = 280.612, + minBet = 10, + maxBet = 1000 + }, + -- [3] = { + -- position = vector3(984.6, 56.603, 70.238), + -- rot = 162.406, + -- minBet = 250, + -- maxBet = 1000 + -- }, + -- [4] = { + -- position = vector3(986.43, 48.482, 70.238), + -- rot = 42.96, + -- minBet = 250, + -- maxBet = 1000 + -- }, + -- [5] = { + -- position = vector3(982.953, 52.36, 70.238), + -- rot = 103.648, + -- minBet = 250, + -- maxBet = 1000 + -- } +} + +Config.ChairIds = { + ['Chair_Base_01'] = 1, + ['Chair_Base_02'] = 2, + ['Chair_Base_03'] = 3, + ['Chair_Base_04'] = 4 +} + +Config.rouletteSzamok = { + [1] = '00', + [2] = '27', + [3] = '10', + [4] = '25', + [5] = '29', + [6] = '12', + [7] = '8', + [8] = '19', + [9] = '31', + [10] = '18', + [11] = '6', + [12] = '21', + [13] = '33', + [14] = '16', + [15] = '4', + [16] = '23', + [17] = '35', + [18] = '14', + [19] = '2', + [20] = '0', + [21] = '28', + [22] = '9', + [23] = '26', + [24] = '30', + [25] = '11', + [26] = '7', + [27] = '20', + [28] = '32', + [29] = '17', + [30] = '5', + [31] = '22', + [32] = '34', + [33] = '15', + [34] = '3', + [35] = '24', + [36] = '36', + [37] = '13', + [38] = '1' +} + +Config.DebugMsg = function(msg) + if Config.Debug then + print(msg) + end +end + +RULETT_NUMBERS = {} +RULETT_NUMBERS.Pirosak = { + ['1'] = true, + ['3'] = true, + ['5'] = true, + ['7'] = true, + ['9'] = true, + ['12'] = true, + ['14'] = true, + ['16'] = true, + ['18'] = true, + ['19'] = true, + ['21'] = true, + ['23'] = true, + ['25'] = true, + ['27'] = true, + ['30'] = true, + ['32'] = true, + ['34'] = true, + ['36'] = true +} +RULETT_NUMBERS.Feketek = { + ['2'] = true, + ['4'] = true, + ['6'] = true, + ['8'] = true, + ['10'] = true, + ['11'] = true, + ['13'] = true, + ['15'] = true, + ['17'] = true, + ['20'] = true, + ['22'] = true, + ['24'] = true, + ['26'] = true, + ['28'] = true, + ['29'] = true, + ['31'] = true, + ['33'] = true, + ['35'] = true +} +RULETT_NUMBERS.Parosak = { + ['2'] = true, + ['4'] = true, + ['6'] = true, + ['8'] = true, + ['10'] = true, + ['12'] = true, + ['14'] = true, + ['16'] = true, + ['18'] = true, + ['20'] = true, + ['22'] = true, + ['24'] = true, + ['26'] = true, + ['28'] = true, + ['30'] = true, + ['32'] = true, + ['34'] = true, + ['36'] = true +} +RULETT_NUMBERS.Paratlanok = { + ['1'] = true, + ['3'] = true, + ['5'] = true, + ['7'] = true, + ['9'] = true, + ['11'] = true, + ['13'] = true, + ['15'] = true, + ['17'] = true, + ['19'] = true, + ['21'] = true, + ['23'] = true, + ['25'] = true, + ['27'] = true, + ['29'] = true, + ['31'] = true, + ['33'] = true, + ['35'] = true +} +RULETT_NUMBERS.to18 = { + ['1'] = true, + ['2'] = true, + ['3'] = true, + ['4'] = true, + ['5'] = true, + ['6'] = true, + ['7'] = true, + ['8'] = true, + ['9'] = true, + ['10'] = true, + ['11'] = true, + ['12'] = true, + ['13'] = true, + ['14'] = true, + ['15'] = true, + ['16'] = true, + ['17'] = true, + ['18'] = true +} +RULETT_NUMBERS.to36 = { + ['19'] = true, + ['20'] = true, + ['21'] = true, + ['22'] = true, + ['23'] = true, + ['24'] = true, + ['25'] = true, + ['26'] = true, + ['27'] = true, + ['28'] = true, + ['29'] = true, + ['30'] = true, + ['31'] = true, + ['32'] = true, + ['33'] = true, + ['34'] = true, + ['35'] = true, + ['36'] = true +} +RULETT_NUMBERS.st12 = { + ['1'] = true, + ['2'] = true, + ['3'] = true, + ['4'] = true, + ['5'] = true, + ['6'] = true, + ['7'] = true, + ['8'] = true, + ['9'] = true, + ['10'] = true, + ['11'] = true, + ['12'] = true +} +RULETT_NUMBERS.sn12 = { + ['13'] = true, + ['14'] = true, + ['15'] = true, + ['16'] = true, + ['17'] = true, + ['18'] = true, + ['19'] = true, + ['20'] = true, + ['21'] = true, + ['22'] = true, + ['23'] = true, + ['24'] = true +} +RULETT_NUMBERS.rd12 = { + ['25'] = true, + ['26'] = true, + ['27'] = true, + ['28'] = true, + ['29'] = true, + ['30'] = true, + ['31'] = true, + ['32'] = true, + ['33'] = true, + ['34'] = true, + ['35'] = true, + ['36'] = true +} +RULETT_NUMBERS.ket_to_1 = { + ['1'] = true, + ['4'] = true, + ['7'] = true, + ['10'] = true, + ['13'] = true, + ['16'] = true, + ['19'] = true, + ['22'] = true, + ['25'] = true, + ['28'] = true, + ['31'] = true, + ['34'] = true +} +RULETT_NUMBERS.ket_to_2 = { + ['2'] = true, + ['5'] = true, + ['8'] = true, + ['11'] = true, + ['14'] = true, + ['17'] = true, + ['20'] = true, + ['23'] = true, + ['26'] = true, + ['29'] = true, + ['32'] = true, + ['35'] = true +} +RULETT_NUMBERS.ket_to_3 = { + ['3'] = true, + ['6'] = true, + ['9'] = true, + ['12'] = true, + ['15'] = true, + ['18'] = true, + ['21'] = true, + ['24'] = true, + ['27'] = true, + ['30'] = true, + ['33'] = true, + ['36'] = true +} diff --git a/resources/[qb]/[qb_casino]/casino-roulette/fxmanifest.lua b/resources/[qb]/[qb_casino]/casino-roulette/fxmanifest.lua new file mode 100644 index 0000000..8330866 --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-roulette/fxmanifest.lua @@ -0,0 +1,23 @@ +version '1.0.2' +author 'freamee' +decription 'Aquiver rulett' + + +client_scripts { + 'config.lua', + '@PolyZone/client.lua', + '@PolyZone/BoxZone.lua', + '@PolyZone/EntityZone.lua', + '@PolyZone/CircleZone.lua', + '@PolyZone/ComboZone.lua', + 'client/cl_main.lua' +} + +server_scripts { + 'config.lua', + -- 'translations.lua', + 'server/sv_main.lua' +} + +game 'gta5' +fx_version 'cerulean' diff --git a/resources/[qb]/[qb_casino]/casino-roulette/server/sv_main.lua b/resources/[qb]/[qb_casino]/casino-roulette/server/sv_main.lua new file mode 100644 index 0000000..0dd65e2 --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-roulette/server/sv_main.lua @@ -0,0 +1,335 @@ + +local QBCore = exports['qb-core']:GetCoreObject() + +local aktivRulettek = {} + + +function getPlayerChips(source) + -- local Player = QBCore.Functions.GetPlayer(source) + -- local Chips = Player.Functions.GetItemByName("casino_redchip") + -- if Chips ~= nil then + -- if Chips.amount >= 10 then + -- return Chips.amount + -- else end + -- else end + + local Player = QBCore.Functions.GetPlayer(source) + local retval = 0 + local Item = Player.Functions.GetItemByName('casino_redchip') + if Item then + retval = Item.amount + end + return retval +end + +function giveChips(source, amount) + local Player = QBCore.Functions.GetPlayer(source) + if Player.Functions.AddItem("casino_redchip", amount, nil, nil, false, GetCurrentResourceName(), "", "", "") then + TriggerClientEvent('inventory:client:ItemBox', source, QBCore.Shared.Items['casino_redchip'], "add") + TriggerClientEvent('QBCore:Notify', source, "+ "..amount.." Chips") + end +end + +function removeChips(source, amount) + local Player = QBCore.Functions.GetPlayer(source) + if Player.Functions.RemoveItem("casino_redchip", amount) then + TriggerClientEvent('inventory:client:ItemBox', source, QBCore.Shared.Items['casino_redchip'], "remove") + TriggerClientEvent('QBCore:Notify', source, "- "..amount.." Chips") + end +end + + +local ItemList = { + ["casino_redchip"] = 1 +} +QBCore.Functions.CreateCallback('roulette:server:ChipsAmount', function(source, cb) + local retval = 0 + local Player = QBCore.Functions.GetPlayer(source) + if Player.PlayerData.items ~= nil and next(Player.PlayerData.items) ~= nil then + for k, v in pairs(Player.PlayerData.items) do + if Player.PlayerData.items[k] ~= nil then + if ItemList[Player.PlayerData.items[k].name] ~= nil then + retval = retval + (ItemList[Player.PlayerData.items[k].name] * Player.PlayerData.items[k].amount) + end + end + end + end + cb(retval) +end) +--//////////////////////////--//////////////////////////--////////////////////////// + +function isPlayerExist(source) + if GetPlayerName(source) ~= nil then + return true + else + return false + end +end + +RegisterNetEvent('server_remote:rulett:taskSitDown',function(rulettIndex, chairData) + local source = source + local chairId = chairData.chairId + + if aktivRulettek[rulettIndex] ~= nil then + if aktivRulettek[rulettIndex].chairsUsed[chairId] ~= nil then + return TriggerClientEvent('QBCore:Notify', source, 'This chair is occupied.','error') + else + TriggerClientEvent('client_callback:rulett:taskSitDown', source, rulettIndex, chairData) + end + else + TriggerClientEvent('client_callback:rulett:taskSitDown', source, rulettIndex, chairData) + end +end) + +RegisterNetEvent('casino:taskStartRoulette',function(rulettIndex, chairId) + local source = source + if aktivRulettek[rulettIndex] == nil then + aktivRulettek[rulettIndex] = { + statusz = false, + ido = Config.RouletteStart, + bets = {}, + chairsUsed = {} + } + + Config.DebugMsg(string.format('created rulett on serverside. table: %s', rulettIndex)) + end + if aktivRulettek[rulettIndex].chairsUsed[chairId] == nil then + aktivRulettek[rulettIndex].chairsUsed[chairId] = source + TriggerClientEvent('client:casino:openRulett', source, rulettIndex) + else + TriggerClientEvent('QBCore:Notify', source, 'This chair is occupied.','error') + end +end) + +function countTablePlayers(rulettIndex) + local count = 0 + if aktivRulettek[rulettIndex] ~= nil then + for chairId, _ in pairs(aktivRulettek[rulettIndex].chairsUsed) do + count = count + 1 + end + return count + else + return count + end +end + +RegisterNetEvent('casino:rulett:notUsing') +AddEventHandler('casino:rulett:notUsing',function(rulettIndex) + local source = source + if aktivRulettek[rulettIndex] ~= nil then + for chairId, src in pairs(aktivRulettek[rulettIndex].chairsUsed) do + if src == source then + aktivRulettek[rulettIndex].chairsUsed[chairId] = nil + end + end + end +end) + +AddEventHandler('playerDropped',function(reason) + local source = source + for rulettIndex, v in pairs(aktivRulettek) do + for chairId, src in pairs(v.chairsUsed) do + if src == source then + aktivRulettek[rulettIndex].chairsUsed[chairId] = nil + end + end + end +end) + +Citizen.CreateThread( + function() + while true do + Citizen.Wait(1000) + + for rulettIndex, v in pairs(aktivRulettek) do + if v.statusz == false then + if v.ido > 0 then + aktivRulettek[rulettIndex].ido = v.ido - 1 + TriggerClientEvent('client:rulett:updateStatusz', -1, rulettIndex, v.ido, v.statusz) + end + + if v.ido < 1 then + local randomSpinNumber = math.random(1, 38) -- do not modify this you idiot + if Config.TestTicker ~= nil then + randomSpinNumber = Config.TestTicker + end + local WinningBetIndex = Config.rouletteSzamok[randomSpinNumber] + + Config.DebugMsg(string.format('Rulett randomSpinNumber: %s, which is number: %s', randomSpinNumber, WinningBetIndex)) + + aktivRulettek[rulettIndex].statusz = true + aktivRulettek[rulettIndex].WinningBetIndex = WinningBetIndex + TriggerClientEvent('client:rulett:updateStatusz', -1, rulettIndex, v.ido, v.statusz) + + Citizen.CreateThread( + function() + Config.DebugMsg('time remaining 0, starting the spin events.') + TriggerClientEvent('casino:rulett:startSpin', -1, rulettIndex, randomSpinNumber) + Citizen.Wait(15500) + + if #v.bets > 0 then + CheckWinners(v.bets, aktivRulettek[rulettIndex].WinningBetIndex) + aktivRulettek[rulettIndex].statusz = false + aktivRulettek[rulettIndex].ido = Config.RouletteStart + aktivRulettek[rulettIndex].WinningBetIndex = nil + aktivRulettek[rulettIndex].bets = {} -- reset the bets on the table, very importante + TriggerClientEvent('client:rulett:updateTableBets', -1, rulettIndex, aktivRulettek[rulettIndex].bets) + else + if countTablePlayers(rulettIndex) < 1 then + aktivRulettek[rulettIndex] = nil -- deleting the table from srv + Config.DebugMsg(string.format('Rulett table id %s, stopped because no one using it.', rulettIndex)) + TriggerClientEvent('client:rulett:updateStatusz', -1, rulettIndex, nil, nil) + else + aktivRulettek[rulettIndex].statusz = false + aktivRulettek[rulettIndex].ido = Config.RouletteStart + aktivRulettek[rulettIndex].WinningBetIndex = nil + aktivRulettek[rulettIndex].bets = {} -- reset the bets on the table, very importante + TriggerClientEvent('client:rulett:updateTableBets', -1, rulettIndex, aktivRulettek[rulettIndex].bets) + end + end + end + ) + end + end + end + end + end +) + +function CheckWinners(bets, WinningBetIndex) + local playersWon = {} + local playersLoss = {} + + for i = 1, #bets, 1 do + local betData = bets[i] + + local targetSrc = betData.playerSrc + local PLAYER_HANDLE = isPlayerExist(targetSrc) + if PLAYER_HANDLE then + betData.betId = tostring(betData.betId) + if (WinningBetIndex == '0' and betData.betId == '37') or (WinningBetIndex == '00' and betData.betId == '38') then -- dbl zero, and zero + giveWinningChips(targetSrc, betData.betAmount, 35) + playersWon[targetSrc] = true + if playersLoss[targetSrc] then + playersWon[targetSrc] = nil + end + elseif + (betData.betId == '39' and RULETT_NUMBERS.Pirosak[WinningBetIndex]) or (betData.betId == '40' and RULETT_NUMBERS.Feketek[WinningBetIndex]) or + (betData.betId == '41' and RULETT_NUMBERS.Parosak[WinningBetIndex]) or + (betData.betId == '42' and RULETT_NUMBERS.Paratlanok[WinningBetIndex]) or + (betData.betId == '43' and RULETT_NUMBERS.to18[WinningBetIndex]) or + (betData.betId == '44' and RULETT_NUMBERS.to36[WinningBetIndex]) + then + giveWinningChips(targetSrc, betData.betAmount, 2) + playersWon[targetSrc] = true + if playersLoss[targetSrc] then + playersWon[targetSrc] = nil + end + elseif betData.betId <= '36' and WinningBetIndex == betData.betId then -- the numbers + giveWinningChips(targetSrc, betData.betAmount, 35) + playersWon[targetSrc] = true + if playersLoss[targetSrc] then + playersWon[targetSrc] = nil + end + elseif + (betData.betId == '45' and RULETT_NUMBERS.st12[WinningBetIndex]) or (betData.betId == '46' and RULETT_NUMBERS.sn12[WinningBetIndex]) or + (betData.betId == '47' and RULETT_NUMBERS.rd12[WinningBetIndex]) or + (betData.betId == '48' and RULETT_NUMBERS.ket_to_1[WinningBetIndex]) or + (betData.betId == '49' and RULETT_NUMBERS.ket_to_2[WinningBetIndex]) or + (betData.betId == '50' and RULETT_NUMBERS.ket_to_3[WinningBetIndex]) + then + giveWinningChips(targetSrc, betData.betAmount, 3) + playersWon[targetSrc] = true + + if playersLoss[targetSrc] then + playersWon[targetSrc] = nil + end + else -- LOSS + if playersWon[targetSrc] == nil then + playersLoss[targetSrc] = true + else + playersLoss[targetSrc] = nil + end + end + end + end + + for targetSrc, _ in pairs(playersLoss) do + local chairId = getPlayerTableSeat(targetSrc) + if chairId ~= nil then + TriggerClientEvent('client:rulett:playLossAnim', targetSrc, chairId) + TriggerClientEvent('QBCore:Notify', targetSrc, 'You Lost... Better luck next time', "error") + end + end + + for targetSrc, _ in pairs(playersWon) do + local chairId = getPlayerTableSeat(targetSrc) + if chairId ~= nil then + TriggerClientEvent('client:rulett:playWinAnim', targetSrc, chairId) + end + end +end + +function giveWinningChips(source, amount, szorzo) + amount = math.floor(amount * szorzo) + if amount > 0 then + giveChips(source, amount) + end +end + +RegisterNetEvent('casino:taskBetRulett',function(rulettIndex, betId, betAmount) + local src = source + if aktivRulettek[rulettIndex] ~= nil then + if aktivRulettek[rulettIndex].statusz then + return TriggerClientEvent('QBCore:Notify', src, 'The game started, you can not bet at the moment.', "error") + end + local chipsAmount = getPlayerChips(src) + if chipsAmount ~= nil then + if chipsAmount >= betAmount then + removeChips(src, betAmount) + -- TriggerClientEvent('QBCore:Notify', src, betAmount..' chips bet on ['..betId..']') + Config.DebugMsg(string.format('player %s betted %s chips on betId: %s', GetPlayerName(src), betAmount, betId)) + local exist = false + for i = 1, #aktivRulettek[rulettIndex].bets, 1 do + local d = aktivRulettek[rulettIndex].bets[i] + if d.betId == betId and d.playerSrc == src then + exist = true + aktivRulettek[rulettIndex].bets[i].betAmount = aktivRulettek[rulettIndex].bets[i].betAmount + betAmount + end + end + if not exist then + table.insert( + aktivRulettek[rulettIndex].bets, + { + betId = betId, + playerSrc = src, + betAmount = betAmount + } + ) + end + TriggerClientEvent('client:rulett:updateTableBets', -1, rulettIndex, aktivRulettek[rulettIndex].bets) + local chairId = getPlayerTableSeat(src) + if chairId ~= nil then + Config.DebugMsg(string.format('%s chair betanim play', chairId)) + TriggerClientEvent('client:rulett:playBetAnim', src, chairId) + end + else + TriggerClientEvent('QBCore:Notify', src, 'You do not have enough Casino chips to place bet.', "error") + end + else + TriggerClientEvent('QBCore:Notify', src, 'You dont have any caino chips', 'error') + end + else + TriggerClientEvent('QBCore:Notify', src, 'error', 'An error occurred on a non-existent roulette table server side?') + end +end) + +function getPlayerTableSeat(source) + for rulettIndex, v in pairs(aktivRulettek) do + for chairId, src in pairs(v.chairsUsed) do + if src == source then + return chairId + end + end + end +end diff --git a/resources/[qb]/[qb_casino]/casino-slots/client.lua b/resources/[qb]/[qb_casino]/casino-slots/client.lua new file mode 100644 index 0000000..d9a4c00 --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-slots/client.lua @@ -0,0 +1,489 @@ +local QBCore = exports['qb-core']:GetCoreObject() +local Slot +local SlotCoords +local ClosestSlot +local ClosestSlotCoord = vector3(0, 0, 0) +local ClosestSlotRotation +local NearbySlot +local EnteredSlot +local IsSpinning +local ReelLocation1 +local ReelLocation2 +local ReelLocation3 +local ClosestSlotForwardX +local ClosestSlotForwardY +local ShouldDrawScaleForm = false +local Scaleform +local ClosestSlotModel +local AnimDict = 'anim_casino_a@amb@casino@games@slots@male' +local Sounds = { + function() local SoundId = GetSoundId() PlaySoundFromCoord(SoundId, 'no_win', ClosestSlotCoord, SlotReferences[ClosestSlotModel].sound, false, 20, false) ReleaseSoundId(SoundId) end, + function() local SoundId = GetSoundId() PlaySoundFromCoord(SoundId, 'small_win', ClosestSlotCoord, SlotReferences[ClosestSlotModel].sound, false, 20, false) ReleaseSoundId(SoundId) end, + function() local SoundId = GetSoundId() PlaySoundFromCoord(SoundId, 'big_win', ClosestSlotCoord, SlotReferences[ClosestSlotModel].sound, false, 20, false) ReleaseSoundId(SoundId) end, + function() local SoundId = GetSoundId() PlaySoundFromCoord(SoundId, 'jackpot', ClosestSlotCoord, SlotReferences[ClosestSlotModel].sound, false, 20, false) ReleaseSoundId(SoundId) end, + function() local SoundId = GetSoundId() PlaySoundFromCoord(SoundId, 'place_bet', ClosestSlotCoord, SlotReferences[ClosestSlotModel].sound, false, 20, false) ReleaseSoundId(SoundId) end, + function() local SoundId = GetSoundId() PlaySoundFromCoord(SoundId, 'place_max_bet', ClosestSlotCoord, SlotReferences[ClosestSlotModel].sound, false, 20, false) ReleaseSoundId(SoundId) end, + function() local SoundId = GetSoundId() PlaySoundFromCoord(SoundId, 'spinning', ClosestSlotCoord, SlotReferences[ClosestSlotModel].sound, false, 20, false) ReleaseSoundId(SoundId) end, + function() local SoundId = GetSoundId() PlaySoundFromCoord(SoundId, 'start_spin', ClosestSlotCoord, SlotReferences[ClosestSlotModel].sound, false, 20, false) ReleaseSoundId(SoundId) end, + function() local SoundId = GetSoundId() PlaySoundFromCoord(SoundId, 'wheel_stop_clunk', ClosestSlotCoord, SlotReferences[ClosestSlotModel].sound, false, 20, false) ReleaseSoundId(SoundId) end, + function() local SoundId = GetSoundId() PlaySoundFromCoord(SoundId, 'wheel_stop_on_prize', ClosestSlotCoord, SlotReferences[ClosestSlotModel].sound, false, 20, false) ReleaseSoundId(SoundId) end, + function() local SoundId = GetSoundId() PlaySoundFromCoord(SoundId, 'welcome_stinger', ClosestSlotCoord, SlotReferences[ClosestSlotModel].sound, false, 20, false) ReleaseSoundId(SoundId) end, + function() local SoundId = GetSoundId() PlaySoundFromCoord(SoundId, 'spin_wheel', ClosestSlotCoord, SlotReferences[ClosestSlotModel].sound, false, 20, false) ReleaseSoundId(SoundId) end, + function() local SoundId = GetSoundId() PlaySoundFromCoord(SoundId, 'spin_wheel_win', ClosestSlotCoord, SlotReferences[ClosestSlotModel].sound, false, 20, false) ReleaseSoundId(SoundId) end +} +local Slots = { + 2362925439, + 2775323096, + 3863977906, + 654385216, + 161343630, + 1096374064, + 207578973, + 3807744938 +} +local RandomEnter = { + 'enter_left', + 'enter_right', + 'enter_left_short', + 'enter_right_short' +} +local RandomLeave = { + 'exit_left', + 'exit_right' +} +local RandomIdle = { + 'base_idle_a', + 'base_idle_b', + 'base_idle_c', + 'base_idle_d', + 'base_idle_e', + 'base_idle_f' +} +local RandomSpin = { + 'press_spin_a', + 'press_spin_b', + 'pull_spin_a', + 'pull_spin_b' +} +local RandomSpinningIdle = { + 'spinning_a', + 'spinning_b', + 'spinning_c' +} +local RandomWin = { + 'win_a', + 'win_b', + 'win_c', + 'win_d', + 'win_e', + 'win_f', + 'win_g', + 'win_spinning_wheel' +} +local RandomLose = { + 'lose_a', + 'lose_b', + 'lose_c', + 'lose_d', + 'lose_e', + 'lose_f', + 'lose_cruel_a', + 'lose_cruel_b' +} +local RandomBigWin = { + 'win_big_a', + 'win_big_b', + 'win_big_c' +} +local RandomEnterMessage = { + 'Daring today?', + 'Spin to win', + 'You will lose money!', + 'Feelin lucky punk?!', + 'Test your might', + 'You have coins?' +} +local ChosenBetAmount = 1 + +local function DrawText3D(x, y, z, text) + SetTextScale(0.35, 0.35) + SetTextFont(4) + SetTextProportional(1) + SetTextColour(255, 255, 255, 215) + SetTextEntry("STRING") + SetTextCentre(true) + AddTextComponentString(text) + SetDrawOrigin(x,y,z, 0) + DrawText(0.0, 0.0) + local factor = (string.len(text)) / 370 + DrawRect(0.0, 0.0+0.0125, 0.017+ factor, 0.03, 0, 0, 0, 75) + ClearDrawOrigin() +end + +local function LoadAnimDict(dict) while not HasAnimDictLoaded(dict) do RequestAnimDict(dict) Wait(0) end end + +local function StartIdleScene(CurrentAnimation) + Wait(GetAnimDuration(AnimDict, CurrentAnimation) * 800) + local IdleScene = NetworkCreateSynchronisedScene(ClosestSlotCoord, ClosestSlotRotation, 2, 2, 0, 1.0, 0, 1.0) + LoadAnimDict(AnimDict) + local RandomAnimName = RandomIdle[math.random(1, #RandomIdle)] + NetworkAddPedToSynchronisedScene(PlayerPedId(), IdleScene, AnimDict, RandomAnimName, 2.0, -1.5, 13, 16, 2.0, 0) + NetworkStartSynchronisedScene(IdleScene) +end + +local function CreateNamedRenderTargetForModel(name, model) + local handle = 0 + if not IsNamedRendertargetRegistered(name) then + RegisterNamedRendertarget(name, 0) + end + if not IsNamedRendertargetLinked(model) then + LinkNamedRendertarget(model) + end + if IsNamedRendertargetRegistered(name) then + handle = GetNamedRendertargetRenderId(name) + end + return handle +end + +local function CallScaleformMethod(method, ...) + local t + local args = { ... } + BeginScaleformMovieMethod(Scaleform, method) + for _, v in ipairs(args) do + t = type(v) + if t == 'string' then + PushScaleformMovieMethodParameterString(v) + elseif t == 'number' then + if string.match(tostring(v), "%.") then + PushScaleformMovieFunctionParameterFloat(v) + else + PushScaleformMovieFunctionParameterInt(v) + end + elseif t == 'boolean' then + PushScaleformMovieMethodParameterBool(v) + end + end + EndScaleformMovieMethod() +end + +local function SetupScaleform() + CreateThread(function() + Scaleform = RequestScaleformMovie('SLOT_MACHINE') + while not HasScaleformMovieLoaded(Scaleform) do Wait(0) end + if SlotReferences[ClosestSlotModel].theme then CallScaleformMethod('SET_THEME', SlotReferences[ClosestSlotModel].theme) else CallScaleformMethod('SET_THEME') end + local model = ClosestSlotModel + local handle = CreateNamedRenderTargetForModel("machine_"..SlotReferences[ClosestSlotModel].scriptrt, model) + while ShouldDrawScaleForm do + N_0x32f34ff7f617643b(Scaleform, 1) + SetTextRenderId(handle) -- Sets the render target to the handle we grab above + SetScriptGfxDrawOrder(4) + SetScriptGfxDrawBehindPausemenu(true) + DrawScaleformMovie(Scaleform, 0.401, 0.09, 0.805, 0.195, 255, 255, 255, 255, 0) + SetTextRenderId(GetDefaultScriptRendertargetRenderId()) -- Resets the render target + Wait(0) + end + end) +end + +local function SlotMachineHandler() + local LeverScene = 0 + local IdleScene = NetworkCreateSynchronisedScene(ClosestSlotCoord, ClosestSlotRotation, 2, 2, 0, 1.0, 0, 1.0) + LoadAnimDict(AnimDict) + local RandomAnimName = RandomIdle[math.random(1, #RandomIdle)] + NetworkAddPedToSynchronisedScene(PlayerPedId(), IdleScene, AnimDict, RandomAnimName, 2.0, -1.5, 13, 16, 2.0, 0) + NetworkStartSynchronisedScene(IdleScene) + exports['qb-core']:DrawText('Spin:
Leave:
Adjust Bet: ↑') + CreateThread(function() + while true do + QBCore.Functions.TriggerCallback('doj:server:CasinoChipsAmount', function(result) + exports['casinoUi']:DrawCasinoUi('show', "The Diamond Casino & Resort Slots

"..SlotReferences[ClosestSlotModel].name.."

Availble chips: "..math.floor(result)) + end) + if not IsSpinning then + if IsControlJustPressed(0, 202) then -- BACKSPACE + local LeaveScene = NetworkCreateSynchronisedScene(ClosestSlotCoord, ClosestSlotRotation, 2, 2, 0, 1.0, 0, 1.0) + LoadAnimDict(AnimDict) + RandomAnimName = RandomLeave[math.random(1, #RandomLeave)] + NetworkAddPedToSynchronisedScene(PlayerPedId(), LeaveScene, AnimDict, RandomAnimName, 2.0, -1.5, 13, 16, 2.0, 0) + NetworkStartSynchronisedScene(LeaveScene) + Wait(GetAnimDuration(AnimDict, RandomAnimName) * 700) + NetworkStopSynchronisedScene(LeaveScene) + EnteredSlot = false + ShouldDrawScaleForm = false + exports['qb-core']:HideText() + exports['casinoUi']:HideCasinoUi('hide') + CallScaleformMethod('SET_BET') + CallScaleformMethod('SET_LAST_WIN') + CallScaleformMethod('SET_MESSAGE', '') + TriggerServerEvent('dc-casino:slots:server:leave') + break + elseif IsControlJustPressed(0, 201) then -- ENTER + local SpinScene = NetworkCreateSynchronisedScene(ClosestSlotCoord, ClosestSlotRotation, 2, 2, 0, 1.0, 0, 1.0) + LoadAnimDict(AnimDict) + RandomAnimName = RandomSpin[math.random(1, #RandomSpin)] + NetworkAddPedToSynchronisedScene(PlayerPedId(), SpinScene, AnimDict, RandomAnimName, 2.0, -1.5, 13, 16, 1000.0, 0) + NetworkStartSynchronisedScene(SpinScene) + local AnimationDuration = GetAnimDuration(AnimDict, RandomAnimName) + if RandomAnimName == 'pull_spin_a' then + LeverScene = NetworkCreateSynchronisedScene(ClosestSlotCoord, ClosestSlotRotation, 2, 2, 0, 1.0, 0, 1.0) + N_0x45f35c0edc33b03b(LeverScene, GetEntityModel(ClosestSlot), ClosestSlotCoord, AnimDict, 'pull_spin_a_SLOTMACHINE', 2.0, -1.5, 13.0) + NetworkStartSynchronisedScene(LeverScene) + Wait(AnimationDuration * 320) + elseif RandomAnimName == 'pull_spin_b' then + LeverScene = NetworkCreateSynchronisedScene(ClosestSlotCoord, ClosestSlotRotation, 2, 2, 0, 1.0, 0, 1.0) + N_0x45f35c0edc33b03b(LeverScene, GetEntityModel(ClosestSlot), ClosestSlotCoord, AnimDict, 'pull_spin_b_SLOTMACHINE', 2.0, -1.5, 13.0) + NetworkStartSynchronisedScene(LeverScene) + Wait(AnimationDuration * 320) + end + Wait(AnimationDuration * 180) + Sounds[8]() + TriggerServerEvent('dc-casino:slots:server:spin', ChosenBetAmount) + Wait(AnimationDuration * 500) + local SpinningScene = NetworkCreateSynchronisedScene(ClosestSlotCoord, ClosestSlotRotation, 2, 2, 0, 1.0, 0, 1.0) + LoadAnimDict(AnimDict) + NetworkAddPedToSynchronisedScene(PlayerPedId(), SpinningScene, AnimDict, RandomSpinningIdle[math.random(1, #RandomSpinningIdle)], 2.0, -1.5, 13, 16, 2.0, 0) + NetworkStartSynchronisedScene(SpinningScene) + NetworkStopSynchronisedScene(LeverScene) --- Has to be stopped otherwise it will only work 50% of the time + FreezeEntityPosition(ClosestSlot, true) --- N_0x45f35c0edc33b03b will prevent the machine being stuck to their position for some reason? + elseif IsControlJustPressed(0, 172) then -- UP ARROW + Sounds[5]() + if not SlotReferences[ClosestSlotModel].betamounts[ChosenBetAmount + 1] then ChosenBetAmount = 1 else ChosenBetAmount = ChosenBetAmount + 1 end + local BetOneScene = NetworkCreateSynchronisedScene(ClosestSlotCoord, ClosestSlotRotation, 2, 2, 0, 1.0, 0, 1.0) + LoadAnimDict(AnimDict) + NetworkAddPedToSynchronisedScene(PlayerPedId(), BetOneScene, AnimDict, 'press_betone_a', 2.0, -1.5, 13, 16, 2.0, 0) + NetworkStartSynchronisedScene(BetOneScene) + Wait(GetAnimDuration(AnimDict, 'press_betone_a') * 200) + CallScaleformMethod('SET_BET', SlotReferences[ClosestSlotModel].betamounts[ChosenBetAmount]) + StartIdleScene('press_betone_a') + elseif IsControlJustPressed(0, 45) then -- R + Sounds[6]() + ChosenBetAmount = #SlotReferences[ClosestSlotModel].betamounts + local BetMaxScene = NetworkCreateSynchronisedScene(ClosestSlotCoord, ClosestSlotRotation, 2, 2, 0, 1.0, 0, 1.0) + LoadAnimDict(AnimDict) + NetworkAddPedToSynchronisedScene(PlayerPedId(), BetMaxScene, AnimDict, 'press_betmax_a', 2.0, -1.5, 13, 16, 2.0, 0) + NetworkStartSynchronisedScene(BetMaxScene) + Wait(GetAnimDuration(AnimDict, 'press_betmax_a') * 200) + CallScaleformMethod('SET_BET', SlotReferences[ClosestSlotModel].betamounts[ChosenBetAmount]) + StartIdleScene('press_betmax_a') + elseif IsEntityDead(PlayerPedId()) then + EnteredSlot = false + ShouldDrawScaleForm = false + exports['qb-core']:HideText() + CallScaleformMethod('SET_BET') + CallScaleformMethod('SET_MESSAGE', '') + TriggerServerEvent('dc-casino:slots:server:leave') + break + end + end + Wait(0) + end + end) +end + +CreateThread(function() + while not RequestScriptAudioBank("dlc_vinewood/casino_slot_machines_01", 0) do Wait(0) end + while not RequestScriptAudioBank("dlc_vinewood/casino_slot_machines_02", 0) do Wait(0) end + while not RequestScriptAudioBank("dlc_vinewood/casino_slot_machines_03", 0) do Wait(0) end + while true do + local PlayerCoords = GetEntityCoords(PlayerPedId()) + for i = 1, #Slots do + Slot = GetClosestObjectOfType(PlayerCoords, 1.2, Slots[i], true) + if Slot ~= 0 then + SlotCoords = GetEntityCoords(Slot) + local CurrentDistance = #(PlayerCoords - SlotCoords) + if CurrentDistance < 1.9 and CurrentDistance < #(PlayerCoords - ClosestSlotCoord) then + NearbySlot = true + ClosestSlot = Slot + ClosestSlotCoord = SlotCoords + ClosestSlotForwardX = GetEntityForwardX(ClosestSlot) + ClosestSlotForwardY = GetEntityForwardY(ClosestSlot) + ClosestSlotModel = GetEntityModel(ClosestSlot) + ClosestSlotRotation = GetEntityRotation(ClosestSlot) + ReelLocation1 = GetObjectOffsetFromCoords(ClosestSlotCoord, GetEntityHeading(ClosestSlot), -0.115, 0.047, 0.906) + ReelLocation2 = GetObjectOffsetFromCoords(ClosestSlotCoord, GetEntityHeading(ClosestSlot), 0.005, 0.047, 0.906) + ReelLocation3 = GetObjectOffsetFromCoords(ClosestSlotCoord, GetEntityHeading(ClosestSlot), 0.125, 0.047, 0.906) + end + elseif #(PlayerCoords - ClosestSlotCoord) > 1.9 then + NearbySlot = false + end + end + Wait(600) + end +end) + +-- CreateThread(function() +-- while true do +-- local WaitTime = 500 +-- if NearbySlot and not EnteredSlot then +-- WaitTime = 0 +-- -- exports['qb-core']:DrawText('Diamond Casino Slots

'..SlotReferences[ClosestSlotModel].name..'

Press [E] to sit

') +-- DrawText3D(ClosestSlotCoord.x - ClosestSlotForwardX, ClosestSlotCoord.y - ClosestSlotForwardY, ClosestSlotCoord.z + 1, "[E] - Play ~b~"..SlotReferences[ClosestSlotModel].name) +-- if IsControlJustReleased(0, 38) then + +-- QBCore.Functions.TriggerCallback('doj:server:HasCasinoMembership', function(HasItem) +-- if HasItem then +-- local netID = NetworkGetEntityIsNetworked(ClosestSlot) and NetworkGetNetworkIdFromEntity(ClosestSlot) +-- if not netID then +-- NetworkRegisterEntityAsNetworked(ClosestSlot) +-- netID = NetworkGetNetworkIdFromEntity(ClosestSlot) +-- NetworkUseHighPrecisionBlending(netID, false) +-- SetNetworkIdExistsOnAllMachines(netID, true) +-- SetNetworkIdCanMigrate(netID, true) +-- end +-- NetworkRequestControlOfEntity(ClosestSlot) +-- TriggerServerEvent('dc-casino:slots:server:enter', netID, ReelLocation1, ReelLocation2, ReelLocation3) +-- else +-- QBCore.Functions.Notify('You are not a member of the casino!', 'error', 3500) +-- end +-- end) +-- end +-- -- elseif not NearbySlot then +-- -- exports["qb-core"]:HideText() +-- end +-- Wait(WaitTime) +-- end +-- end) + + + +Citizen.CreateThread(function() + local alreadyEnteredZone = false + local text = nil + while true do + if NearbySlot and not EnteredSlot then + wait = 5 + local pedCo = GetEntityCoords(PlayerPedId()) + local dist = #(pedCo - ClosestSlotCoord) + local inZone = false + if dist <= 2.0 then + wait = 5 + inZone = true + text = 'The Diamond Casino & Resort

Slot: '..SlotReferences[ClosestSlotModel].name..'

Press E to sit' + if IsControlJustPressed(0, 38) then + local netID = NetworkGetEntityIsNetworked(ClosestSlot) and NetworkGetNetworkIdFromEntity(ClosestSlot) + if not netID then + NetworkRegisterEntityAsNetworked(ClosestSlot) + netID = NetworkGetNetworkIdFromEntity(ClosestSlot) + NetworkUseHighPrecisionBlending(netID, false) + SetNetworkIdExistsOnAllMachines(netID, true) + SetNetworkIdCanMigrate(netID, true) + end + NetworkRequestControlOfEntity(ClosestSlot) + TriggerServerEvent('dc-casino:slots:server:enter', netID, ReelLocation1, ReelLocation2, ReelLocation3) + end + else + wait = 2000 + end + if inZone and not alreadyEnteredZone then + alreadyEnteredZone = true + exports["qb-core"]:DrawText(text) + end + if not inZone and alreadyEnteredZone then + alreadyEnteredZone = false + exports["qb-core"]:HideText() + end + end + Wait(wait) + end +end) + +RegisterNetEvent('dc-casino:slots:client:enter', function() + local Ped = PlayerPedId() + exports["qb-core"]:HideText() + if GetEntityModel(Ped) == `mp_f_freemode_01` then AnimDict = 'anim_casino_a@amb@casino@games@slots@female' end + local EnterScene = NetworkCreateSynchronisedScene(ClosestSlotCoord, ClosestSlotRotation, 2, 2, 0, 1.0, 0, 1.0) + LoadAnimDict(AnimDict) + local RandomAnimName = RandomEnter[math.random(1, #RandomEnter)] + NetworkAddPedToSynchronisedScene(Ped, EnterScene, AnimDict, RandomAnimName, 2.0, -1.5, 13, 16, 2.0, 0) + NetworkStartSynchronisedScene(EnterScene) + EnteredSlot = true + ShouldDrawScaleForm = true + SetupScaleform() + Wait(GetAnimDuration(AnimDict, RandomAnimName) * 1000) + CallScaleformMethod('SET_MESSAGE', RandomEnterMessage[math.random(1, #RandomEnterMessage)]) + CallScaleformMethod('SET_BET', SlotReferences[ClosestSlotModel].betamounts[ChosenBetAmount]) + Sounds[11]() + SlotMachineHandler() +end) + +RegisterNetEvent('dc-casino:slots:client:spinreels', function(SpinTime, ReelRewards, BlurryReelID1, BlurryReelID2, BlurryReelID3, ReelID1, ReelID2, ReelID3, RewardMultiplier) + local EndTime = GetGameTimer() + SpinTime + local FirstReelStop = SpinTime * math.random(2, 4) / 10 + local SecondReelStop = SpinTime * math.random(5, 7) / 10 + local ReelReward1 = ReelRewards[1] * 22.5 + local ReelReward2 = ReelRewards[2] * 22.5 + local ReelReward3 = ReelRewards[3] * 22.5 + local SlotHeading = GetEntityHeading(ClosestSlot) + local BlurryReel1 = NetworkGetEntityFromNetworkId(BlurryReelID1) + local BlurryReel2 = NetworkGetEntityFromNetworkId(BlurryReelID2) + local BlurryReel3 = NetworkGetEntityFromNetworkId(BlurryReelID3) + local Reel1 = NetworkGetEntityFromNetworkId(ReelID1) + local Reel2 = NetworkGetEntityFromNetworkId(ReelID2) + local Reel3 = NetworkGetEntityFromNetworkId(ReelID3) + while not NetworkRequestControlOfEntity(BlurryReel1) do Wait(0) end + while not NetworkRequestControlOfEntity(BlurryReel2) do Wait(0) end + while not NetworkRequestControlOfEntity(BlurryReel3) do Wait(0) end + while not NetworkRequestControlOfEntity(Reel1) do Wait(0) end + while not NetworkRequestControlOfEntity(Reel2) do Wait(0) end + while not NetworkRequestControlOfEntity(Reel3) do Wait(0) end + + IsSpinning = true + SetEntityVisible(Reel1, false) + SetEntityVisible(Reel2, false) + SetEntityVisible(Reel3, false) + Sounds[7]() + while GetGameTimer() < EndTime do + SetEntityRotation(BlurryReel1, math.random(0, 15) * 22.5 + math.random(1, 60), 0.0, SlotHeading, 2, true) + if EndTime - GetGameTimer() > FirstReelStop then + SetEntityRotation(BlurryReel2, math.random(0, 15) * 22.5 + math.random(1, 60), 0.0, SlotHeading, 2, true) + if EndTime - GetGameTimer() < FirstReelStop + 15 then + if ReelRewards[2] == math.floor(ReelRewards[2]) then Sounds[9]() else Sounds[10]() end + DeleteObject(BlurryReel2) + SetEntityRotation(Reel2, ReelReward2, 0.0, SlotHeading, 2, true) + SetEntityVisible(Reel2, true) + end + if EndTime - GetGameTimer() > SecondReelStop then + SetEntityRotation(BlurryReel3, math.random(0, 15) * 22.5 + math.random(1, 60), 0.0, SlotHeading, 2, true) + if EndTime - GetGameTimer() < SecondReelStop + 15 then + if ReelRewards[3] == math.floor(ReelRewards[3]) then Sounds[9]() else Sounds[10]() end + DeleteObject(BlurryReel3) + SetEntityRotation(Reel3, ReelReward3, 0.0, SlotHeading, 2, true) + SetEntityVisible(Reel3, true) + end + end + end + Wait(0) + end + DeleteObject(BlurryReel1) + SetEntityRotation(Reel1, ReelReward1, 0.0, SlotHeading, 2, true) + SetEntityVisible(Reel1, true) + CallScaleformMethod('SET_LAST_WIN', SlotReferences[ClosestSlotModel].betamounts[ChosenBetAmount] * RewardMultiplier) + if ReelRewards[1] == math.floor(ReelRewards[1]) then Sounds[9]() else Sounds[10]() end + if RewardMultiplier == 0 then + Sounds[1]() + QBCore.Functions.Notify('You Lose', 'error', 3500) + -- exports['qb-core']:DrawText('You Lose) + local LoseScene = NetworkCreateSynchronisedScene(ClosestSlotCoord, ClosestSlotRotation, 2, 2, 0, 1.0, 0, 1.0) + LoadAnimDict(AnimDict) + local RandomAnim = RandomLose[math.random(1, #RandomLose)] + NetworkAddPedToSynchronisedScene(PlayerPedId(), LoseScene, AnimDict, RandomLose[math.random(1, #RandomLose)], 2.0, -1.5, 13, 16, 2.0, 0) + NetworkStartSynchronisedScene(LoseScene) + StartIdleScene(RandomAnim) + elseif RewardMultiplier > 7 then + if ReelReward1 == 5 and ReelReward2 == 5 and ReelReward3 == 5 then Sounds[4]() else Sounds[3]() end + local BigWinScene = NetworkCreateSynchronisedScene(ClosestSlotCoord, ClosestSlotRotation, 2, 2, 0, 1.0, 0, 1.0) + LoadAnimDict(AnimDict) + local RandomAnim = RandomBigWin[math.random(1, #RandomBigWin)] + NetworkAddPedToSynchronisedScene(PlayerPedId(), BigWinScene, AnimDict, RandomBigWin[math.random(1, #RandomBigWin)], 2.0, -1.5, 13, 16, 2.0, 0) + NetworkStartSynchronisedScene(BigWinScene) + StartIdleScene(RandomAnim) + else + Sounds[2]() + local WinScene = NetworkCreateSynchronisedScene(ClosestSlotCoord, ClosestSlotRotation, 2, 2, 0, 1.0, 0, 1.0) + LoadAnimDict(AnimDict) + local RandomAnim = RandomWin[math.random(1, #RandomWin)] + NetworkAddPedToSynchronisedScene(PlayerPedId(), WinScene, AnimDict, RandomAnim, 2.0, -1.5, 13, 16, 2.0, 0) + NetworkStartSynchronisedScene(WinScene) + StartIdleScene(RandomAnim) + end + IsSpinning = false +end) diff --git a/resources/[qb]/[qb_casino]/casino-slots/config.lua b/resources/[qb]/[qb_casino]/casino-slots/config.lua new file mode 100644 index 0000000..e009bba --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-slots/config.lua @@ -0,0 +1,234 @@ +SlotReferences = { + [-1932041857] = { + sound = 'dlc_vw_casino_slot_machine_ak_npc_sounds', + texture = 'CasinoUI_Slots_Angel', + name = 'Angel And The Knight', + reela = `vw_prop_casino_slot_01a_reels`, + reelb = `vw_prop_casino_slot_01b_reels`, + scriptrt = '01a', + misschance = math.random(10, 40), + betamounts = { + 50, + 100, + 150, + 250, + 500 + } + }, + [-1519644200] = { + sound = 'dlc_vw_casino_slot_machine_ir_npc_sounds', + texture = 'CasinoUI_Slots_Impotent', + name = 'Impotent Rage', + reela = `vw_prop_casino_slot_02a_reels`, + reelb = `vw_prop_casino_slot_02b_reels`, + scriptrt = '02a', + theme = 2, + misschance = math.random(10, 40), + betamounts = { + 50, + 100, + 150, + 250, + 500 + } + }, + [-430989390] = { + sound = 'dlc_vw_casino_slot_machine_rsr_npc_sounds', + texture = 'CasinoUI_Slots_Ranger', + name = 'Republican Space Rangers', + reela = `vw_prop_casino_slot_03a_reels`, + reelb = `vw_prop_casino_slot_03b_reels`, + scriptrt = '03a', + misschance = math.random(10, 40), + betamounts = { + 50, + 100, + 150, + 250, + 500 + } + }, + [654385216] = { + sound = 'dlc_vw_casino_slot_machine_fs_npc_sounds', + texture = 'CasinoUI_Slots_Fame', + name = 'Fame Or Shame', + reela = `vw_prop_casino_slot_04a_reels`, + reelb = `vw_prop_casino_slot_04b_reels`, + scriptrt = '04a', + misschance = math.random(10, 40), + betamounts = { + 50, + 100, + 150, + 250, + 500 + } + }, + [161343630] = { + sound = 'dlc_vw_casino_slot_machine_ds_npc_sounds', + texture = 'CasinoUI_Slots_Deity', + name = 'Deity Of The Sun', + reela = `vw_prop_casino_slot_05a_reels`, + reelb = `vw_prop_casino_slot_05b_reels`, + scriptrt = '05a', + theme = 5, + misschance = math.random(10, 40), + betamounts = { + 50, + 100, + 150, + 250, + 500 + } + }, + [1096374064] = { + sound = 'dlc_vw_casino_slot_machine_kd_npc_sounds', + texture = 'CasinoUI_Slots_Knife', + name = 'Twilight Knife', + reela = `vw_prop_casino_slot_06a_reels`, + reelb = `vw_prop_casino_slot_06b_reels`, + scriptrt = '06a', + theme = 6, + misschance = math.random(10, 40), + betamounts = { + 50, + 100, + 150, + 250, + 500 + } + }, + [207578973] = { + sound = 'dlc_vw_casino_slot_machine_td_npc_sounds', + texture = 'CasinoUI_Slots_Diamond', + name = 'Diamond Miner', + reela = `vw_prop_casino_slot_07a_reels`, + reelb = `vw_prop_casino_slot_07b_reels`, + scriptrt = '07a', + theme = 7, + misschance = math.random(10, 40), + betamounts = { + 50, + 100, + 150, + 250, + 500 + } + }, + [-487222358] = { + sound = 'dlc_vw_casino_slot_machine_hz_npc_sounds', + texture = 'CasinoUI_Slots_Evacuator', + name = 'Evacuator', + reela = `vw_prop_casino_slot_08a_reels`, + reelb = `vw_prop_casino_slot_08b_reels`, + scriptrt = '08a', + theme = 8, + misschance = math.random(10, 40), + betamounts = { + 50, + 100, + 150, + 250, + 500 + } + }, +} + +Rewards = { + [{0, 0, 0}] = 10.0, --- Sevens + [{0, 0, 8}] = 10.0, --- Sevens + [{0, 8, 0}] = 10.0, --- Sevens + [{8, 0, 0}] = 10.0, --- Sevens + [{0, 8, 8}] = 10.0, --- Sevens + [{8, 0, 8}] = 10.0, --- Sevens + [{8, 8, 0}] = 10.0, --- Sevens + [{8, 8, 8}] = 10.0, --- Sevens + + [{1, 1, 1}] = 1.5, --- Plums + [{1, 1, 9}] = 1.5, --- Plums + [{1, 9, 1}] = 1.5, --- Plums + [{9, 1, 1}] = 1.5, --- Plums + [{1, 1, 12}] = 1.5, --- Plums + [{1, 12, 1}] = 1.5, --- Plums + [{12, 1, 1}] = 1.5, --- Plums + [{1, 9, 9}] = 1.5, --- Plums + [{9, 1, 9}] = 1.5, --- Plums + [{9, 9, 1}] = 1.5, --- Plums + [{1, 9, 12}] = 1.5, --- Plums + [{1, 12, 9}] = 1.5, --- Plums + [{9, 1, 12}] = 1.5, --- Plums + [{9, 12, 1}] = 1.5, --- Plums + [{12, 1, 9}] = 1.5, --- Plums + [{12, 9, 1}] = 1.5, --- Plums + [{1, 12, 12}] = 1.5, --- Plums + [{12, 1, 12}] = 1.5, --- Plums + [{12, 12, 1}] = 1.5, --- Plums + [{9, 9, 9}] = 1.5, --- Plums + [{9, 9, 12}] = 1.5, --- Plums + [{9, 12, 9}] = 1.5, --- Plums + [{12, 9, 9}] = 1.5, --- Plums + [{9, 12, 12}] = 1.5, --- Plums + [{12, 9, 12}] = 1.5, --- Plums + [{12, 12, 9}] = 1.5, --- Plums + [{12, 12, 12}] = 1.5, --- Plums + + [{2, 2, 2}] = 2.5, --- Cherries + [{2, 2, 6}] = 2.5, --- Cherries + [{2, 6, 2}] = 2.5, --- Cherries + [{6, 2, 2}] = 2.5, --- Cherries + [{2, 2, 14}] = 2.5, --- Cherries + [{2, 14, 2}] = 2.5, --- Cherries + [{14, 2, 2}] = 2.5, --- Cherries + [{2, 6, 6}] = 2.5, --- Cherries + [{6, 2, 6}] = 2.5, --- Cherries + [{6, 6, 2}] = 2.5, --- Cherries + [{2, 6, 14}] = 2.5, --- Cherries + [{2, 14, 6}] = 2.5, --- Cherries + [{6, 2, 14}] = 2.5, --- Cherries + [{6, 14, 2}] = 2.5, --- Cherries + [{14, 2, 6}] = 2.5, --- Cherries + [{14, 6, 2}] = 2.5, --- Cherries + [{2, 14, 14}] = 2.5, --- Cherries + [{14, 2, 14}] = 2.5, --- Cherries + [{14, 14, 2}] = 2.5, --- Cherries + [{6, 6, 6}] = 2.5, --- Cherries + [{6, 6, 14}] = 2.5, --- Cherries + [{6, 14, 6}] = 2.5, --- Cherries + [{14, 6, 6}] = 2.5, --- Cherries + [{6, 14, 14}] = 2.5, --- Cherries + [{14, 6, 14}] = 2.5, --- Cherries + [{14, 14, 6}] = 2.5, --- Cherries + [{14, 14, 14}] = 2.5, --- Cherries + + [{3, 3, 3}] = 5.0, --- Melons + [{3, 3, 10}] = 5.0, --- Melons + [{3, 10, 3}] = 5.0, --- Melons + [{10, 3, 3}] = 5.0, --- Melons + [{3, 10, 10}] = 5.0, --- Melons + [{10, 3, 10}] = 5.0, --- Melons + [{10, 10, 3}] = 5.0, --- Melons + [{10, 10, 10}] = 5.0, --- Melons + + [{7, 7, 7}] = 7.5, --- Bells + [{7, 7, 13}] = 7.5, --- Bells + [{7, 13, 7}] = 7.5, --- Bells + [{13, 7, 7}] = 7.5, --- Bells + [{7, 13, 13}] = 7.5, --- Bells + [{13, 7, 13}] = 7.5, --- Bells + [{13, 13, 7}] = 7.5, --- Bells + [{13, 13, 13}] = 7.5, --- Bells + + [{5, 5, 5}] = 25.0, --- Jackpot +} + +--- E.g. Diamond slot | Only one diamond will give 1.0x, 2 diamonds will give 3.5x, 3 will give 9.0x. +SpecialReward = { + 1.0, + 3.5, + 9.0 +} + +UseCash = false +UseBank = false +UseItem = true +ItemName = 'casino_redchip' diff --git a/resources/[qb]/[qb_casino]/casino-slots/fxmanifest.lua b/resources/[qb]/[qb_casino]/casino-slots/fxmanifest.lua new file mode 100644 index 0000000..cb7b052 --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-slots/fxmanifest.lua @@ -0,0 +1,13 @@ +fx_version 'cerulean' +game 'gta5' + +author 'Disabled Coding' +description 'A casino resource for which you do not have to sell a kidney for' +version '1.0.0' +repository 'https://github.com/Disabled-Coding/dc-casino' + +shared_script 'config.lua' +client_script 'client.lua' +server_script 'server.lua' + +lua54 'yes' diff --git a/resources/[qb]/[qb_casino]/casino-slots/server.lua b/resources/[qb]/[qb_casino]/casino-slots/server.lua new file mode 100644 index 0000000..bc103f2 --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-slots/server.lua @@ -0,0 +1,170 @@ +local QBCore = exports['qb-core']:GetCoreObject() +local UsedSlots = {} +local Slots = {} + +local function table_matches(t1, t2) + local type1, type2 = type(t1), type(t2) + if type1 ~= type2 then return false end + if type1 ~= 'table' and type2 ~= 'table' then return t1 == t2 end + + for k1,v1 in pairs(t1) do + local v2 = t2[k1] + if v2 == nil or not table_matches(v1,v2) then return false end + end + + for k2,v2 in pairs(t2) do + local v1 = t1[k2] + if v1 == nil or not table_matches(v1,v2) then return false end + end + return true +end + +local function LeaveSlot(source) + if not Slots[source] then return end + if DoesEntityExist(Slots[source].Reel1) then DeleteEntity(Slots[source].Reel1) end + if DoesEntityExist(Slots[source].Reel2) then DeleteEntity(Slots[source].Reel2) end + if DoesEntityExist(Slots[source].Reel3) then DeleteEntity(Slots[source].Reel3) end + UsedSlots[Slots[source].SlotNetID] = false + Slots[source] = {} +end + +RegisterNetEvent('dc-casino:slots:server:enter', function(netID, ReelLocation1, ReelLocation2, ReelLocation3) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local PlayerCoords = GetEntityCoords(GetPlayerPed(src)) + local SlotEntity = NetworkGetEntityFromNetworkId(netID) + local SlotModel = GetEntityModel(SlotEntity) + local SlotCoords = GetEntityCoords(SlotEntity) + + if not SlotReferences[SlotModel] then return end + if #(PlayerCoords - SlotCoords) > 4 then return end + if #(SlotCoords - ReelLocation1) > 2 or #(SlotCoords - ReelLocation2) > 2 or #(SlotCoords - ReelLocation2) > 2 then return end + if UsedSlots[netID] then return end + + UsedSlots[netID] = true + TriggerClientEvent('dc-casino:slots:client:enter', src) + SetTimeout(1000, function() + local ReelEntity1 = CreateObject(SlotReferences[SlotModel].reela, ReelLocation1, true, false, false) + local ReelEntity2 = CreateObject(SlotReferences[SlotModel].reela, ReelLocation2, true, false, false) + local ReelEntity3 = CreateObject(SlotReferences[SlotModel].reela, ReelLocation3, true, false, false) + while not DoesEntityExist(ReelEntity1) do Wait(0) end + while not DoesEntityExist(ReelEntity2) do Wait(0) end + while not DoesEntityExist(ReelEntity3) do Wait(0) end + Slots[src] = { + Slot = NetworkGetEntityFromNetworkId(netID), + SlotNetID = netID, + Reel1 = ReelEntity1, + Reel2 = ReelEntity2, + Reel3 = ReelEntity3, + ReelLoc1 = ReelLocation1, + ReelLoc2 = ReelLocation2, + ReelLoc3 = ReelLocation3, + } + FreezeEntityPosition(Slots[src].Reel1, true) + FreezeEntityPosition(Slots[src].Reel2, true) + FreezeEntityPosition(Slots[src].Reel3, true) + local SlotHeading = GetEntityHeading(SlotEntity) + SetEntityRotation(Slots[src].Reel1, 0.0, 0.0, SlotHeading, 2, 1) + SetEntityRotation(Slots[src].Reel2, 0.0, 0.0, SlotHeading, 2, 1) + SetEntityRotation(Slots[src].Reel3, 0.0, 0.0, SlotHeading, 2, 1) + -- TriggerEvent('qb-log:server:CreateLog', 'casino', 'Casino Slots', 'green', string.format("**%s** (CitizenID: %s | ID: %s) - Entered a slot | Slot NetID %s | Slot Locations %s | Reel Locations %s %s %s | Player Location %s | Slot Model %s", + -- GetPlayerName(src), Player.PlayerData.citizenid, src, netID, SlotCoords, ReelLocation1, ReelLocation2, ReelLocation3, PlayerCoords, SlotModel)) + end) +end) + +RegisterNetEvent('dc-casino:slots:server:spin', function(ChosenBetAmount) + local src = source + local SpinTime = math.random(4000, 6000) + local ReelRewards = {math.random(0, 15), math.random(0, 15), math.random(0, 15)} + local SlotHeading = GetEntityHeading(Slots[src].Slot) + local SlotModel = GetEntityModel(Slots[src].Slot) + local Player = QBCore.Functions.GetPlayer(src) + + if not Slots[src] then return end + if not SlotReferences[SlotModel].betamounts[ChosenBetAmount] then return end + if UseCash and Player.Functions.RemoveMoney('cash', SlotReferences[SlotModel].betamounts[ChosenBetAmount], 'Casino Slot Spin') + or UseBank and Player.Functions.RemoveMoney('bank', SlotReferences[SlotModel].betamounts[ChosenBetAmount], 'Casino Slot Spin') + -- luacheck: ignore + or UseItem and Player.Functions.RemoveItem(ItemName, SlotReferences[SlotModel].betamounts[ChosenBetAmount]) then TriggerClientEvent('inventory:client:ItemBox', src, QBCore.Shared.Items['casino_redchip'], "remove", ChosenBetAmount) + + else TriggerClientEvent('QBCore:Notify', src, 'Nothing left to bet with', 'error') return end + + for i = 1, #ReelRewards do + if SlotReferences[SlotModel].misschance > math.random(1, 100) then ReelRewards[i] = ReelRewards[i] + math.random(4, 6) / 10 end + end + local BlurryReel1 = CreateObject(SlotReferences[SlotModel].reelb, Slots[src].ReelLoc1, true, false, false) + local BlurryReel2 = CreateObject(SlotReferences[SlotModel].reelb, Slots[src].ReelLoc2, true, false, false) + local BlurryReel3 = CreateObject(SlotReferences[SlotModel].reelb, Slots[src].ReelLoc3, true, false, false) + while not DoesEntityExist(BlurryReel1) do Wait(0) end + while not DoesEntityExist(BlurryReel2) do Wait(0) end + while not DoesEntityExist(BlurryReel3) do Wait(0) end + FreezeEntityPosition(BlurryReel1, true) + FreezeEntityPosition(BlurryReel2, true) + FreezeEntityPosition(BlurryReel3, true) + SetEntityRotation(BlurryReel1, 0.0, 0.0, SlotHeading, 2, 1) + SetEntityRotation(BlurryReel2, 0.0, 0.0, SlotHeading, 2, 1) + SetEntityRotation(BlurryReel3, 0.0, 0.0, SlotHeading, 2, 1) + local RewardMultiplier = 0 + for k, v in pairs(Rewards) do + if table_matches(k, ReelRewards) then + RewardMultiplier = v + break + end + end + if RewardMultiplier == 0 then + for i = 1, #ReelRewards do + if ReelRewards[i] == 4 or ReelRewards[i] == 11 or ReelRewards[i] == 15 then + RewardMultiplier = RewardMultiplier + 1 + end + end + RewardMultiplier = SpecialReward[RewardMultiplier] or 0 + end + TriggerClientEvent('dc-casino:slots:client:spinreels', src, SpinTime, ReelRewards, NetworkGetNetworkIdFromEntity(BlurryReel1), NetworkGetNetworkIdFromEntity(BlurryReel2), NetworkGetNetworkIdFromEntity(BlurryReel3), NetworkGetNetworkIdFromEntity(Slots[src].Reel1), NetworkGetNetworkIdFromEntity(Slots[src].Reel2), NetworkGetNetworkIdFromEntity(Slots[src].Reel3), RewardMultiplier) + SetTimeout(SpinTime, function() + local RewardAmount = SlotReferences[SlotModel].betamounts[ChosenBetAmount] * RewardMultiplier + -- TriggerEvent('qb-log:server:CreateLog', 'casino', 'Casino Slots', 'green', string.format("**%s** (CitizenID: %s | ID: %s) - Spinned a casino slot for %s and won %s", + -- GetPlayerName(src), Player.PlayerData.citizenid, src, SlotReferences[SlotModel].betamounts[ChosenBetAmount], RewardAmount)) + if RewardMultiplier == 0 then return end + if UseCash and Player.Functions.AddMoney('cash', RewardAmount, 'Casino Slot Spin') + or UseBank and Player.Functions.AddMoney('bank', RewardAmount, 'Casino Slot Spin') + -- luacheck: ignore + or UseItem and Player.Functions.AddItem(ItemName, RewardAmount) then + TriggerClientEvent('inventory:client:ItemBox', src, QBCore.Shared.Items['casino_redchip'], "add", RewardAmount) + -- TriggerClientEvent('QBCore:Notify', src, 'You won '..tonumber(RewardAmount)..' chips!', 'success') + end + end) +end) + +RegisterNetEvent('dc-casino:slots:server:leave', function() + LeaveSlot(source) +end) + +AddEventHandler("playerDropped", function() + LeaveSlot(source) +end) + +-- PerformHttpRequest('https://api.github.com/repos/Disabled-Coding/dc-casino/releases/latest', function(_, resultData, _) +-- if not resultData then print('Failed to check for updates') return end +-- local result = json.decode(resultData) +-- if GetResourceMetadata(GetCurrentResourceName(), 'version') ~= result.tag_name then +-- print('New version of '..GetCurrentResourceName()..' is available!') +-- end +-- end) + +local ItemList = { + ["casino_redchip"] = 1 +} +QBCore.Functions.CreateCallback('doj:server:CasinoChipsAmount', function(source, cb) + local retval = 0 + local Player = QBCore.Functions.GetPlayer(source) + if Player.PlayerData.items ~= nil and next(Player.PlayerData.items) ~= nil then + for k, v in pairs(Player.PlayerData.items) do + if Player.PlayerData.items[k] ~= nil then + if ItemList[Player.PlayerData.items[k].name] ~= nil then + retval = retval + (ItemList[Player.PlayerData.items[k].name] * Player.PlayerData.items[k].amount) + end + end + end + end + cb(retval) +end) diff --git a/resources/[qb]/[qb_casino]/casino-walls/client/ambient-peds.lua b/resources/[qb]/[qb_casino]/casino-walls/client/ambient-peds.lua new file mode 100644 index 0000000..6596994 --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-walls/client/ambient-peds.lua @@ -0,0 +1,944 @@ +CreateThread(function() + CasinoPeds() +end) + +function setBlackjackDealerClothes(randomNumber,ped) + if randomNumber == 0 then + SetPedDefaultComponentVariation(ped) + SetPedComponentVariation(ped, 0, 3, 0, 0) + SetPedComponentVariation(ped, 1, 1, 0, 0) + SetPedComponentVariation(ped, 2, 3, 0, 0) + SetPedComponentVariation(ped, 3, 1, 0, 0) + SetPedComponentVariation(ped, 4, 0, 0, 0) + SetPedComponentVariation(ped, 6, 1, 0, 0) + SetPedComponentVariation(ped, 7, 2, 0, 0) + SetPedComponentVariation(ped, 8, 3, 0, 0) + SetPedComponentVariation(ped, 10, 1, 0, 0) + SetPedComponentVariation(ped, 11, 1, 0, 0) + elseif randomNumber == 1 then + SetPedDefaultComponentVariation(ped) + SetPedComponentVariation(ped, 0, 2, 2, 0) + SetPedComponentVariation(ped, 1, 1, 0, 0) + SetPedComponentVariation(ped, 2, 4, 0, 0) + SetPedComponentVariation(ped, 3, 0, 3, 0) + SetPedComponentVariation(ped, 4, 0, 0, 0) + SetPedComponentVariation(ped, 6, 1, 0, 0) + SetPedComponentVariation(ped, 7, 2, 0, 0) + SetPedComponentVariation(ped, 8, 1, 0, 0) + SetPedComponentVariation(ped, 10, 1, 0, 0) + SetPedComponentVariation(ped, 11, 1, 0, 0) + elseif randomNumber == 2 then + SetPedDefaultComponentVariation(ped) + SetPedComponentVariation(ped, 0, 2, 1, 0) + SetPedComponentVariation(ped, 1, 1, 0, 0) + SetPedComponentVariation(ped, 2, 2, 0, 0) + SetPedComponentVariation(ped, 3, 0, 3, 0) + SetPedComponentVariation(ped, 4, 0, 0, 0) + SetPedComponentVariation(ped, 6, 1, 0, 0) + SetPedComponentVariation(ped, 7, 2, 0, 0) + SetPedComponentVariation(ped, 8, 1, 0, 0) + SetPedComponentVariation(ped, 10, 1, 0, 0) + SetPedComponentVariation(ped, 11, 1, 0, 0) + elseif randomNumber == 3 then + SetPedDefaultComponentVariation(ped) + SetPedComponentVariation(ped, 0, 2, 0, 0) + SetPedComponentVariation(ped, 1, 1, 0, 0) + SetPedComponentVariation(ped, 2, 3, 0, 0) + SetPedComponentVariation(ped, 3, 1, 3, 0) + SetPedComponentVariation(ped, 4, 0, 0, 0) + SetPedComponentVariation(ped, 6, 1, 0, 0) + SetPedComponentVariation(ped, 7, 2, 0, 0) + SetPedComponentVariation(ped, 8, 3, 0, 0) + SetPedComponentVariation(ped, 10, 1, 0, 0) + SetPedComponentVariation(ped, 11, 1, 0, 0) + elseif randomNumber == 4 then + SetPedDefaultComponentVariation(ped) + SetPedComponentVariation(ped, 0, 4, 2, 0) + SetPedComponentVariation(ped, 1, 1, 0, 0) + SetPedComponentVariation(ped, 2, 3, 0, 0) + SetPedComponentVariation(ped, 3, 0, 0, 0) + SetPedComponentVariation(ped, 4, 0, 0, 0) + SetPedComponentVariation(ped, 6, 1, 0, 0) + SetPedComponentVariation(ped, 7, 2, 0, 0) + SetPedComponentVariation(ped, 8, 1, 0, 0) + SetPedComponentVariation(ped, 10, 1, 0, 0) + SetPedComponentVariation(ped, 11, 1, 0, 0) + elseif randomNumber == 5 then + SetPedDefaultComponentVariation(ped) + SetPedComponentVariation(ped, 0, 4, 0, 0) + SetPedComponentVariation(ped, 1, 1, 0, 0) + SetPedComponentVariation(ped, 2, 0, 0, 0) + SetPedComponentVariation(ped, 3, 0, 0, 0) + SetPedComponentVariation(ped, 4, 0, 0, 0) + SetPedComponentVariation(ped, 6, 1, 0, 0) + SetPedComponentVariation(ped, 7, 2, 0, 0) + SetPedComponentVariation(ped, 8, 1, 0, 0) + SetPedComponentVariation(ped, 10, 1, 0, 0) + SetPedComponentVariation(ped, 11, 1, 0, 0) + elseif randomNumber == 6 then + SetPedDefaultComponentVariation(ped) + SetPedComponentVariation(ped, 0, 4, 1, 0) + SetPedComponentVariation(ped, 1, 1, 0, 0) + SetPedComponentVariation(ped, 2, 4, 0, 0) + SetPedComponentVariation(ped, 3, 1, 0, 0) + SetPedComponentVariation(ped, 4, 0, 0, 0) + SetPedComponentVariation(ped, 6, 1, 0, 0) + SetPedComponentVariation(ped, 7, 2, 0, 0) + SetPedComponentVariation(ped, 8, 3, 0, 0) + SetPedComponentVariation(ped, 10, 1, 0, 0) + SetPedComponentVariation(ped, 11, 1, 0, 0) + elseif randomNumber == 7 then + SetPedDefaultComponentVariation(ped) + SetPedComponentVariation(ped, 0, 1, 1, 0) + SetPedComponentVariation(ped, 1, 0, 0, 0) + SetPedComponentVariation(ped, 2, 1, 0, 0) + SetPedComponentVariation(ped, 3, 0, 3, 0) + SetPedComponentVariation(ped, 4, 0, 0, 0) + SetPedComponentVariation(ped, 6, 0, 0, 0) + SetPedComponentVariation(ped, 7, 0, 0, 0) + SetPedComponentVariation(ped, 8, 0, 0, 0) + SetPedComponentVariation(ped, 10, 0, 0, 0) + SetPedComponentVariation(ped, 11, 0, 0, 0) + elseif randomNumber == 8 then + SetPedDefaultComponentVariation(ped) + SetPedComponentVariation(ped, 0, 1, 1, 0) + SetPedComponentVariation(ped, 1, 0, 0, 0) + SetPedComponentVariation(ped, 2, 1, 1, 0) + SetPedComponentVariation(ped, 3, 1, 3, 0) + SetPedComponentVariation(ped, 4, 0, 0, 0) + SetPedComponentVariation(ped, 6, 0, 0, 0) + SetPedComponentVariation(ped, 7, 2, 0, 0) + SetPedComponentVariation(ped, 8, 1, 0, 0) + SetPedComponentVariation(ped, 10, 0, 0, 0) + SetPedComponentVariation(ped, 11, 0, 0, 0) + elseif randomNumber == 9 then + SetPedDefaultComponentVariation(ped) + SetPedComponentVariation(ped, 0, 2, 0, 0) + SetPedComponentVariation(ped, 1, 0, 0, 0) + SetPedComponentVariation(ped, 2, 2, 0, 0) + SetPedComponentVariation(ped, 3, 2, 3, 0) + SetPedComponentVariation(ped, 4, 0, 0, 0) + SetPedComponentVariation(ped, 6, 0, 0, 0) + SetPedComponentVariation(ped, 7, 0, 0, 0) + SetPedComponentVariation(ped, 8, 2, 0, 0) + SetPedComponentVariation(ped, 10, 0, 0, 0) + SetPedComponentVariation(ped, 11, 0, 0, 0) + elseif randomNumber == 10 then + SetPedDefaultComponentVariation(ped) + SetPedComponentVariation(ped, 0, 2, 1, 0) + SetPedComponentVariation(ped, 1, 0, 0, 0) + SetPedComponentVariation(ped, 2, 2, 1, 0) + SetPedComponentVariation(ped, 3, 3, 3, 0) + SetPedComponentVariation(ped, 4, 1, 0, 0) + SetPedComponentVariation(ped, 6, 1, 0, 0) + SetPedComponentVariation(ped, 7, 2, 0, 0) + SetPedComponentVariation(ped, 8, 3, 0, 0) + SetPedComponentVariation(ped, 10, 0, 0, 0) + SetPedComponentVariation(ped, 11, 0, 0, 0) + elseif randomNumber == 11 then + SetPedDefaultComponentVariation(ped) + SetPedComponentVariation(ped, 0, 3, 0, 0) + SetPedComponentVariation(ped, 1, 0, 0, 0) + SetPedComponentVariation(ped, 2, 3, 0, 0) + SetPedComponentVariation(ped, 3, 0, 1, 0) + SetPedComponentVariation(ped, 4, 1, 0, 0) + SetPedComponentVariation(ped, 6, 1, 0, 0) + SetPedComponentVariation(ped, 7, 1, 0, 0) + SetPedComponentVariation(ped, 8, 0, 0, 0) + SetPedComponentVariation(ped, 10, 0, 0, 0) + SetPedComponentVariation(ped, 11, 0, 0, 0) + SetPedPropIndex(ped, 1, 0, 0, false) + elseif randomNumber == 12 then + SetPedDefaultComponentVariation(ped) + SetPedComponentVariation(ped, 0, 3, 1, 0) + SetPedComponentVariation(ped, 1, 0, 0, 0) + SetPedComponentVariation(ped, 2, 3, 1, 0) + SetPedComponentVariation(ped, 3, 1, 1, 0) + SetPedComponentVariation(ped, 4, 1, 0, 0) + SetPedComponentVariation(ped, 6, 1, 0, 0) + SetPedComponentVariation(ped, 7, 2, 0, 0) + SetPedComponentVariation(ped, 8, 1, 0, 0) + SetPedComponentVariation(ped, 10, 0, 0, 0) + SetPedComponentVariation(ped, 11, 0, 0, 0) + elseif randomNumber == 13 then + SetPedDefaultComponentVariation(ped) + SetPedComponentVariation(ped, 0, 4, 0, 0) + SetPedComponentVariation(ped, 1, 0, 0, 0) + SetPedComponentVariation(ped, 2, 4, 0, 0) + SetPedComponentVariation(ped, 3, 2, 1, 0) + SetPedComponentVariation(ped, 4, 1, 0, 0) + SetPedComponentVariation(ped, 6, 1, 0, 0) + SetPedComponentVariation(ped, 7, 1, 0, 0) + SetPedComponentVariation(ped, 8, 2, 0, 0) + SetPedComponentVariation(ped, 10, 0, 0, 0) + SetPedComponentVariation(ped, 11, 0, 0, 0) + SetPedPropIndex(ped, 1, 0, 0, false) + end +end + + + +function CasinoPeds() + + setBlackjackDealerClothes(randomBlackShit,ped) + + -- Employee + model = GetHashKey("U_F_M_CasinoCash_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model ,949.75, 33.422, 70.839, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 57.052 ) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_STAND_IMPATIENT", 0, true) + -- Inside Track + model = GetHashKey("S_M_Y_Doorman_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 955.619, 70.179, 69.433, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 190.937) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_STAND_IMPATIENT", 0, true) + -- Membership + model = GetHashKey("u_f_m_casinoshop_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model ,920.726, 45.883, 71.073, true) + + SetPedDefaultComponentVariation(ped) + SetPedComponentVariation(ped, 2, 1, 0, 0) + SetPedComponentVariation(ped, 11, 2, 0, 0) + + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 276.635) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_STAND_IMPATIENT", 0, true) + + + -- ambient workers + -------------------------------------------------------------------------------------------------------------------------------------- + + model = GetHashKey("S_F_Y_Casino_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 947.994, 45.233, 70.638, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 220.199) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_STAND_IMPATIENT", 0, true) + + model = GetHashKey("S_F_Y_Casino_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 998.848, 45.308, 68.833, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 43.086) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_STAND_IMPATIENT", 0, true) + + model = GetHashKey("S_F_Y_Casino_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 973.283, 41.411, 70.233, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 184.428) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_STAND_IMPATIENT", 0, true) + + model = GetHashKey("S_F_Y_Casino_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 996.982, 37.33, 69.233, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 45.32) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_STAND_IMPATIENT", 0, true) + + model = GetHashKey("S_F_Y_Casino_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 989.222, 71.748, 69.233, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 161.745) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_STAND_IMPATIENT", 0, true) + + model = GetHashKey("S_F_Y_Casino_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 974.901, 34.128, 69.833, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 1.995) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_STAND_IMPATIENT", 0, true) + + model = GetHashKey("S_F_Y_Casino_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 970.651, 65.43, 69.833, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 192.896) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_STAND_IMPATIENT", 0, true) + + model = GetHashKey("S_F_Y_Casino_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 920.48, 46.759, 71.073, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 277.117) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_STAND_IMPATIENT", 0, true) + + model = GetHashKey("S_F_Y_Casino_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 933.011, 40.807, 80.089, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 54.913) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_STAND_IMPATIENT", 0, true) + + model = GetHashKey("S_F_Y_Casino_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 929.086, 34.707, 80.089, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 328.4) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_STAND_IMPATIENT", 0, true) + + model = GetHashKey("S_F_Y_Casino_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 949.615, 25.983, 70.834, true) + SetPedComponentVariation(ped, 8, 3, 3, 3) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 53.202) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_STAND_IMPATIENT", 0, true) + + model = GetHashKey("S_F_Y_Casino_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 939.71, 47.066, 71.279, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 4164.51) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_STAND_IMPATIENT", 0, true) + -------------------------------------------------------------------------------------------------------------------------------------- + + model = GetHashKey("S_M_Y_Casino_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 933.62, 41.631, 80.089, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 56.923) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_STAND_IMPATIENT", 0, true) + + model = GetHashKey("S_M_Y_Casino_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 938.974, 27.776, 70.834, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 13.513) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_WINDOW_SHOP_BROWSE", 0, true) + + model = GetHashKey("S_M_Y_Casino_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 969.729, 57.35, 70.233, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 23.352) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_STAND_IMPATIENT", 0, true) + + model = GetHashKey("S_M_Y_Casino_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 978.838, 69.432, 69.233, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 237.674) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_STAND_IMPATIENT", 0, true) + + model = GetHashKey("S_M_Y_Casino_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 984.436, 36.494, 69.233, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 314.327) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_STAND_IMPATIENT", 0, true) + + model = GetHashKey("S_M_Y_Casino_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 994.234, 65.098, 68.833, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 151.299) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_STAND_IMPATIENT", 0, true) + + model = GetHashKey("S_M_Y_Casino_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 929.79, 37.618, 71.274, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 35.262) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_STAND_IMPATIENT", 0, true) + + model = GetHashKey("s_m_y_valet_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 925.235, 50.921, 80.106, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 55.601) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_STAND_IMPATIENT", 0, true) + + -- ==================================================================================================================-- + -- ==================================================================================================================--locals- + -- ==================================================================================================================-- + model = GetHashKey("a_m_m_paparazzi_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 949.985, 54.927, 70.433, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 172.611) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_SMOKING", 0, true) + + model = GetHashKey("a_f_y_tourist_02") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 943.266, 64.802, 69.833, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 267.054) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_STAND_MOBILE", 0, true) + + model = GetHashKey("a_f_y_vinewood_02") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 943.723, 65.612, 69.833, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 173.907) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_HANG_OUT_STREET", 0, true) + + model = GetHashKey("cs_tom") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 942.68, 38.882, 70.834, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 154.872) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_WINDOW_SHOP_BROWSE", 0, true) + + model = GetHashKey("cs_jimmyboston") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 942.057, 38.012, 70.834, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 329.05) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_MOBILE_FILM_SHOCKING", 0, true) + + model = GetHashKey("cs_carbuyer") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 929.239, 28.97, 70.834, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 281.783) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_MOBILE_FILM_SHOCKING", 0, true) + + model = GetHashKey("a_m_y_clubcust_03") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 928.8, 29.857, 70.834, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 283.934) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_HANG_OUT_STREET", 0, true) + + model = GetHashKey("cs_gurk") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 934.337, 37.499, 71.279, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 168.193) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_HANG_OUT_STREET", 0, true) + + model = GetHashKey("cs_lazlow_2") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model ,935.38, 37.579, 71.279, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 180.215) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_PARTYING", 0, true) + + model = GetHashKey("a_m_y_clubcust_03") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model ,927.904, 41.925, 71.274, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 179.576) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_SMOKING", 0, true) + + model = GetHashKey("a_m_y_bevhills_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 953.208, 61.601, 69.833, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 339.76) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_PARTYING", 0, true) + + model = GetHashKey("a_f_y_vinewood_04") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 953.94, 61.392, 69.833, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 344.716) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_SMOKING", 0, true) + + model = GetHashKey("a_f_y_scdressy_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 955.158, 61.174, 69.833, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 356.136) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_PARTYING", 0, true) + + model = GetHashKey("a_f_y_genhot_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 965.489, 72.251, 69.833, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 196.752) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_PARTYING", 0, true) + + model = GetHashKey("a_m_m_malibu_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 964.468, 71.471, 69.833, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 221.392) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_STAND_MOBILE", 0, true) + + model = GetHashKey("a_f_y_vinewood_04") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 987.52, 57.652, 68.833, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 204.379) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_PARTYING", 0, true) + + model = GetHashKey("s_m_m_hairdress_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 986.732, 57.113, 68.866, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 222.62) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_STAND_MOBILE", 0, true) + + model = GetHashKey("ig_taostranslator") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 982.362, 46.761, 69.238, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 0.99) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_PARTYING", 0, true) + + model = GetHashKey("u_f_m_miranda_02") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 969.109, 46.507, 69.833, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 82.089) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_STAND_MOBILE", 0, true) + + model = GetHashKey("cs_carbuyer") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 969.066, 47.366, 69.833, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 83.4) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_PARTYING", 0, true) + + model = GetHashKey("cs_dale") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model ,968.666, 45.528, 69.833, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 81.227) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_SMOKING", 0, true) + + model = GetHashKey("ig_taostranslator") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 961.332, 52.542, 69.833, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped,203.484) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_PARTYING", 0, true) + + model = GetHashKey("a_m_y_hipster_02") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 988.817, 49.03, 68.833, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 352.551) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_PARTYING", 0, true) + + model = GetHashKey("u_f_m_miranda_02") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 989.748, 49.294, 68.832, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 6.127) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_SMOKING", 0, true) + + model = GetHashKey("a_f_y_scdressy_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 962.446, 52.816, 69.833, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 207.774) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_PARTYING", 0, true) + + model = GetHashKey("a_m_m_prolhost_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 945.779, 22.762, 70.279, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 44.244) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "PROP_HUMAN_SEAT_CHAIR", 0, true) + + model = GetHashKey("a_f_y_scdressy_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 945.09, 21.745, 70.279, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 51.433) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "PROP_HUMAN_SEAT_DECKCHAIR_DRINK", 0, true) + + model = GetHashKey("a_f_y_bevhills_04") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 940.96, 18.807, 70.305, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 17.935) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "PROP_HUMAN_SEAT_DECKCHAIR_DRINK", 0, true) + + model = GetHashKey("a_m_m_stlat_02") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 941.913, 19.163, 70.288, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 18.65) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "PROP_HUMAN_SEAT_CHAIR_FOOD", 0, true) + + model = GetHashKey("a_f_y_scdressy_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 932.517, 18.882, 70.313, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 7.469) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "PROP_HUMAN_SEAT_CHAIR_UPRIGHT", 0, true) + + model = GetHashKey("a_f_y_bevhills_04") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 933.335, 19.048, 70.33, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 357.385) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "PROP_HUMAN_SEAT_BENCH_DRINK_BEER", 0, true) + + model = GetHashKey("a_m_m_malibu_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 934.378, 19.097, 70.339, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 357.293) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "PROP_HUMAN_SEAT_DECKCHAIR_DRINK", 0, true) + + model = GetHashKey("a_f_y_bevhills_04") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 937.486, 29.38, 70.537, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 202.034) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "PROP_HUMAN_SEAT_BENCH_DRINK_BEER", 0, true) + + model = GetHashKey("a_m_m_malibu_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 938.916, 29.585, 70.534, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 179.067) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "PROP_HUMAN_SEAT_BENCH_DRINK_BEER", 0, true) + + model = GetHashKey("ig_taostranslator") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 935.392, 27.821, 70.834, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 326.627) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_PARTYING", 0, true) + + model = GetHashKey("a_f_y_genhot_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 935.98, 28.242, 70.834, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped,233.585) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "PROP_HUMAN_STAND_IMPATIENT", 0, true) + + model = GetHashKey("a_f_y_genhot_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 962.431, 51.656, 69.833, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 29.831) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_SMOKING", 0, true) + + model = GetHashKey("a_f_y_genhot_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 951.392, 36.326, 70.838, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 101.098) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_PARTYING", 0, true) + + model = GetHashKey("a_f_y_genhot_01") + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + ped = CreatePed(0, model , 950.274, 37.131, 70.838, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, 201.608) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + TaskStartScenarioInPlace(ped, "WORLD_HUMAN_SMOKING", 0, true) +end + diff --git a/resources/[qb]/[qb_casino]/casino-walls/client/casino-addons.lua b/resources/[qb]/[qb_casino]/casino-walls/client/casino-addons.lua new file mode 100644 index 0000000..d292432 --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-walls/client/casino-addons.lua @@ -0,0 +1,166 @@ +local QBCore = exports['qb-core']:GetCoreObject() + +RegisterNetEvent('qb-casino:client:openCasinoShop', function() + local ShopItems = {} + ShopItems.label = "The Diamond Casino & Resort Shop" + ShopItems.items = Config.CasinoShop + ShopItems.slots = #Config.CasinoShop + TriggerServerEvent("inventory:server:OpenInventory", "shop", "Vendingshop_", ShopItems) +end) + +RegisterNetEvent('doj:casinoChipMenu', function() + TriggerEvent('drawtextui:HideUI') + exports['qb-menu']:openMenu({ + { + header = "The Diamond Casino & Resort Chip Exchange", + isMenuHeader = true, + }, + { + header = "Sell All Casino Chips", + txt = "Current Value: $"..Config.casinoChipPrice.." per chip", + params = { + + event = "qb-casino:server:GoldSell", + isServer = true, + } + }, + { + header = "< Return", + txt = "", + params = { + event = "doj:casinoMainMenu" + } + }, + }) +end) + +CreateThread(function() + local CasinoShop = CircleZone:Create(vector3(948.591, 34.207, 71.839), 2.0, { + name="CasinoShop", + heading=160, + debugPoly=false, + useZ=true, + }) + CasinoShop:onPlayerInOut(function(isPointInside) + if isPointInside then + text = 'The Diamond Casino & Resort

Cashier' + exports['qb-core']:DrawText(text) + exports['qb-target']:AddTargetModel(`U_F_M_CasinoCash_01`, { + options = { + { + event = "doj:casinoMainMenu", + icon = "fab fa-speakap", + label = "Speak with Casino Employee", + }, + }, + distance = 3.0 + }) + else + exports['qb-menu']:closeMenu() + exports["qb-core"]:HideText() + end + end) +end) + + +CreateThread(function() + local CasinoMembership = CircleZone:Create(vector3(920.726, 45.883, 71.073), 5.0, { + name="CasinoMembership", + heading=160, + debugPoly=false, + useZ=true, + }) + CasinoMembership:onPlayerInOut(function(isPointInside) + if isPointInside then + text = 'The Diamond Casino & Resort

Front Counter' + exports['qb-core']:DrawText(text) + exports['qb-target']:AddTargetModel(`u_f_m_casinoshop_01`, { + options = { + { + event = "doj:casinoMembershipMenu", + icon = "fab fa-speakap", + label = "Speak with Casino Employee", + }, + }, + distance = 3.0 + }) + else + exports['qb-menu']:closeMenu() + exports["qb-core"]:HideText() + end + end) +end) + + + +RegisterNetEvent('doj:casinoMainMenu', function() + QBCore.Functions.TriggerCallback('doj:server:HasCasinoMembership', function(HasItem) + if HasItem then + -- exports["qb-core"]:HideText() + exports['qb-menu']:openMenu({ + { + header = "The Diamond Casino & Resort", + isMenuHeader = true, + }, + { + header = "Chip Exchange", + txt = "See current prices", + params = { + event = "doj:casinoChipMenu", + } + }, + { + header = "Browse Shop", + txt = "See what we have to offer", + params = { + event = "qb-casino:client:openCasinoShop", + } + }, + { + header = "Cancel", + txt = "", + params = { + event = "" + } + }, + }) + else + text = 'The Diamond Casino & Resort

Please visit the front desk!
' + exports['qb-core']:DrawText(text) + end + end) +end) + + +RegisterNetEvent('doj:casinoMembershipMenu', function() + -- exports["qb-core"]:HideText() + exports['qb-menu']:openMenu({ + { + header = "The Diamond Casino & Resort", + isMenuHeader = true, + }, + { + header = "Purchase Casino Membership", + txt = "Price: $"..Config.casinoMemberPrice, + params = { + isServer = true, + event = "doj:server:purchaseMembership", + } + }, + { + header = "Purchase V.I.P Membership", + txt = "Price: $"..Config.casinoVIPPrice, + params = { + isServer = true, + event = "doj:server:purchaseVIPMembership", + } + }, + { + header = "Cancel", + txt = "", + params = { + event = "" + } + }, + }) +end) diff --git a/resources/[qb]/[qb_casino]/casino-walls/client/casino-interior.lua b/resources/[qb]/[qb_casino]/casino-walls/client/casino-interior.lua new file mode 100644 index 0000000..9ec8724 --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-walls/client/casino-interior.lua @@ -0,0 +1,215 @@ + +local inCasino = false +local videoWallRenderTarget = nil +local showBigWin = false +local spinningObject = nil +local spinningCar = nil +-- +-- Threads +-- + +function startCasinoThreads() + RequestStreamedTextureDict('Prop_Screen_Vinewood') + while not HasStreamedTextureDictLoaded('Prop_Screen_Vinewood') do + Wait(100) + end + RegisterNamedRendertarget('casinoscreen_01') + LinkNamedRendertarget(`vw_vwint01_video_overlay`) + videoWallRenderTarget = GetNamedRendertargetRenderId('casinoscreen_01') + CreateThread(function() + local lastUpdatedTvChannel = 0 + while true do + Wait(0) + if not inCasino then + ReleaseNamedRendertarget('casinoscreen_01') + videoWallRenderTarget = nil + showBigWin = false + break + end + if videoWallRenderTarget then + local currentTime = GetGameTimer() + if showBigWin then + setVideoWallTvChannelWin() + lastUpdatedTvChannel = GetGameTimer() - 33666 + showBigWin = false + else + if (currentTime - lastUpdatedTvChannel) >= 42666 then + setVideoWallTvChannel() + lastUpdatedTvChannel = currentTime + end + end + SetTextRenderId(videoWallRenderTarget) + SetScriptGfxDrawOrder(4) + SetScriptGfxDrawBehindPausemenu(true) + DrawInteractiveSprite('Prop_Screen_Vinewood', 'BG_Wall_Colour_4x4', 0.25, 0.5, 0.5, 1.0, 0.0, 255, 255, 255, 255) + DrawTvChannel(0.5, 0.5, 1.0, 1.0, 0.0, 255, 255, 255, 255) + SetTextRenderId(GetDefaultScriptRendertargetRenderId()) + end + end + end) +end + +function setVideoWallTvChannel() + SetTvChannelPlaylist(0, Config.AnimatedWallNormal, true) + SetTvAudioFrontend(true) + SetTvVolume(-100.0) + SetTvChannel(0) +end + +function setVideoWallTvChannelWin() + SetTvChannelPlaylist(0, Config.AnimatedWallWin, true) + SetTvAudioFrontend(true) + SetTvVolume(-100.0) + SetTvChannel(-1) + SetTvChannel(0) +end + +-- +-- Events +-- + +AddEventHandler("chCasinoWall:enteredCasino", function() + inCasino = true + if Config.SetAnimatedWalls then + startCasinoThreads() + end + if Config.SetShowCarOnDisplay then + spinMeRightRoundBaby() + end + if Config.PlayCasinoAmbientNoise then + playSomeBackgroundAudioBaby() + end +end) + +AddEventHandler("chCasinoWall:exitedCasino", function() + inCasino = false +end) + +RegisterNetEvent('chCasinoWall:bigWin') +AddEventHandler('chCasinoWall:bigWin', function() + if not inCasino then + return + end + showBigWin = true +end) + + +function enterCasino() + InCasino = true + TriggerEvent("chCasinoWall:enteredCasino") + print("Entered Casino area") + if Config.SendWelcomeMail then + TriggerServerEvent('qb-phone:server:sendNewMail', { + sender = Config.WelcomeMailsender, + subject = Config.WelcomeMailsubject, + message = Config.WelcomeMailmessage, + button = {} + }) + end +end + +function exitCasino() + TriggerEvent("chCasinoWall:exitedCasino") + print("Exited Casino area") + InCasino = false + StopAudioScene("DLC_VW_Casino_General") + Wait(5000) + startCasinoThreads() +end + +CreateThread(function() + local CasinoZone = CircleZone:Create(vector3(945.85, 41.58, -170.50), 250.0, { + name="CasinoZone", + heading=0.0, + debugPoly=false, + useZ=true, + }) + CasinoZone:onPlayerInOut(function(inCasino) + if inCasino then + enterCasino() + else + exitCasino() + end + end) +end) + +function spinMeRightRoundBaby() + CreateThread(function() + while inCasino do + if not spinningObject or spinningObject == 0 or not DoesEntityExist(spinningObject) then + spinningObject = GetClosestObjectOfType(935.432, 42.5611, 72.14, 10.0, -1561087446, 0, 0, 0) + drawCarForWins() + end + if spinningObject ~= nil and spinningObject ~= 0 then + local curHeading = GetEntityHeading(spinningObject) + local curHeadingCar = GetEntityHeading(spinningCar) + if curHeading >= 360 then + curHeading = 0.0 + curHeadingCar = 0.0 + elseif curHeading ~= curHeadingCar then + curHeadingCar = curHeading + end + SetEntityHeading(spinningObject, curHeading + 0.075) + SetEntityHeading(spinningCar, curHeadingCar + 0.075) + end + Wait(0) + end + spinningObject = nil + end) +end + +function drawCarForWins() + if DoesEntityExist(spinningCar) then + DeleteEntity(spinningCar) + end + RequestModel(Config.VehicleOnDisplay) + while not HasModelLoaded(Config.VehicleOnDisplay) do + Wait(0) + end + SetModelAsNoLongerNeeded(Config.VehicleOnDisplay) + spinningCar = CreateVehicle(Config.VehicleOnDisplay, 935.432, 42.5611, 72.34, 0.0, 0, 0) + Wait(0) + SetVehicleDirtLevel(spinningCar, 0.0) + SetVehicleOnGroundProperly(spinningCar) + Wait(0) + FreezeEntityPosition(spinningCar, 1) +end + +function playSomeBackgroundAudioBaby() + CreateThread(function() + local function audioBanks() + while not RequestScriptAudioBank("DLC_VINEWOOD/CASINO_GENERAL", false, -1) do + Wait(0) + end + while not RequestScriptAudioBank("DLC_VINEWOOD/CASINO_SLOT_MACHINES_01", false, -1) do + Wait(0) + end + while not RequestScriptAudioBank("DLC_VINEWOOD/CASINO_SLOT_MACHINES_02", false, -1) do + Wait(0) + end + while not RequestScriptAudioBank("DLC_VINEWOOD/CASINO_SLOT_MACHINES_03", false, -1) do + Wait(0) + end + end + audioBanks() + while inCasino do + if not IsStreamPlaying() and LoadStream("casino_walla", "DLC_VW_Casino_Interior_Sounds") then + PlayStreamFromPosition(945.85, 41.58, 75.82) + end + if IsStreamPlaying() and not IsAudioSceneActive("DLC_VW_Casino_General") then + StartAudioScene("DLC_VW_Casino_General") + end + Wait(1000) + end + if IsStreamPlaying() then + StopStream() + end + if IsAudioSceneActive("DLC_VW_Casino_General") then + StopAudioScene("DLC_VW_Casino_General") + end + end) +end + + + + diff --git a/resources/[qb]/[qb_casino]/casino-walls/client/casino-teleports.lua b/resources/[qb]/[qb_casino]/casino-walls/client/casino-teleports.lua new file mode 100644 index 0000000..b56b3a9 --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-walls/client/casino-teleports.lua @@ -0,0 +1,99 @@ +local QBCore = exports['qb-core']:GetCoreObject() + +CreateThread(function() + local TeleportUpZone = CircleZone:Create(vector3(930.251, 34.324, 81.089), 1.5, { + name="TeleportUp", + heading=328.0, + debugPoly=false, + useZ=true, + }) + TeleportUpZone:onPlayerInOut(function(isPointInside) + if isPointInside then + -- exports['qb-core']:DrawText('The Diamond Casino & Resort

Elevators
') + QBCore.Functions.TriggerCallback('doj:server:HasCasinoMembership', function(HasItem) + if HasItem then + TriggerEvent("doj:casinoTeleportUp") + else + exports['qb-core']:DrawText('The Diamond Casino & Resort

NOT A MEMBER

Please visit the front desk!') + end + end) + else + exports['qb-menu']:closeMenu() + exports["qb-core"]:HideText() + end + end) +end) + +CreateThread(function() + local TeleportDownZone = CircleZone:Create(vector3(964.737, 58.743, 112.553), 1.5, { + name="TeleportDown", + heading=328.0, + debugPoly=false, + useZ=true, + }) + TeleportDownZone:onPlayerInOut(function(isPointInside) + if isPointInside then + TriggerEvent("doj:casinoTeleportDown") + else + exports['qb-menu']:closeMenu() + end + end) +end) + +RegisterNetEvent('doj:casinoTeleports', function(args) + local args = tonumber(args) + local ped = PlayerPedId() + if args == 1 then + SetEntityCoords(ped, 965.0619, 58.51287, 112.553, false, false, false, true) + else + SetEntityCoords(ped, 930.0716, 33.86854, 81.09772, false, false, false, true) + end +end) + +RegisterNetEvent('doj:casinoTeleportUp', function() + exports['qb-menu']:showHeader({ + { + header = "The Diamond Casino & Resort Elevators", + isMenuHeader = true, + }, + { + header = "Elevator Up", + txt = "Roof access", + params = { + event = "doj:casinoTeleports", + args = 1, + } + }, + { + header = "return", + txt = "", + params = { + event = "doj:casinoTeleportUp" + } + }, + }) +end) + +RegisterNetEvent('doj:casinoTeleportDown', function() + exports['qb-menu']:showHeader({ + { + header = "The Diamond Casino & Resort Elevators", + isMenuHeader = true, + }, + { + header = "Elevator Down", + txt = "Main Lobby", + params = { + event = "doj:casinoTeleports", + args = 2, + } + }, + { + header = "return", + txt = "", + params = { + event = "doj:casinoTeleportDown" + } + }, + }) +end) diff --git a/resources/[qb]/[qb_casino]/casino-walls/config.lua b/resources/[qb]/[qb_casino]/casino-walls/config.lua new file mode 100644 index 0000000..066a493 --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-walls/config.lua @@ -0,0 +1,32 @@ +Config = {} + +Config.PlayCasinoAmbientNoise = true +Config.SetShowCarOnDisplay = true +Config.VehicleOnDisplay = `crownbp` + + +Config.SetAnimatedWalls = true +Config.AnimatedWallNormal = 'CASINO_DIA_PL' +Config.AnimatedWallWin = 'CASINO_WIN_PL' +-- WALLS +-- CASINO_DIA_PL - Falling Diamonds +-- CASINO_HLW_PL - Falling Skulls +-- CASINO_SNWFLK_PL - Falling Snowflakes +-- CASINO_WIN_PL - Falling Confetti + +Config.SendWelcomeMail = false -- Sends a email to players phone on entering casino +Config.WelcomeMailsender = "The Diamond Casino & Resort" +Config.WelcomeMailsubject ="Welcome!" +Config.WelcomeMailmessage = "Welcome to The Diamond Casino & Resort, We are Open 24/7 & only accept Electronic Transactions" + +Config.payment = "bank" -- or "cash" -- Payment player will recieve by selling casino chips + +Config.casinoChipPrice = 100 +Config.casinoMemberPrice = 500 +Config.casinoVIPPrice = 500 + +Config.CasinoShop = { + [1] = { name = "casino_redchip", price = 100, amount = 1000, info = {}, type = "item", slot = 1 }, + -- [2] = { name = "casino_member", price = 500, amount = 50, info = {}, type = "item", slot = 2 }, + -- [3] = { name = "casino_vip", price = 750, amount = 50, info = {}, type = "item", slot = 3 } +} \ No newline at end of file diff --git a/resources/[qb]/[qb_casino]/casino-walls/fxmanifest.lua b/resources/[qb]/[qb_casino]/casino-walls/fxmanifest.lua new file mode 100644 index 0000000..0f87c4a --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-walls/fxmanifest.lua @@ -0,0 +1,21 @@ +fx_version 'cerulean' +games { 'gta5' } + + +shared_scripts { + 'config.lua', +} + + +client_scripts{ + '@PolyZone/client.lua', + '@PolyZone/BoxZone.lua', + '@PolyZone/EntityZone.lua', + '@PolyZone/CircleZone.lua', + '@PolyZone/ComboZone.lua', + 'client/*.lua' +} + +server_scripts{ + 'server/*.lua' +} \ No newline at end of file diff --git a/resources/[qb]/[qb_casino]/casino-walls/server/casino.lua b/resources/[qb]/[qb_casino]/casino-walls/server/casino.lua new file mode 100644 index 0000000..dc522ef --- /dev/null +++ b/resources/[qb]/[qb_casino]/casino-walls/server/casino.lua @@ -0,0 +1,145 @@ +local QBCore = exports['qb-core']:GetCoreObject() + +local quantity = 0 +local ItemList = { + ["casino_redchip"] = 1, +} + +RegisterServerEvent("qb-casino:server:GoldSell") +AddEventHandler("qb-casino:server:GoldSell", function() + local src = source + local price = Config.casinoChipPrice + local Player = QBCore.Functions.GetPlayer(src) + local xItem = Player.Functions.GetItemByName("casino_redchip") + if xItem ~= nil then + local quantity = 0 + for k, v in pairs(Player.PlayerData.items) do + if Player.PlayerData.items[k] ~= nil then + if ItemList[Player.PlayerData.items[k].name] ~= nil then + quantity = quantity + Player.PlayerData.items[k].amount + Player.Functions.RemoveItem(Player.PlayerData.items[k].name, Player.PlayerData.items[k].amount, k) + end + end + end + price = price * quantity + Player.Functions.AddMoney(Config.payment, price, "sold-casino-chips") + TriggerClientEvent('inventory:client:ItemBox', source, QBCore.Shared.Items['casino_redchip'], "remove", quantity) + TriggerClientEvent('QBCore:Notify', src, "You sold "..quantity.." Gold chips for $"..price) + TriggerEvent('qb-log:server:CreateLog', 'guedesteste', 'Dinheiro Venda | '..Player.PlayerData.name, 'default', quantity.." fichas de ouro por "..price.."€") + TriggerClientEvent("doj:casinoChipMenu", src) + quantity = 0 + else + TriggerClientEvent('QBCore:Notify', src, "You dont have any gold casino chips...", "error") + TriggerClientEvent("doj:casinoChipMenu", src) + end +end) + + +RegisterNetEvent("doj:server:purchaseMembership", function() + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local MembershipCheck = Player.Functions.GetItemByName('casino_member') + if MembershipCheck ~= nil then + TriggerClientEvent('doj:casinoMembershipMenu', src) + TriggerClientEvent('QBCore:Notify', src, 'You already have a Membership', 'error') + else + if Player.Functions.AddItem('casino_member', 1, false, info) then + TriggerClientEvent('inventory:client:ItemBox', src, QBCore.Shared.Items['casino_member'], "add", 1) + TriggerClientEvent('doj:casinoMembershipMenu', src) + + end + end +end) + +RegisterNetEvent("doj:server:purchaseVIPMembership", function() + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local VIPMembershipCheck = Player.Functions.GetItemByName('casino_vip') + if VIPMembershipCheck ~= nil then + TriggerClientEvent('doj:casinoMembershipMenu', src) + TriggerClientEvent('QBCore:Notify', src, 'You already have a Membership', 'error') + else + if Player.Functions.AddItem('casino_vip', 1, false, info) then + TriggerClientEvent('inventory:client:ItemBox', src, QBCore.Shared.Items['casino_vip'], "add", 1) + TriggerClientEvent('doj:casinoMembershipMenu', src) + + end + end +end) + + +QBCore.Functions.CreateCallback('doj:server:HasCasinoMembership', function(source, cb) + local Player = QBCore.Functions.GetPlayer(source) + local Item = Player.Functions.GetItemByName("casino_member") + + if Item ~= nil then + cb(true) + else + cb(false) + end +end) + +QBCore.Functions.CreateCallback('doj:server:HasVIPMembership', function(source, cb) + local Player = QBCore.Functions.GetPlayer(source) + local Item = Player.Functions.GetItemByName("casino_vip") + + if Item ~= nil then + cb(true) + else + cb(false) + end +end) + + + + + +-- RegisterNetEvent("doj:server:validateMembership") +-- AddEventHandler("doj:server:validateMembership", function(args) +-- local src = source +-- local Player = QBCore.Functions.GetPlayer(src) +-- local args = tonumber(args) +-- if args == 1 then +-- if Player.Functions.GetItemByName("casino_member") then +-- Player.Functions.RemoveItem("casino_member", 1) +-- local info = { +-- owner = Player.PlayerData.charinfo.firstname.." "..Player.PlayerData.charinfo.lastname, +-- } +-- Player.Functions.AddItem("casino_member_validated", 1, false, info) +-- TriggerClientEvent('inventory:client:ItemBox', src, QBCore.Shared.Items["casino_member_validated"], "add", 1) +-- TriggerClientEvent('QBCore:Notify', src, "Membership has been validated", "success") +-- else +-- TriggerClientEvent('QBCore:Notify', src, "You need to buy a Casino Membership first", "error") +-- end +-- else +-- if Player.Functions.GetItemByName("casino_vip") then +-- Player.Functions.RemoveItem("casino_vip", 1) +-- local info = { +-- owner = Player.PlayerData.charinfo.firstname.." "..Player.PlayerData.charinfo.lastname, +-- } +-- Player.Functions.AddItem("casino_vip_validated", 1, false, info) +-- TriggerClientEvent('inventory:client:ItemBox', src, QBCore.Shared.Items["casino_vip_validated"], "add", 1) +-- TriggerClientEvent('QBCore:Notify', src, "Membership has been validated", "success") +-- else +-- TriggerClientEvent('QBCore:Notify', src, "You need to buy a V.I.P Membership first", "error") +-- end +-- end +-- end) + + + + + + + + + + + + + + + + + + diff --git a/resources/[qb]/[qb_core]/qb-crypto/LICENSE b/resources/[qb]/[qb_core]/qb-crypto/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-crypto/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/resources/[qb]/[qb_core]/qb-crypto/README.md b/resources/[qb]/[qb_core]/qb-crypto/README.md new file mode 100644 index 0000000..aefb551 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-crypto/README.md @@ -0,0 +1,23 @@ +# qb-crypto +Crypto Currency For QB-Core + +# Dependency +https://github.com/qbcore-framework/mhacking + +# License + + QBCore Framework + Copyright (C) 2021 Joshua Eger + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see diff --git a/resources/[qb]/[qb_core]/qb-crypto/client/main.lua b/resources/[qb]/[qb_core]/qb-crypto/client/main.lua new file mode 100644 index 0000000..caa6002 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-crypto/client/main.lua @@ -0,0 +1,130 @@ +-- Variables +local QBCore = exports['qb-core']:GetCoreObject() +local requiredItemsShowed = false +local requiredItems = {[1] = {name = QBCore.Shared.Items["cryptostick"]["name"], image = QBCore.Shared.Items["cryptostick"]["image"]}} + +-- Functions + +local function DrawText3Ds(coords, text) + SetTextScale(0.35, 0.35) + SetTextFont(4) + SetTextProportional(1) + SetTextColour(255, 255, 255, 215) + SetTextEntry("STRING") + SetTextCentre(true) + AddTextComponentString(text) + SetDrawOrigin(coords.x, coords.y, coords.z, 0) + DrawText(0.0, 0.0) + local factor = (string.len(text)) / 370 + DrawRect(0.0, 0.0+0.0125, 0.017+ factor, 0.03, 0, 0, 0, 75) + ClearDrawOrigin() +end + +local function ExchangeSuccess() + TriggerServerEvent('qb-crypto:server:ExchangeSuccess', math.random(1, 10)) +end + +local function ExchangeFail() + local Odd = 5 + local RemoveChance = math.random(1, Odd) + local LosingNumber = math.random(1, Odd) + if RemoveChance == LosingNumber then + TriggerServerEvent('qb-crypto:server:ExchangeFail') + TriggerServerEvent('qb-crypto:server:SyncReboot') + end +end + +local function SystemCrashCooldown() + CreateThread(function() + while Crypto.Exchange.RebootInfo.state do + if (Crypto.Exchange.RebootInfo.percentage + 1) <= 100 then + Crypto.Exchange.RebootInfo.percentage = Crypto.Exchange.RebootInfo.percentage + 1 + TriggerServerEvent('qb-crypto:server:Rebooting', true, Crypto.Exchange.RebootInfo.percentage) + else + Crypto.Exchange.RebootInfo.percentage = 0 + Crypto.Exchange.RebootInfo.state = false + TriggerServerEvent('qb-crypto:server:Rebooting', false, 0) + end + Wait(1200) + end + end) +end + +local function HackingSuccess(success) + if success then + TriggerEvent('mhacking:hide') + ExchangeSuccess() + else + TriggerEvent('mhacking:hide') + ExchangeFail() + end +end + +CreateThread(function() + while true do + local sleep = 5000 + if LocalPlayer.state.isLoggedIn then + local ped = PlayerPedId() + local pos = GetEntityCoords(ped) + local dist = #(pos - Crypto.Exchange.coords) + if dist < 15 then + sleep = 5 + if dist < 1.5 then + if not Crypto.Exchange.RebootInfo.state then + DrawText3Ds(Crypto.Exchange.coords, Lang:t('text.enter_usb')) + if not requiredItemsShowed then + requiredItemsShowed = true + TriggerEvent('inventory:client:requiredItems', requiredItems, true) + end + + if IsControlJustPressed(0, 38) then + QBCore.Functions.TriggerCallback('qb-crypto:server:HasSticky', function(HasItem) + if HasItem then + TriggerEvent("mhacking:show") + TriggerEvent("mhacking:start", math.random(4, 6), 45, HackingSuccess) + else + QBCore.Functions.Notify(Lang:t('error.you_dont_have_a_cryptostick'), 'error') + end + end) + end + else + DrawText3Ds(Crypto.Exchange.coords, Lang:t('text.system_is_rebooting', {rebootInfoPercentage = Crypto.Exchange.RebootInfo.percentage}) ) + end + else + if requiredItemsShowed then + requiredItemsShowed = false + TriggerEvent('inventory:client:requiredItems', requiredItems, false) + end + end + end + end + Wait(sleep) + end +end) + +-- Events + +RegisterNetEvent('qb-crypto:client:SyncReboot', function() + Crypto.Exchange.RebootInfo.state = true + SystemCrashCooldown() +end) + +RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() + TriggerServerEvent('qb-crypto:server:FetchWorth') + TriggerServerEvent('qb-crypto:server:GetRebootState') +end) + +RegisterNetEvent('qb-crypto:client:UpdateCryptoWorth', function(crypto, amount, history) + Crypto.Worth[crypto] = amount + if history ~= nil then + Crypto.History[crypto] = history + end +end) + +RegisterNetEvent('qb-crypto:client:GetRebootState', function(RebootInfo) + if RebootInfo.state then + Crypto.Exchange.RebootInfo.state = RebootInfo.state + Crypto.Exchange.RebootInfo.percentage = RebootInfo.percentage + SystemCrashCooldown() + end +end) diff --git a/resources/[qb]/[qb_core]/qb-crypto/config.lua b/resources/[qb]/[qb_core]/qb-crypto/config.lua new file mode 100644 index 0000000..4ae4431 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-crypto/config.lua @@ -0,0 +1,55 @@ +Crypto = { + Lower = 500, + Upper = 5000, + History = { + ["qbit"] = {} + }, + + Worth = { + ["qbit"] = 1000 + }, + + Labels = { + ["qbit"] = "Qbit" + }, + + Exchange = { + coords = vector3(1276.21, -1709.88, 54.57), + RebootInfo = { + state = false, + percentage = 0 + }, + }, + + -- For auto updating the value of qbit + Coin = 'qbit', + RefreshTimer = 10, -- In minutes, so every 10 minutes. + + -- Crashes or luck + ChanceOfCrashOrLuck = 2, -- This is in % (1-100) + Crash = {20,80}, -- Min / Max + Luck = {20,45}, -- Min / Max + + -- If not not Chance of crash or luck, then this shit + ChanceOfDown = 30, -- If out of 100 hits less or equal to + ChanceOfUp = 60, -- If out of 100 is greater or equal to + CasualDown = {1,10}, -- Min / Max (If it goes down) + CasualUp = {1,10}, -- Min / Max (If it goes up) +} + + + + +Ticker = { + Enabled = false, -- Decide whether the real life price ticker should be enabled or not :) + coin = 'BTC', --- The coin, please make sure you find the actual name, for example: Bitcoin vs BTC, BTC would be correct + currency = 'DKK', -- For example USD, NOK, SEK, EUR, CAD and more here https://www.countries-ofthe-world.com/world-currencies.html + tick_time = 2, --- Minutes (Minimum is 2 minutes) 20,160 Requests a month, Its recommended to get the free API key so the crypto script doesnt switch on and off if ratelimit is encountered + Api_key = '2c7b4c7ce20a31818c81d9168be47503b756951e42dec288ae4679acc6f6c6d0', -- If you decide to get an api key for the API (https://min-api.cryptocompare.com/pricing) The free plan should be more than enough for 1 Fivem server + --- Error handle stuff, for more user friendly and readable errors, Don't touch. + Error_handle = { + ['fsym is a required param.'] = 'Config error: Invalid / Missing coin name', + ['tsyms is a required param.'] = 'Config error: Invalid / Missing currency', + ['cccagg_or_exchange'] = 'Config error: Invalid currency / coin combination', -- For some reason api throws this error if either coin or currency is invalid + }, +} diff --git a/resources/[qb]/[qb_core]/qb-crypto/fxmanifest.lua b/resources/[qb]/[qb_core]/qb-crypto/fxmanifest.lua new file mode 100644 index 0000000..2bd2801 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-crypto/fxmanifest.lua @@ -0,0 +1,20 @@ +fx_version 'cerulean' +game 'gta5' + +description 'QB-Crypto' +version '1.2.0' + +shared_scripts { + '@qb-core/shared/locale.lua', + 'locales/da.lua', + 'config.lua' +} +server_scripts { + '@oxmysql/lib/MySQL.lua', + 'server/main.lua' +} +client_script 'client/main.lua' + +dependency 'mhacking' + +lua54 'yes' diff --git a/resources/[qb]/[qb_core]/qb-crypto/locales/da.lua b/resources/[qb]/[qb_core]/qb-crypto/locales/da.lua new file mode 100644 index 0000000..36765a9 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-crypto/locales/da.lua @@ -0,0 +1,33 @@ +local Translations = { + error = { + you_dont_have_a_cryptostick = 'Du har ikke en cryptostic', + one_bus_active = 'Du kan kun have en BUS aktiv ad gangen', + drop_off_passengers = 'Aflever passagererne, før du stopper med at arbejde', + cryptostick_malfunctioned = 'Cryptostick fungerede ikke korrekt' + }, + success = { + you_have_exchanged_your_cryptostick_for = 'Du byttede din cryptostick for: %{amount} QBit(s)' + }, + credit = { + there_are_amount_credited = 'Der er %{amount} Qbit(s) krediteret!', + you_have_qbit_purchased = 'Du har købt %{dataCoins} Qbit(s)!' + }, + depreciation = { + you_have_sold = 'Du har solgt %{dataCoins} Qbit(s)!' + }, + text = { + enter_usb = '[E] - Indsæt USB', + system_is_rebooting = 'System genstarter - %{rebootInfoPercentage} %', + you_have_not_given_a_new_value = 'Du har ikke angivet en ny værdi.. Nuværrende værdi: %{crypto}', + this_crypto_does_not_exist = 'Denne Crypto findes ikke. Tilgængelig: Qbit', + you_have_not_provided_crypto_available_qbit = 'Du har ikke angivet Crypto. Tilgængelig: Qbit', + the_qbit_has_a_value_of = 'QBIT har en værdi af: %{crypto}', + you_have_with_a_value_of = 'Du har %{playerPlayerDataMoneyCrypto} QBit, til en værdi af %{mypocket},-' + } +} + +Lang = Locale:new({ + phrases = Translations, + warnOnMissing = true, + fallbackLang = Lang, +}) diff --git a/resources/[qb]/[qb_core]/qb-crypto/qb-crypto.sql b/resources/[qb]/[qb_core]/qb-crypto/qb-crypto.sql new file mode 100644 index 0000000..acff1df --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-crypto/qb-crypto.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS `crypto` ( + `crypto` varchar(50) NOT NULL DEFAULT 'qbit', + `worth` int(11) NOT NULL DEFAULT 0, + `history` text DEFAULT NULL, + PRIMARY KEY (`crypto`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +INSERT INTO `crypto` VALUES ('qbit', 1000, '[{"NewWorth":1000,"PreviousWorth":1000}]'); diff --git a/resources/[qb]/[qb_core]/qb-crypto/server/main.lua b/resources/[qb]/[qb_core]/qb-crypto/server/main.lua new file mode 100644 index 0000000..a741777 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-crypto/server/main.lua @@ -0,0 +1,371 @@ +-- Variables +local coin = Crypto.Coin +local QBCore = exports['qb-core']:GetCoreObject() +local bannedCharacters = {'%','$',';'} + +-- Function +local function RefreshCrypto() + local result = MySQL.query.await('SELECT * FROM crypto WHERE crypto = ?', { coin }) + if result ~= nil and result[1] ~= nil then + Crypto.Worth[coin] = result[1].worth + if result[1].history ~= nil then + Crypto.History[coin] = json.decode(result[1].history) + TriggerClientEvent('qb-crypto:client:UpdateCryptoWorth', -1, coin, result[1].worth, json.decode(result[1].history)) + else + TriggerClientEvent('qb-crypto:client:UpdateCryptoWorth', -1, coin, result[1].worth, nil) + end + end +end + +local function ErrorHandle(error) + for k, v in pairs(Ticker.Error_handle) do + if string.match(error, k) then + return v + end + end + return false +end + +local function GetTickerPrice() -- Touch = no help + local ticker_promise = promise.new() + PerformHttpRequest("https://min-api.cryptocompare.com/data/price?fsym=" .. Ticker.coin .. "&tsyms=" .. Ticker.currency .. '&api_key=' .. Ticker.Api_key, function(Error, Result, _) + local result_obj = json.decode(Result) + if not result_obj['Response'] then + local this_resolve = {error = Error, response_data = result_obj[string.upper(Ticker.currency)]} + ticker_promise:resolve(this_resolve) --- Could resolve Error aswell for more accurate Error messages? Solved in else + else + local this_resolve = {error = result_obj['Message']} + ticker_promise:resolve(this_resolve) + end + end, 'GET') + Citizen.Await(ticker_promise) + if type(ticker_promise.value.error) ~= 'number' then + local get_user_friendly_error = ErrorHandle(ticker_promise.value.error) + if get_user_friendly_error then + return get_user_friendly_error + else + return '\27[31m Unexpected error \27[0m' --- Raised an error which we did not expect, script should be capable of sticking with last recorded price and shutting down the sync logic + end + else + return ticker_promise.value.response_data + end +end + +local function HandlePriceChance() + local currentValue = Crypto.Worth[coin] + local prevValue = Crypto.Worth[coin] + local trend = math.random(0,100) + local event = math.random(0,100) + local chance = event - Crypto.ChanceOfCrashOrLuck + + if event > chance then + if trend <= Crypto.ChanceOfDown then + currentValue = currentValue - math.random(Crypto.CasualDown[1], Crypto.CasualDown[2]) + elseif trend >= Crypto.ChanceOfUp then + currentValue = currentValue + math.random(Crypto.CasualUp[1], Crypto.CasualUp[2]) + end + else + if math.random(0, 1) == 1 then + currentValue = currentValue + math.random(Crypto.Luck[1], Crypto.Luck[2]) + else + currentValue = currentValue - math.random(Crypto.Crash[1], Crypto.Crash[2]) + end + end + + if currentValue <= Crypto.Lower then + currentValue = Crypto.Lower + elseif currentValue >= Crypto.Upper then + currentValue = Crypto.Upper + end + + if Crypto.History[coin][4] then + -- Shift array index 1 to 3 + for k=3,1,-1 do + Crypto.History[coin][k] = Crypto.History[coin][k+1] + end + -- Assign array index 4 to the latest result + Crypto.History[coin][4] = {PreviousWorth = prevValue, NewWorth = currentValue} + else + Crypto.History[coin][#Crypto.History[coin] + 1] = {PreviousWorth = prevValue, NewWorth = currentValue} + end + + Crypto.Worth[coin] = currentValue + + local history = json.encode(Crypto.History[coin]) + local props = { + ['worth'] = currentValue, + ['history'] = history, + ['crypto'] = coin + } + MySQL.update( + 'UPDATE crypto set worth = :worth, history = :history where crypto = :crypto', + props, + function(affectedRows) + if affectedRows < 1 then + print("Crypto not found, inserting new record for " .. coin) + MySQL.insert('INSERT INTO crypto (crypto, worth, history) VALUES (:crypto, :worth, :history)', props) + end + RefreshCrypto() + end + ) +end + +-- Commands + +QBCore.Commands.Add("setcryptoworth", "Sæt crypto værdi", {{name="crypto", help="navn på crypto"}, {name="Værdi", help="Den nye værdi"}}, false, function(source, args) + local src = source + local crypto = tostring(args[1]) + + if crypto ~= nil then + if Crypto.Worth[crypto] ~= nil then + local NewWorth = math.ceil(tonumber(args[2])) + + if NewWorth ~= nil then + local PercentageChange = math.ceil(((NewWorth - Crypto.Worth[crypto]) / Crypto.Worth[crypto]) * 100) + local ChangeLabel = "+" + + if PercentageChange < 0 then + ChangeLabel = "-" + PercentageChange = (PercentageChange * -1) + end + + if Crypto.Worth[crypto] == 0 then + PercentageChange = 0 + ChangeLabel = "" + end + + Crypto.History[crypto][#Crypto.History[crypto]+1] = { + PreviousWorth = Crypto.Worth[crypto], + NewWorth = NewWorth + } + + TriggerClientEvent('QBCore:Notify', src, "Du ændrede værdien af "..Crypto.Labels[crypto].." fra "..Crypto.Worth[crypto]..",- til "..NewWorth..",- ("..ChangeLabel.." "..PercentageChange.."%)") + Crypto.Worth[crypto] = NewWorth + TriggerClientEvent('qb-crypto:client:UpdateCryptoWorth', -1, crypto, NewWorth) + --Update crypto.worth + MySQL.update('UPDATE crypto set worth = :worth where crypto = :crypto', { + ['worth'] = NewWorth, + ['crypto'] = crypto + }) + else + TriggerClientEvent('QBCore:Notify', src, Lang:t('text.you_have_not_given_a_new_value', {crypto = Crypto.Worth[crypto]})) + end + else + TriggerClientEvent('QBCore:Notify', src, Lang:t('text.this_crypto_does_not_exist')) + end + else + TriggerClientEvent('QBCore:Notify', src, Lang:t('text.you_have_not_provided_crypto_available_qbit')) + end +end, "admin") + +QBCore.Commands.Add("checkcryptoworth", "", {}, false, function(source) + local src = source + TriggerClientEvent('QBCore:Notify', src, Lang:t('text.the_qbit_has_a_value_of', {crypto = Crypto.Worth["qbit"]})) +end) + +QBCore.Commands.Add("crypto", "", {}, false, function(source) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local MyPocket = math.ceil(Player.PlayerData.money.crypto * Crypto.Worth["qbit"]) + + TriggerClientEvent('QBCore:Notify', src, Lang:t('text.you_have_with_a_value_of',{playerPlayerDataMoneyCrypto = Player.PlayerData.money.crypto,mypocket = MyPocket})) +end) + +-- Events + +RegisterServerEvent('qb-crypto:server:FetchWorth', function() + for name,_ in pairs(Crypto.Worth) do + local result = MySQL.query.await('SELECT * FROM crypto WHERE crypto = ?', { name }) + if result[1] ~= nil then + Crypto.Worth[name] = result[1].worth + if result[1].history ~= nil then + Crypto.History[name] = json.decode(result[1].history) + TriggerClientEvent('qb-crypto:client:UpdateCryptoWorth', -1, name, result[1].worth, json.decode(result[1].history)) + else + TriggerClientEvent('qb-crypto:client:UpdateCryptoWorth', -1, name, result[1].worth, nil) + end + end + end +end) + +RegisterServerEvent('qb-crypto:server:ExchangeFail', function() + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local ItemData = Player.Functions.GetItemByName("cryptostick") + + if ItemData ~= nil then + Player.Functions.RemoveItem("cryptostick", 1) + TriggerClientEvent('inventory:client:ItemBox', src, QBCore.Shared.Items["cryptostick"], "remove") + TriggerClientEvent('QBCore:Notify', src, Lang:t('error.cryptostick_malfunctioned'), 'error') + end +end) + +RegisterServerEvent('qb-crypto:server:Rebooting', function(state, percentage) + Crypto.Exchange.RebootInfo.state = state + Crypto.Exchange.RebootInfo.percentage = percentage +end) + +RegisterServerEvent('qb-crypto:server:GetRebootState', function() + local src = source + TriggerClientEvent('qb-crypto:client:GetRebootState', src, Crypto.Exchange.RebootInfo) +end) + +RegisterServerEvent('qb-crypto:server:SyncReboot', function() + TriggerClientEvent('qb-crypto:client:SyncReboot', -1) +end) + +RegisterServerEvent('qb-crypto:server:ExchangeSuccess', function(LuckChance) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local ItemData = Player.Functions.GetItemByName("cryptostick") + + if ItemData ~= nil then + local LuckyNumber = math.random(1, 10) + local DeelNumber = 1000000 + local Amount = (math.random(611111, 1599999) / DeelNumber) + if LuckChance == LuckyNumber then + Amount = (math.random(1599999, 2599999) / DeelNumber) + end + + Player.Functions.RemoveItem("cryptostick", 1) + Player.Functions.AddMoney('crypto', Amount) + TriggerClientEvent('QBCore:Notify', src, Lang:t('success.you_have_exchanged_your_cryptostick_for',{amount = Amount}), "success", 3500) + TriggerClientEvent('inventory:client:ItemBox', src, QBCore.Shared.Items["cryptostick"], "remove") + TriggerClientEvent('qb-phone:client:AddTransaction', src, Player, {}, Lang:t('credit.there_are_amount_credited',{amount = Amount}), "Modtog") + end +end) + +-- Callbacks + +QBCore.Functions.CreateCallback('qb-crypto:server:HasSticky', function(source, cb) + local Player = QBCore.Functions.GetPlayer(source) + local Item = Player.Functions.GetItemByName("cryptostick") + + if Item ~= nil then + cb(true) + else + cb(false) + end +end) + +QBCore.Functions.CreateCallback('qb-crypto:server:GetCryptoData', function(source, cb, name) + local Player = QBCore.Functions.GetPlayer(source) + local CryptoData = { + History = Crypto.History[name], + Worth = Crypto.Worth[name], + Portfolio = Player.PlayerData.money.crypto, + WalletId = Player.PlayerData.metadata["walletid"], + } + + cb(CryptoData) +end) + +QBCore.Functions.CreateCallback('qb-crypto:server:BuyCrypto', function(source, cb, data) + local Player = QBCore.Functions.GetPlayer(source) + local total_price = math.floor(tonumber(data.Coins) * tonumber(Crypto.Worth["qbit"])) + if Player.PlayerData.money.bank >= total_price then + local CryptoData = { + History = Crypto.History["qbit"], + Worth = Crypto.Worth["qbit"], + Portfolio = Player.PlayerData.money.crypto + tonumber(data.Coins), + WalletId = Player.PlayerData.metadata["walletid"], + } + Player.Functions.RemoveMoney('bank', total_price) + TriggerClientEvent('qb-phone:client:AddTransaction', source, Player, data, Lang:t('credit.you_have_qbit_purchased',{dataCoins = tonumber(data.Coins)}), "Modtog") + Player.Functions.AddMoney('crypto', tonumber(data.Coins)) + cb(CryptoData) + else + cb(false) + end +end) + +QBCore.Functions.CreateCallback('qb-crypto:server:SellCrypto', function(source, cb, data) + local Player = QBCore.Functions.GetPlayer(source) + + if Player.PlayerData.money.crypto >= tonumber(data.Coins) then + local CryptoData = { + History = Crypto.History["qbit"], + Worth = Crypto.Worth["qbit"], + Portfolio = Player.PlayerData.money.crypto - tonumber(data.Coins), + WalletId = Player.PlayerData.metadata["walletid"], + } + Player.Functions.RemoveMoney('crypto', tonumber(data.Coins)) + local amount = math.floor(tonumber(data.Coins) * tonumber(Crypto.Worth["qbit"])) + TriggerClientEvent('qb-phone:client:AddTransaction', source, Player, data, Lang:t('depreciation.you_have_sold',{dataCoins = tonumber(data.Coins)}), "Solgte") + Player.Functions.AddMoney('bank', amount) + cb(CryptoData) + else + cb(false) + end +end) + +QBCore.Functions.CreateCallback('qb-crypto:server:TransferCrypto', function(source, cb, data) + local newCoin = tostring(data.Coins) + local newWalletId = tostring(data.WalletId) + for _, v in pairs(bannedCharacters) do + newCoin = string.gsub(newCoin, '%' .. v, '') + newWalletId = string.gsub(newWalletId, '%' .. v, '') + end + data.WalletId = newWalletId + data.Coins = tonumber(newCoin) + local Player = QBCore.Functions.GetPlayer(source) + if Player.PlayerData.money.crypto >= tonumber(data.Coins) then + local query = '%"walletid":"' .. data.WalletId .. '"%' + local result = MySQL.query.await('SELECT * FROM `players` WHERE `metadata` LIKE ?', { query }) + if result[1] ~= nil then + local CryptoData = { + History = Crypto.History["qbit"], + Worth = Crypto.Worth["qbit"], + Portfolio = Player.PlayerData.money.crypto - tonumber(data.Coins), + WalletId = Player.PlayerData.metadata["walletid"], + } + Player.Functions.RemoveMoney('crypto', tonumber(data.Coins)) + TriggerClientEvent('qb-phone:client:AddTransaction', source, Player, data, "Du overførte "..tonumber(data.Coins).." Qbit('s)!", "Solgte") + local Target = QBCore.Functions.GetPlayerByCitizenId(result[1].citizenid) + + if Target ~= nil then + Target.Functions.AddMoney('crypto', tonumber(data.Coins)) + TriggerClientEvent('qb-phone:client:AddTransaction', Target.PlayerData.source, Player, data, "Du modtog "..tonumber(data.Coins).." Qbit('s)!", "Modtog") + else + local MoneyData = json.decode(result[1].money) + MoneyData.crypto = MoneyData.crypto + tonumber(data.Coins) + MySQL.update('UPDATE players SET money = ? WHERE citizenid = ?', { json.encode(MoneyData), result[1].citizenid }) + end + cb(CryptoData) + else + cb("notvalid") + end + else + cb("notenough") + end +end) + +-- Threads + +CreateThread(function() + while true do + Wait(Crypto.RefreshTimer*60000) + HandlePriceChance() + end +end) + +-- You touch = you break +if Ticker.Enabled then + Citizen.CreateThread(function() + local Interval = Ticker.tick_time * 60000 + if Ticker.tick_time < 2 then + Interval = 120000 + end + while(true) do + local get_coin_price = GetTickerPrice() + if type(get_coin_price) == 'number' then + Crypto.Worth["qbit"] = get_coin_price + else + print('\27[31m' .. get_coin_price .. '\27[0m') + Ticker.Enabled = false + break + end + Citizen.Wait(Interval) + end + end) +end diff --git a/resources/[qb]/[qb_core]/qb-doorlock/LICENSE b/resources/[qb]/[qb_core]/qb-doorlock/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-doorlock/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/resources/[qb]/[qb_core]/qb-doorlock/README.md b/resources/[qb]/[qb_core]/qb-doorlock/README.md new file mode 100644 index 0000000..7f25dc0 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-doorlock/README.md @@ -0,0 +1,113 @@ +# qb-doorlock +Doorlock System For QBCore + +This doorlock system is based on [nui_doorlock by thelindat](https://github.com/thelindat/nui_doorlock) and contains compatibility with it's format. + +## Dependencies + +* [qb-core](https://github.com/qbcore-framework/qb-core) +* [qb-input](https://github.com/qbcore-framework/qb-input) - For Making New Doors +* [qb-lockpick](https://github.com/qbcore-framework/qb-lockpick) - For Lockpicking Doors + +## Features + +* Quick in-game door creation with /newdoor +* Multiple door types to support all possible doors +* Support for item checking, multiple or single items +* Support for citizenid, gang and job checking +* Support for qb-lockpick +* NUI Text as interaction text +* Great performance +* Uses the native door system +* Support for object names and hashes +* Play any sound you want when unlocking/locking a door +* Highly customisable +* Auto lock, to automatically lock a door after it has been opened +* Admin access option +* Change color based on locked state + +## Single Door Configuration + + ['somesingledoor'] = { -- The index of the table, this is used as the doorID + objName = 'hei_v_ilev_bk_gate2_pris', -- Door object name can be a string or a number. Alias: objHash = 'hei_v_ilev_bk_gate2_pris', + objCoords = vec3(261.83, 221.39, 106.41), -- Object coords + textCoords = vec3(261.83, 221.39, 106.41), -- Coords for the interaction text + authorizedJobs = { ['police'] = 0 }, -- Job access (checks for a minimum grade of 0) + authorizedGangs = { ['vagos'] = 0 }, -- Gang access (checks for a minimum grade of 0) + authorizedCitizenIDs = { ['BUI05180'] = true }, -- Citizen ID access + items = { ['keycard'] = 1, ['banana'] = 3 }, -- Item access, can be a string or table | the value of the item is the amount of items needed when using a table, if it is a string it will always check if this person has one or more of the item + needsAllItems = false, -- true or false | Whether to check if the person has all items to unlock the door or just one of the items + allAuthorized = false, -- true or false | Will give access to everyone if it is true + objYaw = -110.0, -- Heading of the door. Alias: objHeading = -110.0, + locked = true, -- true or false | Is the door locked by default + pickable = false, -- true or false | Can the door be lockpicked. Alias: lockpick = false, + distance = 1.5, -- At what range the interaction text will show. Alis: maxDistance = 1.5, + doorType = 'door', -- The type of door, can be door, double, sliding, doublesliding or garage + fixText = true, -- true or false | fix the text to the center of the door + doorLabel = 'Cloakroom', -- Label of the door that shows up when nearby + audioRemote = true, -- true or false | Play sound from the player instead of the door + audioLock = {['file'] = 'metal-locker.ogg', ['volume'] = 0.6}, -- Play sound on door lock + audioUnlock = {['file'] = 'metallic-creak.ogg', ['volume'] = 0.7}, -- Play sound on door unlock + autoLock = 1000, -- Auto lock after this many miliseconds + doorRate = 1.0, -- The rate of the animation to lock/unlock the door + cantUnlock = true -- true or false | Set to true to not allow the player to unlock the door, only lock it. This means a script will have to trigger the unlock + hideLabel = true, -- Set to true to hide the popup label, for hiding doors + remoteTrigger = true, -- true or false | If you want to be able to remote trigger a door with H, put this here + }, + +## Double Door Configuration + + ['somedoubledoor'] = { -- The index of the table, this is used as the doorID, can be a number or a string + doors = { -- Table of doors which holds both doors' data + { + objName = 'v_ilev_rc_door2', -- Door object name can be a string or a number. Alias: objHash = 'v_ilev_rc_door2', + objYaw = 135.0, -- Heading of the door. Alias: objHeading = 135.0, + objCoords = vec3(-447.7283, 6006.702, 31.86523), -- Object coords + }, + + { + objName = 'v_ilev_rc_door2', -- Door object name can be a string or a number. Alias: objHash = 'v_ilev_rc_door2', + objYaw = -45.0, -- Heading of the door. Alias: objHeading = -45.0, + objCoords = vec3(-449.5656, 6008.538, 31.86523), -- Object coords + }, + }, + textCoords = vec3(-448.67, 6007.52, 31.86523), -- Coords for the interaction text + authorizedJobs = { ['police'] = 0 }, -- Job access (checks for a minimum grade of 0) + authorizedGangs = { ['vagos'] = 0 }, -- Gang access (checks for a minimum grade of 0) + authorizedCitizenIDs = { ['BUI05180'] = true }, -- Citizen ID access + items = { ['keycard'] = 1, ['banana'] = 3 }, -- Item access, can be a string or table | the value of the item is the amount of items needed when using a table, if it is a string it will always check if this person has one or more of the item + needsAllItems = false, -- true or false | Whether to check if the person has all items to unlock the door or just one of the items + allAuthorized = false, -- true or false | Will give access to everyone if it is true + locked = true, -- true or false | Is the door locked by default + pickable = false, -- true or false | Can the door be lockpicked. Alias: lockpick = false, + distance = 2.5, -- At what range the interaction text will show. Alis: maxDistance = 2.5, + doorType = 'double', -- The type of door, can be door, double, sliding, doublesliding or garage + fixText = true, -- true or false | fix the text to the center of the door + doorLabel = 'Cloakroom', -- Label of the door that shows up when nearby + audioRemote = true, -- true or false | Play sound from the player instead of the door + audioLock = {['file'] = 'metal-locker.ogg', ['volume'] = 0.6}, -- Play sound on door lock + audioUnlock = {['file'] = 'metallic-creak.ogg', ['volume'] = 0.7}, -- Play sound on door unlock + autoLock = 1000, -- Auto lock after this many miliseconds + doorRate = 1.0, -- The rate of the animation to lock/unlock the door + cantUnlock = true -- true or false | Set to true to not allow the player to unlock the door, only lock it. This means a script will have to trigger the unlock + hideLabel = true, -- Set to true to hide the popup label, for hiding doors + remoteTrigger = true, -- true or false | If you want to be able to remote trigger a door with H, put this here + }, + +# License + + QBCore Framework + Copyright (C) 2021 Joshua Eger + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-doorlock/client/main.lua b/resources/[qb]/[qb_core]/qb-doorlock/client/main.lua new file mode 100644 index 0000000..c9a07a2 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-doorlock/client/main.lua @@ -0,0 +1,886 @@ +local QBCore = exports['qb-core']:GetCoreObject() +local PlayerData = QBCore.Functions.GetPlayerData() +local isLoggedIn = LocalPlayer.state['isLoggedIn'] +local Config = Config +local canContinue = true +local playerPed = PlayerPedId() +local playerCoords = GetEntityCoords(playerPed) +local lastCoords = playerCoords +local nearbyDoors, closestDoor = {}, {} +local paused = false +local usingAdvanced = false +local doorData = {} + +-- Functions +function Draw3DText(coords, str) + local onScreen, worldX, worldY = World3dToScreen2d(coords.x, coords.y, coords.z) + local camCoords = GetGameplayCamCoord() + local scale = 200 / (GetGameplayCamFov() * #(camCoords - coords)) + if onScreen then + SetTextScale(1.0, 0.5 * scale) + SetTextFont(4) + SetTextColour(255, 255, 255, 255) + SetTextEdge(2, 0, 0, 0, 150) + SetTextProportional(1) + SetTextOutline() + SetTextCentre(1) + SetTextEntry("STRING") + AddTextComponentString(str) + DrawText(worldX, worldY) + end +end + +local function raycastWeapon() + local offset = GetOffsetFromEntityInWorldCoords(GetCurrentPedWeaponEntityIndex(playerPed), 0, 0, -0.01) + local direction = GetGameplayCamRot() + direction = vec2(direction.x * math.pi / 180.0, direction.z * math.pi / 180.0) + local num = math.abs(math.cos(direction.x)) + direction = vec3((-math.sin(direction.y) * num), (math.cos(direction.y) * num), math.sin(direction.x)) + local destination = vec3(offset.x + direction.x * 30, offset.y + direction.y * 30, offset.z + direction.z * 30) + local hit, entityHit, result + local rayHandle = StartShapeTestLosProbe(offset, destination, -1, playerPed, 0) + repeat + result, hit, _, _, entityHit = GetShapeTestResult(rayHandle) + Wait(0) + until result ~= 1 + if GetEntityType(entityHit) == 3 then return hit, entityHit else return false, 0 end +end + +local function RotationToDirection(rotation) + local adjustedRotation = + { + x = (math.pi / 180) * rotation.x, + y = (math.pi / 180) * rotation.y, + z = (math.pi / 180) * rotation.z + } + local direction = + { + x = -math.sin(adjustedRotation.z) * math.abs(math.cos(adjustedRotation.x)), + y = math.cos(adjustedRotation.z) * math.abs(math.cos(adjustedRotation.x)), + z = math.sin(adjustedRotation.x) + } + return direction +end + +local function RayCastGamePlayCamera(distance) + local cameraRotation = GetGameplayCamRot() + local cameraCoord = GetGameplayCamCoord() + local direction = RotationToDirection(cameraRotation) + local destination = + { + x = cameraCoord.x + direction.x * distance, + y = cameraCoord.y + direction.y * distance, + z = cameraCoord.z + direction.z * distance + } + local _, hit, endCoords, _, _ = GetShapeTestResult(StartShapeTestRay(cameraCoord.x, cameraCoord.y, cameraCoord.z, destination.x, destination.y, destination.z, -1, PlayerPedId(), 0)) + return hit == 1, endCoords +end + + +local function setTextCoords(data) + local minDimension, maxDimension = GetModelDimensions(data.objName or data.objHash) + local dimensions = maxDimension - minDimension + local dx, dy = tonumber(dimensions.x), tonumber(dimensions.y) + if dy <= -1 or dy >= 1 then dx = dy end + if data.fixText then + return GetOffsetFromEntityInWorldCoords(data.object, dx / 2, 0, 0) + else + return GetOffsetFromEntityInWorldCoords(data.object, -dx / 2, 0, 0) + end +end + +local function getTextCoords(door) + if door.setText then return door.textCoords end + return setTextCoords(door) +end + +local function round(value, numDecimalPlaces) + if not numDecimalPlaces then return math.floor(value + 0.5) end + local power = 10 ^ numDecimalPlaces + return math.floor((value * power) + 0.5) / (power) +end + +local function loadAnimDict(dict) + RequestAnimDict(dict) + while not HasAnimDictLoaded(dict) do + Wait(0) + end +end + +local function displayNUIText(text) + local color = Config.ChangeColor and (closestDoor.data.locked and Config.LockedColor or Config.UnlockedColor) or Config.DefaultColor + SendNUIMessage({ + type = "setDoorText", + enable = true, + text = text, + color = color + }) + Wait(1) +end + +local function HandleDoorDebug() + if not Config.DoorDebug then return end + + CreateThread(function() + while Config.DoorDebug do + if closestDoor.data then + Draw3DText(closestDoor.data.textCoords, closestDoor.data.doorLabel or 'Dør her') + end + Wait(0) + end + end) +end + +local function hideNUI() + SendNUIMessage({ + type = "setDoorText", + enable = false + }) + Wait(1) +end + +local function playSound(door, src, enableSounds) + if not Config.EnableSounds or not enableSounds then return end + local origin + if src and src ~= playerPed then src = NetworkGetEntityFromNetworkId(src) end + if not src then origin = door.textCoords elseif src == playerPed then origin = playerCoords else origin = NetworkGetPlayerCoords(src) end + local distance = #(playerCoords - origin) + if distance < 10 then + if not door.audioLock then + if door.audioRemote then + door.audioLock = {['file'] = 'button-remote.ogg', ['volume'] = 0.08} + door.audioUnlock = {['file'] = 'button-remote.ogg', ['volume'] = 0.08} + else + door.audioLock = {['file'] = 'door-bolt-4.ogg', ['volume'] = 0.1} + door.audioUnlock = {['file'] = 'door-bolt-4.ogg', ['volume'] = 0.1} + end + end + local sfx_level = GetProfileSetting(300) + local sound = door.locked and door.audioLock or door.audioUnlock + SendNUIMessage({ + type = 'audio', + audio = sound, + distance = distance, + sfx = sfx_level + }) + end +end + +local function doorAnim() + if not Config.EnableAnimation then return end + CreateThread(function() + loadAnimDict("anim@heists@keycard@") + TaskPlayAnim(playerPed, "anim@heists@keycard@", "exit", 8.0, 1.0, -1, 48, 0, 0, 0, 0) + Wait(550) + ClearPedTasks(playerPed) + end) +end + +local function updateDoors(specificDoor) + if #(playerCoords - lastCoords) > 30 then Wait(1000) end + playerCoords = GetEntityCoords(playerPed) + for doorID, data in pairs(Config.DoorList) do + if not specificDoor or doorID == specificDoor then + if data.doors then + if not data.doorType then data.doorType = 'double' end + for k, v in pairs(data.doors) do + if #(playerCoords - v.objCoords) < 30 then + + if data.doorType == "doublesliding" then + v.object = GetClosestObjectOfType(v.objCoords.x, v.objCoords.y, v.objCoords.z, 5.0, v.objName or v.objHash, false, false, false) + else + v.object = GetClosestObjectOfType(v.objCoords.x, v.objCoords.y, v.objCoords.z, 1.0, v.objName or v.objHash, false, false, false) + end + if v.object and v.object ~= 0 then + v.doorHash = 'door_'..doorID..'_'..k + if not IsDoorRegisteredWithSystem(v.doorHash) then + local objCoords = GetEntityCoords(v.object) + local objHeading = GetEntityHeading(v.object) + local hasHeading = v.objYaw or v.objHeading or false + if v.objCoords ~= objCoords then v.objCoords = objCoords end -- Backwards compatibility fix + if not hasHeading then + v.objYaw = objHeading + else + if hasHeading ~= objHeading then -- Backwards compatibility fix + v.objYaw = v.objYaw and objHeading or nil + v.objHeading = v.objHeading and objHeading or nil + end + end + AddDoorToSystem(v.doorHash, v.objName or v.objHash, v.objCoords.x, v.objCoords.y, v.objCoords.z, false, false, false) + nearbyDoors[doorID] = true + if data.locked then + DoorSystemSetDoorState(v.doorHash, 4, false, false) + DoorSystemSetDoorState(v.doorHash, 1, false, false) + else + DoorSystemSetDoorState(v.doorHash, 0, false, false) + end + end + end + elseif v.object then + RemoveDoorFromSystem(v.doorHash) + nearbyDoors[doorID] = nil + end + end + elseif not data.doors then + if not data.doorType then data.doorType = 'door' end + if #(playerCoords - data.objCoords) < 30 then + if data.doorType == "sliding" or data.doorType == "garage" then + data.object = GetClosestObjectOfType(data.objCoords.x, data.objCoords.y, data.objCoords.z, 5.0, data.objName or data.objHash, false, false, false) + else + data.object = GetClosestObjectOfType(data.objCoords.x, data.objCoords.y, data.objCoords.z, 1.0, data.objName or data.objHash, false, false, false) + end + if data.object and data.object ~= 0 then + data.doorHash = 'door_'..doorID + if not IsDoorRegisteredWithSystem(data.doorHash) then + local objCoords = GetEntityCoords(data.object) + local objHeading = GetEntityHeading(data.object) + local hasHeading = data.objYaw or data.objHeading or false + if data.objCoords ~= objCoords then data.objCoords = objCoords end -- Backwards compatibility fix + if not hasHeading then + data.objYaw = objHeading + else + if hasHeading ~= objHeading then -- Backwards compatibility fix + data.objYaw = data.objYaw and objHeading or nil + data.objHeading = data.objHeading and objHeading or nil + end + end + data.objCoords = GetEntityCoords(data.object) + AddDoorToSystem(data.doorHash, data.objName or data.objHash, data.objCoords.x, data.objCoords.y, data.objCoords.z, false, false, false) + nearbyDoors[doorID] = true + if data.locked then + DoorSystemSetDoorState(data.doorHash, 4, false, false) + DoorSystemSetDoorState(data.doorHash, 1, false, false) + else + DoorSystemSetDoorState(data.doorHash, 0, false, false) + end + end + end + elseif data.object then + RemoveDoorFromSystem(data.doorHash) + nearbyDoors[doorID] = nil + end + end + -- set text coords + if not data.setText and data.doors then + for k, v in pairs(data.doors) do + if k == 1 and DoesEntityExist(v.object) then + data.textCoords = v.objCoords + elseif k == 2 and DoesEntityExist(v.object) and data.textCoords then + local textDistance = data.textCoords - v.objCoords + data.textCoords = data.textCoords - (textDistance / 2) + data.setText = true + end + if k == 2 and data.textCoords and (data.doorType == "sliding" or data.doorType == "garage") then + if GetEntityHeightAboveGround(v.object) < 1 then + data.textCoords = vec3(data.textCoords.x, data.textCoords.y, data.textCoords.z + 1.2) + end + end + end + elseif not data.setText and not data.doors and DoesEntityExist(data.object) then + if data.doorType == "garage" then + data.textCoords = data.objCoords + data.setText = true + else + data.textCoords = setTextCoords(data) + data.setText = true + end + if data.doorType == "sliding" or data.doorType == "garage" then + if GetEntityHeightAboveGround(data.object) < 1 then + data.textCoords = vec3(data.textCoords.x, data.textCoords.y, data.textCoords.z + 1.6) + end + end + end + end + end + lastCoords = playerCoords +end + +local function lockpickFinish(success) + if success then + QBCore.Functions.Notify(Lang:t("success.lockpick_success"), 'success', 2500) + if closestDoor.data.coords then + TaskTurnPedToFaceCoord(playerPed, closestDoor.data.doors[1].objCoords.x, closestDoor.data.doors[1].objCoords.y, closestDoor.data.doors[1].objCoords.z, 0) + else + TaskTurnPedToFaceCoord(playerPed, closestDoor.data.objCoords.x, closestDoor.data.objCoords.y, closestDoor.data.objCoords.z, 0) + end + Wait(300) + local count = 0 + while GetIsTaskActive(playerPed, 225) do + Wait(10) + if count == 150 then break end + count += 1 + end + Wait(1800) + TriggerServerEvent('qb-doorlock:server:updateState', closestDoor.id, false, false, true, false) -- Broadcast new state of the door to everyone + else + QBCore.Functions.Notify(Lang:t("error.lockpick_fail"), 'error', 2500) + if math.random(1,100) <= 17 then + if usingAdvanced then + TriggerServerEvent("qb-doorlock:server:removeLockpick", "advancedlockpick") + TriggerEvent('inventory:client:ItemBox', QBCore.Shared.Items["advancedlockpick"], "remove") + else + TriggerServerEvent("qb-doorlock:server:removeLockpick", "lockpick") + TriggerEvent('inventory:client:ItemBox', QBCore.Shared.Items["lockpick"], "remove") + end + end + end +end + +local function isAuthorized(door) + if door.allAuthorized then return true end + + if door.authorizedJobs then + if door.authorizedJobs[PlayerData.job.name] and PlayerData.job.grade.level >= door.authorizedJobs[PlayerData.job.name] then + return true + elseif type(door.authorizedJobs[1]) == 'string' then + for _, job in pairs(door.authorizedJobs) do -- Support for old format + if job == PlayerData.job.name then return true end + end + end + end + + if door.authorizedGangs then + if door.authorizedGangs[PlayerData.gang.name] and PlayerData.gang.grade.level >= door.authorizedGangs[PlayerData.gang.name] then + return true + elseif type(door.authorizedGangs[1]) == 'string' then + for _, gang in pairs(door.authorizedGangs) do -- Support for old format + if gang == PlayerData.gang.name then return true end + end + end + end + + if door.authorizedCitizenIDs then + if door.authorizedCitizenIDs[PlayerData.citizenid] then + return true + elseif type(door.authorizedCitizenIDs[1]) == 'string' then + for _, id in pairs(door.authorizedCitizenIDs) do -- Support for old format + if id == PlayerData.citizenid then return true end + end + end + end + + if door.items then + local p = promise.new() + QBCore.Functions.TriggerCallback('qb-doorlock:server:checkItems', function(result) + p:resolve(result) + end, door.items, door.needsAllItems) + return Citizen.Await(p) + end + + return false +end + +function SetupDoors() + local p = promise.new() + QBCore.Functions.TriggerCallback('qb-doorlock:server:setupDoors', function(result) + p:resolve(result) + end) + Config.DoorList = Citizen.Await(p) +end + +-- Events + +RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() + QBCore.Functions.TriggerCallback('qb-doorlock:server:setupDoors', function(result) + Config.DoorList = result + PlayerData = QBCore.Functions.GetPlayerData() + isLoggedIn = true + end) +end) + +RegisterNetEvent('QBCore:Client:OnPlayerUnload', function() + isLoggedIn = false + PlayerData = {} +end) + +RegisterNetEvent('QBCore:Player:SetPlayerData', function(val) + PlayerData = val +end) + +RegisterNetEvent('qb-doorlock:client:setState', function(serverId, doorID, state, src, enableAnimation, enableSounds) + if not Config.DoorList[doorID] then return end + if enableAnimation == nil then enableAnimation = true end + if enableSounds == nil then enableSounds = true end + if serverId == PlayerData.source and enableAnimation then doorAnim() end + Config.DoorList[doorID].locked = state + updateDoors(doorID) + local current = Config.DoorList[doorID] + while true do + if current.doors then + if not current.doorType then current.doorType = 'double' end + for k, v in pairs(current.doors) do + if not IsDoorRegisteredWithSystem(v.doorHash) then return end + v.currentHeading = GetEntityHeading(v.object) + v.doorState = DoorSystemGetDoorState(v.doorHash) + if current.doorType == "doublesliding" then + DoorSystemSetAutomaticRate(v.doorHash, v.doorRate or 1.0, false, false) + if current.locked then + DoorSystemSetDoorState(v.doorHash, 1, false, false) + DoorSystemSetAutomaticDistance(v.doorHash, 0.0, false, false) + if k == 2 then + playSound(current, src, enableSounds) + return + end + else + DoorSystemSetDoorState(v.doorHash, 0, false, false) + DoorSystemSetAutomaticDistance(v.doorHash, 30.0, false, false) + if k == 2 then + playSound(current, src, enableSounds) + return + end + end + elseif current.locked and v.doorState == 4 then + DoorSystemSetDoorState(v.doorHash, 1, false, false) + if current.doors[1].doorState == current.doors[2].doorState then + playSound(current, src, enableSounds) + return + end + elseif not current.locked then + DoorSystemSetDoorState(v.doorHash, 0, false, false) + if current.doors[1].doorState == current.doors[2].doorState then + playSound(current, src, enableSounds) + return + end + else + if round(v.currentHeading, 0) == round(v.objYaw or v.objHeading, 0) then + DoorSystemSetDoorState(v.doorHash, 4, false, false) + end + end + end + else + if not IsDoorRegisteredWithSystem(current.doorHash) then return end + if not current.doorType then current.doorType = 'door' end + current.currentHeading = GetEntityHeading(current.object) + current.doorState = DoorSystemGetDoorState(current.doorHash) + if current.doorType == "sliding" or current.doorType == "garage" then + DoorSystemSetAutomaticRate(current.doorHash, current.doorRate or 1.0, false, false) + if current.locked then + DoorSystemSetDoorState(current.doorHash, 1, false, false) + DoorSystemSetAutomaticDistance(current.doorHash, 0.0, false, false) + playSound(current, src, enableSounds) + return + else + DoorSystemSetDoorState(current.doorHash, 0, false, false) + DoorSystemSetAutomaticDistance(current.doorHash, 30.0, false, false) + playSound(current, src, enableSounds) + return + end + elseif current.locked and current.doorState == 4 then + DoorSystemSetDoorState(current.doorHash, 1, false, false) + playSound(current, src, enableSounds) + return + elseif not current.locked then + DoorSystemSetDoorState(current.doorHash, 0, false, false) + playSound(current, src, enableSounds) + return + else + if round(current.currentHeading, 0) == round(current.objYaw or current.objHeading, 0) then + DoorSystemSetDoorState(current.doorHash, 4, false, false) + end + end + end + Wait(0) + end +end) + +RegisterNetEvent('lockpicks:UseLockpick', function(isAdvanced) + if not closestDoor.data or not next(closestDoor.data) or PlayerData.metadata['isdead'] or PlayerData.metadata['ishandcuffed'] or (not closestDoor.data.pickable and not closestDoor.data.lockpick) or not closestDoor.data.locked then return end + usingAdvanced = isAdvanced + TriggerEvent('qb-lockpick:client:openLockpick', lockpickFinish) +end) + +RegisterNetEvent('qb-doorlock:client:addNewDoor', function() + canContinue = false + hideNUI() + if not Config.SaveDoorDialog then doorData = {} end + local dialog = exports['qb-input']:ShowInput({ + header = Lang:t("general.newdoor_menu_title"), + submitText = Lang:t("general.submit_text"), + inputs = { + { + text = Lang:t("general.configfile_title"), + name = "configfile", + type = "text", + isRequired = true, + default = Config.SaveDoorDialog and doorData.configfile, + }, + { + text = Lang:t("general.dooridentifier_title"), + name = "dooridentifier", + type = "text", + isRequired = true, + default = Config.SaveDoorDialog and doorData.dooridentifier, + }, + { + text = Lang:t("general.doorlabel_title"), + name = "doorlabel", + type = "text", + isRequired = false, + default = Config.SaveDoorDialog and doorData.doorlabel, + }, + { + text = Lang:t("general.doortype_title"), + name = "doortype", + type = "select", + options = { + { value = "door", text = Lang:t("general.doortype_door") }, + { value = "double", text = Lang:t("general.doortype_double") }, + { value = "sliding", text = Lang:t("general.doortype_sliding") }, + { value = "doublesliding", text = Lang:t("general.doortype_doublesliding") }, + { value = "garage", text = Lang:t("general.doortype_garage") } + }, + default = Config.SaveDoorDialog and doorData.doortype, + }, + { + text = Lang:t("general.job_authorisation_menu"), + name = "job", + type = "text", + isRequired = false, + default = Config.SaveDoorDialog and doorData.job, + }, + { + text = Lang:t("general.gang_authorisation_menu"), + name = "gang", + type = "text", + isRequired = false, + default = Config.SaveDoorDialog and doorData.gang, + }, + { + text = Lang:t("general.citizenid_authorisation_menu"), + name = "cid", + type = "text", + isRequired = false, + default = Config.SaveDoorDialog and doorData.cid, + }, + { + text = Lang:t("general.item_authorisation_menu"), + name = "item", + type = "text", + isRequired = false, + default = Config.SaveDoorDialog and doorData.item, + }, + { + text = Lang:t("general.distance_menu"), + name = "distance", + type = "number", + isRequired = true, + default = Config.SaveDoorDialog and doorData.distance, + }, + { + text = "", + name = "checklock", + type = "checkbox", + options = { + { value = "locked", text = Lang:t("general.locked_menu"), checked = (Config.SaveDoorDialog and doorData.locked) }, + { value = "pickable", text = Lang:t("general.pickable_menu"), checked = (Config.SaveDoorDialog and doorData.pickable == 'true') }, + { value = "cantunlock", text = Lang:t("general.cantunlock_menu"), checked = (Config.SaveDoorDialog and doorData.cantunlock == 'true') }, + { value = "hidelabel", text = Lang:t("general.hidelabel_menu"), checked = (Config.SaveDoorDialog and doorData.hidelabel == 'true') }, + } + } + } + }) + if not dialog or not next(dialog) then canContinue = true return end + doorData = dialog + + local identifier = doorData.configfile..'-'..doorData.dooridentifier + if Config.DoorList[identifier] then + QBCore.Functions.Notify((Lang:t("error.door_identifier_exists")):format(identifier), 'error') + canContinue = true + return + end + + if doorData.configfile == '' then doorData.configfile = false end + if doorData.job == '' then doorData.job = false end + if doorData.gang == '' then doorData.gang = false end + if doorData.cid == '' then doorData.cid = false end + if doorData.item == '' then doorData.item = false end + if doorData.doorlabel == '' then doorData.doorlabel = nil end + if doorData.pickable ~= 'true' then doorData.pickable = nil end + if doorData.cantunlock ~= 'true' then doorData.cantunlock = nil end + if doorData.hidelabel ~= 'true' then doorData.hidelabel = nil end + + doorData.locked = doorData.locked == 'true' + doorData.distance = tonumber(doorData.distance) + if doorData.doortype == 'door' or doorData.doortype == 'sliding' or doorData.doortype == 'garage' then + SendNUIMessage({ + type = "setText", + aim = "block" + }) + local heading, result, entityHit + local entity, coords, model= 0, 0, 0 + while true do + if IsPlayerFreeAiming(PlayerId()) then + result, entityHit = raycastWeapon() + if result and entityHit ~= entity then + SetEntityDrawOutline(entity, false) + SetEntityDrawOutline(entityHit, true) + entity = entityHit + coords = GetEntityCoords(entity) + model = GetEntityModel(entity) + heading = GetEntityHeading(entity) + SendNUIMessage({ + type = "setText", + aim = "none", + details = "block", + coords = coords, + heading = heading, + hash = model + }) + end + if entity and IsControlPressed(0, 24) then break end + end + Wait(0) + end + SetEntityDrawOutline(entity, false) + SendNUIMessage({ + type = "setText", + aim = "none", + details = "none", + coords = "", + heading = "", + hash = "" + }) + if not model or model == 0 then QBCore.Functions.Notify(Lang:t("error.door_not_found"), 'error') canContinue = true return end + result = DoorSystemFindExistingDoor(coords.x, coords.y, coords.z, model) + if result then QBCore.Functions.Notify(Lang:t("error.door_registered"), 'error') canContinue = true return end + doorData.doorHash = 'door_'..doorData.dooridentifier + AddDoorToSystem(doorData.doorHash, model, coords, false, false, false) + DoorSystemSetDoorState(doorData.doorHash, 4, false, false) + coords = GetEntityCoords(entity) + heading = GetEntityHeading(entity) + RemoveDoorFromSystem(doorData.doorHash) + doorData.entity = entity + doorData.coords = coords + doorData.model = model + doorData.heading = heading + TriggerServerEvent('qb-doorlock:server:saveNewDoor', doorData, false) + canContinue = true + else + local result, entityHit + local entity, coords, heading, model = {0, 0}, {0, 0}, {0, 0}, {0, 0} + for i = 1, 2 do + SendNUIMessage({ + type = "setText", + aim = "block", + details = "none", + coords = "", + heading = "", + hash = "" + }) + while true do + if IsPlayerFreeAiming(PlayerId()) then + result, entityHit = raycastWeapon() + if result and entityHit ~= entity[i] then + SetEntityDrawOutline(entity[i], false) + SetEntityDrawOutline(entityHit, true) + entity[i] = entityHit + coords[i] = GetEntityCoords(entity[i]) + model[i] = GetEntityModel(entity[i]) + heading[i] = GetEntityHeading(entity[i]) + SendNUIMessage({ + type = "setText", + aim = "none", + details = "block", + coords = coords[i], + heading = heading[i], + hash = model[i] + }) + end + if entity[i] and IsControlPressed(0, 24) then break end + end + Wait(0) + end + Wait(200) + end + SetEntityDrawOutline(entity[1], false) + SetEntityDrawOutline(entity[2], false) + SendNUIMessage({ + type = "setText", + aim = "none", + details = "none", + coords = "", + heading = "", + hash = "" + }) + if not model[1] or model[1] == 0 or not model[2] or model[2] == 0 then QBCore.Functions.Notify(Lang:t("error.door_not_found"), 'error') return end + if entity[1] == entity[2] then QBCore.Functions.Notify(Lang:t("error.same_entity"), 'error') canContinue = true return end + doorData.doorHash = {} + for i = 1, 2 do + result = DoorSystemFindExistingDoor(coords[i].x, coords[i].y, coords[i].z, model[i]) + if result then QBCore.Functions.Notify(Lang:t("error.door_registered"), 'error') canContinue = true return end + doorData.doorHash[i] = 'door_'..doorData.dooridentifier..'_'..i + AddDoorToSystem(doorData.doorHash[i], model[i], coords[i], false, false, false) + DoorSystemSetDoorState(doorData.doorHash[i], 4, false, false) + coords[i] = GetEntityCoords(entity[i]) + heading[i] = GetEntityHeading(entity[i]) + RemoveDoorFromSystem(doorData.doorHash[i]) + end + doorData.entity = entity + doorData.coords = coords + doorData.model = model + doorData.heading = heading + TriggerServerEvent('qb-doorlock:server:saveNewDoor', doorData, true) + canContinue = true + end +end) + +RegisterNetEvent('qb-doorlock:client:newDoorAdded', function(data, id, creatorSrc) + Config.DoorList[id] = data + TriggerEvent('qb-doorlock:client:setState', creatorSrc, id, data.locked, false, true, true) +end) + +RegisterNetEvent('qb-doorlock:client:ToggleDoorDebug', function() + Config.DoorDebug = not Config.DoorDebug + HandleDoorDebug() +end) + +-- Commands + +RegisterCommand('toggledoorlock', function() + if not closestDoor.data or not next(closestDoor.data) then return end + + local distanceCheck = closestDoor.distance > (closestDoor.data.distance or closestDoor.data.maxDistance) + local unlockableCheck = (closestDoor.data.cantUnlock and closestDoor.data.locked) + local busyCheck = PlayerData.metadata['isdead'] or PlayerData.metadata['inlaststand'] or PlayerData.metadata['ishandcuffed'] + if distanceCheck or unlockableCheck or busyCheck then return end + + playerPed = PlayerPedId() + local veh = GetVehiclePedIsIn(playerPed) + if veh then + CreateThread(function() + local siren = IsVehicleSirenOn(veh) + for _ = 0, 100 do + DisableControlAction(0, 86, true) + SetHornEnabled(veh, false) + if not siren then SetVehicleSiren(veh, false) end + Wait(0) + end + SetHornEnabled(veh, true) + end) + end + local locked = not closestDoor.data.locked + local src = false + if closestDoor.data.audioRemote then src = NetworkGetNetworkIdFromEntity(playerPed) end + + TriggerServerEvent('qb-doorlock:server:updateState', closestDoor.id, locked, src, false, false, true, true) -- Broadcast new state of the door to everyone +end, false) +TriggerEvent("chat:removeSuggestion", "/toggledoorlock") +RegisterKeyMapping('toggledoorlock', Lang:t("general.keymapping_description"), 'keyboard', 'E') + + +RegisterCommand('remotetriggerdoor', function() + local hit, raycastCoords = RayCastGamePlayCamera(Config.RemoteTriggerDistance) + if not hit then return end + + local nearestDoor = nil + for k in pairs(nearbyDoors) do + local door = Config.DoorList[k] + local canTrigger = door.remoteTrigger + local distance = #(raycastCoords - getTextCoords(door)) + + if canTrigger and (not nearestDoor or distance < nearestDoor.distance) and distance < math.max(door.distance,Config.RemoteTriggerMinDistance) then + nearestDoor = { + data = door, + id = k, + distance = distance + } + end + end + + if not nearestDoor then return end + + local unlockableCheck = (nearestDoor.data.cantUnlock and nearestDoor.data.locked) + local busyCheck = PlayerData.metadata['isdead'] or PlayerData.metadata['inlaststand'] or PlayerData.metadata['ishandcuffed'] + if unlockableCheck or busyCheck then return end + + playerPed = PlayerPedId() + local veh = GetVehiclePedIsIn(playerPed) + if veh then + CreateThread(function() + for _ = 0, 100 do + DisableControlAction(0, 74, true) + Wait(0) + end + end) + end + + TriggerServerEvent('qb-doorlock:server:updateState', nearestDoor.id, not nearestDoor.data.locked, NetworkGetNetworkIdFromEntity(playerPed), false, false, true, true) -- Broadcast new state of the door to everyone +end, false) +TriggerEvent("chat:removeSuggestion", "/remotetriggerdoor") +RegisterKeyMapping('remotetriggerdoor', Lang:t("general.keymapping_remotetriggerdoor"), 'keyboard', 'H') + +-- Threads + +CreateThread(function() + if Config.PersistentDoorStates and isLoggedIn then Wait(1000) SetupDoors() end -- Required for pulling in door states properly from live ensures + + updateDoors() + HandleDoorDebug() + while true do + local sleep = 100 + if isLoggedIn and canContinue then + playerPed = PlayerPedId() + playerCoords = GetEntityCoords(playerPed) + if not closestDoor.id then + local distance = #(playerCoords - lastCoords) + if distance > 15 then + updateDoors() + sleep = 1000 + else + for k in pairs(nearbyDoors) do + local door = Config.DoorList[k] + if door.setText and door.textCoords then + distance = #(playerCoords - door.textCoords) + if distance < (closestDoor.distance or 15) then + if distance < (door.distance or door.maxDistance) then + closestDoor = {distance = distance, id = k, data = door} + sleep = 0 + end + end + end + end + end + end + if closestDoor.id then + while isLoggedIn do + if not paused and IsPauseMenuActive() then + hideNUI() + paused = true + elseif paused then + if not IsPauseMenuActive() then paused = false end + else + playerCoords = GetEntityCoords(playerPed) + closestDoor.distance = #(closestDoor.data.textCoords - playerCoords) + if closestDoor.distance < (closestDoor.data.distance or closestDoor.data.maxDistance) then + local authorized = isAuthorized(closestDoor.data) + local displayText = "" + + if not closestDoor.data.hideLabel and Config.UseDoorLabelText and closestDoor.data.doorLabel then + displayText = closestDoor.data.doorLabel + else + if not closestDoor.data.locked and not authorized then + displayText = Lang:t("general.unlocked") + elseif not closestDoor.data.locked and authorized then + displayText = Lang:t("general.unlocked_button") + elseif closestDoor.data.locked and not authorized then + displayText = Lang:t("general.locked") + elseif closestDoor.data.locked and authorized then + displayText = Lang:t("general.locked_button") + end + end + + if displayText ~= "" and (closestDoor.data.hideLabel == nil or not closestDoor.data.hideLabel) then displayNUIText(displayText) end + else + hideNUI() + break + end + end + Wait(100) + end + closestDoor = {} + sleep = 0 + end + end + Wait(sleep) + end +end) diff --git a/resources/[qb]/[qb_core]/qb-doorlock/config.lua b/resources/[qb]/[qb_core]/qb-doorlock/config.lua new file mode 100644 index 0000000..a5500b2 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-doorlock/config.lua @@ -0,0 +1,251 @@ +Config = {} + +Config.CommandPermission = 'admin' -- permission level for creating new doors +Config.AdminAccess = true -- Enable admins to unlock any door +Config.AdminPermission = 'admin' -- The permission needed to get AdminAccess if it is enabled +Config.Warnings = true -- if true it will show warnings in the console when certain requirements aren't met +Config.FallbackDistance = 3.0 -- Distance to fall back on when it is not set +Config.EnableSounds = true -- Enable sounds when unlocking/locking doors (doors triggered by scripts can have the sound manually disabled) +Config.EnableAnimation = true -- Enable the animation when unlocking/locking doors (doors triggered by scripts can have the animation manually disabled) +Config.SaveDoorDialog = true -- Saves the door dialogue popup between door saves +Config.PersistentDoorStates = true -- Persists the door state between server restarts +Config.PersistentSaveInternal = 60000 -- How often door states are saved to the file system, in miliseconds. + +Config.ChangeColor = true -- Change the color of the box of the popup text based on if it is locked or not +Config.DefaultColor = 'rgb(19, 28, 74)' -- The default color of the box of the popup text if Config.ChangeColor is false +Config.LockedColor = 'rgb(219 58 58)' -- The color of the box of the popup text if Config.ChangeColor is true and the door is locked +Config.UnlockedColor = 'rgb(27 195 63)' -- The color of the box of the popup text if Config.ChangeColor is true and the door is unlocked +Config.UseDoorLabelText = false -- Will use the LABEL field as the nui text instead of locked/unlocked +Config.DoorDebug = false -- Enable DRAWTEXT in the world at the coords where the door 'center' is +Config.RemoteTriggerDistance = 15.0 -- This is how far from your camera the raycast will go to try to hit something solid +Config.RemoteTriggerMinDistance = 5.0 -- This is the minimum distance required for the raycast hit to count near a door. It will take the larger two between this and 'distance' option + +Config.Consumables = { ['ticket'] = 1, ['paperclip'] = 1 } -- The items will get removed once used on a door that has the item on it + +--[[ -- Configuration Options +Config.DoorList['configname-identifier'] = { + fixText = false, -- Adjusts guess of center of door + textCoords = vector3(x, y, z) -- Set the text coordinates to a specific location + setText = true -- Use with above setting + distance = 2.0, -- Max interact distance + lockpick = true, -- Alows for lockpicking + allAuthorized = true, -- Anyone can use door + authorizedJobs = { ['police']=0, ['bcso']=0, ['sasp]=0 } -- Authorize door access based on job grade + authorizedGangs = { ['vagos']=0, ['ballas']=0 } -- Authorize door access based on gang grade + authorizedCitizenIDs = { 'ABC123', 'DEF456' } -- Authorize door access based on citizenid + items = {'item_1','item_2'} -- Authorize based on items. Must have all items in this list. + doorLabel = 'Cloakroom', -- Label of room that shows up when nearby + locked = true, -- Default lock state + audioRemote = true, -- Play sound from the player instead of the door + audioLock = {['file'] = 'metal-locker.ogg', ['volume'] = 0.6}, -- Play sound on door lock + audioUnlock = {['file'] = 'metallic-creak.ogg', ['volume'] = 0.7}, -- Play sound on door unlock + autoLock = 1000, -- Auto lock after this many miliseconds + doorRate = 1.0, -- Time till auto closes? Needs more testing + cantUnlock = true -- Set to true to not allow the player to unlock the door, only lock it. This means a script will have to trigger the unlock. + pickable = true, -- Can use a lockpick to unlock, only need if true + hideLabel = true, -- Set to true to hide the popup label, for hiding doors ;) + remoteTrigger = true, -- If you want to be able to remote trigger a door with H, put this here +} +]] + +Config.DoorStates = {} +Config.DoorList = { + { + objName = 'hei_v_ilev_bk_gate2_pris', + objCoords = vec3(261.83, 221.39, 106.41), + textCoords = vec3(261.83, 221.39, 106.41), + authorizedJobs = { 'police' }, + objYaw = -110.0, + locked = true, + pickable = false, + distance = 1.5, + }, + --door2 for pacific opened with thermite right near the vault door + { + objName = 'hei_v_ilev_bk_safegate_pris', + objCoords = vec3(252.98, 220.65, 101.8), + textCoords = vec3(252.98, 220.65, 101.8), + authorizedJobs = { 'police' }, + objYaw = 160.0, + locked = true, + pickable = false, + distance = 1.5 + }, + -- door3 for pacific opened with thermite after passing the door near vault + { + objName = 'hei_v_ilev_bk_safegate_pris', + objCoords = vec3(261.68, 215.62, 101.81), + textCoords = vec3(261.68, 215.62, 101.81), + authorizedJobs = { 'police' }, + objYaw = -110.0, + locked = true, + pickable = false, + distance = 1.5 + }, + -- First Pacific Door opened with lockpick + { + objName = 'hei_v_ilev_bk_gate_pris', + objCoords = vec3(257.41, 220.25, 106.4), + textCoords = vec3(257.41, 220.25, 106.4), + authorizedJobs = { 'police' }, + objYaw = -20.0, + locked = true, + pickable = true, + distance = 1.5 + }, + -- Second Pacific Door opened with lockpick + { + objName = 'v_ilev_bk_door', + objCoords = vec3(265.19, 217.84, 110.28), + textCoords = vec3(265.19, 217.84, 110.28), + authorizedJobs = { 'police' }, + objYaw = -20.0, + locked = true, + pickable = true, + distance = 1.5 + }, + -- Fleeca Door opened with lockpick + { + objName = 'v_ilev_gb_vaubar', + objCoords = vec3(314.61, -285.82, 54.49), + textCoords = vec3(313.3, -285.45, 54.49), + authorizedJobs = { 'police' }, + locked = true, + pickable = true, + distance = 1.5 + }, + -- Fleeca Door opened with lockpick + { + objName = 'v_ilev_gb_vaubar', + objCoords = vec3(148.96, -1047.12, 29.7), + textCoords = vec3(148.96, -1047.12, 29.7), + authorizedJobs = { 'police' }, + locked = true, + pickable = true, + distance = 1.5 + }, + -- Fleeca Door opened with lockpick + { + objName = 'v_ilev_gb_vaubar', + objCoords = vec3(-351.7, -56.28, 49.38), + textCoords = vec3(-351.7, -56.28, 49.38), + authorizedJobs = { 'police' }, + locked = true, + pickable = true, + distance = 1.5 + }, + -- Fleeca Door opened with lockpick + { + objName = 'v_ilev_gb_vaubar', + objCoords = vec3(-1208.12, -335.586, 37.759), + textCoords = vec3(-1208.12, -335.586, 37.759), + authorizedJobs = { 'police' }, + locked = true, + pickable = true, + distance = 1.5 + }, + -- Fleeca Door opened with lockpick + { + objName = 'v_ilev_gb_vaubar', + objCoords = vec3(-2956.18, 483.96, 16.02), + textCoords = vec3(-2956.18, 483.96, 16.02), + authorizedJobs = { 'police' }, + locked = true, + pickable = true, + distance = 1.5 + }, + -- Prison Door 1 + { + objName = 'prop_gate_prison_01', + objCoords = vec3(1844.9, 2604.8, 44.6), + textCoords = vec3(1844.9, 2608.5, 48.0), + authorizedJobs = { 'police' }, + locked = true, + pickable = false, + distance = 13 + }, + -- Prison Door 2 + { + objName = 'prop_gate_prison_01', + objCoords = vec3(1818.5, 2604.8, 44.6), + textCoords = vec3(1818.5, 2608.4, 48.0), + authorizedJobs = { 'police' }, + locked = true, + pickable = false, + distance = 13 + }, + -- Prison Door 3 + { + objName = 'prop_gate_prison_01', + objCoords = vec3(1799.237, 2616.303, 44.6), + textCoords = vec3(1795.941, 2616.969, 48.0), + authorizedJobs = { 'police' }, + locked = true, + pickable = false, + distance = 10 + }, + ------------------------------------------Always add new doors below this line for your bank robberies to work!!!--------------------------------- + ------------------------------------------End Fixed Doors!!!--------------------------------- + -- Luxury Cars + -- Entrance Doors + { + textCoords = vec3(-803.0223, -223.8222, 37.87975), + authorizedJobs = { 'cardealer', 'police' }, + locked = true, + pickable = false, + distance = 3.5, + doors = { + { + objName = 'prop_doorluxyry2', + objYaw = 120.0, + objCoords = vec3(-803.0223, -222.5841, 37.87975) + }, + + { + objName = 'prop_doorluxyry2', + objYaw = -60.0, + objCoords = vec3(-801.9622, -224.5203, 37.87975) + } + } + }, + -- Side Entrance Doors + { + textCoords = vec3(-777.1855, -244.0013, 37.333889), + authorizedJobs = { 'cardealer', 'police' }, + locked = true, + pickable = false, + distance = 3.5, + doors = { + { + objName = 'prop_doorluxyry', + objYaw = -160.0, + objCoords = vec3(-778.1855, -244.3013, 37.33388) + }, + + { + objName = 'prop_doorluxyry', + objYaw = 23.0, + objCoords = vec3(-776.1591, -243.5013, 37.33388) + } + } + }, + -- Garage Doors + { + textCoords = vec3(-768.1264, -238.9737, 37.43247), + authorizedJobs = { 'cardealer', 'police' }, + locked = true, + pickable = false, + distance = 13.0, + doors = { + { + objName = 'prop_autodoor', + objCoords = vec3(-770.6311, -240.0069, 37.43247) + }, + + { + objName = 'prop_autodoor', + objCoords = vec3(-765.6217, -237.9405, 37.43247) + } + } + }, +} \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-doorlock/configs/1robbery.lua b/resources/[qb]/[qb_core]/qb-doorlock/configs/1robbery.lua new file mode 100644 index 0000000..c37e54c --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-doorlock/configs/1robbery.lua @@ -0,0 +1,56 @@ + + +-- GuardTower5 created by THX1139 +Config.DoorList['1robbery-GuardTower5'] = { + doorRate = 1.0, + objYaw = 270.0, + locked = true, + objName = -1033001619, + distance = 2, + doorLabel = 'PrisonTower5', + fixText = false, + doorType = 'door', + objCoords = vec3(1537.810913, 2585.995117, 45.689148), + authorizedJobs = { ['police'] = 0 }, +} +-- infirmaryoffice created by THX1139 +-- infirmarylab created by THX1139 +Config.DoorList['1robbery-infirmarylab'] = { + distance = 1, + objYaw = 90.000007629395, + doorType = 'door', + authorizedJobs = { ['police'] = 0, ['bcso'] = 0, ['corrections'] = 0 }, + doorLabel = 'infirmary2', + fixText = false, + locked = true, + doorRate = 1.0, + objName = -1392981450, + objCoords = vec3(1767.323242, 2580.832031, 45.747826), +} +-- cameraroom1 created by THX1139 +Config.DoorList['1robbery-cameraroom1'] = { + distance = 1, + objYaw = 29.999996185303, + doorType = 'door', + authorizedJobs = { ['police'] = 0, ['bcso'] = 0, ['corrections'] = 0 }, + doorLabel = 'camera1', + fixText = false, + locked = true, + doorRate = 1.0, + objName = 241550507, + objCoords = vec3(1772.938599, 2495.313232, 49.840057), +} + +-- cameraroom2 created by THX1139 +Config.DoorList['1robbery-cameraroom2'] = { + distance = 1, + objYaw = 29.999996185303, + doorType = 'door', + authorizedJobs = { ['police'] = 0, ['bcso'] = 0, ['corrections'] = 0 }, + doorLabel = 'camera2', + fixText = false, + locked = true, + doorRate = 1.0, + objName = 241550507, + objCoords = vec3(1775.414185, 2491.025391, 49.840057), +} \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-doorlock/configs/bean.lua b/resources/[qb]/[qb_core]/qb-doorlock/configs/bean.lua new file mode 100644 index 0000000..9c6048c --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-doorlock/configs/bean.lua @@ -0,0 +1,29 @@ + + +-- b1 created by isakh +Config.DoorList['bean-b1'] = { + objYaw = 340.00003051758, + objCoords = vec3(128.213394, -1029.455322, 29.261801), + locked = false, + objName = 494354570, + distance = 2, + fixText = false, + doorType = 'door', + doorRate = 1.0, + doorLabel = 'b1', + authorizedJobs = { ['beanmachine'] = 0 }, +} + +-- b2 created by isakh +Config.DoorList['bean-b2'] = { + authorizedJobs = { ['beanmachine'] = 0 }, + distance = 2, + locked = true, + doors = { + {objName = -747011272, objYaw = 69.999992370605, objCoords = vec3(115.375694, -1037.655029, 29.348320)}, + {objName = -1182160879, objYaw = 250.00004577637, objCoords = vec3(114.562912, -1039.888184, 29.348320)} + }, + doorRate = 1.0, + doorLabel = 'b2', + doorType = 'double', +} \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-doorlock/configs/burgershot.lua b/resources/[qb]/[qb_core]/qb-doorlock/configs/burgershot.lua new file mode 100644 index 0000000..229a8d4 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-doorlock/configs/burgershot.lua @@ -0,0 +1,15 @@ + + +-- burgershot created by s0b1esk1_86 +Config.DoorList['burgershot-burgershot'] = { + distance = 2, + objCoords = vec3(-1680.785034, -1095.974976, 13.305560), + objName = -1300743830, + doorRate = 1.0, + doorType = 'door', + fixText = false, + doorLabel = 'Burgershot', + locked = false, + authorizedJobs = { ['burgershot'] = 0 }, + objYaw = 313.00003051758, +} \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-doorlock/configs/luxury.lua b/resources/[qb]/[qb_core]/qb-doorlock/configs/luxury.lua new file mode 100644 index 0000000..fb1b972 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-doorlock/configs/luxury.lua @@ -0,0 +1,43 @@ + + +-- LuxuryGarage created by Hawk +Config.DoorList['LuxuryGarage-LuxuryGarage'] = { + doorLabel = 'LuxuryGarage', + doorType = 'garage', + distance = 5, + authorizedJobs = { ['cardealer'] = 0 }, + fixText = false, + locked = true, + objYaw = 207.0, + objName = 1430328167, + objCoords = vec3(-1237.843628, -338.194764, 37.607274), + doorRate = 1.0, +} + +-- LuxuryFrontDoor1 created by Hawk +Config.DoorList['LuxuryFrontDoor1-LuxuryFrontDoor1'] = { + locked = false, + doorType = 'double', + doors = { + {objName = 73386408, objYaw = 207.0, objCoords = vec3(-1259.591308, -348.532258, 37.111160)}, + {objName = -1152174184, objYaw = 207.0, objCoords = vec3(-1261.875610, -349.696198, 37.111160)} + }, + distance = 3, + authorizedJobs = { ['cardealer'] = 0 }, + doorLabel = 'LuxuryFrontDoor1', + doorRate = 1.0, +} + +-- LuxuryFrontDoor2 created by Hawk +Config.DoorList['LuxuryFrontDoor2-LuxuryFrontDoor2'] = { + locked = true, + doorType = 'double', + doors = { + {objName = -1152174184, objYaw = 298.0, objCoords = vec3(-1268.578002, -370.370178, 37.099244)}, + {objName = 73386408, objYaw = 298.0, objCoords = vec3(-1269.793090, -368.111480, 37.111160)} + }, + distance = 3, + authorizedJobs = { ['cardealer'] = 0 }, + doorLabel = 'LuxuryFrontDoor2', + doorRate = 1.0, +} \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-doorlock/configs/mrpd.lua b/resources/[qb]/[qb_core]/qb-doorlock/configs/mrpd.lua new file mode 100644 index 0000000..e56fed8 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-doorlock/configs/mrpd.lua @@ -0,0 +1,628 @@ +-- MRPD + +--Custody A? + +-- -103609059 created by Hawk +Config.DoorList['MRPD_CustodyA6--103609059'] = { + doorRate = 1.0, + objYaw = 89.999961853028, + distance = 2, + objCoords = vec3(480.817292, -1014.956788, 30.612476), + doorType = 'door', + authorizedJobs = { ['police'] = 0 }, + locked = true, + fixText = false, + doorLabel = '3960322', + objName = -1036090959, + autoLock = 5000 +} + +-- -103609059 created by Hawk +Config.DoorList['MRPD_CustodyA5--103609059'] = { + doorRate = 1.0, + objYaw = 89.999961853028, + distance = 2, + objCoords = vec3(480.817292, -1010.842468, 30.612476), + doorType = 'door', + authorizedJobs = { ['police'] = 0 }, + locked = true, + fixText = false, + doorLabel = '3960578', + objName = -1036090959, + autoLock = 5000 +} + +-- -103609059 created by Hawk +Config.DoorList['MRPD_CustodyA4--103609059'] = { + doorRate = 1.0, + objYaw = 89.999961853028, + distance = 2, + objCoords = vec3(480.817352, -1006.746032, 30.612476), + doorType = 'door', + authorizedJobs = { ['police'] = 0 }, + locked = true, + fixText = false, + doorLabel = '4028674', + objName = -1036090959, + autoLock = 5000 +} + +-- -103609059 created by Hawk +Config.DoorList['MRPD_CustodyA3--103609059'] = { + doorRate = 1.0, + objYaw = 89.999961853028, + distance = 2, + objCoords = vec3(484.083068, -1014.943298, 30.612476), + doorType = 'door', + authorizedJobs = { ['police'] = 0 }, + locked = true, + fixText = false, + doorLabel = '4028418', + objName = -1036090959, + autoLock = 5000 +} + +-- -103609059 created by Hawk +Config.DoorList['MRPD_CustodyA2--103609059'] = { + doorRate = 1.0, + objYaw = 89.999961853028, + distance = 2, + objCoords = vec3(484.083068, -1010.842530, 30.612476), + doorType = 'door', + authorizedJobs = { ['police'] = 0 }, + locked = true, + fixText = false, + doorLabel = '4027906', + objName = -1036090959, + autoLock = 5000 +} + +-- -103609059 created by Hawk +Config.DoorList['MRPD_CustodyA1--103609059'] = { + doorRate = 1.0, + objYaw = 89.999961853028, + distance = 2, + objCoords = vec3(484.083130, -1006.736146, 30.612476), + doorType = 'door', + authorizedJobs = { ['police'] = 0 }, + locked = true, + fixText = false, + doorLabel = '4028162', + objName = -1036090959, + autoLock = 5000 +} + +-- Custody B? + +-- -1036090959 created by Hawk +Config.DoorList['Custody_B6--1036090959'] = { + distance = 2, + objName = -1036090959, + objCoords = vec3(460.823852, -983.660828, 30.612476), + doorType = 'door', + fixText = false, + authorizedJobs = { ['police'] = 0 }, + locked = true, + doorRate = 1.0, + doorLabel = '3849986', + objYaw = 1.0017911336036e-05, + autoLock = 5000 +} + +-- -1036090959 created by Hawk +Config.DoorList['Custody_B5--1036090959'] = { + distance = 2, + objName = -1036090959, + objCoords = vec3(464.938140, -983.660828, 30.612476), + doorType = 'door', + fixText = false, + authorizedJobs = { ['police'] = 0 }, + locked = true, + doorRate = 1.0, + doorLabel = '3938562', + objYaw = 1.0017911336036e-05, + autoLock = 5000 +} + +-- -1036090959 created by Hawk +Config.DoorList['Custody_B4--1036090959'] = { + distance = 2, + objName = -1036090959, + objCoords = vec3(469.034606, -983.660888, 30.612476), + doorType = 'door', + fixText = false, + authorizedJobs = { ['police'] = 0 }, + locked = true, + doorRate = 1.0, + doorLabel = '3991042', + objYaw = 1.0017911336036e-05, + autoLock = 5000 +} + +-- -1036090959 created by Hawk +Config.DoorList['Custody_B3--1036090959'] = { + distance = 2, + objName = -1036090959, + objCoords = vec3(460.837310, -986.926636, 30.612476), + doorType = 'door', + fixText = false, + authorizedJobs = { ['police'] = 0 }, + locked = true, + doorRate = 1.0, + doorLabel = '3850242', + objYaw = 1.0017911336036e-05, + autoLock = 5000 +} + +-- -1036090959 created by Hawk +Config.DoorList['Custody_B2--1036090959'] = { + distance = 2, + objName = -1036090959, + objCoords = vec3(464.938080, -986.926636, 30.612476), + doorType = 'door', + fixText = false, + authorizedJobs = { ['police'] = 0 }, + locked = true, + doorRate = 1.0, + doorLabel = '3938306', + objYaw = 1.0017911336036e-05, + autoLock = 5000 +} + +-- -1036090959 created by Hawk +Config.DoorList['Custody_B1--1036090959'] = { + distance = 2, + objName = -1036090959, + objCoords = vec3(469.044464, -986.926696, 30.612476), + doorType = 'door', + fixText = false, + authorizedJobs = { ['police'] = 0 }, + locked = true, + doorRate = 1.0, + doorLabel = '3938050', + objYaw = 1.0017911336036e-05, + autoLock = 5000 +} + +--Pressroom + +-- 952639784 created by Hawk +Config.DoorList['PressroomD1-952639784'] = { + doorType = 'double', + authorizedJobs = { ['police'] = 0 }, + locked = false, + distance = 3, + doors = { + {objName = 952639784, objYaw = 180.00001525878, objCoords = vec3(437.124390, -989.491760, 30.873428)}, + {objName = -1481015543, objYaw = 179.99998474122, objCoords = vec3(434.526550, -989.491760, 30.873428)} + }, + doorLabel = '3680514', + doorRate = 1.0, +} + +-- -824920418 created by Hawk +Config.DoorList['PressroomD2-824920418'] = { + doorRate = 1.0, + objYaw = 90.0, + distance = 3, + objCoords = vec3(437.510040, -997.986634, 30.873428), + doorType = 'door', + authorizedJobs = { ['police'] = 0 }, + locked = false, + fixText = false, + doorLabel = '4668738', + objName = -824920418, +} + +-- Entry-doors + +-- -1481015543 created by Hawk +Config.DoorList['MRPD_M_Entry--1481015543'] = { + doorType = 'double', + authorizedJobs = { ['police'] = 0 }, + locked = true, + distance = 2, + doors = { + {objName = -1481015543, objYaw = 89.999977111816, objCoords = vec3(449.522370, -980.361022, 30.874448)}, + {objName = 952639784, objYaw = 89.999977111816, objCoords = vec3(449.522370, -982.958436, 30.874448)} + }, + doorLabel = '3832578', + doorRate = 1.0, + autoLock = 5000 +} + +-- 1847320387 created by Hawk +Config.DoorList['MRPDParkingEntry-1847320387'] = { + doorType = 'double', + authorizedJobs = { ['police'] = 0 }, + locked = true, + distance = 3, + doors = { + {objName = 1847320387, objYaw = 360.0, objCoords = vec3(442.750886, -998.583802, 31.118602)}, + {objName = -688443112, objYaw = 0.0, objCoords = vec3(440.114258, -998.584350, 31.118142)} + }, + doorLabel = '36599522', + doorRate = 1.0, + autoLock = 5000 +} + +-- -1481015543 created by Hawk +Config.DoorList['MRPD_E_Entry--1481015543'] = { + doorType = 'double', + authorizedJobs = { ['police'] = 0 }, + locked = true, + distance = 2, + doors = { + {objName = -1481015543, objYaw = 270.00003051758, objCoords = vec3(445.918518, -995.031250, 30.874198)}, + {objName = 952639784, objYaw = 270.00003051758, objCoords = vec3(445.918518, -992.436646, 30.874198)} + }, + doorLabel = '3674114', + doorRate = 1.0, + autoLock = 5000 +} + +-- -1036090959 created by Hawk +Config.DoorList['MRDP_Back_Entry--1036090959'] = { + authorizedJobs = { ['police'] = 0 }, + distance = 2, + locked = true, + doors = { + {objName = -1036090959, objYaw = 180.00001525878, objCoords = vec3(479.690888, -979.453002, 28.143826)}, + {objName = -1036090959, objYaw = 0.0, objCoords = vec3(477.091370, -979.453002, 28.143826)} + }, + doorType = 'double', + doorLabel = '4082434', + doorRate = 1.0, + autoLock = 5000 +} + +-- Evidence Door + +-- 749848321 created by Hawk +Config.DoorList['MRPD_EvidenceDoor-749848321'] = { + doorRate = 1.0, + objYaw = 89.999977111816, + distance = 2, + objCoords = vec3(468.018616, -998.500244, 30.611514), + doorType = 'door', + authorizedJobs = { ['police'] = 0 }, + locked = true, + fixText = false, + doorLabel = '3880706', + objName = 749848321, + autoLock = 10000 +} + +-- Storage + +-- -1036090959 created by Hawk +Config.DoorList['MRDP_Storage--1036090959'] = { + distance = 2, + objName = -1036090959, + objCoords = vec3(467.507172, -994.437988, 35.212628), + doorType = 'door', + fixText = false, + authorizedJobs = { ['police'] = 0 }, + locked = true, + doorRate = 1.0, + doorLabel = '3885570', + objYaw = 134.99995422364, + autoLock = 10000 +} + +-- Forensics + +-- 161378502 created by Hawk +Config.DoorList['MRDP_Forensics-161378502'] = { + authorizedJobs = { ['police'] = 0 }, + distance = 2, + locked = true, + doors = { + {objName = 161378502, objYaw = 0.0, objCoords = vec3(480.575318, -1004.605590, 24.471722)}, + {objName = -1572101598, objYaw = 1.0017911336036e-05, objCoords = vec3(477.975372, -1004.605590, 24.471722)} + }, + doorType = 'doublesliding', + doorLabel = '4031490', + doorRate = 1.0, + autoLock = 5000 +} + +-- Interrogation + +-- -1481015543 created by Hawk +Config.DoorList['MRDP_2IntA--1481015543'] = { + distance = 2, + objName = -1481015543, + objCoords = vec3(477.647492, -996.258484, 35.212360), + doorType = 'door', + fixText = false, + authorizedJobs = { ['police'] = 0 }, + locked = true, + doorRate = 1.0, + doorLabel = 'IntA', + objYaw = 269.99996948242, + autoLock = 5000 +} + +-- -82420418 created by Hawk +Config.DoorList['MRDP_2IntA1--82420418'] = { + distance = 2, + objName = -824920418, + objCoords = vec3(477.647492, -988.151062, 35.212360), + doorType = 'door', + fixText = false, + authorizedJobs = { ['police'] = 0 }, + locked = true, + doorRate = 1.0, + doorLabel = 'IntA1', + objYaw = 269.99996948242, + autoLock = 5000 +} + +-- -1481015543 created by Hawk +Config.DoorList['MRDP_2IntB--1481015543'] = { + distance = 2, + objName = -1481015543, + objCoords = vec3(477.643096, -987.182006, 35.211696), + doorType = 'door', + fixText = false, + authorizedJobs = { ['police'] = 0 }, + locked = true, + doorRate = 1.0, + doorLabel = 'IntB', + objYaw = 269.99996948242, + autoLock = 5000 +} + +-- -82420418 created by Hawk +Config.DoorList['MRDP_2IntB1--82420418'] = { + distance = 2, + objName = -824920418, + objCoords = vec3(477.643830, -979.079346, 35.209060), + doorType = 'door', + fixText = false, + authorizedJobs = { ['police'] = 0 }, + locked = true, + doorRate = 1.0, + doorLabel = 'IntB1', + objYaw = 269.99996948242, + autoLock = 5000 +} + +-- -1481015543 created by Hawk +Config.DoorList['MRDP_2IntC--1481015543'] = { + distance = 2, + objName = -1481015543, + objCoords = vec3(482.664460, -996.257568, 35.214234), + doorType = 'door', + fixText = false, + authorizedJobs = { ['police'] = 0 }, + locked = true, + doorRate = 1.0, + doorLabel = 'IntC', + objYaw = 270.00003051758, + autoLock = 5000 +} + +-- -82420418 created by Hawk +Config.DoorList['MRDP_2IntC1--82420418'] = { + distance = 2, + objName = -824920418, + objCoords = vec3(482.664460, -988.146362, 35.214234), + doorType = 'door', + fixText = false, + authorizedJobs = { ['police'] = 0 }, + locked = true, + doorRate = 1.0, + doorLabel = 'IntC1', + objYaw = 270.00003051758, + autoLock = 5000 +} + +-- -1481015543 created by Hawk +Config.DoorList['MRDP_2IntD--1481015543'] = { + distance = 2, + objName = -1481015543, + objCoords = vec3(482.660064, -987.188050, 35.213570), + doorType = 'door', + fixText = false, + authorizedJobs = { ['police'] = 0 }, + locked = true, + doorRate = 1.0, + doorLabel = 'IntD', + objYaw = 270.00003051758, + autoLock = 5000 +} + +-- -82420418 created by Hawk +Config.DoorList['MRDP_2IntD1--82420418'] = { + distance = 2, + objName = -824920418, + objCoords = vec3(482.660798, -979.072022, 35.210934), + doorType = 'door', + fixText = false, + authorizedJobs = { ['police'] = 0 }, + locked = true, + doorRate = 1.0, + doorLabel = 'IntD1', + objYaw = 270.00003051758, + autoLock = 5000 +} + +-- ARMOURY + +-- -1036090959 created by Hawk +Config.DoorList['MRDP_Armoury--1036090959'] = { + distance = 2, + objName = -1036090959, + objCoords = vec3(458.398224, -992.107056, 35.212628), + doorType = 'door', + fixText = false, + authorizedJobs = { ['police'] = 0 }, + locked = true, + doorRate = 1.0, + doorLabel = '3791362', + objYaw = 315.0, + autoLock = 5000 +} + +-- 2nd Floor Doubles + +-- 952639784 created by Hawk +Config.DoorList['MRDP_2Floor_Doubles-952639784'] = { + authorizedJobs = { ['police'] = 0 }, + distance = 2, + locked = true, + doors = { + {objName = 952639784, objYaw = 89.999977111816, objCoords = vec3(455.546630, -988.383118, 35.212100)}, + {objName = -1481015543, objYaw = 89.999977111816, objCoords = vec3(455.546630, -985.783264, 35.212100)} + }, + doorType = 'double', + doorLabel = '3832322', + doorRate = 1.0, + autoLock = 15000 +} + +-- -1481015543 created by Hawk +Config.DoorList['MRDP_2DoubleEntry--1481015543'] = { + authorizedJobs = { ['police'] = 0 }, + distance = 2, + locked = true, + doors = { + {objName = -1481015543, objYaw = 270.00003051758, objCoords = vec3(470.490570, -1000.717712, 35.206952)}, + {objName = 952639784, objYaw = 270.0, objCoords = vec3(470.489686, -998.117920, 35.206952)} + }, + doorType = 'double', + doorLabel = '3886850', + doorRate = 1.0, + autoLock = 15000 +} + +-- Garage Entries + +-- 1255964982 created by Hawk +Config.DoorList['MRDP_Garage_Doubles-1255964982'] = { + authorizedJobs = { ['police'] = 0 }, + distance = 2, + locked = true, + doors = { + {objName = 1255964982, objYaw = 270.00003051758, objCoords = vec3(463.187622, -991.515686, 25.617482)}, + {objName = 1255964982, objYaw = 90.000022888184, objCoords = vec3(463.187622, -988.916992, 25.617482)} + }, + doorType = 'double', + doorLabel = '3880962', + doorRate = 1.0, + autoLock = 5000 +} + +-- 1255964982 created by Hawk +Config.DoorList['MRDP_Garage_Doubles2-1255964982'] = { + authorizedJobs = { ['police'] = 0 }, + distance = 2, + locked = true, + doors = { + {objName = 1255964982, objYaw = 180.00001525878, objCoords = vec3(471.858216, -993.203370, 25.616542)}, + {objName = 1255964982, objYaw = 1.0017911336036e-05, objCoords = vec3(474.457062, -993.203370, 25.616542)} + }, + doorType = 'double', + doorLabel = '39121944', + doorRate = 1.0, + autoLock = 5000 +} + +-- 1255964982 created by Hawk +Config.DoorList['MRDP_Garage_Doubles2_Interior-1255964982'] = { + authorizedJobs = { ['police'] = 0 }, + distance = 2, + locked = true, + doors = { + {objName = 1255964982, objYaw = 89.999977111816, objCoords = vec3(476.721894, -986.831298, 25.615550)}, + {objName = 1255964982, objYaw = 270.00003051758, objCoords = vec3(476.721894, -989.433410, 25.615550)} + }, + doorType = 'double', + doorLabel = '4025090', + doorRate = 1.0, + autoLock = 5000 +} + +-- HelipadDoors + +-- -1036090959 created by Hawk +Config.DoorList['MRDP_Roof_Door--1036090959'] = { + distance = 2, + objName = -1036090959, + objCoords = vec3(464.230468, -984.680420, 43.843894), + doorType = 'door', + fixText = false, + authorizedJobs = { ['police'] = 0 }, + locked = true, + doorRate = 1.0, + doorLabel = 'HeliPadDoor', + objYaw = 90.000022888184, + autoLock = 5000 +} + +-- -1036090959 created by Hawk +Config.DoorList['MRDP_Roof_Door_Interior--1036090959'] = { + distance = 2, + objName = -1036090959, + objCoords = vec3(463.323792, -982.196900, 38.760418), + doorType = 'door', + fixText = false, + authorizedJobs = { ['police'] = 0 }, + locked = true, + doorRate = 1.0, + doorLabel = 'HeliPadDoorInterior', + objYaw = 270.00003051758, + autoLock = 5000 +} + +-- Garage doors + +-- -1195127879 created by Hawk +Config.DoorList['MRPD_GarageDoor--1195127879'] = { + authorizedJobs = { ['police'] = 0 }, + locked = true, + doorType = 'garage', + objName = -1195127879, + doorLabel = 'MRPD_FirstGarageDoor', + doorRate = 1.0, + fixText = false, + objCoords = vec3(433.815124, -999.920472, 26.115582), + distance = 5, + objYaw = 0.0, + autoLock = 10000, + remoteTrigger = true +} + +-- -1195127879 created by Hawk +Config.DoorList['MRPD_GarageDoor2--1195127879'] = { + authorizedJobs = { ['police'] = 0 }, + locked = true, + doorType = 'garage', + objName = -1195127879, + doorLabel = 'MRPD_SecondGarageDoor', + doorRate = 1.0, + fixText = false, + objCoords = vec3(449.897766, -999.903198, 26.114242), + distance = 5, + objYaw = 0.0, + autoLock = 10000, + remoteTrigger = true +} + +-- -1195127879 created by Hawk +Config.DoorList['MRPD_GarageDoor3--1195127879'] = { + authorizedJobs = { ['police'] = 0 }, + locked = true, + doorType = 'garage', + objName = -1195127879, + doorLabel = 'MRPD_ThirdGarageDoor', + doorRate = 1.0, + fixText = false, + objCoords = vec3(488.767578, -1021.413146, 28.898114), + distance = 5, + objYaw = 90.000022888184, + autoLock = 10000, + remoteTrigger = true +} \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-doorlock/configs/paletobank.lua b/resources/[qb]/[qb_core]/qb-doorlock/configs/paletobank.lua new file mode 100644 index 0000000..3e2ac9d --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-doorlock/configs/paletobank.lua @@ -0,0 +1,71 @@ + + +-- PaletoInteriorVault3 created by Hawk +Config.DoorList['PaletoInteriorVault3-PaletoInteriorVault3'] = { + objYaw = 180, + doorRate = 2.0, + locked = true, + fixText = false, + objName = 1622278560, + objCoords = vec3(-104.813636, 6473.646484, 31.954800), + distance = 1, + authorizedJobs = { ['police'] = 0 }, + doorType = 'door', + doorLabel = 'PaletoInteriorVault3', +} + +-- PaletoInteriorVault created by Hawk +Config.DoorList['PaletoInteriorVault-PaletoInteriorVault'] = { + authorizedJobs = { ['police'] = 0 }, + doorRate = 1.0, + objCoords = vec3(-105.6242, 6474.6509, 31.6267), + locked = true, + fixText = false, + doorLabel = 'PaletoInteriorVault', + objName = 1309269072, + objYaw = 315.00006103516, + doorType = 'door', + distance = 3, +} + +-- PaletoCashRegister created by Hawk +Config.DoorList['PaletoCashRegister- PaletoCashRegister'] = { + objYaw = 45.000061035156, + objCoords = vec3(-108.914688, 6469.104980, 31.910284), + doorType = 'door', + fixText = false, + doorRate = 1.0, + locked = true, + distance = 0, + objName = -1184592117, + authorizedJobs = { ['police'] = 0 }, + doorLabel = 'PaletoCashRegister', +} + +-- UWUGarage created by Hawk +Config.DoorList['UWUGarage-UWUGarage'] = { + doorLabel = 'UWUGarage', + doorType = 'garage', + distance = 2, + authorizedJobs = { ['uwu'] = 0 }, + fixText = false, + locked = true, + objYaw = 270.00003051758, + objName = 522844070, + objCoords = vec3(-600.910584, -1059.217652, 21.721432), + doorRate = 1.0, +} + +-- UWUFrontDoor created by Hawk +Config.DoorList['UWUFrontDoor-UWUFrontDoor'] = { + locked = true, + doorType = 'double', + doors = { + {objName = 526179188, objYaw = 0.0, objCoords = vec3(-581.667786, -1069.627198, 22.489748)}, + {objName = -69331849, objYaw = 0.0, objCoords = vec3(-580.361084, -1069.627198, 22.489748)} + }, + distance = 5, + authorizedJobs = { ['uwu'] = 0 }, + doorLabel = 'UWUFrontDoor', + doorRate = 1.0, +} \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-doorlock/configs/pillbox.lua b/resources/[qb]/[qb_core]/qb-doorlock/configs/pillbox.lua new file mode 100644 index 0000000..876c765 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-doorlock/configs/pillbox.lua @@ -0,0 +1,193 @@ + + +-- Pillbox Upper MRI created by Hawk +Config.DoorList['PillboxMRIUpper-Pillbox Upper MRI'] = { + objYaw = 340.0, + doorLabel = 'MRI Dør', + doorRate = 1.0, + fixText = false, + distance = 2, + locked = false, + objCoords = vec3(309.252930, -570.573608, 43.395038), + authorizedJobs = { ['ambulance, police'] = 0 }, + objName = 1281019151, + doorType = 'door', +} + +-- ElevatorPillbox1 created by Hawk +Config.DoorList['ElevatorPillbox1-ElevatorPillbox1'] = { + doorRate = 1.0, + distance = 0, + locked = true, + doorLabel = 'ElevatorPillbox1', + doors = { + {objName = -1240156945, objYaw = 250.24739074708, objCoords = vec3(334.584960, -591.236450, 42.264454)}, + {objName = -1240156945, objYaw = 69.999992370606, objCoords = vec3(334.071808, -592.646180, 42.264454)} + }, + doorType = 'doublesliding', +} + +-- ElevatorPillbox2 created by Hawk +Config.DoorList['ElevatorPillbox2-ElevatorPillbox2'] = { + doorRate = 1.0, + distance = 0, + locked = true, + doorLabel = 'ElevatorPillbox2', + doors = { + {objName = -1240156945, objYaw = 249.99337768554, objCoords = vec3(335.764954, -587.991516, 42.269192)}, + {objName = -1240156945, objYaw = 69.999992370606, objCoords = vec3(335.251832, -589.401246, 42.269192)} + }, + doorType = 'doublesliding', +} + +-- PillboxElevator3 created by Hawk +Config.DoorList['PillboxElevator3-PillboxElevator3'] = { + doors = { + {objName = -1240156945, objYaw = 70.166603088378, objCoords = vec3(324.674682, -584.547302, 27.848122)}, + {objName = -1240156945, objYaw = 250.00146484375, objCoords = vec3(325.187774, -583.137574, 27.848122)} + }, + doorLabel = 'PillboxElevator3', + doorType = 'doublesliding', + locked = true, + doorRate = 1.0, + distance = 0, +} + +-- Elevator4 created by Hawk +Config.DoorList['Elevator4-Elevator4'] = { + distance = 0, + locked = true, + doorLabel = 'Elevator4', + doorRate = 1.0, + doors = { + {objName = -1240156945, objYaw = 69.99998474121, objCoords = vec3(323.490510, -587.791198, 27.847042)}, + {objName = -1240156945, objYaw = 250.0, objCoords = vec3(324.003418, -586.381958, 27.847042)} + }, + doorType = 'doublesliding', +} + +-- PillboxMRIOBS created by Hawk +Config.DoorList['PillboxMRIOBS-PillboxMRIOBS'] = { + objYaw = 250.00003051758, + doorLabel = 'PillboxMRIOBS', + doorRate = 1.0, + fixText = false, + distance = 1, + locked = true, + objCoords = vec3(311.624390, -570.570618, 43.395828), + authorizedJobs = { ['ambulance, police'] = 0 }, + objName = 1281019151, + doorType = 'door', +} + +-- PillboxUpperEntry created by Hawk +Config.DoorList['PillboxUpperEntry-PillboxUpperEntry'] = { + doorRate = 1.0, + distance = 5, + locked = false, + doorLabel = 'Hovedør', + authorizedJobs = { ['ambulance, police'] = 0 }, + doors = { + {objName = -923364535, objYaw = 250.0, objCoords = vec3(299.994964, -583.781250, 43.366504)}, + {objName = -69142243, objYaw = 250.0, objCoords = vec3(299.220368, -585.875366, 43.371972)} + }, + doorType = 'doublesliding', +} + +-- Pillbox Observation Upper created by Hawk +Config.DoorList['PillboxObservationUpper-Pillbox Observation Upper'] = { + objYaw = 340.0, + doorLabel = 'Observation Upper', + doorRate = 1.0, + fixText = false, + distance = 2, + locked = true, + objCoords = vec3(314.226990, -572.384034, 43.395038), + authorizedJobs = { ['ambulance, police'] = 0 }, + objName = 459631401, + doorType = 'door', +} + +-- PillboxWardCDør created by Hawk +Config.DoorList['PillboxWardCDør-PillboxWardCDør'] = { + doorRate = 1.0, + distance = 2, + locked = true, + doorLabel = 'PillboxWardCDør', + authorizedJobs = { ['ambulance, police'] = 0 }, + doors = { + {objName = 630194374, objYaw = 250.00003051758, objCoords = vec3(337.002136, -584.447326, 43.394818)}, + {objName = 630194374, objYaw = 69.999992370606, objCoords = vec3(337.817626, -582.206848, 43.394818)} + }, + doorType = 'double', +} + +-- PillboxOBSXRAY created by Hawk +Config.DoorList['PillboxOBSXRAY-PillboxOBSXRAY'] = { + objYaw = 250.00003051758, + doorLabel = 'PillboxOBSXRAY', + doorRate = 1.0, + fixText = false, + distance = 1, + locked = true, + objCoords = vec3(316.242492, -572.251464, 43.395828), + authorizedJobs = { ['ambulance, police'] = 0 }, + objName = 1281019151, + doorType = 'door', +} + +-- PillboxWardAMellemdør created by Hawk +Config.DoorList['PillboxWardAMellemdør-PillboxWardAMellemdør'] = { + doorRate = 1.0, + distance = 2, + locked = true, + doorLabel = 'PillboxWardAMellemdør', + authorizedJobs = { ['ambulance, police'] = 0 }, + doors = { + {objName = 1527147442, objYaw = 70.000030517578, objCoords = vec3(333.393464, -580.596558, 43.394822)}, + {objName = 1527147442, objYaw = 250.00003051758, objCoords = vec3(332.579650, -582.837646, 43.394822)} + }, + doorType = 'double', +} + +-- PillboxXRAYUpper created by Hawk +Config.DoorList['PillboxXRAYUpper-PillboxXRAYUpper'] = { + objYaw = 340.0, + doorLabel = 'PillboxXRAYUpper', + doorRate = 1.0, + fixText = false, + distance = 2, + locked = true, + objCoords = vec3(319.202302, -574.194886, 43.395038), + authorizedJobs = { ['ambulance, police'] = 0 }, + objName = 1281019151, + doorType = 'door', +} + +-- PillboxUpperPauserum created by Hawk +Config.DoorList['PillboxUpperPauserum-PillboxUpperPauserum'] = { + objYaw = 159.99998474122, + doorLabel = 'PillboxUpperPauserum', + doorRate = 1.0, + fixText = false, + distance = 2, + locked = true, + objCoords = vec3(310.535644, -591.911438, 43.395894), + authorizedJobs = { ['ambulance, police'] = 0 }, + objName = 102514839, + doorType = 'door', +} + +-- PillboxWardBOuterEntry created by Hawk +Config.DoorList['PillboxWardBOuterEntry-PillboxWardBOuterEntry'] = { + doorRate = 1.0, + distance = 3, + locked = false, + doorLabel = 'WardBYdreDøre', + authorizedJobs = { ['ambulance, police'] = 0 }, + doors = { + {objName = 1527147442, objYaw = 160.0, objCoords = vec3(309.472930, -575.663392, 43.396018)}, + {objName = 1527147442, objYaw = 340.0, objCoords = vec3(311.713044, -576.478698, 43.396018)} + }, + doorType = 'double', +} \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-doorlock/configs/prison.lua b/resources/[qb]/[qb_core]/qb-doorlock/configs/prison.lua new file mode 100644 index 0000000..e69de29 diff --git a/resources/[qb]/[qb_core]/qb-doorlock/configs/tequila.lua b/resources/[qb]/[qb_core]/qb-doorlock/configs/tequila.lua new file mode 100644 index 0000000..717f910 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-doorlock/configs/tequila.lua @@ -0,0 +1,85 @@ + + +-- tequila01 criado por GG. ! m. +Config.DoorList['tequila-tequila01'] = { + objCoords = vec3(-563.887329, 276.516632, 83.286263), + doorRate = 1.0, + objYaw = 355.00003051758, + authorizedJobs = { ['tequilala'] = 0 }, + doorLabel = 'tequila01', + fixText = false, + locked = true, + doorType = 'door', + distance = 2, + objName = -2037125726, +} + +-- tequila02 criado por GG. ! m. +Config.DoorList['tequila-tequila02'] = { + authorizedJobs = { ['tequilala'] = 0 }, + doorLabel = 'tequila02', + doorRate = 1.0, + doors = { + {objName = 1888438146, objYaw = 355.00003051758, objCoords = vec3(-561.399963, 278.584595, 83.125275)}, + {objName = 272205552, objYaw = 355.00003051758, objCoords = vec3(-559.460327, 278.414886, 83.124260)} + }, + locked = true, + distance = 2, + doorType = 'double', +} + +-- tequila03 criado por GG. ! m. +Config.DoorList['tequila-tequila03'] = { + objCoords = vec3(-567.583252, 280.997650, 83.126434), + doorRate = 1.0, + objYaw = 355.00003051758, + authorizedJobs = { ['tequilala'] = 0 }, + doorLabel = 'tequila03', + fixText = false, + locked = true, + doorType = 'door', + distance = 2, + objName = 1927676967, +} + +-- tequila04 criado por GG. ! m. +Config.DoorList['tequila-tequila04'] = { + objCoords = vec3(-567.935120, 291.926422, 85.524986), + doorRate = 1.0, + objYaw = 175.00003051758, + authorizedJobs = { ['tequilala'] = 0 }, + doorLabel = 'tequila04', + fixText = false, + locked = true, + doorType = 'door', + distance = 2, + objName = 1927676967, +} + +-- tequila05 criado por GG. ! m. +Config.DoorList['tequila-tequila05'] = { + authorizedJobs = { ['tequilala'] = 0 }, + doorLabel = 'tequila05', + doorRate = 1.0, + doors = { + {objName = 1888438146, objYaw = 265.00006103516, objCoords = vec3(-563.527100, 290.733093, 85.404839)}, + {objName = 272205552, objYaw = 265.00006103516, objCoords = vec3(-563.696777, 288.794495, 85.404839)} + }, + locked = true, + distance = 2, + doorType = 'double', +} + +-- tequila06 criado por GG. ! m. +Config.DoorList['tequila-tequila06'] = { + objCoords = vec3(-560.350647, 291.715027, 82.326088), + doorRate = 1.0, + objYaw = 265.00006103516, + authorizedJobs = { ['tequilala'] = 0 }, + doorLabel = 'tequila06', + fixText = false, + locked = true, + doorType = 'door', + distance = 2, + objName = 1927676967, +} \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-doorlock/configs/uwu.lua b/resources/[qb]/[qb_core]/qb-doorlock/configs/uwu.lua new file mode 100644 index 0000000..39955af --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-doorlock/configs/uwu.lua @@ -0,0 +1,43 @@ + + +-- UWUGarage created by Hawk +Config.DoorList['UWUGarage-UWUGarage'] = { + doorLabel = 'UWUGarage', + doorType = 'garage', + distance = 2, + authorizedJobs = { ['uwu'] = 0 }, + fixText = false, + locked = true, + objYaw = 270.00003051758, + objName = 522844070, + objCoords = vec3(-600.910584, -1059.217652, 21.721432), + doorRate = 1.0, +} + +-- UWUFrontDoor created by Hawk +Config.DoorList['UWUFrontDoor-UWUFrontDoor'] = { + locked = true, + doorType = 'double', + doors = { + {objName = 526179188, objYaw = 0.0, objCoords = vec3(-581.667786, -1069.627198, 22.489748)}, + {objName = -69331849, objYaw = 0.0, objCoords = vec3(-580.361084, -1069.627198, 22.489748)} + }, + distance = 5, + authorizedJobs = { ['uwu'] = 0 }, + doorLabel = 'UWUFrontDoor', + doorRate = 1.0, +} + +-- UWUBackdoor created by Hawk +Config.DoorList['UWUBackdoor-UWUBackdoor'] = { + doorLabel = 'UWUBackdoor', + doorType = 'door', + distance = 1, + authorizedJobs = { ['uwu'] = 0 }, + fixText = false, + locked = true, + objYaw = 269.99996948242, + objName = 1099436502, + objCoords = vec3(-600.888610, -1055.131470, 22.713028), + doorRate = 1.0, +} \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-doorlock/configs/vange.lua b/resources/[qb]/[qb_core]/qb-doorlock/configs/vange.lua new file mode 100644 index 0000000..67ac9d0 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-doorlock/configs/vange.lua @@ -0,0 +1,15 @@ + + +-- vange created by Shrek +Config.DoorList['vange-vange'] = { + doors = { + {objName = 1425919976, objYaw = 306.00003051758, objCoords = vec3(-631.955383, -236.333267, 38.206532)}, + {objName = 9467943, objYaw = 306.00003051758, objCoords = vec3(-630.426514, -238.437546, 38.206532)} + }, + doorLabel = 'vange', + doorRate = 1.0, + locked = true, + doorType = 'double', + distance = 2, + authorizedJobs = { ['police'] = 0 }, +} \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-doorlock/configs/vange2.lua b/resources/[qb]/[qb_core]/qb-doorlock/configs/vange2.lua new file mode 100644 index 0000000..0930705 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-doorlock/configs/vange2.lua @@ -0,0 +1,15 @@ + + +-- vange2 created by Shrek +Config.DoorList['vange2-vange2'] = { + objYaw = 36.000022888184, + objName = 1335309163, + distance = 2, + locked = true, + fixText = false, + doorType = 'door', + authorizedJobs = { ['police'] = 0 }, + doorRate = 1.0, + objCoords = vec3(-629.133850, -230.151703, 38.206585), + doorLabel = 'vange2', +} \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-doorlock/fxmanifest.lua b/resources/[qb]/[qb_core]/qb-doorlock/fxmanifest.lua new file mode 100644 index 0000000..65b29cc --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-doorlock/fxmanifest.lua @@ -0,0 +1,26 @@ +fx_version 'cerulean' +game 'gta5' + +description 'Doorlock system for the QBCore Framework' +version '2.0.0' + +ui_page 'html/index.html' + +shared_scripts { + 'config.lua', + 'configs/*.lua', + '@qb-core/shared/locale.lua', + 'locales/da.lua' -- Change this to your preferred language +} + +server_script 'server/main.lua' +client_script 'client/main.lua' + +files { + 'html/*.html', + 'html/*.js', + 'html/*.css', + 'html/sounds/*.ogg', +} + +lua54 'yes' \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-doorlock/html/app.js b/resources/[qb]/[qb_core]/qb-doorlock/html/app.js new file mode 100644 index 0000000..0f68abd --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-doorlock/html/app.js @@ -0,0 +1,80 @@ +const app = Vue.createApp({ + data() { + return { + coords: "", + heading: "", + hash: "", + doorlockStyleObject: { + "display": "none", + "background-color": "rgb(19, 28, 74)" + }, + doorlockClassObject: { + "slide_in": false, + "slide_out": false + }, + doorText: "" + } + }, + mounted() { + this.listener = window.addEventListener("message", (event) => { + switch (event.data.type) { + case "setText": + this.setText(event.data); + break; + case "setDoorText": + this.setDoorText(event.data) + break; + case "audio": + this.playAudio(event.data) + break; + default: + break; + } + }) + }, + beforeUnmount() { + window.removeEventListener(this.listener); + }, + methods: { + setText(data) { + if (data.aim) { + const element = document.getElementById("aim"); + element.style.display = data.aim; + } + + if (data.details) { + this.coords = data.coords; + this.heading = data.heading; + this.hash = data.hash; + const element = document.getElementById("textDetails"); + element.style.display = data.details; + } + }, + setDoorText(data) { + if (data.enable) { + if (this.doorlockStyleObject["display"] == "none") { + this.doorlockClassObject["slide_in"] = true; + this.doorlockStyleObject["display"] = "flex"; + } + this.doorlockStyleObject["background-color"] = data.color; + this.doorText = data.text; + } else { + this.doorlockClassObject["slide_in"] = false; + this.doorlockClassObject["slide_out"] = true; + setTimeout(() => { + this.doorlockStyleObject["display"] = "none"; + this.doorlockClassObject["slide_out"] = false; + this.doorText = ""; + }, 1000); + } + }, + playAudio(data) { + var volume = (data.audio['volume'] / 10) * data.sfx + if (data.distance !== 0) volume /= data.distance; + if (volume > 1.0) volume = 1.0; + const sound = new Audio('sounds/' + data.audio['file']); + sound.volume = volume; + sound.play(); + } + } +}).mount('#mainContainer'); diff --git a/resources/[qb]/[qb_core]/qb-doorlock/html/index.html b/resources/[qb]/[qb_core]/qb-doorlock/html/index.html new file mode 100644 index 0000000..58e0203 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-doorlock/html/index.html @@ -0,0 +1,23 @@ + + + + + + + + +
+ +
{{ doorText }}
+
+ + +
+
+ + + \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-doorlock/html/sounds/button-remote.ogg b/resources/[qb]/[qb_core]/qb-doorlock/html/sounds/button-remote.ogg new file mode 100644 index 0000000..4657023 Binary files /dev/null and b/resources/[qb]/[qb_core]/qb-doorlock/html/sounds/button-remote.ogg differ diff --git a/resources/[qb]/[qb_core]/qb-doorlock/html/sounds/door-bolt-4.ogg b/resources/[qb]/[qb_core]/qb-doorlock/html/sounds/door-bolt-4.ogg new file mode 100644 index 0000000..61a43bb Binary files /dev/null and b/resources/[qb]/[qb_core]/qb-doorlock/html/sounds/door-bolt-4.ogg differ diff --git a/resources/[qb]/[qb_core]/qb-doorlock/html/sounds/metal-locker.ogg b/resources/[qb]/[qb_core]/qb-doorlock/html/sounds/metal-locker.ogg new file mode 100644 index 0000000..deea9de Binary files /dev/null and b/resources/[qb]/[qb_core]/qb-doorlock/html/sounds/metal-locker.ogg differ diff --git a/resources/[qb]/[qb_core]/qb-doorlock/html/sounds/metallic-creak.ogg b/resources/[qb]/[qb_core]/qb-doorlock/html/sounds/metallic-creak.ogg new file mode 100644 index 0000000..9c66233 Binary files /dev/null and b/resources/[qb]/[qb_core]/qb-doorlock/html/sounds/metallic-creak.ogg differ diff --git a/resources/[qb]/[qb_core]/qb-doorlock/html/style.css b/resources/[qb]/[qb_core]/qb-doorlock/html/style.css new file mode 100644 index 0000000..da912c2 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-doorlock/html/style.css @@ -0,0 +1,47 @@ +body { + font-family: 'Poppins', sans-serif; +} + +.textInfo { + position: absolute; + top: 50%; + float: left; + color: white; +} + +#doorlock-container { + position: absolute; + flex-wrap: wrap; + top: 45vh; + padding: 0.6em; + width: fit-content; + color: white; + border-radius: 0.3em; + left: -15%; +} + +.slide_in { + animation: slide-in 1s forwards; +} + +.slide_out { + animation: slide-out 1s forwards; +} + +@keyframes slide-in { + 0% { + left: -15%; + } + 100% { + left: 0.5%; + } +} + +@keyframes slide-out { + 0% { + left: 0.5%; + } + 100% { + left: -15%; + } +} \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-doorlock/locales/da.lua b/resources/[qb]/[qb_core]/qb-doorlock/locales/da.lua new file mode 100644 index 0000000..640bbaa --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-doorlock/locales/da.lua @@ -0,0 +1,56 @@ +local Translations = { + error = { + lockpick_fail = "Fejlet", + door_not_found = "Fandt ikke en model hash, hvis døren er gennemsigtig, skal du sørge for at sigte på dørkarmen", + same_entity = "Begge døre kan ikke være samme enhed", + door_registered = "Denne dør er allerede registreret", + door_identifier_exists = "En dør med denne identifikator findes allerede i konfigurationen. (%s)", + }, + success = { + lockpick_success = "Success" + }, + general = { + locked = "Låst", + unlocked = "Ulåst", + locked_button = "[E] - Låst", + unlocked_button = "[E] - Ulåst", + keymapping_description = "Interager med dørlåse", + keymapping_remotetriggerdoor = "Fjernudløs en dør", + locked_menu = "Låst", + pickable_menu = "Kan dirkes op", + cantunlock_menu = 'Kan ikke låses op', + hidelabel_menu = 'Gem label', + distance_menu = "Max Distance", + item_authorisation_menu = "Genstands-tilladelse", + citizenid_authorisation_menu = "BorgerID tilladelse", + gang_authorisation_menu = "Bande tilladelse", + job_authorisation_menu = "Job tilladelse", + doortype_title = "Dørtype", + doortype_door = "Enkel dør", + doortype_double = "Dobbledør", + doortype_sliding = "Enkel skydedør", + doortype_doublesliding = "Dobbel skydedør", + doortype_garage = "Garage", + dooridentifier_title = "Unikt ID", + doorlabel_title = "Navn på dør", + configfile_title = "Navn på konfigurationsfil", + submit_text = "Gem", + newdoor_menu_title = "Tilføj ny dør", + newdoor_command_description = "Tilføj en ny dør til dørlåsesystemet", + doordebug_command_description = "Skift debug-tilstand", + warning = "Advarsel", + created_by = "lavet af", + warn_no_permission_newdoor = "%{player} (%{license}) prøvede at åbne en dør uden tilladel (Source: %{source})", + warn_no_authorisation = "%{player} (%{license}) prøvede at åbne en dør uden tilladelse (Source: %{doorID})", + warn_wrong_doorid = "%{player} (%{license}) prøvede at opdatere en ugyldig dør (Source: %{doorID})", + warn_wrong_state = "%{player} (%{license}) prøvede at opdatere til en ugyldig tilstand (Source: %{state})", + warn_wrong_doorid_type = "%{player} (%{license}) sendte ikke en passende dørID (Sendt: %{doorID})", + warn_admin_privilege_used = "%{player} (%{license}) åbnede en dør ved hjælp af admin-privilegier" + } +} + +Lang = Locale:new({ + phrases = Translations, + warnOnMissing = true, + fallbackLang = Lang, +}) \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-doorlock/saves/doorstates.json b/resources/[qb]/[qb_core]/qb-doorlock/saves/doorstates.json new file mode 100644 index 0000000..a0f0835 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-doorlock/saves/doorstates.json @@ -0,0 +1 @@ +{"Custody_B2--1036090959":true,"71":true,"MRPD_CustodyA1--103609059":true,"PillboxXRAYUpper-PillboxXRAYUpper":false,"MRPD_GarageDoor3--1195127879":true,"Custody_B1--1036090959":true,"PressroomD2--824920418":true,"4":false,"MRPD_CustodyA2--103609059":true,"PillboxMRIUpper-Pillbox Upper MRI":true,"16":true,"MRPD_EvidenceDoor-749848321":true,"prison-innergate":false,"15":false,"LuxuryFrontDoor1-LuxuryFrontDoor1":true,"12":false,"13":false,"MRPD_GarageDoor2--1195127879":true} \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-doorlock/server/main.lua b/resources/[qb]/[qb_core]/qb-doorlock/server/main.lua new file mode 100644 index 0000000..cf1a873 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-doorlock/server/main.lua @@ -0,0 +1,324 @@ +local QBCore = exports['qb-core']:GetCoreObject() + +-- Functions + +local function showWarning(msg) + print(('^3%s: %s^0'):format(Lang:t("general.warning"), msg)) +end + +local function removeItem(Player, item) + if Config.Consumables[item.name] then + Player.Functions.RemoveItem(item.name, item.amount >= Config.Consumables[item.name] and Config.Consumables[item.name] or 1) + end +end + +local function checkAndRemoveItem(Player, item, shouldRemove) + if not item then return false end + if shouldRemove then + removeItem(Player, item) + end + return true +end + +local function checkItems(Player, items, needsAll, shouldRemove) + if needsAll == nil then needsAll = true end + local isTable = type(items) == 'table' + local isArray = isTable and table.type(items) == 'array' or false + local totalItems = 0 + local count = 0 + if isTable then for _ in pairs(items) do totalItems += 1 end else totalItems = #items end + local kvIndex + if isArray then kvIndex = 2 else kvIndex = 1 end + if isTable then + for k, v in pairs(items) do + local itemKV = {k, v} + local item = Player.Functions.GetItemByName(itemKV[kvIndex]) + if needsAll then + if checkAndRemoveItem(Player, item, false) then + count += 1 + end + else + if checkAndRemoveItem(Player, item, shouldRemove) then + return true + end + end + end + if count == totalItems then + for k, v in pairs(items) do + local itemKV = {k, v} + local item = Player.Functions.GetItemByName(itemKV[kvIndex]) + checkAndRemoveItem(Player, item, shouldRemove) + end + return true + end + else -- Single item as string + local item = Player.Functions.GetItemByName(items) + return checkAndRemoveItem(Player, item, shouldRemove) + end + return false +end + +local function isAuthorized(Player, door, usedLockpick) + if door.allAuthorized then return true end + + if (door.pickable or door.lockpick) and usedLockpick then return true end + + if door.authorizedJobs then + if door.authorizedJobs[Player.PlayerData.job.name] and Player.PlayerData.job.grade.level >= door.authorizedJobs[Player.PlayerData.job.name] then + return true + elseif type(door.authorizedJobs[1]) == 'string' then + for _, job in pairs(door.authorizedJobs) do -- Support for old format + if job == Player.PlayerData.job.name then return true end + end + end + end + if door.authorizedGangs then + if door.authorizedGangs[Player.PlayerData.gang.name] and Player.PlayerData.gang.grade.level >= door.authorizedGangs[Player.PlayerData.gang.name] then + return true + elseif type(door.authorizedGangs[1]) == 'string' then + for _, gang in pairs(door.authorizedGangs) do -- Support for old format + if gang == Player.PlayerData.gang.name then return true end + end + end + end + + if door.authorizedCitizenIDs then + if door.authorizedCitizenIDs[Player.PlayerData.citizenid] then + return true + elseif type(door.authorizedCitizenIDs[1]) == 'string' then + for _, id in pairs(door.authorizedCitizenIDs) do -- Support for old format + if id == Player.PlayerData.citizenid then return true end + end + end + end + + if door.items then return checkItems(Player, door.items, door.needsAllItems, true) end + + if Config.AdminAccess and QBCore.Functions.HasPermission(Player.PlayerData.source, Config.AdminPermission) then + if Config.Warnings then + showWarning(Lang:t("general.warn_admin_privilege_used", {player = Player.PlayerData.name, license = Player.PlayerData.license})) + end + return true + end + + return false +end + +local function SaveDoorStates() + SaveResourceFile(GetCurrentResourceName(), "./saves/doorstates.json", json.encode(Config.DoorStates), -1) +end + +local function LoadDoorStates() + local DoorStates = LoadResourceFile(GetCurrentResourceName(), "./saves/doorstates.json") + if DoorStates then + DoorStates = json.decode(DoorStates) + if not next(DoorStates) then return end + + for key,isLocked in pairs(DoorStates) do + if Config.DoorList[key] ~= nil then + Config.DoorList[key].locked = isLocked + end + end + Config.DoorStates = DoorStates + end +end + +-- Callbacks + +QBCore.Functions.CreateCallback('qb-doorlock:server:setupDoors', function(_, cb) + cb(Config.DoorList) +end) + +QBCore.Functions.CreateCallback('qb-doorlock:server:checkItems', function(source, cb, items, needsAll) + local Player = QBCore.Functions.GetPlayer(source) + cb(checkItems(Player, items, needsAll, false)) +end) + +-- Events + +RegisterNetEvent('qb-doorlock:server:updateState', function(doorID, locked, src, usedLockpick, unlockAnyway, enableSounds, enableAnimation, sentSource) + local playerId = sentSource or source + local Player = QBCore.Functions.GetPlayer(playerId) + if not Player then return end + if type(doorID) ~= 'number' and type(doorID) ~= 'string' then + if Config.Warnings then + showWarning((Lang:t("general.warn_wrong_doorid_type", {player = Player.PlayerData.name, license = Player.PlayerData.license, doorID = doorID}))) + end + return + end + + if type(locked) ~= 'boolean' then + if Config.Warnings then + showWarning((Lang:t("general.warn_wrong_state", {player = Player.PlayerData.name, license = Player.PlayerData.license, state = locked}))) + end + return + end + + if not Config.DoorList[doorID] then + if Config.Warnings then + showWarning(Lang:t("general.warn_wrong_doorid", {player = Player.PlayerData.name, license = Player.PlayerData.license, doorID = doorID})) + end + return + end + + if not unlockAnyway and not isAuthorized(Player, Config.DoorList[doorID], usedLockpick) then + if Config.Warnings then + showWarning(Lang:t("general.warn_no_authorisation", {player = Player.PlayerData.name, license = Player.PlayerData.license, doorID = doorID})) + end + return + end + + Config.DoorList[doorID].locked = locked + if Config.DoorStates[doorID] == nil then Config.DoorStates[doorID] = locked elseif Config.DoorStates[doorID] ~= locked then Config.DoorStates[doorID] = nil end + TriggerClientEvent('qb-doorlock:client:setState', -1, playerId, doorID, locked, src or false, enableSounds, enableAnimation) + + if not Config.DoorList[doorID].autoLock then return end + SetTimeout(Config.DoorList[doorID].autoLock, function() + if Config.DoorList[doorID].locked then return end + Config.DoorList[doorID].locked = true + if Config.DoorStates[doorID] == nil then Config.DoorStates[doorID] = locked elseif Config.DoorStates[doorID] ~= locked then Config.DoorStates[doorID] = nil end + TriggerClientEvent('qb-doorlock:client:setState', -1, playerId, doorID, true, src or false, enableSounds, enableAnimation) + end) +end) + +RegisterNetEvent('qb-doorlock:server:saveNewDoor', function(data, doubleDoor) + local src = source + if not QBCore.Functions.HasPermission(src, Config.CommandPermission) and not IsPlayerAceAllowed(src, 'command') then + if Config.Warnings then + showWarning(Lang:t("general.warn_no_permission_newdoor", {player = GetPlayerName(src), license = QBCore.Functions.GetIdentifier(src, 'license'), source = src})) + end + return + end + local Player = QBCore.Functions.GetPlayer(src) + if not Player then return end + local configData = {} + local jobs, gangs, cids, items, doorType, identifier + if data.job then configData.authorizedJobs = { [data.job] = 0 } jobs = "['"..data.job.."'] = 0" end + if data.gang then configData.authorizedGangs = { [data.gang] = 0 } gangs = "['"..data.gang.."'] = 0" end + if data.cid then configData.authorizedCitizenIDs = { [data.cid] = true } cids = "['"..data.cid.."'] = true" end + if data.item then configData.items = { [data.item] = 1 } items = "['"..data.item.."'] = 1" end + configData.locked = data.locked + configData.pickable = data.pickable + configData.cantUnlock = data.cantunlock + configData.hideLabel = data.hidelabel + configData.distance = data.distance + configData.doorType = data.doortype + configData.doorRate = 1.0 + configData.doorLabel = data.doorlabel + doorType = "'"..data.doortype.."'" + identifier = data.configfile..'-'..data.dooridentifier + if doubleDoor then + configData.doors = { + {objName = data.model[1], objYaw = data.heading[1], objCoords = data.coords[1]}, + {objName = data.model[2], objYaw = data.heading[2], objCoords = data.coords[2]} + } + else + configData.objName = data.model + configData.objYaw = data.heading + configData.objCoords = data.coords + configData.fixText = false + end + + local path = GetResourcePath(GetCurrentResourceName()) + + if data.configfile then + local tempfile, err = io.open(path:gsub('//', '/')..'/configs/'..string.gsub(data.configfile, ".lua", "")..'.lua', 'a+') + if tempfile then + tempfile:close() + path = path:gsub('//', '/')..'/configs/'..string.gsub(data.configfile, ".lua", "")..'.lua' + else + return error(err) + end + else + path = path:gsub('//', '/')..'/config.lua' + end + + local file = io.open(path, 'a+') + local label = "\n\n-- "..data.dooridentifier.." ".. Lang:t("general.created_by") .." "..Player.PlayerData.name.."\nConfig.DoorList['"..identifier.."'] = {" + file:write(label) + for k, v in pairs(configData) do + if k == 'authorizedJobs' or k == 'authorizedGangs' or k == 'authorizedCitizenIDs' or k == 'items' then + local auth = jobs + if k == 'authorizedGangs' then + auth = gangs + elseif k == 'authorizedCitizenIDs' then + auth = cids + elseif k == 'items' then + auth = items + end + local str = ("\n %s = { %s },"):format(k, auth) + file:write(str) + elseif k == 'doors' then + local doors = {} + for i = 1, 2 do + doors[i] = (" {objName = %s, objYaw = %s, objCoords = %s}"):format(configData.doors[i].objName, configData.doors[i].objYaw, configData.doors[i].objCoords) + end + local str = ("\n %s = {\n %s,\n %s\n },"):format(k, doors[1], doors[2]) + file:write(str) + elseif k == 'doorType' then + local str = ("\n %s = %s,"):format(k, doorType) + file:write(str) + elseif k == 'doorLabel' then + local str = ("\n %s = '%s',"):format(k, v) + file:write(str) + else + local str = ("\n %s = %s,"):format(k, v) + file:write(str) + end + end + file:write("\n}") + file:close() + + Config.DoorList[identifier] = configData + TriggerClientEvent('qb-doorlock:client:newDoorAdded', -1, configData, identifier, src) +end) + +AddEventHandler('onResourceStart', function(resource) + if GetCurrentResourceName() == resource and Config.PersistentDoorStates then + CreateThread(function() + LoadDoorStates() + Wait(1000) + while true do + Wait(Config.PersistentSaveInternal) + SaveDoorStates() + end + end) + end +end) + +AddEventHandler('onResourceStop', function(resource) + if GetCurrentResourceName() == resource and Config.PersistentDoorStates then + SaveDoorStates() + end +end) + +RegisterNetEvent('txAdmin:events:scheduledRestart', function(eventData) + if eventData.secondsRemaining == 60 then + CreateThread(function() + Wait(45000) + SaveDoorStates() + end) + else + SaveDoorStates() + end +end) + +RegisterNetEvent('qb-doorlock:server:removeLockpick', function(type) + local Player = QBCore.Functions.GetPlayer(source) + + if not Player then return end + + if type == "advancedlockpick" or type == "lockpick" then + Player.Functions.RemoveItem(type, 1) + end +end) + +-- Commands + +QBCore.Commands.Add('newdoor', Lang:t("general.newdoor_command_description"), {}, false, function(source) + TriggerClientEvent('qb-doorlock:client:addNewDoor', source) +end, Config.CommandPermission) + +QBCore.Commands.Add('doordebug', Lang:t("general.doordebug_command_description"), {}, false, function(source) + TriggerClientEvent('qb-doorlock:client:ToggleDoorDebug', source) +end, Config.CommandPermission) diff --git a/resources/[qb]/[qb_core]/qb-drawtext/LICENSE b/resources/[qb]/[qb_core]/qb-drawtext/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-drawtext/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/resources/[qb]/[qb_core]/qb-drawtext/README.md b/resources/[qb]/[qb_core]/qb-drawtext/README.md new file mode 100644 index 0000000..c932bcb --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-drawtext/README.md @@ -0,0 +1,43 @@ +# Feature should be baked into qb-core soon :) Same events and exports but qb-core instead of qb-drawtext + +# qb-drawtext + +A NUI DrawText with a few different config options. Please make sure you handle the showing and hiding as if done incorrectly it will just repeatadly send nui messages. + +## How to use + +This can be triggered from the server or client. A more advanced example is posted below. +|Function or Event | Description | +|--|--| +| `exports['qb-drawtext']:DrawText('message','position')` | This is a client export that will draw a message at the specified position (listed below) | +| `exports['qb-drawtext']:ChangeText('message','position')` | This is a client export that will change the currently displayed message at the specified position (listed below) | +| `exports['qb-drawtext']:HideText()` | This will hide the text display | +| `exports['qb-drawtext']:KeyPressed()` | This is useful if you want to change the background and hide the text on keypress (if not handled correctly users will have to renter the zone to display) | +| `TriggerClientEvent('qb-drawtext:client:DrawText', message, position)` | The same as the export but as an event | +| `TriggerClientEvent('qb-drawtext:client:ChangeText', message, position)` | The same as the export but as an event | +| `TriggerClientEvent('qb-drawtext:client:HideText')` | The same as the export but as an event | +| `TriggerClientEvent('qb-drawtext:client:KeyPressed')` | The same as the export but as an event | + +Positions accepted are: + +* left +* right +* top + +## License + + qb-drawtext + Copyright (C) 2021 IdrisDose + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see diff --git a/resources/[qb]/[qb_core]/qb-drawtext/client/main.lua b/resources/[qb]/[qb_core]/qb-drawtext/client/main.lua new file mode 100644 index 0000000..4d4b7a7 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-drawtext/client/main.lua @@ -0,0 +1,61 @@ +local function HideText() + SendNUIMessage({ + action = 'HIDE_TEXT', + }) +end + +local function DrawText(text, position) + if (not type(position) == "string") then position = "left" end + + SendNUIMessage({ + action = 'DRAW_TEXT', + data = { + text = text, + position = position + } + }) +end + + +local function ChangeText(text, position) + if (not type(position) == "string") then position = "left" end + + SendNUIMessage({ + action = 'CHANGE_TEXT', + data = { + text = text, + position = position + } + }) +end + +local function KeyPressed() + Citizen.CreateThread(function() -- Not sure if a thread is needed but why not eh? + SendNUIMessage({ + action = 'KEY_PRESSED', + }) + Wait(500) + HideText() + end) +end + +RegisterNetEvent('qb-drawtext:client:DrawText', function(text, position) + DrawText(text, position) +end) + +RegisterNetEvent('qb-drawtext:client:ChangeText', function(text, position) + ChangeText(text, position) +end) + +RegisterNetEvent('qb-drawtext:client:HideText', function() + HideText() +end) + +RegisterNetEvent('qb-drawtext:client:KeyPressed', function() + KeyPressed() +end) + +exports('DrawText', DrawText) +exports('ChangeText', ChangeText) +exports('HideText', HideText) +exports('KeyPressed', KeyPressed) \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-drawtext/fxmanifest.lua b/resources/[qb]/[qb_core]/qb-drawtext/fxmanifest.lua new file mode 100644 index 0000000..0b0e9c6 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-drawtext/fxmanifest.lua @@ -0,0 +1,14 @@ +fx_version 'cerulean' +game 'gta5' + +version 'V1.0' + +client_script 'client/main.lua' + +ui_page 'html/index.html' + +files { + 'html/index.html', + 'html/script.js', + 'html/style.css' +} \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-drawtext/html/index.html b/resources/[qb]/[qb_core]/qb-drawtext/html/index.html new file mode 100644 index 0000000..a1c4fd7 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-drawtext/html/index.html @@ -0,0 +1,16 @@ + + + + + + QB DrawText + + + + +
+
+
+ + + diff --git a/resources/[qb]/[qb_core]/qb-drawtext/html/script.js b/resources/[qb]/[qb_core]/qb-drawtext/html/script.js new file mode 100644 index 0000000..b32ca1d --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-drawtext/html/script.js @@ -0,0 +1,124 @@ +let direction = null; + +const drawText = async (textData) => { + const text = document.getElementById("text"); + let {position} = textData; + switch (textData.position) { + case "left": + addClass(text, position); + direction = "left"; + break; + case "top": + addClass(text, position); + direction = "top"; + break; + case "right": + addClass(text, position); + direction = "right"; + break; + default: + addClass(text, "left"); + direction = "left"; + break; + } + + text.innerHTML = textData.text; + document.getElementById("container").style.display = "block"; + await sleep(100); + addClass(text, "show"); +}; + +const changeText = async (textData) => { + const text = document.getElementById("text"); + let {position} = textData; + + removeClass(text, "show"); + addClass(text, "pressed"); + addClass(text, "hide"); + + await sleep(500); + removeClass(text, "left"); + removeClass(text, "right"); + removeClass(text, "top"); + removeClass(text, "bottom"); + removeClass(text, "hide"); + removeClass(text, "pressed"); + + switch (textData.position) { + case "left": + addClass(text, position); + direction = "left"; + break; + case "top": + addClass(text, position); + direction = "top"; + break; + case "right": + addClass(text, position); + direction = "right"; + break; + default: + addClass(text, "left"); + direction = "left"; + break; + } + text.innerHTML = textData.text; + + await sleep(100); + text.classList.add("show"); +}; + +const hideText = async () => { + const text = document.getElementById("text"); + removeClass(text, "show"); + addClass(text, "hide"); + + setTimeout(() => { + removeClass(text, "left"); + removeClass(text, "right"); + removeClass(text, "top"); + removeClass(text, "bottom"); + removeClass(text, "hide"); + removeClass(text, "pressed"); + document.getElementById("container").style.display = "none"; + }, 1000); +}; + +const keyPressed = () => { + const text = document.getElementById("text"); + addClass(text, "pressed"); +}; + +window.addEventListener("message", (event) => { + const data = event.data; + const action = data.action; + const textData = data.data; + switch (action) { + case "DRAW_TEXT": + return drawText(textData); + case "CHANGE_TEXT": + return changeText(textData); + case "HIDE_TEXT": + return hideText(); + case "KEY_PRESSED": + return keyPressed(); + default: + return; + } +}); + +const sleep = (ms) => { + return new Promise((resolve) => setTimeout(resolve, ms)); +}; + +const removeClass = (element, name) => { + if (element.classList.contains(name)) { + element.classList.remove(name); + } +}; + +const addClass = (element, name) => { + if (!element.classList.contains(name)) { + element.classList.add(name); + } +}; diff --git a/resources/[qb]/[qb_core]/qb-drawtext/html/style.css b/resources/[qb]/[qb_core]/qb-drawtext/html/style.css new file mode 100644 index 0000000..8319aa6 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-drawtext/html/style.css @@ -0,0 +1,83 @@ +@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;500&display=swap"); +:root { + --primary-bg: rgba(23, 23, 23, 90%); + --active-bg: #dc143c; + --font-color: white; +} + +* { + padding: 0; + margin: 0; + font-family: "Poppins", sans-serif !important; + font-weight: 300; +} + +html::-webkit-scrollbar { + display: none; +} + +#container { + display: none; + width: 100%; + height: 100%; + overflow: hidden; +} +.text { + position: absolute; + background: var(--primary-bg); + color: var(--font-color); + margin-top: 0.5rem; + padding: 0.45rem; + border-radius: 0.15rem; + box-shadow: 0rem 0rem 0.1rem 0.05rem #000000; +} + +.text.pressed { + background: var(--active-bg); +} + +.top { + left: 45vw; + top: -100px; +} +.top.show { + transition: 0.5s; + top: 10px; + opacity: 1; +} +.top.hide { + transition: 0.5s; + top: -100px; + opacity: 0; +} + +.right { + top: 50%; + right: -100px; +} +.right.show { + transition: 0.5s; + right: 10px; + opacity: 1; +} +.right.hide { + transition: 0.5s; + right: -100px; + opacity: 0; +} + +.left { + top: 50%; + left: -100px; +} + +.left.show { + transition: 0.5s; + left: 10px; + opacity: 1; +} +.left.hide { + transition: 0.5s; + left: -100px; + opacity: 0; +} diff --git a/resources/[qb]/[qb_core]/qb-fitbit/LICENSE b/resources/[qb]/[qb_core]/qb-fitbit/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-fitbit/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/resources/[qb]/[qb_core]/qb-fitbit/README.md b/resources/[qb]/[qb_core]/qb-fitbit/README.md new file mode 100644 index 0000000..4e6a664 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-fitbit/README.md @@ -0,0 +1,20 @@ +# qb-fitbit +FitBit For QB-Core + +# License + + QBCore Framework + Copyright (C) 2021 Joshua Eger + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see diff --git a/resources/[qb]/[qb_core]/qb-fitbit/client/main.lua b/resources/[qb]/[qb_core]/qb-fitbit/client/main.lua new file mode 100644 index 0000000..f607038 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-fitbit/client/main.lua @@ -0,0 +1,105 @@ +local QBCore = exports['qb-core']:GetCoreObject() +local PlayerData = QBCore.Functions.GetPlayerData() +local isLoggedIn = LocalPlayer.state.isLoggedIn +local hasFitbit = false +local cooldown = false +-- Functions +local function fitbitCheck(PlayerItems) + for _, item in pairs(PlayerItems) do + if item.name == "fitbit" then + return true + end + end +end + +local function openWatch() + SendNUIMessage({ + action = "openWatch", + watchData = {} + }) + SetNuiFocus(true, true) +end + +local function closeWatch() + SetNuiFocus(false, false) +end + +local function round(num, numDecimalPlaces) + local mult = 10^(numDecimalPlaces or 0) + return math.floor(num * mult + 0.5) / mult +end + +local function activateCooldown() + cooldown = true + Wait(2*60*1000) + cooldown = false +end +-- Events + +AddStateBagChangeHandler('isLoggedIn', nil, function(_, _, value) + isLoggedIn = value +end) + +AddEventHandler('onResourceStart', function(resourceName) + if GetCurrentResourceName() ~= resourceName or not isLoggedIn then return end + PlayerData = QBCore.Functions.GetPlayerData() + hasFitbit = fitbitCheck(PlayerData.items) +end) + +RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() + PlayerData = QBCore.Functions.GetPlayerData() + hasFitbit = fitbitCheck(PlayerData.items) +end) + +RegisterNetEvent('QBCore:Client:OnPlayerUnload', function() + hasFitbit = false + PlayerData = {} +end) + +RegisterNetEvent('QBCore:Player:SetPlayerData', function(val) + PlayerData = val + hasFitbit = fitbitCheck(PlayerData.items) +end) + +RegisterNetEvent('hud:client:UpdateNeeds', function(newHunger, newThirst) + if not hasFitbit or cooldown then return end + if PlayerData.metadata["fitbit"].food then + if newHunger < PlayerData.metadata["fitbit"].food then + TriggerEvent("chatMessage", Lang:t('info.fitbit'), "warning", Lang:t('warning.hunger_warning', {hunger = round(newHunger, 2)})) + PlaySound(-1, "Event_Start_Text", "GTAO_FM_Events_Soundset", 0, 0, 1) + end + end + + if PlayerData.metadata["fitbit"].thirst then + if newThirst < PlayerData.metadata["fitbit"].thirst then + TriggerEvent("chatMessage", Lang:t('info.fitbit'), "warning", Lang:t('warning.thirst_warning', {thirst = round(newThirst, 2)})) + PlaySound(-1, "Event_Start_Text", "GTAO_FM_Events_Soundset", 0, 0, 1) + end + end + activateCooldown() +end) + +RegisterNetEvent('qb-fitbit:use', function() + openWatch() +end) + +-- NUI Callbacks + +RegisterNUICallback('close', function(_, cb) + closeWatch() + cb('ok') +end) + +RegisterNUICallback('setFoodWarning', function(data, cb) + local foodValue = tonumber(data.value) + TriggerServerEvent('qb-fitbit:server:setValue', 'food', foodValue) + QBCore.Functions.Notify(Lang:t('success.hunger_set', {hungervalue = foodValue}), 'success') + cb('ok') +end) + +RegisterNUICallback('setThirstWarning', function(data, cb) + local thirstValue = tonumber(data.value) + TriggerServerEvent('qb-fitbit:server:setValue', 'thirst', thirstValue) + QBCore.Functions.Notify(Lang:t('success.thirst_set', {thirstvalue = thirstValue}), 'success') + cb('ok') +end) \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-fitbit/fxmanifest.lua b/resources/[qb]/[qb_core]/qb-fitbit/fxmanifest.lua new file mode 100644 index 0000000..aefaa58 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-fitbit/fxmanifest.lua @@ -0,0 +1,19 @@ +fx_version 'cerulean' +game 'gta5' + +description 'QB-FitBit' +version '1.2.0' + +ui_page 'html/index.html' + +shared_scripts { + '@qb-core/shared/locale.lua', + 'locales/da.lua', +} + +server_script 'server/main.lua' +client_script 'client/main.lua' + +files { + 'html/*' +} diff --git a/resources/[qb]/[qb_core]/qb-fitbit/html/app.js b/resources/[qb]/[qb_core]/qb-fitbit/html/app.js new file mode 100644 index 0000000..75043ca --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-fitbit/html/app.js @@ -0,0 +1,83 @@ +let openedApp = '.main-screen'; +const escKey = 27; +const fadeTime = 150; + +qbFitbit = {} + +$(document).ready(function() { + window.addEventListener('message', function(event) { + const eventData = event.data; + + if (event.data.action === 'openWatch') { + qbFitbit.Open(); + } + }); +}); + +$(document).on('keydown', function(e) { + if (e.keyCode === escKey) { + qbFitbit.Close(); + } +}); + +qbFitbit.Open = function() { + $('.container').fadeIn(fadeTime); +} + +qbFitbit.Close = function() { + $('.container').fadeOut(fadeTime); + $.post('https://qb-fitbit/close') +} + +$(document).on('click', '.fitbit-app', function(e) { + e.preventDefault(); + + const pressedApp = $(this).data('app'); + + $(openedApp).css({ display: 'none'}); + $(`.${pressedApp}-app`).css({ display: 'block'}); + + openedApp = pressedApp; +}); + +$(document).on('click', '.back-food-settings', function(e) { + e.preventDefault(); + + $('.food-app').css({ display: 'none' }); + $('.main-screen').css({ display: 'block' }); + + openedApp = '.main-screen'; +}); + +$(document).on('click', '.back-thirst-settings', function(e) { + e.preventDefault(); + + $('.thirst-app').css({ display: 'none' }); + $('.main-screen').css({ display: 'block'}); + + openedApp = '.main-screen'; +}); + +$(document).on('click', '.save-food-settings', function(e) { + e.preventDefault(); + + const foodValue = $(this).parent().parent().find('input'); + + if (parseInt(foodValue.val()) <= 100) { + $.post('https://qb-fitbit/setFoodWarning', JSON.stringify({ + value: foodValue.val() + })); + } +}); + +$(document).on('click', '.save-thirst-settings', function(e) { + e.preventDefault(); + + const thirstValue = $(this).parent().parent().find('input'); + + if (parseInt(thirstValue.val()) <= 100) { + $.post('https://qb-fitbit/setThirstWarning', JSON.stringify({ + value: thirstValue.val() + })); + } +}); diff --git a/resources/[qb]/[qb_core]/qb-fitbit/html/index.html b/resources/[qb]/[qb_core]/qb-fitbit/html/index.html new file mode 100644 index 0000000..0088205 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-fitbit/html/index.html @@ -0,0 +1,48 @@ + + + + + + + + + + + QBCore Fitbit + + +
+ watch +
+
+ +
+
+
+
+ Sult +
+

Hvilken % vil du modtage advarsler ved?

+
+ +
+

Gem

+

Tilbage

+
+
+
+ Tørst +
+

Hvilken % vil du modtage advarsler ved?

+
+ +
+

Gem

+

Tilbage

+
+
+
+
+ + + diff --git a/resources/[qb]/[qb_core]/qb-fitbit/html/style.css b/resources/[qb]/[qb_core]/qb-fitbit/html/style.css new file mode 100644 index 0000000..4bcc7a5 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-fitbit/html/style.css @@ -0,0 +1,170 @@ +@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@100&display=swap'); + +body { + margin: 0; + padding: 0; +} + +.container { + display: none; +} + +.watch { + position: absolute; + height: 35vh; + right: 15vh; + bottom: 10vh; +} + +.watch-screen { + position: absolute; + right: 18.5vh; + bottom: 20vh; + height: 16.5vh; + width: 14.5vh; + background: rgba(0, 0, 0, 0.2); + border-radius: 2vh; +} + +.main-screen { + display: block; + position: relative; + height: 100%; + width: 100%; +} + +.food-app { + display: none; + position: relative; + height: 100%; + width: 100%; + text-align: center; +} + +.thirst-app { + display: none; + position: relative; + height: 100%; + width: 100%; + text-align: center; +} + +.app-title { + position: relative; + color: white; + font-family: 'Poppins', sans-serif; + text-transform: uppercase; + font-weight: bold; + letter-spacing: 0.1vh; + top: 1.5vh; + font-size: 1.3vh; +} + +.app-buttons { + position: absolute; + margin: 0 auto; + left: 0; + bottom: 1vh; + width: 100%; +} + +.app-button { + position: relative; + float: left; + background-color: rgba(32, 32, 32, 0.685); + width: 40%; + border-radius: 1vh; + left: 0; + right: 0; + margin: 0 auto 0 1vh; + transition: all 0.1s linear; +} + +.app-button:hover { + background-color: rgba(56, 56, 56, 0.877); +} + +.app-button > p { + color: white; + font-family: 'Poppins', sans-serif; + text-transform: uppercase; + font-weight: bold; + font-size: 0.8vh; +} + +.alert-setting { + position: absolute; + width: 70%; + height: 50%; + margin: 0 auto; + left: 0; + right: 0; + top: 4vh; +} + +.alert-setting > p { + color: white; + font-family: 'Poppins', sans-serif; + text-transform: uppercase; + font-weight: bold; + font-size: 0.9vh; +} + +.percentage { + background: none; + position: absolute; + left: 0; + width: 30%; + top: 9vh; + margin: 0 auto; + right: 0; + border: none; + outline: none; + border-bottom: 1px solid #fff; + text-align: center; + color: white; +} + +input[type="number"] { + color: white; + font-family: 'Poppins', sans-serif; + text-transform: uppercase; + font-weight: bold; + font-size: 1.2vh; +} + +::-webkit-inner-spin-button { + display: none; +} + +.fitbit-app { + position: relative; + float: left; + width: 3.5vh; + height: 3.5vh; + top: 0.5vh; + left: 1.2vh; + border-radius: 0.4vh; + margin-right: 0.8vh; + margin-top: 0.8vh; + background: rgba(158, 158, 158, 0.096); + transition: all 0.1s linear; + text-align: center; +} + +.fitbit-app.food { + color: #3da83b; +} + +.fitbit-app.thirst { + color: #6767dd; +} + +.fitbit-app:hover { + background: rgba(158, 158, 158, 0.219); +} + +.fitbit-app > i { + padding-top: 1vh; + font-size: 1.8vh; +} diff --git a/resources/[qb]/[qb_core]/qb-fitbit/html/watch.png b/resources/[qb]/[qb_core]/qb-fitbit/html/watch.png new file mode 100644 index 0000000..328ecc0 Binary files /dev/null and b/resources/[qb]/[qb_core]/qb-fitbit/html/watch.png differ diff --git a/resources/[qb]/[qb_core]/qb-fitbit/locales/da.lua b/resources/[qb]/[qb_core]/qb-fitbit/locales/da.lua new file mode 100644 index 0000000..48b7df1 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-fitbit/locales/da.lua @@ -0,0 +1,19 @@ +local Translations = { + success = { + hunger_set = 'Fitbit: Lyd ved sult er sat til %{hungervalue}%', + thirst_set = 'Fitbit: Lyd ved tørst er sat til %{thirstvalue}%', + }, + warning = { + hunger_warning = 'Dit sult niveau er %{hunger}%', + thirst_warning = 'Dit tørst niveau er %{thirst}%' + }, + info = { + fitbit = 'FITBIT ' + } +} + +Lang = Locale:new({ + phrases = Translations, + warnOnMissing = true, + fallbackLang = Lang, +}) diff --git a/resources/[qb]/[qb_core]/qb-fitbit/server/main.lua b/resources/[qb]/[qb_core]/qb-fitbit/server/main.lua new file mode 100644 index 0000000..6ba292a --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-fitbit/server/main.lua @@ -0,0 +1,19 @@ +local QBCore = exports['qb-core']:GetCoreObject() + +QBCore.Functions.CreateUseableItem("fitbit", function(source) + TriggerClientEvent('qb-fitbit:use', source) +end) + +RegisterNetEvent('qb-fitbit:server:setValue', function(type, value) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if not Player then return end + if not Player.Functions.GetItemByName("fitbit") then return end + + local currentMeta = Player.PlayerData.metadata["fitbit"] + local fitbitData = { + thirst = type == "thirst" and value or currentMeta.thirst, + food = type == "food" and value or currentMeta.food + } + Player.Functions.SetMetaData('fitbit', fitbitData) +end) diff --git a/resources/[qb]/[qb_core]/qb-fuel/.github/ISSUE_TEMPLATE/bug_report.md b/resources/[qb]/[qb_core]/qb-fuel/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..cd0bce5 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-fuel/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: Create a bug report so we can fix your issue! +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots | Video** +If applicable, add screenshots or video to help explain your problem. + +**Additional context** +Add any other context about the problem here. diff --git a/resources/[qb]/[qb_core]/qb-fuel/.github/ISSUE_TEMPLATE/feature_request.md b/resources/[qb]/[qb_core]/qb-fuel/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..01f4287 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-fuel/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[Suggestion] - " +labels: Suggestion +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/resources/[qb]/[qb_core]/qb-fuel/LICENSE b/resources/[qb]/[qb_core]/qb-fuel/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-fuel/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/resources/[qb]/[qb_core]/qb-fuel/README.md b/resources/[qb]/[qb_core]/qb-fuel/README.md new file mode 100644 index 0000000..7d7cc17 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-fuel/README.md @@ -0,0 +1,414 @@ +![Codine Development Fuel Script Banner](https://i.imgur.com/qVOMMvW.png) + +# _qb-fuel (2.1.4)_ + +A highly in-depth fuel system for **FiveM** with support for the **QBCore Framework & QBox Remastered**. + +# _Lastest Patch Information_ +*Additions:* +- Various Optimizations Throughout +- Config Option for Having Owners Pickup Reserves they Purchase + +*Fixes:* +- Blip Color on Normal Blip not working. +- [Jerry Can Flipped Orientation](https://github.com/dnelyk/cdn-grub-updates/assets/95599217/0489397e-99c9-43d9-8aab-0d4a45463cfd) +- Various Syphoning Issues (Item Data / Input / Menu etc) +- Various Jerry Can Issues (Similar to Syphon Issues) +- Station Owners Not Recieving Full Price on Discounted Purchases + +*Removals:* +- NPWD "Support" as there are various large issues. + +
+
+ +![Codine Development Fuel Script Features Banner](https://i.imgur.com/ISHQJUL.png) + +#### Why should you pick **qb-fuel**? + +- Show all gas station blips via Config Options. +- Vehicle blowing up chance percent via Config Options. +- Pump Explosion Chance when running away with Nozzles via Config Options. +- Global Tax and Fuel Prices via Config Options. +- Target eye for all base fuel actions, not including Jerry Can & Syphoning. +- Fuel and Charging Nozzle with realistic animations. +- Custom sounds for every action, like refueling & charging. +- Select amount of fuel you want to put in your vehicle. +- On cancel, the amount you put in will be filled. +- Option to pay cash or with your bank. +- Toggleable Jerry Cans via Config Options. +- [CDN-Syphoning](https://github.com/CodineDev/cdn-syphoning) built-in via Config Options. +- Electric Charging with a [Custom Model](https://i.imgur.com/WDxGoT6.png) & pre-configured locations. +- [Player Owned Gas Stations](https://www.youtube.com/watch?v=3glln0S2QXo) that can be maintained by the Owner. +- [Highly User Friendly Menus](https://i.imgur.com/f64IxpA.png) for Gas Station Owners. +- Reserve Levels which are maintained by the Owner of the Gas Station or Unlimited based on Config Options. +- Renamable Gas Stations on Map with Blacklisted words. +- Helicopter and Boat Refueling. +- Configurable discounts for Emergency Services. +- Configurable Hose for the Fuel Nozzle. +- Official Support for the [OX Library](https://github.com/overextended/ox_lib). (Inventory/Menus/Target) + + +![Codine Development Fuel Script Install Banner](https://i.imgur.com/bEiV8G0.png) + +### Before your installation: +Make sure you have the following dependencies, otherwise, issues most likely will arise: + +### Dependencies: + +- [qb-target](https://github.com/BerkieBb/qb-target) +- [qb-menu](https://github.com/qbcore-framework/qb-menu) **&** [qb-input](https://github.com/qbcore-framework/qb-input) **OR** [ox_lib](https://github.com/overextended/ox_lib) +- [interact-sound](https://github.com/plunkettscott/interact-sound) +- [PolyZone](https://github.com/qbcore-framework/PolyZone) +- _Other dependencies are included in the resource._ + + +### Begin your installation + +Here, we shall provide a step-by-step guide on installing qb-fuel to your server and making it work with other scripts you may already have installed. + +### Step 1: + +First, we will start by renaming the resource "qb-fuel-main" to just "qb-fuel".

Next, we will drag the "qb-fuel" resource into your desired folder in your servers resources directory. + +![Step 1](https://i.imgur.com/8kg0LWe.gif) + +### Step 2: + +Next, we're going to drag the sounds from the *qb-fuel/assets/sounds* folder in qb-fuel, into your interact-sounds folder located at *resources/[standalone]/interact-sound/client/html/sounds* + +![explorer_8jBjdkgaeQ](https://user-images.githubusercontent.com/95599217/209605265-c8f67612-b8df-4c38-bf23-0c355cfa6c8e.gif) + +### Step 3: + +Next, we're going to open our entire resources folder in whichever IDE you use, (we will be using Visual Studio Code for this example) and replace all of your current exports titled "LegacyFuel", "ps-fuel" or "lj-fuel", with "qb-fuel". Then you want to ensure qb-fuel in your server's config file. +

+![step 3](https://i.imgur.com/VZnQpcS.gif) + +
+ +### Step 4: + +Next, we're going to run our SQL file, which is needed if we want to use the Player Owned Gas Stations, otherwise you do not have to run it. + +
+ +The file you need to run is located @ _qb-fuel/assets/sql/qb-fuel.sql_ + +
+ +Here is a GIF to run you through the process of running an SQL file: + +![Step4-Gif](https://user-images.githubusercontent.com/95599217/209601625-af7ee908-c367-48b1-8487-b52359148224.gif) + +### Step 5: + +It is highly recommended, if you plan on restarting the script at all, that you move the _stream_ folder & _data_file_ paramaters found in the _fxmanifest.lua_ to another resource for the time being. If you do not do this, you & anyone in the server's game will most likely crash when restarting _qb-fuel_. The process is very simple & it is outlined in the GIF & Instructions below. + +
+ +**Firstly**, we will move our _stream_ folder to our new resource, or existing resource.

In this example, I have a dummy resource named _cdn-fool_. +![explorer_4tflJ0RowY](https://user-images.githubusercontent.com/95599217/209604683-79e18fa7-96ad-456d-b0c4-20632fb4d04c.gif) + + +Next, we will move our _fxmanifest.lua's_ entries for _data_file_ into our new resource, and **REMOVE IT** from _qb-fuel_. + +![jRtUg319mL](https://user-images.githubusercontent.com/95599217/209604640-54e0a450-6a54-4afa-9fab-cda4f02e7091.gif) + + +```Lua +data_file 'DLC_ITYP_REQUEST' 'stream/[electric_nozzle]/electric_nozzle_typ.ytyp' +data_file 'DLC_ITYP_REQUEST' 'stream/[electric_charger]/electric_charger_typ.ytyp' +``` + +Make sure to **ensure** this new resource as well as _qb-fuel_ in your _server.cfg_! + + +**If you do not want the Jerry Can or Syphoning Kit items, you are now finished with installation.** + +
+*Otherwise, navigate to Step 6 & Step 7 below, and finish installation.* + +### Step 6: +We will now be installing the Jerry Can & Syphoning Kit items into your server. You don't have to install either, but they are recommended additions. You can install them & disable them in the config, until you want to use them later on! +

+If you plan to not use them, you can skip this Step and Step 7! +

+The first step of installing our items is to navigate to your *qb-core/shared/items.lua*. +

+Once there, we will paste the following items at the bottom of our items table. +```Lua + ["syphoningkit"] = {["name"] = "syphoningkit", ["label"] = "Syphoning Kit", ["weight"] = 5000, ["type"] = "item", ["image"] = "syphoningkit.png", ["unique"] = true, ["useable"] = true, ["shouldClose"] = false, ["combinable"] = nil, ["description"] = "A kit made to siphon gasoline from vehicles."}, + ["jerrycan"] = {["name"] = "jerrycan", ["label"] = "Jerry Can", ["weight"] = 15000, ["type"] = "item", ["image"] = "jerrycan.png", ["unique"] = true, ["useable"] = true, ["shouldClose"] = false, ["combinable"] = nil, ["description"] = "A Jerry Can made to hold gasoline."}, +``` +**For people using inventories with built-in decay, you must add those onto the item, as it doesn't come with it!** +

+You can follow this GIF to better understand how to install the items: +
+![Step4GIF](https://i.imgur.com/Oiy7X5W.gif) +
+Now, we need to format item data in our inventory. Firstly, find the *app.js* located at *inventoryname/html/js/app.js*. +

+Now we will CTRL+F the following line: +
+```js +} else if (itemData.name == "harness") { +``` +Once you have found this line, copy the following one line above it: +
+```js + } else if (itemData.name == "syphoningkit") { // Syphoning Kit (qb-fuel or CDN-Syphoning!) + $(".item-info-title").html("

" + itemData.label + "

"); + $(".item-info-description").html( + "

" + "A kit used to syphon gasoline from vehicles!

" + itemData.info.gasamount + " Liters Inside.

" + + "

Weight: " + ((itemData.weight * itemData.amount) / 1000).toFixed(1) + " | Amount: " + itemData.amount + ); + } else if (itemData.name == "jerrycan") { // Jerry Can (qb-fuel!) + $(".item-info-title").html("

" + itemData.label + "

"); + $(".item-info-description").html( + "

" + "A Jerry Can, designed to hold fuel!

" + itemData.info.gasamount + " Liters Inside.

" + + "

Weight: " + ((itemData.weight * itemData.amount) / 1000).toFixed(1) + " | Amount: " + itemData.amount + ); +``` +**Again, if you have decay, you must add in the options yourself!** +

+*Here is a GIF to better understand how to install the "jerrycan" and "syphoningkit" in the app.js* +
+![Step4JSGIF](https://i.imgur.com/lKq9WDR.gif) +

+Next, we'll add the item's images into our Inventory resource. This is a simple process. +

+Navigate to the qb-fuel resource and follow this path: *qb-fuel/assets/images* +

+Once there, select both images and either drag or *CTRL + X and CTRL + V* them into your inventory's image folder, usually the path is: *inventoryname/html/images/* +

+You can follow this GIF to get a better understanding: +
+![Step4ImagesGIF](https://i.imgur.com/C0uwjfX.gif) +
+ +### Step 6: +This step is only necessary for you to be able to do the */giveitem* command or to put items in the qb-shops. + +Navigate to inventoryname/server/server.lua, and CTRL + F the following line: +```Lua + elseif itemData["name"] == "harness" then + info.uses = 20 +``` +
+Now we will add the following above the line below: + +```Lua + elseif itemData["name"] == "syphoningkit" then + info.gasamount = 0 + elseif itemData["name"] == "jerrycan" then + info.gasamount = 0 +``` + +Alternatively, watch this GIF to better understand the process: +
+![Step6 GIF](https://i.imgur.com/yrkR7cJ.gif) + +
+ +##### QB-Shop Setup + +Here are some preconfigured shop items if you wish to put them in the shop. (The Jerry Can is buyable via the Gas Pump!) + +```Lua + [10] = { + name = "syphoningkit", + price = 5000, + amount = 5, + info = { gasamount = 0 }, -- This must be included or, your item will not store fuel properly! + type = "item", + slot = 10, + }, -- qb-fuel / CDN-Syphoning + [11] = { + name = "jerrycan", + price = 750, + amount = 5, + info = { gasamount = 0 }, -- This must be included or, your item will not store fuel properly! + type = "item", + slot = 11, + }, -- qb-fuel +``` +
+You will most likely have to change the slot it is in for it to work properly! +

+ +### QB-Target Issue Fix + +There is a **possible** issue with *qb-target* if you are using the *Config.GlobalVehicleOptions* or *Config.TargetBones* options. +
+### **If you are NOT having this issue occur, do not follow the instructions below, as it could mess up other things.** +
+ +*Here is a simple fix for that issue:* + +
+ +Firstly, this option will have to be added to your *Config.TargetBones* under the bones you are having trouble with: +```Lua + { + type = "client", + event = "qb-fuel:client:SendMenuToServer", + icon = "fas fa-gas-pump", + label = "Insert Nozzle", + canInteract = function() return Allowrefuel end + }, + { + type = "client", + action = function() + TriggerEvent('qb-fuel:client:electric:RefuelMenu') + end, + icon = "fas fa-bolt", + label = "Insert Electric Nozzle", + canInteract = function() return AllowElectricRefuel end + }, +``` + +*Here is an example of how to add this option:* + +![Step5part33 QB-Target](https://i.imgur.com/UOgPJRi.png) +
+*This is **specifically** for the "**boot**" bone, but, add it on which bone you are having trouble with.* + +
+
+ +*Next, we'll add this simple Function & Export into our QB-Target in the Functions() area:* + +```Lua +local function AllowRefuel(state, electric) + if state then + if electric then + AllowElectricRefuel = true + else + Allowrefuel = true + end + else + if electric then + AllowElectricRefuel = false + else + Allowrefuel = false + end + end +end exports('AllowRefuel', AllowRefuel) +``` + +
+ + +**Example Image:** + +![Step5 Part 421421412](https://i.imgur.com/pwpa5Tk.png) + +
+ +Lastly, add the following to the top of your _init.lua_ in QB-Target: +``` +local Allowrefuel = false +local AllowElectricRefuel = false +``` +**Example Image:** + +![Example Image](https://user-images.githubusercontent.com/95599217/209600631-31c6f6f0-67e2-46da-bf52-f35114cfb1e9.png) + + + +Now, set the *Config.FuelTargetExport* in *qb-fuel/shared/config.lua* to **true**. + +
+ +![Step5 Part 1421942151251](https://i.imgur.com/InBl500.png) + +## ___Recommended Snippets:___ + +We highly recommend you add the following snippet to your engine toggle command. It will make it to where players cannot turn their vehicle on if they have no fuel! Seems pretty important to us! + +##### ***Engine Toggle Snippet*** +```Lua +-- FOR QB-VEHICLEKEYS, FUNCTION ToggleEngine(); +local NotifyCooldown = false +function ToggleEngine(veh) + if veh then + local EngineOn = GetIsVehicleEngineRunning(veh) + if not isBlacklistedVehicle(veh) then + if HasKeys(QBCore.Functions.GetPlate(veh)) or AreKeysJobShared(veh) then + if EngineOn then + SetVehicleEngineOn(veh, false, false, true) + else + if exports['qb-fuel']:GetFuel(veh) ~= 0 then + SetVehicleEngineOn(veh, true, false, true) + else + if not NotifyCooldown then + RequestAmbientAudioBank("DLC_PILOT_ENGINE_FAILURE_SOUNDS", 0) + PlaySoundFromEntity(l_2613, "Landing_Tone", PlayerPedId(), "DLC_PILOT_ENGINE_FAILURE_SOUNDS", 0, 0) + NotifyCooldown = true + QBCore.Functions.Notify('No fuel..', 'error') + Wait(1500) + StopSound(l_2613) + Wait(3500) + NotifyCooldown = false + end + end + end + end + end + end +end +``` + +
+ +### You are now officially done installing! + +
+ +Enjoy using **qb-fuel**, if you have an issues, [create an issue](https://github.com/CodineDev/qb-fuel/issues/new/choose) on the repository, and we will fix it **ASAP**! + +
+
+ +![Codine Development Fuel Script Showcase Banner](https://i.imgur.com/HQOH3AX.png) + +### Demonstration of the script + +Here's a couple of videos showcasing the script in action! + +- [Main Fueling & Charging!](https://www.youtube.com/watch?v=_h-66IDs8Kw) +- [Player Owned Gas Stations!](https://www.youtube.com/watch?v=3glln0S2QXo) +- [Jerry Cans!](https://www.youtube.com/watch?v=M14nZTzltB0) +- [Siphoning!](https://youtu.be/2CJjM_9hmNA) + +
+
+ +![Codine Development Fuel Script Future Plans Banner](https://i.imgur.com/1RoBsmo.png) + +### Future Plans + +- Oil Rig Integration. +- Send more suggestions in our discord server! + +
+
+ +![Codine Development Links Banner](https://i.imgur.com/SAqArzg.png) + +### Codine Links + +- [Discord](https://discord.gg/Ta6QNnuxM2) +- [Tebex](https://codine.tebex.io/) +- [Youtube](https://www.youtube.com/channel/UC3Nr0qtyQP9cGRK1m25pOqg) + +### Credits: + +- **OX Conversion:** +

+**[NoobySloth](https://github.com/noobysloth)** +for making the initial **OX** portion of the script. +

+**[xViperAG](https://www.github.com/xViperAG)** +for adding more OX functionality & support for QBox Remastered. diff --git a/resources/[qb]/[qb_core]/qb-fuel/assets/images/jerrycan.png b/resources/[qb]/[qb_core]/qb-fuel/assets/images/jerrycan.png new file mode 100644 index 0000000..cf5f211 Binary files /dev/null and b/resources/[qb]/[qb_core]/qb-fuel/assets/images/jerrycan.png differ diff --git a/resources/[qb]/[qb_core]/qb-fuel/assets/images/syphoningkit.png b/resources/[qb]/[qb_core]/qb-fuel/assets/images/syphoningkit.png new file mode 100644 index 0000000..10957c7 Binary files /dev/null and b/resources/[qb]/[qb_core]/qb-fuel/assets/images/syphoningkit.png differ diff --git a/resources/[qb]/[qb_core]/qb-fuel/assets/sounds/chargestop.ogg b/resources/[qb]/[qb_core]/qb-fuel/assets/sounds/chargestop.ogg new file mode 100644 index 0000000..46e6281 Binary files /dev/null and b/resources/[qb]/[qb_core]/qb-fuel/assets/sounds/chargestop.ogg differ diff --git a/resources/[qb]/[qb_core]/qb-fuel/assets/sounds/charging.ogg b/resources/[qb]/[qb_core]/qb-fuel/assets/sounds/charging.ogg new file mode 100644 index 0000000..fee8d6b Binary files /dev/null and b/resources/[qb]/[qb_core]/qb-fuel/assets/sounds/charging.ogg differ diff --git a/resources/[qb]/[qb_core]/qb-fuel/assets/sounds/fuelstop.ogg b/resources/[qb]/[qb_core]/qb-fuel/assets/sounds/fuelstop.ogg new file mode 100644 index 0000000..b1415e9 Binary files /dev/null and b/resources/[qb]/[qb_core]/qb-fuel/assets/sounds/fuelstop.ogg differ diff --git a/resources/[qb]/[qb_core]/qb-fuel/assets/sounds/pickupnozzle.ogg b/resources/[qb]/[qb_core]/qb-fuel/assets/sounds/pickupnozzle.ogg new file mode 100644 index 0000000..c8032a6 Binary files /dev/null and b/resources/[qb]/[qb_core]/qb-fuel/assets/sounds/pickupnozzle.ogg differ diff --git a/resources/[qb]/[qb_core]/qb-fuel/assets/sounds/putbackcharger.ogg b/resources/[qb]/[qb_core]/qb-fuel/assets/sounds/putbackcharger.ogg new file mode 100644 index 0000000..f1434c6 Binary files /dev/null and b/resources/[qb]/[qb_core]/qb-fuel/assets/sounds/putbackcharger.ogg differ diff --git a/resources/[qb]/[qb_core]/qb-fuel/assets/sounds/putbacknozzle.ogg b/resources/[qb]/[qb_core]/qb-fuel/assets/sounds/putbacknozzle.ogg new file mode 100644 index 0000000..bac025e Binary files /dev/null and b/resources/[qb]/[qb_core]/qb-fuel/assets/sounds/putbacknozzle.ogg differ diff --git a/resources/[qb]/[qb_core]/qb-fuel/assets/sounds/refuel.ogg b/resources/[qb]/[qb_core]/qb-fuel/assets/sounds/refuel.ogg new file mode 100644 index 0000000..8b1e416 Binary files /dev/null and b/resources/[qb]/[qb_core]/qb-fuel/assets/sounds/refuel.ogg differ diff --git a/resources/[qb]/[qb_core]/qb-fuel/assets/sql/cdn-fuel.sql b/resources/[qb]/[qb_core]/qb-fuel/assets/sql/cdn-fuel.sql new file mode 100644 index 0000000..747a3d3 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-fuel/assets/sql/cdn-fuel.sql @@ -0,0 +1,40 @@ +-- Create Table for Fuel Stations -- +CREATE TABLE IF NOT EXISTS `fuel_stations` ( + `location` int(11) DEFAULT NULL, + `owned` int(11) DEFAULT NULL, + `owner` varchar(50) DEFAULT NULL, + `fuel` int(11) DEFAULT NULL, + `fuelprice` int(11) DEFAULT NULL, + `balance` int(255) DEFAULT NULL, + `label` varchar(255) DEFAULT NULL, + PRIMARY KEY (`location`) +) ENGINE=InnoDB; + +-- Insert Default Information into the Table Created! -- +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (1, 0, '0', 100000, 3, 0, 'Davis Avenue Ron'); +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (2, 0, '0', 100000, 3, 0, 'Grove Street LTD'); +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (3, 0, '0', 100000, 3, 0, 'Dutch London Xero'); +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (4, 0, '0', 100000, 3, 0, 'Little Seoul LTD'); +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (5, 0, '0', 100000, 3, 0, 'Strawberry Ave Xero'); +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (6, 0, '0', 100000, 3, 0, 'Popular Street Ron'); +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (7, 0, '0', 100000, 3, 0, 'Capital Blvd Ron'); +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (8, 0, '0', 100000, 3, 0, 'Mirror Park LTD'); +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (9, 0, '0', 100000, 3, 0, 'Clinton Ave Globe Oil'); +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (10, 0, '0', 100000, 3, 0, 'North Rockford Ron'); +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (11, 0, '0', 100000, 3, 0, 'Great Ocean Xero'); +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (12, 0, '0', 100000, 3, 0, 'Paleto Blvd Xero'); +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (13, 0, '0', 100000, 3, 0, 'Paleto Ron'); +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (14, 0, '0', 100000, 3, 0, 'Paleto Globe Oil'); +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (15, 0, '0', 100000, 3, 0, 'Grapeseed LTD'); +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (16, 0, '0', 100000, 3, 0, 'Sandy Shores Xero'); +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (17, 0, '0', 100000, 3, 0, 'Sandy Shores Globe Oil'); +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (18, 0, '0', 100000, 3, 0, 'Senora Freeway Xero'); +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (19, 0, '0', 100000, 3, 0, 'Harmony Globe Oil'); +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (20, 0, '0', 100000, 3, 0, 'Route 68 Globe Oil'); +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (21, 0, '0', 100000, 3, 0, 'Route 68 Workshop Globe O'); +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (22, 0, '0', 100000, 3, 0, 'Route 68 Xero'); +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (23, 0, '0', 100000, 3, 0, 'Route 68 Ron'); +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (24, 0, '0', 100000, 3, 0, "Rex\'s Diner Globe Oil"); +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (25, 0, '0', 100000, 3, 0, 'Palmino Freeway Ron'); +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (26, 0, '0', 100000, 3, 0, 'North Rockford LTD'); +INSERT INTO `fuel_stations` (`location`, `owned`, `owner`, `fuel`, `fuelprice`, `balance`, `label`) VALUES (27, 0, '0', 100000, 3, 0, 'Alta Street Globe Oil'); diff --git a/resources/[qb]/[qb_core]/qb-fuel/client/electric_cl.lua b/resources/[qb]/[qb_core]/qb-fuel/client/electric_cl.lua new file mode 100644 index 0000000..0292a29 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-fuel/client/electric_cl.lua @@ -0,0 +1,762 @@ +if Config.ElectricVehicleCharging then + -- Variables + local QBCore = exports[Config.Core]:GetCoreObject() + local HoldingElectricNozzle = false + local RefuelPossible = false + local RefuelPossibleAmount = 0 + local RefuelCancelled = false + local RefuelPurchaseType = 'bank' + + if Config.PumpHose then + Rope = nil + end + + -- Start + AddEventHandler('onResourceStart', function(resource) + if resource == GetCurrentResourceName() then + Wait(100) + DeleteObject(ElectricNozzle) + HoldingElectricNozzle = false + end + end) + + -- Functions + function IsHoldingElectricNozzle() + return HoldingElectricNozzle + end exports('IsHoldingElectricNozzle', IsHoldingElectricNozzle) + + function SetElectricNozzle(state) + if state == "putback" then + TriggerServerEvent("InteractSound_SV:PlayOnSource", "putbackcharger", 0.4) + Wait(250) + if Config.FuelTargetExport then exports[Config.TargetResource]:AllowRefuel(false, true) end + DeleteObject(ElectricNozzle) + HoldingElectricNozzle = false + if Config.PumpHose == true then + RopeUnloadTextures() + DeleteRope(Rope) + end + elseif state == "pickup" then + TriggerEvent('qb-fuel:client:grabelectricnozzle') + HoldingElectricNozzle = true + else + if Config.FuelDebug then print("State is not valid, it must be pickup or putback.") end + end + end exports('SetElectricNozzle', SetElectricNozzle) + + -- Events + if Config.Ox.Menu then + RegisterNetEvent('cdn-electric:client:OpenContextMenu', function(total, fuelamounttotal, purchasetype) + lib.registerContext({ + id = 'electricconfirmationmenu', + title = Lang:t("menu_purchase_station_header_1")..math.ceil(total)..Lang:t("menu_purchase_station_header_2"), + options = { + { + title = Lang:t("menu_purchase_station_confirm_header"), + description = Lang:t("menu_electric_accept"), + icon = "fas fa-check-circle", + arrow = false, -- puts arrow to the right + event = 'qb-fuel:client:electric:ChargeVehicle', + args = { + fuelamounttotal = fuelamounttotal, + purchasetype = purchasetype, + } + }, + { + title = Lang:t("menu_header_close"), + description = Lang:t("menu_refuel_cancel"), + icon = "fas fa-times-circle", + arrow = false, -- puts arrow to the right + onSelect = function() + lib.hideContext() + end, + }, + }, + }) + lib.showContext('electricconfirmationmenu') + end) + end + + RegisterNetEvent('qb-fuel:client:electric:FinalMenu', function(purchasetype) + local money = nil + if purchasetype == "bank" then money = QBCore.Functions.GetPlayerData().money['bank'] elseif purchasetype == 'cash' then money = QBCore.Functions.GetPlayerData().money['cash'] end + FuelPrice = (1 * Config.ElectricChargingPrice) + local vehicle = GetClosestVehicle() + + -- Police Discount Math -- + if Config.EmergencyServicesDiscount['enabled'] == true and (Config.EmergencyServicesDiscount['emergency_vehicles_only'] == false or (Config.EmergencyServicesDiscount['emergency_vehicles_only'] == true and GetVehicleClass(vehicle) == 18)) then + local discountedJobs = Config.EmergencyServicesDiscount['job'] + local plyJob = QBCore.Functions.GetPlayerData().job.name + local shouldRecieveDiscount = false + + if type(discountedJobs) == "table" then + for i = 1, #discountedJobs, 1 do + if plyJob == discountedJobs[i] then + shouldRecieveDiscount = true + break + end + end + elseif plyJob == discountedJobs then + shouldRecieveDiscount = true + end + + if shouldRecieveDiscount == true and not QBCore.Functions.GetPlayerData().job.onduty and Config.EmergencyServicesDiscount['ondutyonly'] then + QBCore.Functions.Notify(Lang:t("you_are_discount_eligible"), 'primary', 7500) + shouldRecieveDiscount = false + end + + if shouldRecieveDiscount then + local discount = Config.EmergencyServicesDiscount['discount'] + if discount > 100 then + discount = 100 + else + if discount <= 0 then discount = 0 end + end + if discount ~= 0 then + if discount == 100 then + FuelPrice = 0 + if Config.FuelDebug then + print("Your discount for Emergency Services is set @ "..discount.."% so fuel is free!") + end + else + discount = discount / 100 + FuelPrice = FuelPrice - (FuelPrice*discount) + + if Config.FuelDebug then + print("Your discount for Emergency Services is set @ "..discount.."%. Setting new price to: $"..FuelPrice) + end + end + else + if Config.FuelDebug then + print("Your discount for Emergency Services is set @ "..discount.."%. It cannot be 0 or < 0!") + end + end + end + end + + local curfuel = GetFuel(vehicle) + local finalfuel + if curfuel < 10 then finalfuel = string.sub(curfuel, 1, 1) else finalfuel = string.sub(curfuel, 1, 2) end + local maxfuel = (100 - finalfuel - 1) + local wholetankcost = (FuelPrice * maxfuel) + local wholetankcostwithtax = math.ceil((wholetankcost) + GlobalTax(wholetankcost)) + if Config.FuelDebug then print("Attempting to open Input with the total: $"..wholetankcostwithtax.." at $"..FuelPrice.." / L".." Maximum Fuel Amount: "..maxfuel) end + if Config.Ox.Input then + Electricity = lib.inputDialog('Electric Charger', { + { type = "input", label = 'El pris', + default = FuelPrice .. ',- /KWh', + disabled = true }, + { type = "input", label = 'Nuværende opladning', + default = finalfuel .. ' KWh', + disabled = true }, + { type = "input", label = 'Krævet maksimal opladning', + default = maxfuel, + disabled = true }, + { type = "slider", label = 'Fuld opladning: ' ..wholetankcostwithtax.. ',-', + default = maxfuel, + min = 0, + max = maxfuel + }, + }) + + if not Electricity then return end + ElectricityAmount = tonumber(Electricity[4]) + + if Electricity then + if not ElectricityAmount then if Config.FuelDebug then print("ElectricityAmount is invalid!") end return end + if not HoldingElectricNozzle then QBCore.Functions.Notify(Lang:t("electric_no_nozzle"), 'error', 7500) return end + if (ElectricityAmount + finalfuel) >= 100 then + QBCore.Functions.Notify(Lang:t("tank_already_full"), "error") + else + if GlobalTax(ElectricityAmount * FuelPrice) + (ElectricityAmount * FuelPrice) <= money then + TriggerServerEvent('qb-fuel:server:electric:OpenMenu', ElectricityAmount, IsInGasStation(), false, purchasetype, FuelPrice) + else + QBCore.Functions.Notify(Lang:t("not_enough_money"), 'error', 7500) + end + end + end + else + Electricity = exports['qb-input']:ShowInput({ + header = "Vælg hvor meget du vil oplade
Pris: " .. + FuelPrice .. ",- / KWh
Nuværrende opladning: " .. finalfuel .. " KWh
Fuld opladning: " .. + wholetankcostwithtax .. ",-", + submitText = "Insert Charger", + inputs = {{ + type = 'number', + isRequired = true, + name = 'amount', + text = 'Batteriet kan holde ' .. maxfuel .. ' KWh mere.' + }} + }) + if Electricity then + if not Electricity.amount then print("Electricity.amount is invalid!") return end + if not HoldingElectricNozzle then QBCore.Functions.Notify(Lang:t("electric_no_nozzle"), 'error', 7500) return end + if (Electricity.amount + finalfuel) >= 100 then + QBCore.Functions.Notify(Lang:t("tank_already_full"), "error") + else + if GlobalTax(Electricity.amount * FuelPrice) + (Electricity.amount * FuelPrice) <= money then + TriggerServerEvent('qb-fuel:server:electric:OpenMenu', Electricity.amount, IsInGasStation(), false, purchasetype, FuelPrice) + else + QBCore.Functions.Notify(Lang:t("not_enough_money"), 'error', 7500) + end + end + end + end + end) + + RegisterNetEvent('qb-fuel:client:electric:SendMenuToServer', function() + local vehicle = GetClosestVehicle() + local vehiclename = GetEntityModel(vehicle) + AwaitingElectricCheck = true + FoundElectricVehicle = false + :: ChargingMenu :: -- Register the starting point for the goto + if not AwaitingElectricCheck then if Config.FuelDebug then print("Attempting to go to Charging Menu") end end + if not AwaitingElectricCheck and FoundElectricVehicle then + local CurFuel = GetVehicleFuelLevel(vehicle) + local playercashamount = QBCore.Functions.GetPlayerData().money['cash'] + if not IsHoldingElectricNozzle() then QBCore.Functions.Notify(Lang:t("electric_no_nozzle"), 'error', 7500) return end + if CurFuel < 95 then + if Config.Ox.Menu then + lib.registerContext({ + id = 'electricmenu', + title = Config.GasStations[FetchCurrentLocation()].label, + options = { + { + title = Lang:t("menu_header_cash"), + description = Lang:t("menu_pay_with_cash") .. playercashamount..",-", + icon = "fas fa-usd", + arrow = false, -- puts arrow to the right + event = "qb-fuel:client:electric:FinalMenu", + args = 'cash', + }, + { + title = Lang:t("menu_header_bank"), + description = Lang:t("menu_pay_with_bank"), + icon = "fas fa-credit-card", + arrow = false, -- puts arrow to the right + event = "qb-fuel:client:electric:FinalMenu", + args = 'bank', + }, + { + title = Lang:t("menu_header_close"), + description = Lang:t("menu_refuel_cancel"), + icon = "fas fa-times-circle", + arrow = false, -- puts arrow to the right + onSelect = function() + lib.hideContext() + end, + }, + }, + }) + lib.showContext('electricmenu') + else + exports['qb-menu']:openMenu({ + { + header = Config.GasStations[FetchCurrentLocation()].label, + isMenuHeader = true, + icon = "fas fa-bolt", + }, + { + header = Lang:t("menu_header_cash"), + txt = Lang:t("menu_pay_with_cash") .. playercashamount..",-", + icon = "fas fa-usd", + params = { + event = "qb-fuel:client:electric:FinalMenu", + args = 'cash', + } + }, + { + header = Lang:t("menu_header_bank"), + txt = Lang:t("menu_pay_with_bank"), + icon = "fas fa-credit-card", + params = { + event = "qb-fuel:client:electric:FinalMenu", + args = 'bank', + } + }, + { + header = Lang:t("menu_header_close"), + txt = Lang:t("menu_electric_cancel"), + icon = "fas fa-times-circle", + params = { + event = "qb-menu:closeMenu", + } + }, + }) + end + else + QBCore.Functions.Notify(Lang:t("tank_already_full"), 'error') + end + else + if Config.FuelDebug then print("Checking") end + if AwaitingElectricCheck then + for i = 1, #Config.ElectricVehicles do + if AwaitingElectricCheck then + if Config.FuelDebug then print(i) end + local current = joaat(Config.ElectricVehicles[i]) + if Config.FuelDebug then print("^5Current Search: ^2"..current.." ^5Player's Vehicle: ^2"..vehiclename) end + if current == vehiclename then + AwaitingElectricCheck = false + FoundElectricVehicle = true + if Config.FuelDebug then print("^2"..current.. "^5 has been found. It ^2matches ^5the Player's Vehicle: ^2"..vehiclename..". ^5This means charging will be allowed.") end + Wait(50) + goto ChargingMenu -- Attempt to go to the charging menu, now that we have found that there was an electric vehicle. + elseif i == #Config.ElectricVehicles then + FoundElectricVehicle = false + AwaitingElectricCheck = false + Wait(50) + if Config.FuelDebug then print("^2An electric vehicle^5 has NOT been found. ^5This means charging will not be allowed.") end + goto ChargingMenu -- Attempt to go to the charging menu, now that we have not found that there was an electric vehicle. + end + else + if Config.FuelDebug then print('Search ended..') end + end + end + else + QBCore.Functions.Notify(Lang:t("electric_vehicle_not_electric"), 'error', 7500) + end + end + end) + + RegisterNetEvent('qb-fuel:client:electric:ChargeVehicle', function(data) + if Config.FuelDebug then print("Charging Vehicle") end + if not Config.RenewedPhonePayment then + purchasetype = data.purchasetype + elseif data.purchasetype == "cash" then + purchasetype = "cash" + else + purchasetype = RefuelPurchaseType + end + if Config.FuelDebug then print("Purchase Type: "..purchasetype) end + if not Config.RenewedPhonePayment then + amount = data.fuelamounttotal + elseif data.purchasetype == "cash" then + amount = data.fuelamounttotal + elseif not data.fuelamounttotal then + amount = RefuelPossibleAmount + end + if not HoldingElectricNozzle then return end + amount = tonumber(amount) + if amount < 1 then return end + if amount < 10 then fuelamount = string.sub(amount, 1, 1) else fuelamount = string.sub(amount, 1, 2) end + local FuelPrice = (Config.ElectricChargingPrice * 1) + local vehicle = GetClosestVehicle() + + -- Police Discount Math -- + if Config.EmergencyServicesDiscount['enabled'] == true and (Config.EmergencyServicesDiscount['emergency_vehicles_only'] == false or (Config.EmergencyServicesDiscount['emergency_vehicles_only'] == true and GetVehicleClass(vehicle) == 18)) then + local discountedJobs = Config.EmergencyServicesDiscount['job'] + local plyJob = QBCore.Functions.GetPlayerData().job.name + local shouldRecieveDiscount = false + + if type(discountedJobs) == "table" then + for i = 1, #discountedJobs, 1 do + if plyJob == discountedJobs[i] then + shouldRecieveDiscount = true + break + end + end + elseif plyJob == discountedJobs then + shouldRecieveDiscount = true + end + + if shouldRecieveDiscount == true and not QBCore.Functions.GetPlayerData().job.onduty and Config.EmergencyServicesDiscount['ondutyonly'] then + QBCore.Functions.Notify(Lang:t("you_are_discount_eligible"), 'primary', 7500) + shouldRecieveDiscount = false + end + + if shouldRecieveDiscount then + local discount = Config.EmergencyServicesDiscount['discount'] + if discount > 100 then + discount = 100 + else + if discount <= 0 then discount = 0 end + end + if discount ~= 0 then + if discount == 100 then + FuelPrice = 0 + if Config.FuelDebug then + print("Your discount for Emergency Services is set @ "..discount.."% so fuel is free!") + end + else + discount = discount / 100 + FuelPrice = FuelPrice - (FuelPrice*discount) + + if Config.FuelDebug then + print("Your discount for Emergency Services is set @ "..discount.."%. Setting new price to: $"..FuelPrice) + end + end + else + if Config.FuelDebug then + print("Your discount for Emergency Services is set @ "..discount.."%. It cannot be 0 or < 0!") + end + end + end + end + + local refillCost = (fuelamount * FuelPrice) + GlobalTax(fuelamount*FuelPrice) + local vehicle = GetClosestVehicle() + local ped = PlayerPedId() + local time = amount * Config.RefuelTime + if amount < 10 then time = 10 * Config.RefuelTime end + local vehicleCoords = GetEntityCoords(vehicle) + if IsInGasStation() then + if IsPlayerNearVehicle() then + RequestAnimDict(Config.RefuelAnimationDictionary) + while not HasAnimDictLoaded('timetable@gardener@filling_can') do Wait(100) end + if GetIsVehicleEngineRunning(vehicle) and Config.VehicleBlowUp then + local Chance = math.random(1, 100) + if Chance <= Config.BlowUpChance then + AddExplosion(vehicleCoords, 5, 50.0, true, false, true) + return + end + end + TaskPlayAnim(ped, Config.RefuelAnimationDictionary, Config.RefuelAnimation, 8.0, 1.0, -1, 1, 0, 0, 0, 0) + refueling = true + Refuelamount = 0 + CreateThread(function() + while refueling do + if Refuelamount == nil then Refuelamount = 0 end + Wait(Config.RefuelTime) + Refuelamount = Refuelamount + 1 + if Cancelledrefuel then + local finalrefuelamount = math.floor(Refuelamount) + local refillCost = (finalrefuelamount * FuelPrice) + GlobalTax(finalrefuelamount * FuelPrice) + if Config.RenewedPhonePayment and purchasetype == "bank" then + local remainingamount = (amount - Refuelamount) + MoneyToGiveBack = (GlobalTax(remainingamount * FuelPrice) + (remainingamount * FuelPrice)) + TriggerServerEvent("qb-fuel:server:phone:givebackmoney", MoneyToGiveBack) + else + TriggerServerEvent('qb-fuel:server:PayForFuel', refillCost, purchasetype, FuelPrice) + end + local curfuel = GetFuel(vehicle) + local finalfuel = (curfuel + Refuelamount) + if finalfuel >= 98 and finalfuel < 100 then + SetFuel(vehicle, 100) + else + SetFuel(vehicle, finalfuel) + end + if Config.RenewedPhonePayment then + RefuelCancelled = true + RefuelPossibleAmount = 0 + RefuelPossible = false + end + Cancelledrefuel = false + end + end + end) + TriggerServerEvent("InteractSound_SV:PlayOnSource", "charging", 0.3) + if Config.Ox.Progress then + if lib.progressCircle({ + duration = time, + label = Lang:t("prog_electric_charging"), + position = 'bottom', + useWhileDead = false, + canCancel = true, + disable = { + move = true, + combat = true + }, + }) then + refueling = false + if purchasetype == "cash" then + TriggerServerEvent('qb-fuel:server:PayForFuel', refillCost, purchasetype, FuelPrice, true) + elseif purchasetype == "bank" then + TriggerServerEvent('qb-fuel:server:PayForFuel', refillCost, purchasetype, FuelPrice, true) + end + local curfuel = GetFuel(vehicle) + local finalfuel = (curfuel + fuelamount) + if finalfuel > 99 and finalfuel < 100 then + SetFuel(vehicle, 100) + else + SetFuel(vehicle, finalfuel) + end + if Config.RenewedPhonePayment then + RefuelCancelled = true + RefuelPossibleAmount = 0 + RefuelPossible = false + end + StopAnimTask(ped, Config.RefuelAnimationDictionary, Config.RefuelAnimation, 3.0, 3.0, -1, 2, 0, 0, 0, 0) + TriggerServerEvent("InteractSound_SV:PlayOnSource", "chargestop", 0.4) + else + refueling = false + Cancelledrefuel = true + StopAnimTask(ped, Config.RefuelAnimationDictionary, Config.RefuelAnimation, 3.0, 3.0, -1, 2, 0, 0, 0, 0) + TriggerServerEvent("InteractSound_SV:PlayOnSource", "chargestop", 0.4) + end + else + QBCore.Functions.Progressbar("charge-car", Lang:t("prog_electric_charging"), time, false, true, { + disableMovement = true, + disableCarMovement = true, + disableMouse = false, + disableCombat = true, + }, {}, {}, {}, function() + refueling = false + if not Config.RenewedPhonePayment or purchasetype == 'cash' then TriggerServerEvent('qb-fuel:server:PayForFuel', refillCost, purchasetype, FuelPrice, true) end + local curfuel = GetFuel(vehicle) + local finalfuel = (curfuel + fuelamount) + if finalfuel > 99 and finalfuel < 100 then + SetFuel(vehicle, 100) + else + SetFuel(vehicle, finalfuel) + end + if Config.RenewedPhonePayment then + RefuelCancelled = true + RefuelPossibleAmount = 0 + RefuelPossible = false + end + StopAnimTask(ped, Config.RefuelAnimationDictionary, Config.RefuelAnimation, 3.0, 3.0, -1, 2, 0, 0, 0, 0) + TriggerServerEvent("InteractSound_SV:PlayOnSource", "chargestop", 0.4) + end, function() + refueling = false + Cancelledrefuel = true + StopAnimTask(ped, Config.RefuelAnimationDictionary, Config.RefuelAnimation, 3.0, 3.0, -1, 2, 0, 0, 0, 0) + TriggerServerEvent("InteractSound_SV:PlayOnSource", "chargestop", 0.4) + end, "fas fa-charging-station") + end + end + else return end + end) + + RegisterNetEvent('qb-fuel:client:grabelectricnozzle', function() + local ped = PlayerPedId() + if HoldingElectricNozzle then return end + LoadAnimDict("anim@am_hold_up@male") + TaskPlayAnim(ped, "anim@am_hold_up@male", "shoplift_high", 2.0, 8.0, -1, 50, 0, 0, 0, 0) + TriggerServerEvent("InteractSound_SV:PlayOnSource", "pickupnozzle", 0.4) + Wait(300) + StopAnimTask(ped, "anim@am_hold_up@male", "shoplift_high", 1.0) + ElectricNozzle = CreateObject(joaat('electric_nozzle'), 1.0, 1.0, 1.0, true, true, false) + local lefthand = GetPedBoneIndex(ped, 18905) + AttachEntityToEntity(ElectricNozzle, ped, lefthand, 0.24, 0.10, -0.052 --[[FWD BWD]], -45.0 --[[ClockWise]], 120.0 --[[Weird Middle Axis]], 75.00 --[[Counter Clockwise]], 0, 1, 0, 1, 0, 1) + local grabbedelectricnozzlecoords = GetEntityCoords(ped) + HoldingElectricNozzle = true + if Config.PumpHose == true then + local pumpCoords, pump = GetClosestPump(grabbedelectricnozzlecoords, true) + RopeLoadTextures() + while not RopeAreTexturesLoaded() do + Wait(0) + RopeLoadTextures() + end + while not pump do + Wait(0) + end + Rope = AddRope(pumpCoords.x, pumpCoords.y, pumpCoords.z, 0.0, 0.0, 0.0, 3.0, Config.RopeType['electric'], 1000.0, 0.0, 1.0, false, false, false, 1.0, true) + while not Rope do + Wait(0) + end + ActivatePhysics(Rope) + Wait(100) + local nozzlePos = GetEntityCoords(ElectricNozzle) + nozzlePos = GetOffsetFromEntityInWorldCoords(ElectricNozzle, -0.005, 0.185, -0.05) + AttachEntitiesToRope(Rope, pump, ElectricNozzle, pumpCoords.x, pumpCoords.y, pumpCoords.z + 1.76, nozzlePos.x, nozzlePos.y, nozzlePos.z, 5.0, false, false, nil, nil) + end + Citizen.CreateThread(function() + while HoldingElectricNozzle do + local currentcoords = GetEntityCoords(ped) + local dist = #(grabbedelectricnozzlecoords - currentcoords) + if not TargetCreated then if Config.FuelTargetExport then exports[Config.TargetResource]:AllowRefuel(true, true) end end + TargetCreated = true + if dist > 7.5 then + if TargetCreated then if Config.FuelTargetExport then exports[Config.TargetResource]:AllowRefuel(false, true) end end + TargetCreated = true + HoldingElectricNozzle = false + DeleteObject(ElectricNozzle) + QBCore.Functions.Notify(Lang:t("nozzle_cannot_reach"), 'error') + if Config.PumpHose == true then + if Config.FuelDebug then print("Removing ELECTRIC Rope.") end + RopeUnloadTextures() + DeleteRope(Rope) + end + end + Wait(2500) + end + end) + end) + + RegisterNetEvent('qb-fuel:client:electric:RefuelMenu', function() + if Config.RenewedPhonePayment then + if not RefuelPossible then + TriggerEvent('qb-fuel:client:electric:SendMenuToServer') + else + if Config.RenewedPhonePayment then + if not Cancelledrefuel and not RefuelCancelled then + if RefuelPossibleAmount then + local purchasetype = "bank" + local fuelamounttotal = tonumber(RefuelPossibleAmount) + if Config.FuelDebug then print("Attempting to charge vehicle.") end + TriggerEvent('qb-fuel:client:electric:ChargeVehicle', purchasetype, fuelamounttotal) + else + QBCore.Functions.Notify(Lang:t("electric_more_than_zero"), 'error', 7500) + end + end + end + end + else + TriggerEvent("qb-fuel:client:electric:SendMenuToServer") + end + end) + + if Config.RenewedPhonePayment then + RegisterNetEvent('qb-fuel:client:electric:phone:PayForFuel', function(amount) + FuelPrice = Config.ElectricChargingPrice + + -- Police Discount Math -- + if Config.EmergencyServicesDiscount['enabled'] == true then + local discountedJobs = Config.EmergencyServicesDiscount['job'] + local plyJob = QBCore.Functions.GetPlayerData().job.name + local shouldRecieveDiscount = false + + if type(discountedJobs) == "table" then + for i = 1, #discountedJobs, 1 do + if plyJob == discountedJobs[i] then + shouldRecieveDiscount = true + break + end + end + elseif plyJob == discountedJobs then + shouldRecieveDiscount = true + end + + if shouldRecieveDiscount == true and not QBCore.Functions.GetPlayerData().job.onduty and Config.EmergencyServicesDiscount['ondutyonly'] then + QBCore.Functions.Notify(Lang:t("you_are_discount_eligible"), 'primary', 7500) + shouldRecieveDiscount = false + end + + if shouldRecieveDiscount then + local discount = Config.EmergencyServicesDiscount['discount'] + if discount > 100 then + discount = 100 + else + if discount <= 0 then discount = 0 end + end + if discount ~= 0 then + if discount == 100 then + FuelPrice = 0 + if Config.FuelDebug then + print("Your discount for Emergency Services is set @ "..discount.."% so fuel is free!") + end + else + discount = discount / 100 + FuelPrice = FuelPrice - (FuelPrice*discount) + + if Config.FuelDebug then + print("Your discount for Emergency Services is set @ "..discount.."%. Setting new price to: $"..FuelPrice) + end + end + else + if Config.FuelDebug then + print("Your discount for Emergency Services is set @ "..discount.."%. It cannot be 0 or < 0!") + end + end + end + end + local cost = amount * FuelPrice + local tax = GlobalTax(cost) + local total = math.ceil(cost + tax) + local success = exports['qb-phone']:PhoneNotification(Lang:t("electric_phone_header"), Lang:t("electric_phone_notification")..total..",-", 'fas fa-bolt', '#9f0e63', "NONE", 'fas fa-check-circle', 'fas fa-times-circle') + if success then + if QBCore.Functions.GetPlayerData().money['bank'] <= (GlobalTax(amount) + amount) then + QBCore.Functions.Notify(Lang:t("not_enough_money_in_bank"), "error") + else + TriggerServerEvent('qb-fuel:server:PayForFuel', total, "bank", FuelPrice, true) + RefuelPossible = true + RefuelPossibleAmount = amount + RefuelPurchaseType = "bank" + RefuelCancelled = false + end + end + end) + end + + -- Threads + + if Config.ElectricChargerModel then + CreateThread(function() + for i = 1, #Config.GasStations do + if Config.GasStations[i].electricchargercoords ~= nil then + if Config.FuelDebug then print(i) end + local heading = Config.GasStations[i].electricchargercoords[4] - 180 + RequestModel('electric_charger') + while not HasModelLoaded('electric_charger') do + Wait(50) + if Config.FuelDebug then + print("Loading Electric Charger Model.") + end + end + Config.GasStations[i].electriccharger = CreateObject('electric_charger', Config.GasStations[i].electricchargercoords.x, Config.GasStations[i].electricchargercoords.y, Config.GasStations[i].electricchargercoords.z, false, true, true) + if Config.FuelDebug then print("Created Electric Charger @ Location #"..i) end + SetEntityHeading(Config.GasStations[i].electriccharger, heading) + FreezeEntityPosition(Config.GasStations[i].electriccharger, 1) + end + end + end) + end + + -- Resource Stop + + AddEventHandler('onResourceStop', function(resource) + if resource == GetCurrentResourceName() then + for i = 1, #Config.GasStations do + if Config.GasStations[i].electricchargercoords ~= nil then + DeleteEntity(Config.GasStations[i].electriccharger) + if IsHoldingElectricNozzle() then DeleteEntity(ElectricNozzle) end + end + end + + if Config.PumpHose then + RopeUnloadTextures() + DeleteObject(Rope) + end + end + end) + + -- Target + local TargetResource = Config.TargetResource + if Config.TargetResource == 'ox_target' then + TargetResource = 'qb-target' + end + + exports[TargetResource]:AddTargetModel('electric_charger', { + options = { + { + num = 1, + type = "client", + event = "qb-fuel:client:grabelectricnozzle", + icon = "fas fa-bolt", + label = Lang:t("grab_electric_nozzle"), + canInteract = function() + if not IsHoldingElectricNozzle() and not IsPedInAnyVehicle(PlayerPedId()) then + return true + end + end + }, + { + num = 2, + type = "client", + event = "qb-fuel:client:returnnozzle", + icon = "fas fa-hand", + label = Lang:t("return_nozzle"), + canInteract = function() + if IsHoldingElectricNozzle() and not refueling then + return true + end + end + }, + }, + distance = 2.0 + }) +end + + +CreateThread(function() + while true do + Wait(0) + -- Disable vehicle turning on when pressing W while the car is off. + local veh = GetVehiclePedIsIn(PlayerPedId(), false) + if IsPedInVehicle(PlayerPedId(), veh, false) and (GetIsVehicleEngineRunning(veh) == false) then + DisableControlAction(0, 71, true) + elseif IsPedInVehicle(PlayerPedId(), veh, false) and (GetIsVehicleEngineRunning(veh) == true) then + EnableControlAction(0, 71, true) + end + end +end) \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-fuel/client/fuel_cl.lua b/resources/[qb]/[qb_core]/qb-fuel/client/fuel_cl.lua new file mode 100644 index 0000000..d9be87e --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-fuel/client/fuel_cl.lua @@ -0,0 +1,2600 @@ +-- Variables +local QBCore = exports[Config.Core]:GetCoreObject() +local fuelSynced = false +local inGasStation = false +local inBlacklisted = false +local holdingnozzle = false +local Stations = {} +local props = { + "prop_gas_pump_1d", + "prop_gas_pump_1a", + "prop_gas_pump_1b", + "prop_gas_pump_1c", + "prop_vintage_pump", + "prop_gas_pump_old2", + "prop_gas_pump_old3", + "denis3d_prop_gas_pump", -- Gabz Ballas Gas Station Pump. +} +local refueling = false +local GasStationBlips = {}-- Used for managing blips on the client, so labels can be updated. +local RefuelingType = nil +local PlayerInSpecialFuelZone = false +local Rope = nil +local CachedFuelPrice = nil + +-- Debug --- +if Config.FuelDebug then + RegisterCommand('setfuel', function(source, args) + if args[1] == nil then print("You forgot to put a fuel level!") return end + local vehicle = GetClosestVehicle() + SetFuel(vehicle, tonumber(args[1])) + QBCore.Functions.Notify(Lang:t("set_fuel_debug") .. ' ' .. args[1] .. 'L', 'success') + end, false) + + RegisterCommand('getCachedFuelPrice', function() + print(CachedFuelPrice) + end, false) + + RegisterCommand('getVehNameForBlacklist', function() + local veh = GetVehiclePedIsIn(PlayerPedId(), false) + if veh ~= 0 then + print(string.lower(GetDisplayNameFromVehicleModel(GetEntityModel(veh)))) + end + end, false) +end + +-- Functions +function GetClosestPump(coords, isElectric) + if isElectric then + local electricPump = nil + electricPump = GetClosestObjectOfType(coords.x, coords.y, coords.z, 3.0, joaat("electric_charger"), true, true, true) + local pumpCoords = GetEntityCoords(electricPump) + if Config.FuelDebug then + print(electricPump, pumpCoords) + end + return pumpCoords, electricPump + else + local pump = nil + local pumpCoords + for i = 1, #props, 1 do + local currentPumpModel = props[i] + pump = GetClosestObjectOfType(coords.x, coords.y, coords.z, 3.0, joaat(currentPumpModel), true, true, true) + pumpCoords = GetEntityCoords(pump) + if Config.FuelDebug then print("Gas Pump: " .. pump, "Pump Coords: " .. pumpCoords) end + if pump ~= 0 then break end + end + return pumpCoords, pump + end +end + +local function FetchStationInfo(info) + if not Config.PlayerOwnedGasStationsEnabled then ReserveLevels = 1000 StationFuelPrice = Config.CostMultiplier return end + if Config.FuelDebug then print("Fetching Information for Location #" .. CurrentLocation) end + QBCore.Functions.TriggerCallback('qb-fuel:server:fetchinfo', function(result) + if result then + for _, v in pairs(result) do + -- Reserves -- + if info == "all" or info == "reserves" then + Currentreserveamount = math.floor(v.fuel) + ReserveLevels = tonumber(Currentreserveamount) + if Config.FuelDebug then print("Fetched Reserve Levels: " .. ReserveLevels .. " Liters!") end + if Currentreserveamount < Config.MaxFuelReserves then + ReservesNotBuyable = false + else + ReservesNotBuyable = true + end + if Config.UnlimitedFuel then ReservesNotBuyable = true if Config.FuelDebug then print("Reserves are not buyable, because Config.UnlimitedFuel is set to true.") end end + end + -- Fuel Price -- + if info == "all" or info == "fuelprice" then + StationFuelPrice = v.fuelprice + end + -- Balance -- + if info == "all" or info == "balance" then + StationBalance = v.balance + if info == "balance" then + return StationBalance + end + end + ---------------- + end + else + if Config.FuelDebug then print("Error, fetching information failed.") end + end + + end, CurrentLocation) +end exports(FetchStationInfo, FetchStationInfo) + +local function HandleFuelConsumption(vehicle) + if not DecorExistOn(vehicle, Config.FuelDecor) then + SetFuel(vehicle, math.random(200, 800) / 10) + elseif not fuelSynced then + SetFuel(vehicle, GetFuel(vehicle)) + fuelSynced = true + end + + if IsVehicleEngineOn(vehicle) then + SetFuel(vehicle, GetVehicleFuelLevel(vehicle) - Config.FuelUsage[Round(GetVehicleCurrentRpm(vehicle), 1)] * (Config.Classes[GetVehicleClass(vehicle)] or 1.0) / 10) + end +end + +local function CanAfford(price, purchasetype) + local purchasetype = purchasetype + if purchasetype == "bank" then Money = QBCore.Functions.GetPlayerData().money['bank'] elseif purchasetype == 'cash' then Money = QBCore.Functions.GetPlayerData().money['cash'] end + if Money < price then + return false + else + return true + end +end + +function FetchCurrentLocation() + if Config.FuelDebug then print("Fetching Current Location") end + return CurrentLocation +end + +function IsInGasStation() + return inGasStation +end + + +-- Thread Stuff -- +if Config.LeaveEngineRunning then + CreateThread(function() + while true do + Wait(100) + local ped = PlayerPedId() + if IsPedInAnyVehicle(ped, false) and IsControlPressed(2, 75) and not IsEntityDead(ped) then + local vehicle = GetVehiclePedIsIn(ped, true) + local enginerunning = GetIsVehicleEngineRunning(vehicle) + if Config.FuelDebug then if enginerunning then print('Engine is running!') else print('Engine is not running!') end end + Wait(900) + if IsPedInAnyVehicle(ped, false) and IsControlPressed(2, 75) and not IsEntityDead(ped) and GetPedInVehicleSeat(GetVehiclePedIsIn(PlayerPedId()), -1) == PlayerPedId() then + if enginerunning then SetVehicleEngineOn(vehicle, true, true, false)enginerunning = false end + TaskLeaveVehicle(ped, veh, keepDooRopen and 256 or 0) + end + end + end + end) +end + +if Config.ShowNearestGasStationOnly then + RegisterNetEvent('qb-fuel:client:updatestationlabels', function(location, newLabel) + if not location then if Config.FuelDebug then print('location is nil') end return end + if not newLabel then if Config.FuelDebug then print('newLabel is nil') end return end + if Config.FuelDebug then print("Changing Label for Location #" .. location .. ' to ' .. newLabel) end + Config.GasStations[location].label = newLabel + end) + + CreateThread(function() + if Config.PlayerOwnedGasStationsEnabled then + TriggerServerEvent('qb-fuel:server:updatelocationlabels') + end + -- Wait(1000) + local currentGasBlip = 0 + while true do + local coords = GetEntityCoords(PlayerPedId()) + local closest = 3000 + local closestCoords + local closestLocation + local location = 0 + local label = "Tankstation" -- Prevent nil just in case, set default name. + for _, ourCoords in pairs(Config.GasStations) do + location = location + 1 + if not (location > #Config.GasStations) then -- Make sure we are not going over the amount of locations available. + local gasStationCoords = vector3(Config.GasStations[location].pedcoords.x, Config.GasStations[location].pedcoords.y, Config.GasStations[location].pedcoords.z) + local dstcheck = #(coords - gasStationCoords) + if dstcheck < closest then + closest = dstcheck + closestCoords = gasStationCoords + closestLocation = location + label = Config.GasStations[closestLocation].label + end + else + break + end + end + if DoesBlipExist(currentGasBlip) then + RemoveBlip(currentGasBlip) + end + if label == "Tankstation" then return end -- Midlertidig løsning + currentGasBlip = CreateBlip(closestCoords, label) + Wait(10000) + end + end) +else + RegisterNetEvent('qb-fuel:client:updatestationlabels', function(location, newLabel) + if not location then if Config.FuelDebug then print('location is nil') end return end + if not newLabel then if Config.FuelDebug then print('newLabel is nil') end return end + if Config.FuelDebug then print("Changing Label for Location #" .. location .. ' to ' .. newLabel) end + Config.GasStations[location].label = newLabel + local coords = vector3(Config.GasStations[location].pedcoords.x, Config.GasStations[location].pedcoords.y, Config.GasStations[location].pedcoords.z) + RemoveBlip(GasStationBlips[location]) + GasStationBlips[location] = CreateBlip(coords, Config.GasStations[location].label) + end) + + CreateThread(function() + TriggerServerEvent('qb-fuel:server:updatelocationlabels') + -- Wait(1000) + local gasStationCoords + for i = 1, #Config.GasStations, 1 do + local location = i + gasStationCoords = vector3(Config.GasStations[location].pedcoords.x, Config.GasStations[location].pedcoords.y, Config.GasStations[location].pedcoords.z) + + -- Check if the blip for this location already exists + if not GasStationBlips[location] then + GasStationBlips[location] = CreateBlip(gasStationCoords, Config.GasStations[location].label) + end + end + end) +end + +CreateThread(function() + for station_id = 1, #Config.GasStations, 1 do + Stations[station_id] = PolyZone:Create(Config.GasStations[station_id].zones, { + name = "CDN_FUEL_GAS_STATION_" .. station_id, + minZ = Config.GasStations[station_id].minz, + maxZ = Config.GasStations[station_id].maxz, + debugPoly = Config.PolyDebug + }) + Stations[station_id]:onPlayerInOut(function(isPointInside) + if isPointInside then + inGasStation = true + CurrentLocation = station_id + if Config.FuelDebug then print("New Location: " .. station_id) end + if Config.PlayerOwnedGasStationsEnabled then + TriggerEvent('qb-fuel:stations:updatelocation', station_id) + end + else + TriggerEvent('qb-fuel:stations:updatelocation', nil) + inGasStation = false + end + end) + end +end) + +CreateThread(function() + DecorRegister(Config.FuelDecor, 1) + while true do + Wait(1000) + local ped = PlayerPedId() + -- Blacklist Electric Vehicles, if you disables the Config.ElectricVehicleCharging or put the vehicle in Config.NoFuelUsage! + if IsPedInAnyVehicle(ped) then + local vehicle = GetVehiclePedIsIn(ped) + inBlacklisted = IsVehicleBlacklisted(vehicle) + if not inBlacklisted and GetPedInVehicleSeat(vehicle, -1) == ped then + HandleFuelConsumption(vehicle) + end + else + if fuelSynced then fuelSynced = false end + if inBlacklisted then inBlacklisted = false end + Wait(500) + end + end +end) + +-- Client Events +if Config.RenewedPhonePayment then + RegisterNetEvent('qb-fuel:client:phone:PayForFuel', function(amount) + if Config.PlayerOwnedGasStationsEnabled and RefuelingType ~= 'special' then + FetchStationInfo("fuelprice") + Wait(100) + else + FuelPrice = Config.CostMultiplier + end + if Config.AirAndWaterVehicleFueling['enabled'] then + local vehClass = GetVehicleClass(vehicle) + if vehClass == 14 then + FuelPrice = Config.AirAndWaterVehicleFueling['water_fuel_price'] + elseif vehClass == 15 or vehClass == 16 then + FuelPrice = Config.AirAndWaterVehicleFueling['air_fuel_price'] + end + end + -- Police Discount Math -- + if Config.EmergencyServicesDiscount['enabled'] == true then + local discountedJobs = Config.EmergencyServicesDiscount['job'] + local plyJob = QBCore.Functions.GetPlayerData().job.name + local shouldRecieveDiscount = false + if type(discountedJobs) == "table" then + for i = 1, #discountedJobs, 1 do + if plyJob == discountedJobs[i] then + shouldRecieveDiscount = true + break + end + end + elseif plyJob == discountedJobs then + shouldRecieveDiscount = true + end + if shouldRecieveDiscount == true and not QBCore.Functions.GetPlayerData().job.onduty and Config.EmergencyServicesDiscount['ondutyonly'] then + QBCore.Functions.Notify(Lang:t("you_are_discount_eligible"), 'primary', 7500) + shouldRecieveDiscount = false + end + if shouldRecieveDiscount then + local discount = Config.EmergencyServicesDiscount['discount'] + if discount > 100 then + discount = 100 + else + if discount <= 0 then discount = 0 end + end + if discount ~= 0 then + if discount == 100 then + CachedFuelPrice = FuelPrice + FuelPrice = 0 + if Config.FuelDebug then + print("Your discount for Emergency Services is set @ " .. discount .. "% so fuel is free!") + end + else + discount = discount / 100 + if Config.FuelDebug then + print(FuelPrice, FuelPrice * discount) + end + CachedFuelPrice = FuelPrice + FuelPrice = FuelPrice - (FuelPrice * discount) + + if Config.FuelDebug then + print("Your discount for Emergency Services is set @ " .. discount .. "%. Setting new price to: $" .. FuelPrice) + end + end + else + if Config.FuelDebug then + print("Your discount for Emergency Services is set @ " .. discount .. "%. It cannot be 0 or < 0!") + end + end + end + end + local cost = amount * FuelPrice + local tax = GlobalTax(cost) + local total = math.ceil(cost + tax) + local success = exports['qb-phone']:PhoneNotification(Lang:t("fuel_phone_header"), Lang:t("phone_notification") .. total..",-", 'fas fa-gas-pump', '#9f0e63', "NONE", 'fas fa-check-circle', 'fas fa-times-circle') + if success then + if QBCore.Functions.GetPlayerData().money['bank'] <= total then + QBCore.Functions.Notify(Lang:t("not_enough_money"), "error") + else + TriggerServerEvent('qb-fuel:server:PayForFuel', total, "bank", FuelPrice, false, CachedFuelPrice) + RefuelPossible = true + RefuelPossibleAmount = amount + RefuelCancelledFuelCost = FuelPrice + RefuelPurchaseType = "bank" + RefuelCancelled = false + end + end + end) +end + + +if Config.Ox.Inventory then + if LocalPlayer.state['isLoggedIn'] then + exports.ox_inventory:displayMetadata({ + cdn_fuel = "Fuel", + }) + end + AddEventHandler("QBCore:Client:OnPlayerLoaded", function() + if GetResourceState('ox_inventory'):match("start") then + exports.ox_inventory:displayMetadata({ + cdn_fuel = "Fuel", + }) + end + end) +end + +if Config.Ox.Menu then + RegisterNetEvent('qb-fuel:client:OpenContextMenu', function(total, fuelamounttotal, purchasetype) + if Config.FuelDebug then print("OpenContextMenu for OX sent from server.") end + lib.registerContext({ + id = 'cdnconfirmationmenu', + title = Lang:t("menu_purchase_station_header_1") .. math.ceil(total) .. Lang:t("menu_purchase_station_header_2"), + options = { + { + title = Lang:t("menu_purchase_station_confirm_header"), + description = Lang:t("menu_refuel_accept"), + icon = "fas fa-check-circle", + arrow = false, -- puts arrow to the right + event = 'qb-fuel:client:RefuelVehicle', + args = { + fuelamounttotal = fuelamounttotal, + purchasetype = purchasetype, + } + }, + { + title = Lang:t("menu_header_close"), + description = Lang:t("menu_refuel_cancel"), + icon = "fas fa-times-circle", + arrow = false, -- puts arrow to the right + onSelect = function() + lib.hideContext() + end, + }, + }, + }) + lib.showContext('cdnconfirmationmenu') + end) +end + +RegisterNetEvent('qb-fuel:client:RefuelMenu', function(type) + if Config.FuelDebug then print("qb-fuel:client:refuelmenu") end + if not type then type = nil end + if Config.RenewedPhonePayment then + if not RefuelPossible then + TriggerEvent('qb-fuel:client:SendMenuToServer', type) + else + if not Cancelledrefuel and not RefuelCancelled then + if RefuelPossibleAmount then + local purchasetype = "bank" + local fuelamounttotal = tonumber(RefuelPossibleAmount) + TriggerEvent('qb-fuel:client:RefuelVehicle', purchasetype, fuelamounttotal) + else + if Config.FuelDebug then + print("RefuelMenu: MORE THAN ZERO!") + end + QBCore.Functions.Notify(Lang:t("more_than_zero"), 'error', 7500) + end + end + end + else + TriggerEvent('qb-fuel:client:SendMenuToServer', type) + end +end) + +local ShutOff1 = false +RegisterNetEvent('qb-fuel:client:grabnozzle', function() + if Config.PlayerOwnedGasStationsEnabled then + QBCore.Functions.TriggerCallback('qb-fuel:server:checkshutoff', function(result) + if result == true then + ShutOff1 = true + QBCore.Functions.Notify(Lang:t("emergency_shutoff_active"), 'error', 7500) + else + ShutOff1 = false + end + end, CurrentLocation) + end + Wait(100) + if ShutOff1 then + return + else + local ped = PlayerPedId() + if holdingnozzle then return end + LoadAnimDict("anim@am_hold_up@male") + TaskPlayAnim(ped, "anim@am_hold_up@male", "shoplift_high", 2.0, 8.0, -1, 50, 0, 0, 0, 0) + TriggerServerEvent("InteractSound_SV:PlayOnSource", "pickupnozzle", 0.4) + Wait(300) + StopAnimTask(ped, "anim@am_hold_up@male", "shoplift_high", 1.0) + fuelnozzle = CreateObject(joaat('prop_cs_fuel_nozle'), 1.0, 1.0, 1.0, true, true, false) + local lefthand = GetPedBoneIndex(ped, 18905) + AttachEntityToEntity(fuelnozzle, ped, lefthand, 0.13, 0.04, 0.01, -42.0, -115.0, -63.42, 0, 1, 0, 1, 0, 1) + local grabbednozzlecoords = GetEntityCoords(ped) + if Config.PumpHose then + local pumpCoords, pump = GetClosestPump(grabbednozzlecoords) + -- Load Rope Textures + RopeLoadTextures() + while not RopeAreTexturesLoaded() do + Wait(0) + RopeLoadTextures() + end + -- Wait for Pump to exist. + while not pump do + Wait(0) + end + Rope = AddRope(pumpCoords.x, pumpCoords.y, pumpCoords.z, 0.0, 0.0, 0.0, 3.0, Config.RopeType['fuel'], 8.0 --[[ DO NOT SET THIS TO 0.0!!! GAME WILL CRASH!]], 0.0, 1.0, false, false, false, 1.0, true) + while not Rope do + Wait(0) + end + ActivatePhysics(Rope) + Wait(100) + local nozzlePos = GetEntityCoords(fuelnozzle) + if Config.FuelDebug then print("NOZZLE POS " .. nozzlePos) end + nozzlePos = GetOffsetFromEntityInWorldCoords(fuelnozzle, 0.0, -0.033, -0.195) + local PumpHeightAdd = nil + if Config.FuelDebug then + print("Grabbing Hose @ Location: #" .. CurrentLocation) + if Config.GasStations[CurrentLocation].pumpheightadd ~= nil then + PumpHeightAdd = Config.GasStations[CurrentLocation].pumpheightadd + print("Pump Height Add: " .. Config.GasStations[CurrentLocation].pumpheightadd) + end + end + if PumpHeightAdd == nil then + PumpHeightAdd = 2.1 + if Config.FuelDebug then + print("PumpHeightAdd was not configured for location: #" .. CurrentLocation .. " so, we are defaulting to 2.0!") + end + end + AttachEntitiesToRope(Rope, pump, fuelnozzle, pumpCoords.x, pumpCoords.y, pumpCoords.z + PumpHeightAdd, nozzlePos.x, nozzlePos.y, nozzlePos.z, length, false, false, nil, nil) + if Config.FuelDebug then + print("Hose Properties:") + print(Rope, pump, fuelnozzle, pumpCoords.x, pumpCoords.y, pumpCoords.z, nozzlePos.x, nozzlePos.y, nozzlePos.z, length) + SetEntityDrawOutline(fuelnozzle --[[ Entity ]], true --[[ boolean ]]) + end + end + holdingnozzle = true + CreateThread(function() + while holdingnozzle do + local currentcoords = GetEntityCoords(ped) + local dist = #(grabbednozzlecoords - currentcoords) + if not TargetCreated then if Config.FuelTargetExport then exports[Config.TargetResource]:AllowRefuel(true) end end + TargetCreated = true + if dist > 7.5 then + if TargetCreated then if Config.FuelTargetExport then exports[Config.TargetResource]:AllowRefuel(false) end end + TargetCreated = true + holdingnozzle = false + DeleteObject(fuelnozzle) + QBCore.Functions.Notify(Lang:t("nozzle_cannot_reach"), 'error') + if Config.PumpHose == true then + RopeUnloadTextures() + DeleteRope(Rope) + end + if Config.FuelNozzleExplosion then + AddExplosion(grabbednozzlecoords.x, grabbednozzlecoords.y, grabbednozzlecoords.z, 'EXP_TAG_PROPANE', 1.0, true, false, 5.0) + StartScriptFire(grabbednozzlecoords.x, grabbednozzlecoords.y, grabbednozzlecoords.z - 1, 25, false) + SetFireSpreadRate(10.0) + Wait(5000) + StopFireInRange(grabbednozzlecoords.x, grabbednozzlecoords.y, grabbednozzlecoords.z - 1, 3.0) + end + end + Wait(2500) + end + end) + end +end) + +RegisterNetEvent('qb-fuel:client:returnnozzle', function() + if Config.ElectricVehicleCharging then + if IsHoldingElectricNozzle() then + SetElectricNozzle("putback") + else + holdingnozzle = false + TargetCreated = false + TriggerServerEvent("InteractSound_SV:PlayOnSource", "putbacknozzle", 0.4) + Wait(250) + if Config.FuelTargetExport then exports[Config.TargetResource]:AllowRefuel(false) end + DeleteObject(fuelnozzle) + end + else + holdingnozzle = false + TargetCreated = false + TriggerServerEvent("InteractSound_SV:PlayOnSource", "putbacknozzle", 0.4) + Wait(250) + if Config.FuelTargetExport then exports[Config.TargetResource]:AllowRefuel(false) end + DeleteObject(fuelnozzle) + end + if Config.PumpHose then + if Config.FuelDebug then print("Removing Hose.") end + RopeUnloadTextures() + DeleteRope(Rope) + end +end) + +AddEventHandler('onResourceStop', function(resource) + if resource == GetCurrentResourceName() then + DeleteObject(fuelnozzle) + DeleteObject(SpecialFuelNozzleObj) + if Config.PumpHose then + RopeUnloadTextures() + DeleteObject(Rope) + end + if Config.TargetResource == 'ox_target' then + exports.ox_target:removeGlobalVehicle('qb-fuel:options:1') + exports.ox_target:removeGlobalVehicle('qb-fuel:options:2') + end + -- Remove Blips from map so they dont double up. + for i = 1, #GasStationBlips, 1 do + RemoveBlip(GasStationBlips[i]) + end + end +end) + +RegisterNetEvent('qb-fuel:client:FinalMenu', function(purchasetype) + if Config.FuelDebug then + print('qb-fuel:client:FinalMenu', purchasetype) + end + if RefuelingType == nil then + FetchStationInfo("all") + Wait(Config.WaitTime) + if Config.PlayerOwnedGasStationsEnabled and not Config.UnlimitedFuel then + if ReserveLevels and ReserveLevels < 1 then + QBCore.Functions.Notify(Lang:t("station_no_fuel"), 'error', 7500) return + end + end + if Config.PlayerOwnedGasStationsEnabled then + FuelPrice = (1 * StationFuelPrice) + end + end + local money = nil + if purchasetype == "bank" then money = QBCore.Functions.GetPlayerData().money['bank'] elseif purchasetype == 'cash' then money = QBCore.Functions.GetPlayerData().money['cash'] end + if not Config.PlayerOwnedGasStationsEnabled then + FuelPrice = (1 * Config.CostMultiplier) + end + local vehicle = GetClosestVehicle() + local curfuel = GetFuel(vehicle) + local finalfuel + if curfuel < 10 then finalfuel = string.sub(curfuel, 1, 1) else finalfuel = string.sub(curfuel, 1, 2) end + local maxfuel = (100 - finalfuel - 1) + if Config.AirAndWaterVehicleFueling['enabled'] then + local vehClass = GetVehicleClass(vehicle) + if vehClass == 14 then + FuelPrice = Config.AirAndWaterVehicleFueling['water_fuel_price'] + RefuelingType = 'special' + elseif vehClass == 15 or vehClass == 16 then + FuelPrice = Config.AirAndWaterVehicleFueling['air_fuel_price'] + RefuelingType = 'special' + end + end + -- Police Discount Math -- + if Config.EmergencyServicesDiscount['enabled'] == true and (Config.EmergencyServicesDiscount['emergency_vehicles_only'] == false or (Config.EmergencyServicesDiscount['emergency_vehicles_only'] == true and GetVehicleClass(vehicle) == 18)) then + local discountedJobs = Config.EmergencyServicesDiscount['job'] + local plyJob = QBCore.Functions.GetPlayerData().job.name + local shouldRecieveDiscount = false + if type(discountedJobs) == "table" then + for i = 1, #discountedJobs, 1 do + if plyJob == discountedJobs[i] then + shouldRecieveDiscount = true + break + end + end + elseif plyJob == discountedJobs then + shouldRecieveDiscount = true + end + if shouldRecieveDiscount == true and not QBCore.Functions.GetPlayerData().job.onduty and Config.EmergencyServicesDiscount['ondutyonly'] then + QBCore.Functions.Notify(Lang:t("you_are_discount_eligible"), 'primary', 7500) + shouldRecieveDiscount = false + end + if shouldRecieveDiscount then + local discount = Config.EmergencyServicesDiscount['discount'] + if discount > 100 then + discount = 100 + else + if discount <= 0 then discount = 0 end + end + if Config.FuelDebug then print("Before we apply the discount the FuelPrice is: $" .. FuelPrice) end + if discount ~= 0 then + if discount == 100 then + CachedFuelPrice = FuelPrice + FuelPrice = 0 + if Config.FuelDebug then + print("Your discount for Emergency Services is set @ " .. discount .. "% so fuel is free!") + end + else + discount = discount / 100 + if Config.FuelDebug then + print("Math( Current Fuel Price: " .. FuelPrice .. " - " .. FuelPrice * discount .. "<<-- FuelPrice * Discount)") + end + CachedFuelPrice = FuelPrice + FuelPrice = (FuelPrice) - (FuelPrice * discount) + if Config.FuelDebug then + print("Your discount for Emergency Services is set @ " .. discount .. "%. Setting new price to: $" .. FuelPrice) + end + end + else + if Config.FuelDebug then + print("Your discount for Emergency Services is set @ " .. discount .. "%. It cannot be 0 or < 0!") + end + end + end + end + local wholetankcost = (tonumber(FuelPrice) * maxfuel) + local wholetankcostwithtax = math.ceil(tonumber(FuelPrice) * maxfuel + GlobalTax(wholetankcost)) + if Config.Ox.Input then + if Config.PlayerOwnedGasStationsEnabled and not Config.UnlimitedFuel and not RefuelingType == 'special' then + if ReserveLevels < maxfuel then + local wholetankcost = (tonumber(FuelPrice) * ReserveLevels) + local wholetankcostwithtax = math.ceil(tonumber(FuelPrice) * ReserveLevels + GlobalTax(wholetankcost)) + fuel = lib.inputDialog('Tankstation', { + {type = "input", label = 'Brændstof pris', default = FuelPrice .. ',- per liter', disabled = true}, + {type = "input", label = 'Nuværrende tankbeholdning', default = finalfuel .. ' liter', disabled = true}, + {type = "input", label = 'Nødvendigt for fuld tank', default = maxfuel .. ' liter', disabled = true}, + {type = "input", label = 'Brændstof tilgængelig på tankstationen', default = ReserveLevels, disabled = true}, + {type = "slider", label = 'Pris for fuld tank: ' .. wholetankcostwithtax .. ',-', default = ReserveLevels, min = 0, max = ReserveLevels}, + }) + if not fuel then if Config.FuelDebug then print("Fuel Is Nil! #1") end return end + fuelAmount = tonumber(fuel[5]) + else + fuel = lib.inputDialog('Tankstation', { + {type = "input", label = 'Brændstof pris', default = FuelPrice .. ',- per liter', disabled = true}, + {type = "input", label = 'Nuværrende tankbeholdning', default = finalfuel .. ' liter', disabled = true}, + {type = "input", label = 'Required For A Full Tank', default = maxfuel .. ' liter', disabled = true}, + {type = "slider", label = 'Pris for fuld tank: ' .. wholetankcostwithtax .. ',-', default = maxfuel, min = 0, max = maxfuel}, + }) + if not fuel then if Config.FuelDebug then print("Fuel Is Nil! #2") end return end + fuelAmount = tonumber(fuel[4]) + end + else + fuel = lib.inputDialog('Tankstation', { + {type = "input", label = 'Brændstof pris', default = FuelPrice .. ',- per liter', disabled = true}, + {type = "input", label = 'Nuværrende tankbeholdning', default = finalfuel .. ' Per Liter', disabled = true}, + {type = "input", label = 'Nødvendigt for fuld tank', default = maxfuel .. ' liter', disabled = true}, + {type = "slider", label = 'Pris for fuld tank: ' .. wholetankcostwithtax .. ',-', default = maxfuel, min = 0, max = maxfuel}, + }) + if not fuel then if Config.FuelDebug then print("Fuel Is Nil! #3") end return end + fuelAmount = tonumber(fuel[4]) + end + if fuel then + if not fuelAmount then print("Fuel Amount Nil") return end + if not holdingnozzle and RefuelingType ~= 'special' then QBCore.Functions.Notify(Lang:t("no_nozzle"), 'error') return end + if Config.PlayerOwnedGasStationsEnabled and not Config.UnlimitedFuel and not RefuelingType == "special" then + if tonumber(fuelAmount) > tonumber(ReserveLevels) then + QBCore.Functions.Notify(Lang:t("station_not_enough_fuel"), "error") return + end + end + if (fuelAmount + finalfuel) >= 100 then + QBCore.Functions.Notify(Lang:t("tank_cannot_fit"), "error") + else + if GlobalTax(fuelAmount * FuelPrice) + (fuelAmount * FuelPrice) <= money then + TriggerServerEvent('qb-fuel:server:OpenMenu', fuelAmount, inGasStation, false, purchasetype, tonumber(FuelPrice)) + else + QBCore.Functions.Notify(Lang:t("not_enough_money"), 'error', 7500) + end + end + else + if Config.FuelDebug then + print("Fuel is nil!") + end + end + else + if Config.PlayerOwnedGasStationsEnabled and not Config.UnlimitedFuel and not RefuelingType == 'special' then + if ReserveLevels < maxfuel then + local wholetankcost = (FuelPrice * ReserveLevels) + local wholetankcostwithtax = math.ceil(FuelPrice * ReserveLevels + GlobalTax(wholetankcost)) + fuel = exports['qb-input']:ShowInput({ + header = "Vælg mængden af brændstof
Nuværrende pris: " .. + FuelPrice .. ",- / Liter
Nuværrende beholdning: " .. finalfuel .. " Liter
Fuld tank koster: " .. + wholetankcostwithtax .. ",-", + submitText = Lang:t("input_insert_nozzle"), + inputs = {{ + type = 'number', + isRequired = true, + name = 'amount', + text = 'Der er ' .. ReserveLevels .. ' liter til rådighed.' + }} + }) + else + fuel = exports['qb-input']:ShowInput({ + header = "Vælg mængden af brændstof
Nuværrende pris: " .. + FuelPrice .. ",- / Liter
Nuværrende beholdning: " .. finalfuel .. " Liter
Fuld tank koster: " .. + wholetankcostwithtax .. ",-", + submitText = Lang:t("input_insert_nozzle"), + inputs = {{ + type = 'number', + isRequired = true, + name = 'amount', + text = 'Tank kan rumme ' .. maxfuel .. ' liter mere.' + }} + }) + end + else + fuel = exports['qb-input']:ShowInput({ + header = "Vælg mængden af brændstof
Nuværrende pris: " .. + FuelPrice .. ",- / Liter
Nuværrende beholdning: " .. finalfuel .. " Liter
Fuld tank koster: " .. + wholetankcostwithtax .. ",-", + submitText = Lang:t("input_insert_nozzle"), + inputs = {{ + type = 'number', + isRequired = true, + name = 'amount', + text = 'Tank kan rumme ' .. maxfuel .. ' liter mere.' + }} + }) + end + if fuel then + if not fuel.amount then if Config.FuelDebug then print("fuel.amount = nil") end return end + if not holdingnozzle and RefuelingType ~= 'special' then QBCore.Functions.Notify(Lang:t("no_nozzle")) return end + if Config.PlayerOwnedGasStationsEnabled and not Config.UnlimitedFuel and not RefuelingType == 'special' then + if tonumber(fuel.amount) > tonumber(ReserveLevels) then + QBCore.Functions.Notify(Lang:t("station_not_enough_fuel"), "error") return + end + end + if (fuel.amount + finalfuel) >= 100 then + QBCore.Functions.Notify(Lang:t("tank_cannot_fit"), "error") + else + if GlobalTax(fuel.amount * FuelPrice) + (fuel.amount * FuelPrice) <= money then + if Config.FuelDebug then + print("Player is getting " .. fuel.amount .. "L of Fuel @ " .. FuelPrice .. '/L, Total Cost: ' .. GlobalTax(fuel.amount * FuelPrice) + (fuel.amount * FuelPrice)) + end + TriggerServerEvent('qb-fuel:server:OpenMenu', fuel.amount, inGasStation, false, purchasetype, tonumber(FuelPrice)) + else + QBCore.Functions.Notify(Lang:t("not_enough_money"), 'error', 7500) + end + end + end + end +end) + +RegisterNetEvent('qb-fuel:client:SendMenuToServer', function(type) + local vehicle = GetClosestVehicle() + local NotElectric = false + if Config.ElectricVehicleCharging then + local isElectric = GetCurrentVehicleType(vehicle) + if isElectric == 'electricvehicle' then + QBCore.Functions.Notify(Lang:t("need_electric_charger"), 'error', 7500) return + end + NotElectric = true + else + NotElectric = true + end + Wait(50) + if NotElectric then + local CurFuel = GetVehicleFuelLevel(vehicle) + local playercashamount = QBCore.Functions.GetPlayerData().money['cash'] + if not holdingnozzle and not type == 'special' then return end + local header + if type == 'special' then + header = "Genopfyldning af brændstof" + RefuelingType = 'special' + else + header = Config.GasStations[CurrentLocation].label + end + if CurFuel < 99 then + if Config.Ox.Menu then + lib.registerContext({ + id = 'cdnfueldmainmenu', + title = 'Tankstation', + icon = "fas fa-gas-pump", + options = { + { + title = Lang:t("menu_header_cash"), + description = Lang:t("menu_pay_with_cash") .. playercashamount..",-", + icon = "fas fa-usd", + arrow = false, -- puts arrow to the right + onSelect = function() + TriggerEvent('qb-fuel:client:FinalMenu', 'cash') + end, + }, + { + title = Lang:t("menu_header_bank"), + description = Lang:t("menu_pay_with_bank"), + icon = "fas fa-credit-card", + arrow = false, -- puts arrow to the right + onSelect = function() + TriggerEvent('qb-fuel:client:FinalMenu', 'bank') + end, + }, + { + title = Lang:t("menu_header_close"), + description = Lang:t("menu_refuel_cancel"), + icon = "fas fa-times-circle", + arrow = false, -- puts arrow to the right + onSelect = function() + lib.hideContext() + end, + }, + }, + }) + lib.showContext('cdnfueldmainmenu') + else + exports['qb-menu']:openMenu({ + { + header = header, + isMenuHeader = true, + icon = "fas fa-gas-pump", + }, + { + header = Lang:t("menu_header_cash"), + txt = Lang:t("menu_pay_with_cash") .. playercashamount..",-", + icon = "fas fa-usd", + params = { + event = "qb-fuel:client:FinalMenu", + args = 'cash', + } + }, + { + header = Lang:t("menu_header_bank"), + txt = Lang:t("menu_pay_with_bank"), + icon = "fas fa-credit-card", + params = { + event = "qb-fuel:client:FinalMenu", + args = 'bank', + } + }, + { + header = Lang:t("menu_header_close"), + txt = Lang:t("menu_refuel_cancel"), + icon = "fas fa-times-circle", + params = { + event = "qb-menu:closeMenu", + } + }, + }) + end + else + QBCore.Functions.Notify(Lang:t("tank_already_full"), 'error') + end + else + QBCore.Functions.Notify(Lang:t("need_electric_charger"), 'error', 7500) + end +end) + +RegisterNetEvent('qb-fuel:client:RefuelVehicle', function(data) + if RefuelingType == nil then + FetchStationInfo("all") + Wait(100) + end + local purchasetype, amount, fuelamount + if not Config.RenewedPhonePayment then + purchasetype = data.purchasetype + elseif data.purchasetype == "cash" then + purchasetype = "cash" + else + purchasetype = RefuelPurchaseType + end + if Config.FuelDebug then print("Purchase Type: " .. purchasetype) end + if not Config.RenewedPhonePayment then + amount = data.fuelamounttotal + elseif data.purchasetype == "cash" then + amount = data.fuelamounttotal + elseif not data.fuelamounttotal then + amount = RefuelPossibleAmount + end + if Config.PlayerOwnedGasStationsEnabled and RefuelingType == nil then + FuelPrice = (1 * StationFuelPrice) + else + FuelPrice = (1 * Config.CostMultiplier) + end + if not holdingnozzle and RefuelingType == nil then return end + amount = tonumber(amount) + if amount < 1 then return end + if amount < 10 then fuelamount = string.sub(amount, 1, 1) else fuelamount = string.sub(amount, 1, 2) end + local vehicle = GetClosestVehicle() + if Config.AirAndWaterVehicleFueling['enabled'] then + local vehClass = GetVehicleClass(vehicle) + if vehClass == 14 then + FuelPrice = Config.AirAndWaterVehicleFueling['water_fuel_price'] + elseif vehClass == 15 or vehClass == 16 then + FuelPrice = Config.AirAndWaterVehicleFueling['air_fuel_price'] + end + end + -- Police Discount Math -- + if Config.EmergencyServicesDiscount['enabled'] == true and (Config.EmergencyServicesDiscount['emergency_vehicles_only'] == false or (Config.EmergencyServicesDiscount['emergency_vehicles_only'] == true and GetVehicleClass(vehicle) == 18)) then + local discountedJobs = Config.EmergencyServicesDiscount['job'] + local plyJob = QBCore.Functions.GetPlayerData().job.name + local shouldRecieveDiscount = false + if type(discountedJobs) == "table" then + for i = 1, #discountedJobs, 1 do + if plyJob == discountedJobs[i] then + shouldRecieveDiscount = true + break + end + end + elseif plyJob == discountedJobs then + shouldRecieveDiscount = true + end + if shouldRecieveDiscount == true and not QBCore.Functions.GetPlayerData().job.onduty and Config.EmergencyServicesDiscount['ondutyonly'] then + QBCore.Functions.Notify(Lang:t("you_are_discount_eligible"), 'primary', 7500) + shouldRecieveDiscount = false + end + if shouldRecieveDiscount then + local discount = Config.EmergencyServicesDiscount['discount'] + if discount > 100 then + discount = 100 + else + if discount <= 0 then discount = 0 end + end + if Config.FuelDebug then print("Before we apply the discount the FuelPrice is: $" .. FuelPrice) end + if discount ~= 0 then + if discount == 100 then + CachedFuelPrice = FuelPrice + FuelPrice = 0 + if Config.FuelDebug then + print("Your discount for Emergency Services is set @ | " .. discount .. "% | so fuel is free!") + end + else + discount = discount / 100 + if Config.FuelDebug then + print("Math( Current Fuel Price: " .. FuelPrice .. " - " .. FuelPrice * discount .. "<<-- FuelPrice * Discount)") + end + + CachedFuelPrice = FuelPrice + FuelPrice = FuelPrice - (FuelPrice * discount) + + if Config.FuelDebug then + print("Your discount for Emergency Services is set @ " .. discount .. "%. Setting new price to: $" .. FuelPrice) + end + end + else + if Config.FuelDebug then + print("Your discount for Emergency Services is set @ " .. discount .. "%. It cannot be 0 or < 0!") + end + end + end + end + local refillCost = (amount * FuelPrice) + GlobalTax(amount * FuelPrice) + local ped = PlayerPedId() + local time = amount * Config.RefuelTime + if amount < 10 then time = 10 * Config.RefuelTime end + local vehicleCoords = GetEntityCoords(vehicle) + if inGasStation then + if IsPlayerNearVehicle() then + RequestAnimDict(Config.RefuelAnimationDictionary) + while not HasAnimDictLoaded(Config.RefuelAnimationDictionary) do Wait(100) end + if GetIsVehicleEngineRunning(vehicle) and Config.VehicleBlowUp then + local Chance = math.random(1, 100) + if Chance <= Config.BlowUpChance then + AddExplosion(vehicleCoords, 5, 50.0, true, false, true) + return + end + end + if Config.FaceTowardsVehicle and RefuelingType ~= 'special' then + local bootBoneIndex = GetEntityBoneIndexByName(vehicle --[[ Entity ]], 'boot' --[[ string ]]) + local vehBootCoords = GetWorldPositionOfEntityBone(vehicle --[[ Entity ]], joaat(bootBoneIndex)--[[ integer ]]) + if Config.FuelDebug then + print("Vehicle Boot Bone Coords: " .. vehBootCoords.x, vehBootCoords.y, vehBootCoords.z) + end + TaskTurnPedToFaceCoord(PlayerPedId(), vehBootCoords, 500) + Wait(500) + end + TaskPlayAnim(ped, Config.RefuelAnimationDictionary, Config.RefuelAnimation, 8.0, 1.0, -1, 1, 0, 0, 0, 0) + refueling = true + Refuelamount = 0 + CreateThread(function() + while refueling do + if Refuelamount == nil then Refuelamount = 0 end + Wait(Config.RefuelTime) + Refuelamount = Refuelamount + 1 + if Cancelledrefuel then + local finalrefuelamount = math.floor(Refuelamount) + local refillCost = (finalrefuelamount * FuelPrice) + GlobalTax(finalrefuelamount * FuelPrice) + if Config.RenewedPhonePayment and purchasetype == "bank" then + local remainingamount = (amount - Refuelamount) + MoneyToGiveBack = (GlobalTax(remainingamount * RefuelCancelledFuelCost) + (remainingamount * RefuelCancelledFuelCost)) + TriggerServerEvent("qb-fuel:server:phone:givebackmoney", MoneyToGiveBack) + CachedFuelPrice = nil + else + TriggerServerEvent('qb-fuel:server:PayForFuel', refillCost, purchasetype, FuelPrice, false, CachedFuelPrice) + CachedFuelPrice = nil + end + if RefuelingType == nil then + if Config.PlayerOwnedGasStationsEnabled and not Config.UnlimitedFuel then + TriggerServerEvent('qb-fuel:station:server:updatereserves', "remove", finalrefuelamount, ReserveLevels, CurrentLocation) + if CachedFuelPrice ~= nil then + if Config.FuelDebug then + print("We have a cached price: $" .. CachedFuelPrice .. ", we will credit this to the gas station.") + end + TriggerServerEvent('qb-fuel:station:server:updatebalance', "add", finalrefuelamount, StationBalance, CurrentLocation, CachedFuelPrice) + CachedFuelPrice = nil + else + TriggerServerEvent('qb-fuel:station:server:updatebalance', "add", finalrefuelamount, StationBalance, CurrentLocation, FuelPrice) + end + end + end + local curfuel = GetFuel(vehicle) + local finalfuel = (curfuel + Refuelamount) + if finalfuel >= 98 and finalfuel < 100 then + SetFuel(vehicle, 100) + else + SetFuel(vehicle, finalfuel) + end + if Config.RenewedPhonePayment then + RefuelCancelled = true + RefuelPossibleAmount = 0 + RefuelPossible = false + RefuelCancelledFuelCost = 0 + end + Cancelledrefuel = false + end + end + end) + TriggerServerEvent("InteractSound_SV:PlayOnSource", "refuel", 0.3) + if Config.Ox.Progress then + if lib.progressCircle({ + duration = time, + label = Lang:t("prog_refueling_vehicle"), + position = 'bottom', + useWhileDead = false, + canCancel = true, + disable = { + move = true, + combat = true + }, + }) then + refueling = false + if purchasetype == "cash" then + TriggerServerEvent('qb-fuel:server:PayForFuel', refillCost, purchasetype, FuelPrice, false, CachedFuelPrice) + elseif purchasetype == "bank" then + if not Config.RenewedPhonePayment or purchasetype == "cash" then + TriggerServerEvent('qb-fuel:server:PayForFuel', refillCost, purchasetype, FuelPrice, false, CachedFuelPrice) + end + end + local curfuel = GetFuel(vehicle) + local finalfuel = (curfuel + fuelamount) + if finalfuel > 99 and finalfuel < 100 then + SetFuel(vehicle, 100) + else + SetFuel(vehicle, finalfuel) + end + if RefuelingType == nil then + if Config.PlayerOwnedGasStationsEnabled and not Config.UnlimitedFuel then + TriggerServerEvent('qb-fuel:station:server:updatereserves', "remove", fuelamount, ReserveLevels, CurrentLocation) + if CachedFuelPrice ~= nil then + if Config.FuelDebug then + print("We have a cached price: $" .. CachedFuelPrice .. ", we will credit this to the gas station.") + end + TriggerServerEvent('qb-fuel:station:server:updatebalance', "add", fuelamount, StationBalance, CurrentLocation, CachedFuelPrice) + CachedFuelPrice = nil + else + TriggerServerEvent('qb-fuel:station:server:updatebalance', "add", fuelamount, StationBalance, CurrentLocation, FuelPrice) + end + else + if Config.FuelDebug then print("Config.PlayerOwnedGasStationsEnabled == false or Config.UnlimitedFuel == true, this means reserves will not be changed.") end + end + if Config.FuelDebug then print("Config.PlayerOwnedGasStationsEnabled == false or Config.UnlimitedFuel == true, this means reserves will not be changed.") end + end + StopAnimTask(ped, Config.RefuelAnimationDictionary, Config.RefuelAnimation, 3.0, 3.0, -1, 2, 0, 0, 0, 0) + TriggerServerEvent("InteractSound_SV:PlayOnSource", "fuelstop", 0.4) + if Config.RenewedPhonePayment then + RefuelPossible = false + RefuelPossibleAmount = 0 + RefuelPurchaseType = "bank" + end + else + refueling = false + Cancelledrefuel = true + StopAnimTask(ped, Config.RefuelAnimationDictionary, Config.RefuelAnimation, 3.0, 3.0, -1, 2, 0, 0, 0, 0) + TriggerServerEvent("InteractSound_SV:PlayOnSource", "fuelstop", 0.4) + end + else + QBCore.Functions.Progressbar("refuel-car", Lang:t("prog_refueling_vehicle"), time, false, true, { + disableMovement = true, + disableCarMovement = true, + disableMouse = false, + disableCombat = true, + }, {}, {}, {}, function() + refueling = false + if not Config.RenewedPhonePayment or purchasetype == "cash" then + TriggerServerEvent('qb-fuel:server:PayForFuel', refillCost, purchasetype, FuelPrice) + end + local curfuel = GetFuel(vehicle) + local finalfuel = (curfuel + fuelamount) + if finalfuel > 99 and finalfuel < 100 then + SetFuel(vehicle, 100) + else + SetFuel(vehicle, finalfuel) + end + if RefuelingType == nil then + if Config.PlayerOwnedGasStationsEnabled and not Config.UnlimitedFuel then + TriggerServerEvent('qb-fuel:station:server:updatereserves', "remove", fuelamount, ReserveLevels, CurrentLocation) + if CachedFuelPrice ~= nil then + if Config.FuelDebug then + print("We have a cached price: $" .. CachedFuelPrice .. ", we will credit this to the gas station.") + end + TriggerServerEvent('qb-fuel:station:server:updatebalance', "add", fuelamount, StationBalance, CurrentLocation, CachedFuelPrice) + CachedFuelPrice = nil + else + TriggerServerEvent('qb-fuel:station:server:updatebalance', "add", fuelamount, StationBalance, CurrentLocation, FuelPrice) + end + else + if Config.FuelDebug then print("Config.PlayerOwnedGasStationsEnabled == false or Config.UnlimitedFuel == true, this means reserves will not be changed.") end + end + end + StopAnimTask(ped, Config.RefuelAnimationDictionary, Config.RefuelAnimation, 3.0, 3.0, -1, 2, 0, 0, 0, 0) + TriggerServerEvent("InteractSound_SV:PlayOnSource", "fuelstop", 0.4) + if Config.RenewedPhonePayment then + RefuelPossible = false + RefuelPossibleAmount = 0 + RefuelPurchaseType = "bank" + end + end, function() + refueling = false + Cancelledrefuel = true + StopAnimTask(ped, Config.RefuelAnimationDictionary, Config.RefuelAnimation, 3.0, 3.0, -1, 2, 0, 0, 0, 0) + TriggerServerEvent("InteractSound_SV:PlayOnSource", "fuelstop", 0.4) + end, "fas fa-gas-pump") + end + end + else + return + end +end) + +-- Jerry Can -- +RegisterNetEvent('qb-fuel:jerrycan:refuelmenu', function(itemData) + if IsPedInAnyVehicle(PlayerPedId(), false) then QBCore.Functions.Notify(Lang:t("cannot_refuel_inside"), 'error') return end + if Config.FuelDebug then print("Item Data: " .. json.encode(itemData)) end + local vehicle = GetClosestVehicle() + local vehiclecoords = GetEntityCoords(vehicle) + local pedcoords = GetEntityCoords(PlayerPedId()) + if GetVehicleBodyHealth(vehicle) < 100 then QBCore.Functions.Notify(Lang:t("vehicle_is_damaged"), 'error') return end + local jerrycanamount + if Config.Ox.Inventory then + jerrycanamount = tonumber(itemData.metadata.cdn_fuel) + else + jerrycanamount = itemData.info.gasamount + end + if Config.Ox.Menu then + if holdingnozzle then + local fulltank + if jerrycanamount == Config.JerryCanCap then fulltank = true + GasString = Lang:t("menu_jerry_can_footer_full_gas") + else fulltank = false + GasString = Lang:t("menu_jerry_can_footer_refuel_gas") + end + + lib.registerContext({ + id = 'cdnrefuelmenu', + title = Lang:t("menu_header_jerry_can"), + options = { + { + title = Lang:t("menu_header_refuel_jerry_can"), + event = 'qb-fuel:jerrycan:refueljerrycan', + args = {itemData = itemData}, + disabled = fulltank + }, + }, + }) + lib.showContext('cdnrefuelmenu') + else + if #(vehiclecoords - pedcoords) > 2.5 then return end + local nogas + if jerrycanamount < 1 then nogas = true + GasString = Lang:t("menu_jerry_can_footer_no_gas") + else nogas = false + GasString = Lang:t("menu_jerry_can_footer_use_gas") + end + + lib.registerContext({ + id = 'cdnrefuelmenu2', + title = Lang:t("menu_header_jerry_can"), + options = { + { + title = Lang:t("menu_header_refuel_vehicle"), + event = 'qb-fuel:jerrycan:refuelvehicle', + args = {itemData = itemData}, + disabled = nogas, + }, + }, + }) + lib.showContext('cdnrefuelmenu2') + end + else + if holdingnozzle then + local fulltank + if jerrycanamount == Config.JerryCanCap then + fulltank = true + GasString = Lang:t("menu_jerry_can_footer_full_gas") + else + fulltank = false + GasString = Lang:t("menu_jerry_can_footer_refuel_gas") + end + exports['qb-menu']:openMenu({ + { + header = Lang:t("menu_header_jerry_can"), + isMenuHeader = true, + }, + { + header = Lang:t("menu_header_refuel_jerry_can"), + txt = GasString, + icon = "fas fa-gas-pump", + params = { + event = "qb-fuel:jerrycan:refueljerrycan", + args = { + itemData = itemData, + }, + }, + disabled = fulltank, + }, + { + header = Lang:t("menu_header_close"), + txt = Lang:t("menu_jerry_can_close"), + icon = "fas fa-times-circle", + params = { + event = "qb-menu:closeMenu", + } + }, + }) + else + if #(vehiclecoords - pedcoords) > 2.5 then return end + local nogas + if jerrycanamount < 1 then nogas = true + GasString = Lang:t("menu_jerry_can_footer_no_gas") + else nogas = false + GasString = Lang:t("menu_jerry_can_footer_use_gas") + end + exports['qb-menu']:openMenu({ + { + header = Lang:t("menu_header_jerry_can"), + isMenuHeader = true, + }, + { + header = Lang:t("menu_header_refuel_vehicle"), + txt = GasString, + icon = "fas fa-gas-pump", + params = { + event = "qb-fuel:jerrycan:refuelvehicle", + args = { + itemData = itemData, + }, + }, + disabled = nogas, + }, + { + header = Lang:t("menu_header_close"), + txt = Lang:t("menu_jerry_can_close"), + icon = "fas fa-times-circle", + params = { + event = "qb-menu:closeMenu", + } + }, + }) + end + end +end) + +RegisterNetEvent('qb-fuel:client:jerrycanfinalmenu', function(purchasetype) + Moneyamount = nil + if purchasetype == 'bank' then + Moneyamount = QBCore.Functions.GetPlayerData().money['bank'] + elseif purchasetype == 'cash' then + Moneyamount = QBCore.Functions.GetPlayerData().money['cash'] + end + if Moneyamount > math.ceil(Config.JerryCanPrice + GlobalTax(Config.JerryCanPrice)) then + TriggerServerEvent('qb-fuel:server:purchase:jerrycan', purchasetype) + else + if purchasetype == 'bank' then QBCore.Functions.Notify(Lang:t("not_enough_money_in_bank"), 'error') end + if purchasetype == "cash" then QBCore.Functions.Notify(Lang:t("not_enough_money_in_cash"), 'error') end + end +end) + +RegisterNetEvent('qb-fuel:client:purchasejerrycan', function() + local playercashamount = QBCore.Functions.GetPlayerData().money['cash'] + if Config.Ox.Menu then + lib.registerContext({ + id = 'purchasejerrycan', + title = Lang:t("menu_jerry_can_purchase_header") .. (math.ceil(Config.JerryCanPrice + GlobalTax(Config.JerryCanPrice)))..",-", + options = { + { + title = Lang:t("menu_header_cash"), + description = Lang:t("menu_pay_with_cash") .. playercashamount..",-", + icon = "fas fa-usd", + event = 'qb-fuel:client:jerrycanfinalmenu', + args = 'cash', + }, + { + title = Lang:t("menu_header_bank"), + description = Lang:t("menu_pay_with_bank"), + icon = "fas fa-credit-card", + event = 'qb-fuel:client:jerrycanfinalmenu', + args = 'bank', + }, + { + title = Lang:t("menu_header_close"), + description = Lang:t("menu_jerry_can_close"), + icon = "fas fa-times-circle", + onSelect = function() + lib.hideContext() + end, + }, + }, + }) + lib.showContext('purchasejerrycan') + else + exports['qb-menu']:openMenu({ + { + header = Lang:t("menu_jerry_can_purchase_header") .. (math.ceil(Config.JerryCanPrice + GlobalTax(Config.JerryCanPrice))), + isMenuHeader = true, + icon = "fas fa-fire-flame-simple", + }, + { + header = Lang:t("menu_header_cash"), + txt = Lang:t("menu_pay_with_cash") .. playercashamount..",-", + icon = "fas fa-usd", + params = { + event = "qb-fuel:client:jerrycanfinalmenu", + args = 'cash', + } + }, + { + header = Lang:t("menu_header_bank"), + txt = Lang:t("menu_pay_with_bank"), + icon = "fas fa-credit-card", + params = { + event = "qb-fuel:client:jerrycanfinalmenu", + args = 'bank', + } + }, + { + header = Lang:t("menu_header_close"), + txt = Lang:t("menu_jerry_can_footer_close"), + icon = "fas fa-times-circle", + params = { + event = "qb-menu:closeMenu", + } + }, + }) + end +end) + +RegisterNetEvent('qb-fuel:jerrycan:refuelvehicle', function(data) + local ped = PlayerPedId() + local vehicle = GetClosestVehicle() + local vehfuel = math.floor(GetFuel(vehicle)) + local maxvehrefuel = (100 - math.ceil(vehfuel)) + local itemData = data.itemData + local jerrycanfuelamount + if Config.Ox.Inventory then + jerrycanfuelamount = tonumber(itemData.metadata.cdn_fuel) + else + jerrycanfuelamount = itemData.info.gasamount + end + local vehicle = GetClosestVehicle() + local NotElectric = false + if Config.ElectricVehicleCharging then + local isElectric = GetCurrentVehicleType(vehicle) + if isElectric == 'electricvehicle' then + QBCore.Functions.Notify(Lang:t("need_electric_charger"), 'error', 7500) return + end + NotElectric = true + else + NotElectric = true + end + Wait(50) + if NotElectric then + if maxvehrefuel < Config.JerryCanCap then + maxvehrefuel = maxvehrefuel + else + maxvehrefuel = Config.JerryCanCap + end + if maxvehrefuel >= jerrycanfuelamount then maxvehrefuel = jerrycanfuelamount elseif maxvehrefuel < jerrycanfuelamount then maxvehrefuel = maxvehrefuel end + -- Need to Convert to OX -- + if Config.Ox.Input then + local refuel = lib.inputDialog(Lang:t("input_select_refuel_header"), {Lang:t("input_max_fuel_footer_1") .. maxvehrefuel .. Lang:t("input_max_fuel_footer_2")}) + if not refuel then return end + local refuelAmount = tonumber(refuel[1]) + -- + if refuel and refuelAmount then + if tonumber(refuelAmount) == 0 then QBCore.Functions.Notify(Lang:t("more_than_zero"), 'error') return elseif tonumber(refuelAmount) < 0 then QBCore.Functions.Notify(Lang:t("more_than_zero"), 'error') return end + if tonumber(refuelAmount) > jerrycanfuelamount then QBCore.Functions.Notify(Lang:t("jerry_can_not_enough_fuel"), 'error') return end + local refueltimer = Config.RefuelTime * tonumber(refuelAmount) + if tonumber(refuelAmount) < 10 then refueltimer = Config.RefuelTime * 10 end + if vehfuel + tonumber(refuelAmount) > 100 then QBCore.Functions.Notify(Lang:t("tank_cannot_fit"), 'error') return end + local refuelAmount = tonumber(refuelAmount) + JerrycanProp = CreateObject(joaat('w_am_jerrycan'), 1.0, 1.0, 1.0, true, true, false) + local lefthand = GetPedBoneIndex(ped, 18905) + AttachEntityToEntity(JerrycanProp, ped, lefthand, 0.11 --[[Left - Right (Kind of)]], 0.0 --[[Up - Down]], 0.25 --[[Forward - Backward]], 15.0, 170.0, 90.42, 0, 1, 0, 1, 0, 1) + if Config.Ox.Progress then + if lib.progressCircle({ + duration = refueltimer, + label = Lang:t("prog_refueling_vehicle"), + position = 'bottom', + useWhileDead = false, + canCancel = true, + disable = { + car = true, + move = true, + combat = true + }, + anim = { + dict = Config.JerryCanAnimDict, + clip = Config.JerryCanAnim + }, + }) then + DeleteObject(JerrycanProp) + StopAnimTask(ped, Config.JerryCanAnimDict, Config.JerryCanAnim, 1.0) + QBCore.Functions.Notify(Lang:t("jerry_can_success_vehicle"), 'success') + local JerryCanItemData = data.itemData + local srcPlayerData = QBCore.Functions.GetPlayerData() + TriggerServerEvent('qb-fuel:info', "remove", tonumber(refuelAmount), srcPlayerData, JerryCanItemData) + SetFuel(vehicle, (vehfuel + refuelAmount)) + else + DeleteObject(JerrycanProp) + StopAnimTask(ped, Config.JerryCanAnimDict, Config.JerryCanAnim, 1.0) + QBCore.Functions.Notify(Lang:t("cancelled"), 'error') + end + else + QBCore.Functions.Progressbar('refuel_gas', Lang:t("prog_refueling_vehicle"), refueltimer, false, true, {-- Name | Label | Time | useWhileDead | canCancel + disableMovement = true, + disableCarMovement = true, + disableMouse = false, + disableCombat = true, + }, { + animDict = Config.JerryCanAnimDict, + anim = Config.JerryCanAnim, + flags = 17, + }, {}, {}, function()-- Play When Done + DeleteObject(JerrycanProp) + StopAnimTask(ped, Config.JerryCanAnimDict, Config.JerryCanAnim, 1.0) + QBCore.Functions.Notify(Lang:t("jerry_can_success_vehicle"), 'success') + local JerryCanItemData = data.itemData + local srcPlayerData = QBCore.Functions.GetPlayerData() + TriggerServerEvent('qb-fuel:info', "remove", tonumber(refuelAmount), srcPlayerData, JerryCanItemData) + SetFuel(vehicle, (vehfuel + refuelAmount)) + end, function()-- Play When Cancel + DeleteObject(JerrycanProp) + StopAnimTask(ped, Config.JerryCanAnimDict, Config.JerryCanAnim, 1.0) + QBCore.Functions.Notify(Lang:t("cancelled"), 'error') + end, "jerrycan") + end + end + else + local refuel = exports['qb-input']:ShowInput({ + header = Lang:t("input_select_refuel_header"), + submitText = Lang:t("input_refuel_submit"), + inputs = { + { + type = 'number', + isRequired = true, + name = 'amount', + text = Lang:t("input_max_fuel_footer_1") .. maxvehrefuel .. Lang:t("input_max_fuel_footer_2") + } + } + }) + if refuel then + if tonumber(refuel.amount) == 0 then QBCore.Functions.Notify(Lang:t("more_than_zero"), 'error') return elseif tonumber(refuel.amount) < 0 then QBCore.Functions.Notify(Lang:t("more_than_zero"), 'error') return end + if tonumber(refuel.amount) > jerrycanfuelamount then QBCore.Functions.Notify(Lang:t("jerry_can_not_enough_fuel"), 'error') return end + local refueltimer = Config.RefuelTime * tonumber(refuel.amount) + if tonumber(refuel.amount) < 10 then refueltimer = Config.RefuelTime * 10 end + if vehfuel + tonumber(refuel.amount) > 100 then QBCore.Functions.Notify(Lang:t("tank_cannot_fit"), 'error') return end + JerrycanProp = CreateObject(joaat('w_am_jerrycan'), 1.0, 1.0, 1.0, true, true, false) + local lefthand = GetPedBoneIndex(ped, 18905) + AttachEntityToEntity(JerrycanProp, ped, lefthand, 0.11 --[[Left - Right (Kind of)]], 0.0 --[[Up - Down]], 0.25 --[[Forward - Backward]], 15.0, 170.0, 90.42, 0, 1, 0, 1, 0, 1) + if Config.Ox.Progress then + if lib.progressCircle({ + duration = refueltimer, + label = Lang:t("prog_refueling_vehicle"), + position = 'bottom', + useWhileDead = false, + canCancel = true, + disable = { + car = true, + move = true, + combat = true + }, + anim = { + dict = Config.JerryCanAnimDict, + clip = Config.JerryCanAnim + }, + }) then + DeleteObject(JerrycanProp) + StopAnimTask(ped, Config.JerryCanAnimDict, Config.JerryCanAnim, 1.0) + QBCore.Functions.Notify(Lang:t("jerry_can_success_vehicle"), 'success') + local JerryCanItemData = data.itemData + local srcPlayerData = QBCore.Functions.GetPlayerData() + TriggerServerEvent('qb-fuel:info', "remove", tonumber(refuel.amount), srcPlayerData, JerryCanItemData) + SetFuel(vehicle, (vehfuel + refuel.amount)) + else + DeleteObject(JerrycanProp) + StopAnimTask(ped, Config.JerryCanAnimDict, Config.JerryCanAnim, 1.0) + QBCore.Functions.Notify(Lang:t("cancelled"), 'error') + end + else + QBCore.Functions.Progressbar('refuel_gas', Lang:t("prog_refueling_vehicle"), refueltimer, false, true, {-- Name | Label | Time | useWhileDead | canCancel + disableMovement = true, + disableCarMovement = true, + disableMouse = false, + disableCombat = true, + }, { + animDict = Config.JerryCanAnimDict, + anim = Config.JerryCanAnim, + flags = 17, + }, {}, {}, function()-- Play When Done + DeleteObject(JerrycanProp) + StopAnimTask(ped, Config.JerryCanAnimDict, Config.JerryCanAnim, 1.0) + QBCore.Functions.Notify(Lang:t("jerry_can_success_vehicle"), 'success') + local JerryCanItemData = data.itemData + local srcPlayerData = QBCore.Functions.GetPlayerData() + TriggerServerEvent('qb-fuel:info', "remove", tonumber(refuel.amount), srcPlayerData, JerryCanItemData) + SetFuel(vehicle, (vehfuel + refuel.amount)) + end, function()-- Play When Cancel + DeleteObject(JerrycanProp) + StopAnimTask(ped, Config.JerryCanAnimDict, Config.JerryCanAnim, 1.0) + QBCore.Functions.Notify(Lang:t("cancelled"), 'error') + end, "jerrycan") + end + end + end + + else + QBCore.Functions.Notify(Lang:t("need_electric_charger"), 'error', 7500) return + end +end) + +RegisterNetEvent('qb-fuel:jerrycan:refueljerrycan', function(data) + FetchStationInfo('all') + Wait(100) + if Config.PlayerOwnedGasStationsEnabled then + FuelPrice = (1 * StationFuelPrice) + else + FuelPrice = (1 * Config.CostMultiplier) + end + local itemData = data.itemData + local jerrycanfuelamount + if Config.Ox.Inventory then + jerrycanfuelamount = tonumber(itemData.metadata.cdn_fuel) + else + jerrycanfuelamount = itemData.info.gasamount + end + + local ped = PlayerPedId() + + if Config.Ox.Input then + local JerryCanMaxRefuel = (Config.JerryCanCap - jerrycanfuelamount) + local refuel = lib.inputDialog(Lang:t("input_select_refuel_header"), {Lang:t("input_max_fuel_footer_1") .. JerryCanMaxRefuel .. Lang:t("input_max_fuel_footer_2")}) + if not refuel then return end + local refuelAmount = tonumber(refuel[1]) + if refuel then + if tonumber(refuelAmount) == 0 then QBCore.Functions.Notify(Lang:t("more_than_zero"), 'error') return elseif tonumber(refuelAmount) < 0 then QBCore.Functions.Notify(Lang:t("more_than_zero"), 'error') return end + if tonumber(refuelAmount) + tonumber(jerrycanfuelamount) > Config.JerryCanCap then QBCore.Functions.Notify(Lang:t("jerry_can_not_fit_fuel"), 'error') return end + if tonumber(refuelAmount) > Config.JerryCanCap then QBCore.Functions.Notify(Lang:t("jerry_can_not_fit_fuel"), 'error') return end + local refueltimer = Config.RefuelTime * tonumber(refuelAmount) + if tonumber(refuelAmount) < 10 then refueltimer = Config.RefuelTime * 10 end + local price = (tonumber(refuelAmount) * FuelPrice) + GlobalTax(tonumber(refuelAmount) * FuelPrice) + if not CanAfford(price, "cash") then QBCore.Functions.Notify(Lang:t("not_enough_money_in_cash"), 'error') return end + + JerrycanProp = CreateObject(joaat('w_am_jerrycan'), 1.0, 1.0, 1.0, true, true, false) + local lefthand = GetPedBoneIndex(ped, 18905) + AttachEntityToEntity(JerrycanProp, ped, lefthand, 0.11 --[[Left - Right]], 0.05 --[[Up - Down]], 0.27 --[[Forward - Backward]], -15.0, 170.0, -90.42, 0, 1, 0, 1, 0, 1) + SetEntityVisible(fuelnozzle, false, 0) + if lib.progressCircle({ + duration = refueltimer, + label = Lang:t("prog_jerry_can_refuel"), + position = 'bottom', + useWhileDead = false, + canCancel = true, + disable = { + car = true, + move = true, + combat = true + }, + anim = { + dict = Config.JerryCanAnimDict, + clip = Config.JerryCanAnim + }, + }) then + SetEntityVisible(fuelnozzle, true, 0) + DeleteObject(JerrycanProp) + StopAnimTask(ped, Config.JerryCanAnimDict, Config.JerryCanAnim, 1.0) + QBCore.Functions.Notify(Lang:t("jerry_can_success"), 'success') + local srcPlayerData = QBCore.Functions.GetPlayerData() + if Config.Ox.Inventory then + TriggerServerEvent('qb-fuel:info', "add", tonumber(refuelAmount), srcPlayerData, 'jerrycan') + else + TriggerServerEvent('qb-fuel:info', "add", tonumber(refuelAmount), srcPlayerData, itemData) + end + + if Config.PlayerOwnedGasStationsEnabled and not Config.UnlimitedFuel then + TriggerServerEvent('qb-fuel:station:server:updatereserves', "remove", tonumber(refuelAmount), ReserveLevels, CurrentLocation) + if CachedFuelPrice ~= nil then + TriggerServerEvent('qb-fuel:station:server:updatebalance', "add", tonumber(refuelAmount), StationBalance, CurrentLocation, CachedFuelPrice) + else + TriggerServerEvent('qb-fuel:station:server:updatebalance', "add", tonumber(refuelAmount), StationBalance, CurrentLocation, FuelPrice) + end + else + if Config.FuelDebug then print("Config.PlayerOwnedGasStationsEnabled == false or Config.UnlimitedFuel == true, this means reserves will not be changed.") end + end + local total = (tonumber(refuelAmount) * FuelPrice) + GlobalTax(tonumber(refuelAmount) * FuelPrice) + TriggerServerEvent('qb-fuel:server:PayForFuel', total, "cash", FuelPrice) + else + SetEntityVisible(fuelnozzle, true, 0) + DeleteObject(JerrycanProp) + StopAnimTask(ped, Config.JerryCanAnimDict, Config.JerryCanAnim, 1.0) + QBCore.Functions.Notify(Lang:t("cancelled"), 'error') + end + end + else + local JerryCanMaxRefuel = (Config.JerryCanCap - jerrycanfuelamount) + local refuel = exports['qb-input']:ShowInput({ + header = Lang:t("input_select_refuel_header"), + submitText = Lang:t("input_refuel_jerrycan_submit"), + inputs = {{ + type = 'number', + isRequired = true, + name = 'amount', + text = Lang:t("input_max_fuel_footer_1") .. JerryCanMaxRefuel .. Lang:t("input_max_fuel_footer_2") + }} + }) + if refuel then + if tonumber(refuel.amount) == 0 then QBCore.Functions.Notify(Lang:t("more_than_zero"), 'error') return elseif tonumber(refuel.amount) < 0 then QBCore.Functions.Notify(Lang:t("more_than_zero"), 'error') return end + if tonumber(refuel.amount) + tonumber(jerrycanfuelamount) > Config.JerryCanCap then QBCore.Functions.Notify(Lang:t("jerry_can_not_fit_fuel"), 'error') return end + if tonumber(refuel.amount) > Config.JerryCanCap then QBCore.Functions.Notify(Lang:t("jerry_can_not_fit_fuel"), 'error') return end + local refueltimer = Config.RefuelTime * tonumber(refuel.amount) + if tonumber(refuel.amount) < 10 then refueltimer = Config.RefuelTime * 10 end + local price = (tonumber(refuel.amount) * FuelPrice) + GlobalTax(tonumber(refuel.amount) * FuelPrice) + if not CanAfford(price, "cash") then QBCore.Functions.Notify(Lang:t("not_enough_money_in_cash"), 'error') return end + JerrycanProp = CreateObject(joaat('w_am_jerrycan'), 1.0, 1.0, 1.0, true, true, false) + local lefthand = GetPedBoneIndex(ped, 18905) + AttachEntityToEntity(JerrycanProp, ped, lefthand, 0.11 --[[Left - Right]], 0.05 --[[Up - Down]], 0.27 --[[Forward - Backward]], -15.0, 170.0, -90.42, 0, 1, 0, 1, 0, 1) + SetEntityVisible(fuelnozzle, false, 0) + QBCore.Functions.Progressbar('refuel_gas', Lang:t("prog_jerry_can_refuel"), refueltimer, false, true, {-- Name | Label | Time | useWhileDead | canCancel + disableMovement = true, + disableCarMovement = true, + disableMouse = false, + disableCombat = true, + }, { + animDict = Config.JerryCanAnimDict, + anim = Config.JerryCanAnim, + flags = 17, + }, {}, {}, function()-- Play When Done + SetEntityVisible(fuelnozzle, true, 0) + DeleteObject(JerrycanProp) + StopAnimTask(ped, Config.JerryCanAnimDict, Config.JerryCanAnim, 1.0) + QBCore.Functions.Notify(Lang:t("jerry_can_success"), 'success') + local jerryCanData = data.itemData + local srcPlayerData = QBCore.Functions.GetPlayerData() + if Config.Ox.Inventory then + TriggerServerEvent('qb-fuel:info', "add", tonumber(refuelAmount), srcPlayerData, 'jerrycan') + else + TriggerServerEvent('qb-fuel:info', "add", tonumber(refuelAmount), srcPlayerData, jerryCanData) + end + if RefuelingType == nil then + if Config.PlayerOwnedGasStationsEnabled and not Config.UnlimitedFuel then + TriggerServerEvent('qb-fuel:station:server:updatereserves', "remove", tonumber(refuel.amount), ReserveLevels, CurrentLocation) + if CachedFuelPrice ~= nil then + TriggerServerEvent('qb-fuel:station:server:updatebalance', "add", tonumber(refuel.amount), StationBalance, CurrentLocation, CachedFuelPrice) + else + TriggerServerEvent('qb-fuel:station:server:updatebalance', "add", tonumber(refuel.amount), StationBalance, CurrentLocation, FuelPrice) + end + + else + if Config.FuelDebug then print("Config.PlayerOwnedGasStationsEnabled == false or Config.UnlimitedFuel == true, this means reserves will not be changed.") end + end + end + local total = (tonumber(refuel.amount) * FuelPrice) + GlobalTax(tonumber(refuel.amount) * FuelPrice) + TriggerServerEvent('qb-fuel:server:PayForFuel', total, "cash", FuelPrice, false, CachedFuelPrice) + end, function()-- Play When Cancel + SetEntityVisible(fuelnozzle, true, 0) + DeleteObject(JerrycanProp) + StopAnimTask(ped, Config.JerryCanAnimDict, Config.JerryCanAnim, 1.0) + QBCore.Functions.Notify(Lang:t("cancelled"), 'error') + end, "jerrycan") + end + end +end) + +--- Syphoning --- +local function PoliceAlert(coords) + local chance = math.random(1, 100) + if chance < Config.SyphonPoliceCallChance then + if Config.SyphonDispatchSystem == "ps-dispatch" then + exports['ps-dispatch']:SuspiciousActivity() + elseif Config.SyphonDispatchSystem == "qb-dispatch" then + TriggerServerEvent('qb-dispatch:911call', coords) + elseif Config.SyphonDispatchSystem == "qb-default" then + TriggerServerEvent('cdn-syphoning:callcops', coords) + elseif Config.SyphonDispatchSystem == "custom" then + -- Put your own dispatch system here + else + if Config.SyphonDebug then print("There was an attempt to call police but this dispatch system is not supported!") end + end + end +end + +-- Events -- +RegisterNetEvent('cdn-syphoning:syphon:menu', function(itemData) + if IsPedInAnyVehicle(PlayerPedId(), false) then QBCore.Functions.Notify(Lang:t("syphon_inside_vehicle"), 'error') return end + if Config.SyphonDebug then print("Item Data: " .. json.encode(itemData)) end + local vehicle = GetClosestVehicle() + local vehiclename = GetEntityModel(vehicle) + local vehiclecoords = GetEntityCoords(vehicle) + local pedcoords = GetEntityCoords(PlayerPedId()) + if Config.ElectricVehicleCharging then + NotElectric = true + for i = 1, #Config.ElectricVehicles do + local current = joaat(Config.ElectricVehicles[i]) + if Config.SyphonDebug then print("^5Current Search: ^2" .. current .. " ^5Player's Vehicle: ^2" .. vehiclename) end + if current == vehiclename then + NotElectric = false + if Config.SyphonDebug then print("^2" .. current .. "^5 has been found. It ^2matches ^5the Player's Vehicle: ^2" .. vehiclename .. ". ^5This means syphoning will not be allowed.") end + QBCore.Functions.Notify(Lang:t("syphon_electric_vehicle"), 'error', 7500) return + end + end + else + NotElectric = true + end + if NotElectric then + if #(vehiclecoords - pedcoords) > 2.5 then return end + if GetVehicleBodyHealth(vehicle) < 100 then QBCore.Functions.Notify(Lang:t("vehicle_is_damaged"), 'error') return end + local nogas + local syphonfull + + if Config.Ox.Inventory then + if tonumber(itemData.metadata.cdn_fuel) < 1 then nogas = true Nogasstring = Lang:t("menu_syphon_empty") else nogas = false Nogasstring = Lang:t("menu_syphon_refuel") end + if tonumber(itemData.metadata.cdn_fuel) == Config.SyphonKitCap then syphonfull = true Stealfuelstring = Lang:t("menu_syphon_kit_full") elseif GetFuel(vehicle) < 1 then syphonfull = true Stealfuelstring = Lang:t("menu_syphon_vehicle_empty") else syphonfull = false Stealfuelstring = Lang:t("menu_syphon_allowed") end -- Disable Options based on item data + else + if itemData.info.gasamount < 1 then nogas = true Nogasstring = Lang:t("menu_syphon_empty") else nogas = false Nogasstring = Lang:t("menu_syphon_refuel") end + if itemData.info.gasamount == Config.SyphonKitCap then syphonfull = true Stealfuelstring = Lang:t("menu_syphon_kit_full") elseif GetFuel(vehicle) < 1 then syphonfull = true Stealfuelstring = Lang:t("menu_syphon_vehicle_empty") else syphonfull = false Stealfuelstring = Lang:t("menu_syphon_allowed") end -- Disable Options based on item data + end + if Config.Ox.Menu then + lib.registerContext({ + id = 'syphoningmenu', + title = 'Tappe-kit', + options = { + { + title = Lang:t("menu_syphon_header"), + description = Stealfuelstring, + icon = "fas fa-fire-flame-simple", + arrow = false, -- puts arrow to the right + event = 'cdn-syphoning:syphon', + args = { + itemData = itemData, + reason = "syphon", + }, + disabled = syphonfull, + }, + { + title = Lang:t("menu_syphon_refuel_header"), + description = Nogasstring, + icon = "fas fa-gas-pump", + arrow = false, -- puts arrow to the right + event = 'cdn-syphoning:syphon', + args = { + itemData = itemData, + reason = "refuel", + }, + disabled = nogas, + }, + { + title = Lang:t("menu_header_close"), + description = Lang:t("menu_refuel_cancel"), + icon = "fas fa-times-circle", + arrow = false, -- puts arrow to the right + onSelect = function() + lib.hideContext() + end, + }, + }, + }) + lib.showContext('syphoningmenu') + else + exports['qb-menu']:openMenu({ + { + header = "Tappe-kit", + isMenuHeader = true, + }, + { + header = Lang:t("menu_syphon_header"), + txt = Stealfuelstring, + params = { + event = "cdn-syphoning:syphon", + args = { + itemData = itemData, + reason = "syphon", + }, + }, + icon = "fas fa-fire-flame-simple", + disabled = syphonfull, + }, + { + header = Lang:t("menu_syphon_refuel_header"), + txt = Nogasstring, + icon = "fas fa-gas-pump", + params = { + event = "cdn-syphoning:syphon", + args = { + itemData = itemData, + reason = "refuel", + }, + }, + disabled = nogas, + }, + { + header = Lang:t("menu_header_close"), + txt = Lang:t("menu_syphon_cancel"), + icon = "fas fa-times-circle", + params = { + event = "qb-menu:closeMenu", + } + }, + }) + end + end +end) + +RegisterNetEvent('cdn-syphoning:syphon', function(data) + local reason = data.reason + local ped = PlayerPedId() + if Config.SyphonDebug then print('Item Data Syphon: ' .. json.encode(data.itemData)) end + if Config.SyphonDebug then print('Reason: ' .. reason) end + local vehicle = GetClosestVehicle() + local NotElectric = false + if Config.ElectricVehicleCharging then + local isElectric = GetCurrentVehicleType(vehicle) + if isElectric == 'electricvehicle' then + QBCore.Functions.Notify(Lang:t("need_electric_charger"), 'error', 7500) return + end + NotElectric = true + else + NotElectric = true + end + Wait(50) + if NotElectric then + local currentsyphonamount = nil + + if Config.Ox.Inventory then + currentsyphonamount = tonumber(data.itemData.metadata.cdn_fuel) + HasSyphon = exports.ox_inventory:Search('count', 'syphoningkit') + else + currentsyphonamount = data.itemData.info.gasamount + HasSyphon = QBCore.Functions.HasItem("syphoningkit", 1) + end + + if HasSyphon then + local fitamount = (Config.SyphonKitCap - currentsyphonamount) + local vehicle = GetClosestVehicle() + local vehiclecoords = GetEntityCoords(vehicle) + local pedcoords = GetEntityCoords(ped) + if #(vehiclecoords - pedcoords) > 2.5 then return end + local cargasamount = GetFuel(vehicle) + local maxsyphon = math.floor(GetFuel(vehicle)) + if Config.SyphonKitCap <= 100 then + if maxsyphon > Config.SyphonKitCap then + maxsyphon = Config.SyphonKitCap + end + end + if maxsyphon >= fitamount then + Stealstring = fitamount + else + Stealstring = maxsyphon + end + if reason == "syphon" then + if Config.Ox.Input then + syphon = lib.inputDialog('Begin Syphoning', {{type = "number", label = "You can steal " .. Stealstring .. "L from the car.", default = Stealstring}}) + if not syphon then return end + syphonAmount = tonumber(syphon[1]) + if syphon then + if not syphonAmount then return end + if tonumber(syphonAmount) < 0 then QBCore.Functions.Notify(Lang:t("syphon_more_than_zero"), 'error') return end + if tonumber(syphonAmount) == 0 then QBCore.Functions.Notify(Lang:t("syphon_more_than_zero"), 'error') return end + if tonumber(syphonAmount) > maxsyphon then QBCore.Functions.Notify(Lang:t("syphon_kit_cannot_fit_1") .. fitamount .. Lang:t("syphon_kit_cannot_fit_2"), 'error') return end + if currentsyphonamount + syphonAmount > Config.SyphonKitCap then QBCore.Functions.Notify(Lang:t("syphon_kit_cannot_fit_1") .. fitamount .. Lang:t("syphon_kit_cannot_fit_2"), 'error') return end + if (tonumber(syphonAmount) <= tonumber(cargasamount)) then + local removeamount = (tonumber(cargasamount) - tonumber(syphonAmount)) + local syphontimer = Config.RefuelTime * syphonAmount + if tonumber(syphonAmount) < 10 then syphontimer = Config.RefuelTime * 10 end + if lib.progressCircle({ + duration = syphontimer, + label = Lang:t("prog_syphoning"), + position = 'bottom', + useWhileDead = false, + canCancel = true, + disable = { + car = true, + move = true, + combat = true + }, + anim = { + dict = Config.StealAnimDict, + clip = Config.StealAnim + }, + }) then + StopAnimTask(ped, Config.StealAnimDict, Config.StealAnim, 1.0) + if GetFuel(vehicle) >= syphonAmount then + PoliceAlert(GetEntityCoords(ped)) + QBCore.Functions.Notify(Lang:t("syphon_success"), 'success') + SetFuel(vehicle, removeamount) + local syphonData = data.itemData + local srcPlayerData = QBCore.Functions.GetPlayerData() + TriggerServerEvent('qb-fuel:info', "add", tonumber(syphonAmount), srcPlayerData, syphonData) + else + QBCore.Functions.Notify(Lang:t("menu_syphon_vehicle_empty"), 'error') + end + else + PoliceAlert(GetEntityCoords(ped)) + StopAnimTask(ped, Config.StealAnimDict, Config.StealAnim, 1.0) + QBCore.Functions.Notify(Lang:t("cancelled"), 'error') + end + end + end + else + local syphon = exports['qb-input']:ShowInput({ + header = "Vælg hvor meget du vil stjæle.", + submitText = "Begyn tapning", + inputs = { + { + type = 'number', + isRequired = true, + name = 'amount', + text = 'Du kan stjæle ' .. Stealstring .. 'L fra køretøjet.' + } + } + }) + if syphon then + if not syphon.amount then return end + if tonumber(syphon.amount) < 0 then QBCore.Functions.Notify(Lang:t("syphon_more_than_zero"), 'error') return end + if tonumber(syphon.amount) == 0 then QBCore.Functions.Notify(Lang:t("syphon_more_than_zero"), 'error') return end + if tonumber(syphon.amount) > maxsyphon then QBCore.Functions.Notify(Lang:t("syphon_kit_cannot_fit_1") .. fitamount .. Lang:t("syphon_kit_cannot_fit_2"), 'error') return end + if currentsyphonamount + syphon.amount > Config.SyphonKitCap then QBCore.Functions.Notify(Lang:t("syphon_kit_cannot_fit_1") .. fitamount .. Lang:t("syphon_kit_cannot_fit_2"), 'error') return end + if (tonumber(syphon.amount) <= tonumber(cargasamount)) then + local removeamount = (tonumber(cargasamount) - tonumber(syphon.amount)) + local syphontimer = Config.RefuelTime * syphon.amount + if tonumber(syphon.amount) < 10 then syphontimer = Config.RefuelTime * 10 end + QBCore.Functions.Progressbar('syphon_gas', Lang:t("prog_syphoning"), syphontimer, false, true, {-- Name | Label | Time | useWhileDead | canCancel + disableMovement = true, + disableCarMovement = true, + disableMouse = false, + disableCombat = true, + }, { + animDict = Config.StealAnimDict, + anim = Config.StealAnim, + flags = 1, + }, {}, {}, function()-- Play When Done + if GetFuel(vehicle) >= tonumber(syphon.amount) then + PoliceAlert(GetEntityCoords(ped)) + QBCore.Functions.Notify(Lang:t("syphon_success"), 'success') + SetFuel(vehicle, removeamount) + local syphonData = data.itemData + local srcPlayerData = QBCore.Functions.GetPlayerData() + TriggerServerEvent('qb-fuel:info', "add", tonumber(syphon.amount), srcPlayerData, syphonData) + StopAnimTask(ped, Config.StealAnimDict, Config.StealAnim, 1.0) + else + QBCore.Functions.Notify(Lang:t("menu_syphon_vehicle_empty"), 'error') + end + end, function()-- Play When Cancel + PoliceAlert(GetEntityCoords(ped)) + StopAnimTask(ped, Config.StealAnimDict, Config.StealAnim, 1.0) + QBCore.Functions.Notify(Lang:t("cancelled"), 'error') + end, "syphoningkit") + end + end + end + elseif reason == "refuel" then + if 100 - math.ceil(cargasamount) < Config.SyphonKitCap then + Maxrefuel = 100 - math.ceil(cargasamount) + if Maxrefuel > currentsyphonamount then Maxrefuel = currentsyphonamount end + else + Maxrefuel = currentsyphonamount + end + if Config.Ox.Input then + refuel = lib.inputDialog(Lang:t("input_select_refuel_header"), {{type = "number", label = Lang:t("input_max_fuel_footer_1") .. Maxrefuel .. Lang:t("input_max_fuel_footer_2"), default = Maxrefuel}}) + + if not refuel then return end + refuelAmount = tonumber(refuel[1]) + if refuel then + if tonumber(refuelAmount) == 0 then QBCore.Functions.Notify(Lang:t("more_than_zero"), 'error') return elseif tonumber(refuelAmount) < 0 then QBCore.Functions.Notify(Lang:t("more_than_zero"), 'error') return elseif tonumber(refuelAmount) > 100 then QBCore.Functions.Notify("You can't refuel more than 100L!", 'error') return end + if tonumber(refuelAmount) > tonumber(currentsyphonamount) then QBCore.Functions.Notify(Lang:t("syphon_not_enough_gas"), 'error') return end + if tonumber(refuelAmount) + tonumber(cargasamount) > 100 then QBCore.Functions.Notify(Lang:t("tank_cannot_fit"), 'error') return end + local refueltimer = Config.RefuelTime * tonumber(refuelAmount) + if tonumber(refuelAmount) < 10 then refueltimer = Config.RefuelTime * 10 end + if lib.progressCircle({ + duration = refueltimer, + label = Lang:t("prog_refueling_vehicle"), + position = 'bottom', + useWhileDead = false, + canCancel = true, + disable = { + car = true, + move = true, + combat = true + }, + anim = { + dict = Config.JerryCanAnimDict, + clip = Config.JerryCanAnim + }, + }) then + StopAnimTask(ped, Config.JerryCanAnimDict, Config.JerryCanAnim, 1.0) + QBCore.Functions.Notify(Lang:t("syphon_success_vehicle"), 'success') + SetFuel(vehicle, cargasamount + tonumber(refuelAmount)) + local syphonData = data.itemData + local srcPlayerData = QBCore.Functions.GetPlayerData() + TriggerServerEvent('qb-fuel:info', "remove", tonumber(refuelAmount), srcPlayerData, syphonData) + else + StopAnimTask(ped, Config.JerryCanAnimDict, Config.JerryCanAnim, 1.0) + QBCore.Functions.Notify(Lang:t("cancelled"), 'error') + end + end + else + local refuel = exports['qb-input']:ShowInput({ + header = Lang:t("input_select_refuel_header"), + submitText = Lang:t("input_refuel_submit"), + inputs = { + { + type = 'number', + isRequired = true, + name = 'amount', + text = Lang:t("input_max_fuel_footer_1") .. Maxrefuel .. Lang:t("input_max_fuel_footer_2") + } + } + }) + if refuel then + if tonumber(refuel.amount) == 0 then QBCore.Functions.Notify(Lang:t("more_than_zero"), 'error') return elseif tonumber(refuel.amount) < 0 then QBCore.Functions.Notify(Lang:t("more_than_zero"), 'error') return elseif tonumber(refuel.amount) > 100 then QBCore.Functions.Notify("Du kan kun tanke 100L!", 'error') return end + if tonumber(refuel.amount) > tonumber(currentsyphonamount) then QBCore.Functions.Notify(Lang:t("syphon_not_enough_gas"), 'error') return end + if tonumber(refuel.amount) + tonumber(cargasamount) > 100 then QBCore.Functions.Notify(Lang:t("tank_cannot_fit"), 'error') return end + local refueltimer = Config.RefuelTime * tonumber(refuel.amount) + if tonumber(refuel.amount) < 10 then refueltimer = Config.RefuelTime * 10 end + QBCore.Functions.Progressbar('refuel_gas', Lang:t("prog_refueling_vehicle"), refueltimer, false, true, {-- Name | Label | Time | useWhileDead | canCancel + disableMovement = true, + disableCarMovement = true, + disableMouse = false, + disableCombat = true, + }, { + animDict = Config.JerryCanAnimDict, + anim = Config.JerryCanAnim, + flags = 17, + }, {}, {}, function()-- Play When Done + StopAnimTask(ped, Config.JerryCanAnimDict, Config.JerryCanAnim, 1.0) + QBCore.Functions.Notify(Lang:t("syphon_success_vehicle"), 'success') + SetFuel(vehicle, cargasamount + tonumber(refuel.amount)) + local syphonData = data.itemData + local srcPlayerData = QBCore.Functions.GetPlayerData() + TriggerServerEvent('qb-fuel:info', "remove", tonumber(refuel.amount), srcPlayerData, syphonData) + end, function()-- Play When Cancel + StopAnimTask(ped, Config.JerryCanAnimDict, Config.JerryCanAnim, 1.0) + QBCore.Functions.Notify(Lang:t("cancelled"), 'error') + end, "syphoningkit") + end + end + end + else + QBCore.Functions.Notify(Lang:t("syphon_no_syphon_kit"), 'error', 7500) + end + else + QBCore.Functions.Notify(Lang:t("need_electric_charger"), 'error', 7500) return + end +end) + +RegisterNetEvent('cdn-syphoning:client:callcops', function(coords) + local PlayerJob = QBCore.Functions.GetPlayerData().job + if PlayerJob.name ~= "police" or not PlayerJob.onduty then return end + local transG = 250 + local blip = AddBlipForCoord(coords.x, coords.y, coords.z) + SetBlipSprite(blip, 648) + SetBlipColour(blip, 17) + SetBlipDisplay(blip, 4) + SetBlipAlpha(blip, transG) + SetBlipScale(blip, 1.2) + SetBlipFlashes(blip, true) + BeginTextCommandSetBlipName('STRING') + AddTextComponentString(Lang:t("syphon_dispatch_string")) + EndTextCommandSetBlipName(blip) + while transG ~= 0 do + Wait(180 * 4) + transG = transG - 1 + SetBlipAlpha(blip, transG) + if transG == 0 then + SetBlipSprite(blip, 2) + RemoveBlip(blip) + return + end + end +end) + +-- Helicopter Fueling -- +local ShutOff2 = false +RegisterNetEvent('qb-fuel:client:grabnozzle:special', function() + if Config.PlayerOwnedGasStationsEnabled then + QBCore.Functions.TriggerCallback('qb-fuel:server:checkshutoff', function(result) + if result == true then + ShutOff2 = true + QBCore.Functions.Notify(Lang:t("emergency_shutoff_active"), 'error', 7500) + return + else + ShutOff2 = false + end + end, CurrentLocation) + end + if not ShutOff2 then + local ped = PlayerPedId() + if HoldingSpecialNozzle then return end + LoadAnimDict("anim@am_hold_up@male") + TaskPlayAnim(ped, "anim@am_hold_up@male", "shoplift_high", 2.0, 8.0, -1, 50, 0, 0, 0, 0) + TriggerServerEvent("InteractSound_SV:PlayOnSource", "pickupnozzle", 0.4) + Wait(300) + StopAnimTask(ped, "anim@am_hold_up@male", "shoplift_high", 1.0) + SpecialFuelNozzleObj = CreateObject(joaat('prop_cs_fuel_nozle'), 1.0, 1.0, 1.0, true, true, false) + local lefthand = GetPedBoneIndex(ped, 18905) + AttachEntityToEntity(SpecialFuelNozzleObj, ped, lefthand, 0.13, 0.04, 0.01, -42.0, -115.0, -63.42, 0, 1, 0, 1, 0, 1) + local grabbednozzlecoords = GetEntityCoords(ped) + HoldingSpecialNozzle = true + QBCore.Functions.Notify(Lang:t("show_input_key_special")) + if Config.PumpHose then + local pumpCoords, pump = GetClosestPump(grabbednozzlecoords) + -- Load Rope Textures + RopeLoadTextures() + while not RopeAreTexturesLoaded() do + Wait(0) + RopeLoadTextures() + end + -- Wait for Pump to exist. + while not pump do + Wait(0) + end + Rope = AddRope(pumpCoords.x, pumpCoords.y, pumpCoords.z + 2.0, 0.0, 0.0, 0.0, 3.0, Config.RopeType['fuel'], 8.0 --[[ DO NOT SET THIS TO 0.0!!! GAME WILL CRASH!]], 0.0, 1.0, false, false, false, 1.0, true) + while not Rope do + Wait(0) + end + ActivatePhysics(Rope) + Wait(100) + local nozzlePos = GetEntityCoords(SpecialFuelNozzleObj) + if Config.FuelDebug then print("NOZZLE POS " .. nozzlePos) end + nozzlePos = GetOffsetFromEntityInWorldCoords(SpecialFuelNozzleObj, 0.0, -0.033, -0.195) + AttachEntitiesToRope(Rope, pump, SpecialFuelNozzleObj, pumpCoords.x, pumpCoords.y, pumpCoords.z + 2.1, nozzlePos.x, nozzlePos.y, nozzlePos.z, length, false, false, nil, nil) + + if Config.FuelDebug then + print("Hose Properties:") + print(Rope, pump, SpecialFuelNozzleObj, pumpCoords.x, pumpCoords.y, pumpCoords.z, nozzlePos.x, nozzlePos.y, nozzlePos.z, length) + + SetEntityDrawOutline(SpecialFuelNozzleObj --[[ Entity ]], true --[[ boolean ]]) + end + end + CreateThread(function() + while HoldingSpecialNozzle do + local currentcoords = GetEntityCoords(ped) + local dist = #(grabbednozzlecoords - currentcoords) + TargetCreated = true + if dist > Config.AirAndWaterVehicleFueling['nozzle_length'] or IsPedInAnyVehicle(ped, false) then + HoldingSpecialNozzle = false + DeleteObject(SpecialFuelNozzleObj) + QBCore.Functions.Notify(Lang:t("nozzle_cannot_reach"), 'error') + if Config.PumpHose then + if Config.FuelDebug then print("Deleting Rope: " .. tostring(Rope)) end + RopeUnloadTextures() + DeleteRope(Rope) + end + if Config.FuelNozzleExplosion then + AddExplosion(grabbednozzlecoords.x, grabbednozzlecoords.y, grabbednozzlecoords.z, 'EXP_TAG_PROPANE', 1.0, true, false, 5.0) + StartScriptFire(grabbednozzlecoords.x, grabbednozzlecoords.y, grabbednozzlecoords.z - 1, 25, false) + SetFireSpreadRate(10.0) + Wait(5000) + StopFireInRange(grabbednozzlecoords.x, grabbednozzlecoords.y, grabbednozzlecoords.z - 1, 3.0) + end + end + Wait(2500) + end + end) + end +end) + +RegisterNetEvent('qb-fuel:client:returnnozzle:special', function() + HoldingSpecialNozzle = false + TriggerServerEvent("InteractSound_SV:PlayOnSource", "putbacknozzle", 0.4) + Wait(250) + DeleteObject(SpecialFuelNozzleObj) + + if Config.PumpHose then + if Config.FuelDebug then print("Removing Hose.") end + RopeUnloadTextures() + DeleteRope(Rope) + end +end) + +local AirSeaFuelZones = {} +local vehicle = nil +-- Create Polyzones with In-Out functions for handling fueling -- +CreateThread(function() + for i = 1, #Config.AirAndWaterVehicleFueling['locations'], 1 do + local currentLocation = Config.AirAndWaterVehicleFueling['locations'][i] + local k = #AirSeaFuelZones + 1 + local GeneratedName = "air_sea_fuel_zone_" .. k + + AirSeaFuelZones[k] = {}-- Make a new table inside of the Vehicle Pullout Zones representing this zone. + + -- Get Coords for Zone from Config. + AirSeaFuelZones[k].zoneCoords = currentLocation['PolyZone']['coords'] + + -- Grab MinZ & MaxZ from Config. + local minimumZ, maximumZ = currentLocation['PolyZone']['minmax']['min'], currentLocation['PolyZone']['minmax']['max'] + + -- Create Zone + AirSeaFuelZones[k].PolyZone = PolyZone:Create(AirSeaFuelZones[k].zoneCoords, { + name = GeneratedName, + minZ = minimumZ, + maxZ = maximumZ, + debugPoly = Config.PolyDebug + }) + + -- Setup onPlayerInOut Events for zone that is created. + AirSeaFuelZones[k].PolyZone:onPlayerInOut(function(isPointInside) + if isPointInside then + local canUseThisStation = false + if Config.AirAndWaterVehicleFueling['locations'][i]['whitelist']['enabled'] then + local whitelisted_jobs = Config.AirAndWaterVehicleFueling['locations'][i]['whitelist']['whitelisted_jobs'] + local plyJob = QBCore.Functions.GetPlayerData().job + + if Config.FuelDebug then + print("Player Job: " .. plyJob.name .. " Is on Duty?: " .. json.encode(plyJob.onduty)) + end + + if type(whitelisted_jobs) == "table" then + for i = 1, #whitelisted_jobs, 1 do + if plyJob.name == whitelisted_jobs[i] then + if Config.AirAndWaterVehicleFueling['locations'][i]['whitelist']['on_duty_only'] then + if plyJob.onduty == true then + canUseThisStation = true + else + canUseThisStation = false + end + else + canUseThisStation = true + end + end + end + end + else + canUseThisStation = true + end + + if canUseThisStation then + -- Inside + PlayerInSpecialFuelZone = true + inGasStation = true + RefuelingType = 'special' + + local DrawText = Config.AirAndWaterVehicleFueling['locations'][i]['draw_text'] + + if Config.Ox.DrawText then + lib.showTextUI(DrawText, { + position = 'left-center' + }) + else + exports[Config.Core]:DrawText(DrawText, 'top') + end + + CreateThread(function() + while PlayerInSpecialFuelZone do + Wait(3000) + vehicle = GetClosestVehicle() + end + end) + + CreateThread(function() + while PlayerInSpecialFuelZone do + Wait(0) + if PlayerInSpecialFuelZone ~= true then + break + end + if IsControlJustReleased(0, Config.AirAndWaterVehicleFueling['refuel_button'])--[[ Control in Config ]] then + local vehCoords = GetEntityCoords(vehicle) + local dist = #(GetEntityCoords(PlayerPedId()) - vehCoords) + + if not HoldingSpecialNozzle then + QBCore.Functions.Notify(Lang:t("no_nozzle"), 'error', 1250) + elseif dist > 4.5 then + QBCore.Functions.Notify(Lang:t("vehicle_too_far"), 'error', 1250) + elseif IsPedInAnyVehicle(PlayerPedId(), true) then + QBCore.Functions.Notify(Lang:t("inside_vehicle"), 'error', 1250) + else + if Config.FuelDebug then print("Attempting to Open Fuel menu for special vehicles.") end + TriggerEvent('qb-fuel:client:RefuelMenu', 'special') + end + end + end + end) + + if Config.FuelDebug then + print('Player has entered the Heli or Plane Refuel Zone: (' .. GeneratedName .. ')') + end + end + else + if HoldingSpecialNozzle then + QBCore.Functions.Notify(Lang:t("nozzle_cannot_reach"), 'error') + HoldingSpecialNozzle = false + if Config.PumpHose then + if Config.FuelDebug then + print("Deleting Rope: " .. Rope) + end + RopeUnloadTextures() + DeleteObject(Rope) + end + DeleteObject(SpecialFuelNozzleObj) + end + if Config.PumpHose then + if Rope ~= nil then + if Config.FuelDebug then + print("Deleting Rope: " .. Rope) + end + RopeUnloadTextures() + DeleteObject(Rope) + end + end + -- Outside + if Config.Ox.DrawText then + lib.hideTextUI() + else + exports[Config.Core]:HideText() + end + PlayerInSpecialFuelZone = false + inGasStation = false + RefuelingType = nil + if Config.FuelDebug then + print('Player has exited the Heli or Plane Refuel Zone: (' .. GeneratedName .. ')') + end + end + end) + + if currentLocation['prop'] then + local model = currentLocation['prop']['model'] + local modelCoords = currentLocation['prop']['coords'] + local heading = modelCoords[4] - 180.0 + AirSeaFuelZones[k].prop = CreateObject(model, modelCoords.x, modelCoords.y, modelCoords.z, false, true, true) + if Config.FuelDebug then print("Created Special Pump from Location #" .. i) end + SetEntityHeading(AirSeaFuelZones[k].prop, heading) + FreezeEntityPosition(AirSeaFuelZones[k].prop, 1) + else + if Config.FuelDebug then print("Location #" .. i .. " for Special Fueling Zones (Air and Sea) doesn't have a prop set up, so players cannot fuel here.") end + end + + if Config.FuelDebug then + print("Created Location: " .. GeneratedName) + end + end +end) + +AddEventHandler('onResourceStop', function(resource) + if resource == GetCurrentResourceName() then + for i = 1, #AirSeaFuelZones, 1 do + DeleteObject(AirSeaFuelZones[i].prop) + end + end +end) + +CreateThread(function() + local bones = { + "petroltank", + "petroltank_l", + "petroltank_r", + "wheel_rf", + "wheel_rr", + "petrolcap ", + "seat_dside_r", + "engine", + } + + exports[Config.TargetResource]:AddTargetBone(bones, { + options = { + { + type = "client", + action = function() + TriggerEvent('qb-fuel:client:RefuelMenu') + end, + icon = "fas fa-gas-pump", + label = Lang:t("input_insert_nozzle"), + canInteract = function() + if inGasStation and not refueling and holdingnozzle then + return true + end + end + }, + { + type = "client", + action = function() + TriggerEvent('qb-fuel:client:electric:RefuelMenu') + end, + icon = "fas fa-bolt", + label = Lang:t("insert_electric_nozzle"), + canInteract = function() + if Config.ElectricVehicleCharging == true then + if inGasStation and not refueling and IsHoldingElectricNozzle() then + return true + else + return false + end + else + return false + end + end + }, + }, + distance = 1.5, + }) + + exports[Config.TargetResource]:AddTargetModel(props, { + options = { + { + num = 1, + type = "client", + event = "qb-fuel:client:grabnozzle", + icon = "fas fa-gas-pump", + label = Lang:t("grab_nozzle"), + canInteract = function() + if PlayerInSpecialFuelZone then return false end + if not IsPedInAnyVehicle(PlayerPedId()) and not holdingnozzle and not HoldingSpecialNozzle and inGasStation == true and not PlayerInSpecialFuelZone then + return true + end + end, + }, + { + num = 2, + type = "client", + event = "qb-fuel:client:purchasejerrycan", + icon = "fas fa-fire-flame-simple", + label = Lang:t("buy_jerrycan"), + canInteract = function() + if not IsPedInAnyVehicle(PlayerPedId()) and not holdingnozzle and not HoldingSpecialNozzle and inGasStation == true then + return true + end + end, + }, + { + num = 3, + type = "client", + event = "qb-fuel:client:returnnozzle", + icon = "fas fa-hand", + label = Lang:t("return_nozzle"), + canInteract = function() + if holdingnozzle and not refueling then + return true + end + end, + }, + { + num = 4, + type = "client", + event = "qb-fuel:client:grabnozzle:special", + icon = "fas fa-gas-pump", + label = Lang:t("grab_special_nozzle"), + canInteract = function() + if Config.FuelDebug then print("Is Player In Special Fuel Zone?: " .. tostring(PlayerInSpecialFuelZone)) end + if not HoldingSpecialNozzle and not IsPedInAnyVehicle(PlayerPedId()) and PlayerInSpecialFuelZone then + return true + end + end, + }, + { + num = 5, + type = "client", + event = "qb-fuel:client:returnnozzle:special", + icon = "fas fa-hand", + label = Lang:t("return_special_nozzle"), + canInteract = function() + if HoldingSpecialNozzle and not IsPedInAnyVehicle(PlayerPedId()) then + return true + end + end + }, + }, + distance = 2.0 + }) +end) + +CreateThread(function() + while true do + Wait(3000) + local vehPedIsIn = GetVehiclePedIsIn(PlayerPedId(), false) + if not vehPedIsIn or vehPedIsIn == 0 then + Wait(2500) + if inBlacklisted then + inBlacklisted = false + end + else + local vehType = GetCurrentVehicleType(vehPedIsIn) + if not Config.ElectricVehicleCharging and vehType == 'electricvehicle' then + if Config.FuelDebug then + print("Vehicle Type is Electric, so we will not remove shut the engine off.") + end + else + if not IsVehicleBlacklisted(vehPedIsIn) then + local vehFuelLevel = GetFuel(vehPedIsIn) + local vehFuelShutoffLevel = Config.VehicleShutoffOnLowFuel['shutOffLevel'] or 1 + if vehFuelLevel <= vehFuelShutoffLevel then + if GetIsVehicleEngineRunning(vehPedIsIn) then + if Config.FuelDebug then + print("Vehicle is running with zero fuel, shutting it down.") + end + -- If the vehicle is on, we shut the vehicle off: + SetVehicleEngineOn(vehPedIsIn, false, true, true) + -- Then alert the client with notify. + QBCore.Functions.Notify(Lang:t("no_fuel"), 'error', 3500) + -- Play Sound, if enabled in config. + if Config.VehicleShutoffOnLowFuel['sounds']['enabled'] then + RequestAmbientAudioBank("DLC_PILOT_ENGINE_FAILURE_SOUNDS", 0) + PlaySoundFromEntity(l_2613, "Landing_Tone", vehPedIsIn, "DLC_PILOT_ENGINE_FAILURE_SOUNDS", 0, 0) + Wait(1500) + StopSound(l_2613) + end + end + else + if vehFuelLevel - 10 > vehFuelShutoffLevel then + Wait(7500) + end + end + end + end + end + end +end) + +if Config.VehicleShutoffOnLowFuel['shutOffLevel'] == 0 then + Config.VehicleShutoffOnLowFuel['shutOffLevel'] = 0.55 +end + +-- This loop does use quite a bit of performance, but, is needed due to electric vehicles running without fuel & normal vehicles driving backwards! +-- You can remove if you need the performance, but we believe it is very important. +CreateThread(function() + while true do + Wait(0) + local ped = PlayerPedId() + local veh = GetVehiclePedIsIn(ped, false) + if veh ~= 0 then + if not IsVehicleBlacklisted(veh) then + if IsPedInVehicle(ped, veh, false) and (GetIsVehicleEngineRunning(veh) == false) or GetFuel(veh) < (Config.VehicleShutoffOnLowFuel['shutOffLevel'] or 1) then + DisableControlAction(0, 71, true) + elseif IsPedInVehicle(ped, veh, false) and (GetIsVehicleEngineRunning(veh) == true) and GetFuel(veh) > (Config.VehicleShutoffOnLowFuel['shutOffLevel'] or 1) then + EnableControlAction(0, 71, true) + end + end + end + end +end) diff --git a/resources/[qb]/[qb_core]/qb-fuel/client/station_cl.lua b/resources/[qb]/[qb_core]/qb-fuel/client/station_cl.lua new file mode 100644 index 0000000..f0b8cda --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-fuel/client/station_cl.lua @@ -0,0 +1,1402 @@ +if Config.PlayerOwnedGasStationsEnabled then -- This is so Player Owned Gas Stations are a Config Option, instead of forced. Set this option in shared/config.lua! + -- Variables + local QBCore = exports[Config.Core]:GetCoreObject() + local PedsSpawned = false + + -- These are for fuel pickup: + local CreatedEventHandler = false + local locationSwapHandler + local spawnedTankerTrailer + local spawnedDeliveryTruck + local ReservePickupData = {} + + -- Functions + local function RequestAndLoadModel(model) + RequestModel(model) + while not HasModelLoaded(model) do + Wait(5) + end + end + + local function UpdateStationInfo(info) + if Config.FuelDebug then print("Fetching Information for Location #" ..CurrentLocation) end + QBCore.Functions.TriggerCallback('qb-fuel:server:fetchinfo', function(result) + if result then + for _, v in pairs(result) do + -- Reserves -- + if info == "all" or info == "reserves" then + if Config.FuelDebug then print("Fetched Reserve Levels: "..v.fuel.." Liters!") end + Currentreserveamount = v.fuel + ReserveLevels = Currentreserveamount + if Currentreserveamount < Config.MaxFuelReserves then + ReservesNotBuyable = false + else + ReservesNotBuyable = true + end + if Config.UnlimitedFuel then ReservesNotBuyable = true if Config.FuelDebug then print("Reserves are not buyable, because Config.UnlimitedFuel is set to true.") end end + end + -- Fuel Price -- + if info == "all" or info == "fuelprice" then + StationFuelPrice = v.fuelprice + end + -- Fuel Station's Balance -- + if info == "all" or info == "balance" then + StationBalance = v.balance + if Config.FuelDebug then print("Successfully Fetched: Balance") end + end + ---------------- + end + end + end, CurrentLocation) + end exports(UpdateStationInfo, UpdateStationInfo) + + local function SpawnGasStationPeds() + if not Config.GasStations or not next(Config.GasStations) or PedsSpawned then return end + for i = 1, #Config.GasStations do + local current = Config.GasStations[i] + current.pedmodel = type(current.pedmodel) == 'string' and joaat(current.pedmodel) or current.pedmodel + RequestAndLoadModel(current.pedmodel) + local ped = CreatePed(0, current.pedmodel, current.pedcoords.x, current.pedcoords.y, current.pedcoords.z, current.pedcoords.h, false, false) + FreezeEntityPosition(ped, true) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + exports['qb-target']:AddTargetEntity(ped, { + options = { + { + type = "client", + label = Lang:t("station_talk_to_ped"), + icon = "fas fa-building", + action = function() + TriggerEvent('qb-fuel:stations:openmenu', CurrentLocation) + end, + }, + }, + distance = 2.0 + }) + end + PedsSpawned = true + end + + local function GenerateRandomTruckModel() + local possibleTrucks = Config.PossibleDeliveryTrucks + if possibleTrucks then + return possibleTrucks[math.random(#possibleTrucks)] + end + end + + local function SpawnPickupVehicles() + local trailer = GetHashKey('tanker') + local truckToSpawn = GetHashKey(GenerateRandomTruckModel()) + if truckToSpawn then + RequestAndLoadModel(truckToSpawn) + RequestAndLoadModel(trailer) + spawnedDeliveryTruck = CreateVehicle(truckToSpawn, Config.DeliveryTruckSpawns['truck'], true, false) + spawnedTankerTrailer = CreateVehicle(trailer, Config.DeliveryTruckSpawns['trailer'], true, false) + SetVehicleFuelLevel(spawnedDeliveryTruck, 100.0) + SetModelAsNoLongerNeeded(truckToSpawn) -- removes model from game memory as we no longer need it + SetModelAsNoLongerNeeded(trailer) -- removes model from game memory as we no longer need it + SetEntityAsMissionEntity(spawnedDeliveryTruck, 1, 1) + SetEntityAsMissionEntity(spawnedTankerTrailer, 1, 1) + AttachVehicleToTrailer(spawnedDeliveryTruck, spawnedTankerTrailer, 15.0) + -- Now our vehicle is spawned. + if spawnedDeliveryTruck ~= 0 and spawnedTankerTrailer ~= 0 then + TriggerEvent("vehiclekeys:client:SetOwner", QBCore.Functions.GetPlate(spawnedDeliveryTruck)) + return true + else + return false + end + end + end + + -- Events + RegisterNetEvent('qb-fuel:stations:updatelocation', function(updatedlocation) + if Config.FuelDebug then if CurrentLocation == nil then CurrentLocation = 0 end + if updatedlocation == nil then updatedlocation = 0 end + print('Location: '..CurrentLocation..' has been replaced with a new location: ' ..updatedlocation) + end + CurrentLocation = updatedlocation or 0 + end) + + RegisterNetEvent('qb-fuel:stations:client:buyreserves', function(data) + local location = data.location + local price = data.price + local amount = data.amount + TriggerServerEvent('qb-fuel:stations:server:buyreserves', location, price, amount) + if Config.FuelDebug then print("^5Attempting Purchase of ^2"..amount.. "^5 Fuel Reserves for location #"..location.."! Purchase Price: ^2"..price) end + end) + + RegisterNetEvent('qb-fuel:station:client:initiatefuelpickup', function(amountBought, finalReserveAmountAfterPurchase, location) + if amountBought and finalReserveAmountAfterPurchase and location then + ReservePickupData = nil + ReservePickupData = { + finalAmount = finalReserveAmountAfterPurchase, + amountBought = amountBought, + location = location, + } + + if SpawnPickupVehicles() then + QBCore.Functions.Notify(Lang:t("fuel_order_ready"), 'success') + SetNewWaypoint(Config.DeliveryTruckSpawns['truck'].x, Config.DeliveryTruckSpawns['truck'].y) + SetUseWaypointAsDestination(true) + ReservePickupData.blip = CreateBlip(vector3(Config.DeliveryTruckSpawns['truck'].x, Config.DeliveryTruckSpawns['truck'].y, Config.DeliveryTruckSpawns['truck'].z), "Truck Pickup") + SetBlipColour(ReservePickupData.blip, 5) + + -- Create Zone + ReservePickupData.PolyZone = PolyZone:Create(Config.DeliveryTruckSpawns.PolyZone.coords, { + name = "cdn_fuel_zone_delivery_truck_pickup", + minZ = Config.DeliveryTruckSpawns.PolyZone.minz, + maxZ = Config.DeliveryTruckSpawns.PolyZone.maxz, + debugPoly = Config.PolyDebug + }) + + -- Setup onPlayerInOut Events for zone that is created. + ReservePickupData.PolyZone:onPlayerInOut(function(isPointInside) + if isPointInside then + if Config.FuelDebug then + print("Player has arrived at the pickup location!") + end + RemoveBlip(ReservePickupData.blip) + ReservePickupData.blip = nil + CreateThread(function() + local ped = PlayerPedId() + local alreadyHasTruck = false + local hasArrivedAtLocation = false + local VehicleDelivered = false + local EndAwaitListener = false + local stopNotifyTemp = false + local AwaitingInput = false + while true do + Wait(100) + if VehicleDelivered then break end + if IsPedInAnyVehicle(ped, false) then + if GetVehiclePedIsIn(ped, false) == spawnedDeliveryTruck then + if Config.FuelDebug then + print("Player is inside of the delivery truck!") + end + + if not alreadyHasTruck then + local loc = {} + loc.x, loc.y = Config.GasStations[ReservePickupData.location].pedcoords.x, Config.GasStations[ReservePickupData.location].pedcoords.y + SetNewWaypoint(loc.x, loc.y) + SetUseWaypointAsDestination(true) + alreadyHasTruck = true + else + if not CreatedEventHandler then + local function AwaitInput() + if AwaitingInput then return end + AwaitingInput = true + if Config.FuelDebug then print("Executing function `AwaitInput()`") end + CreateThread(function() + while true do + Wait(0) + if EndAwaitListener or not hasArrivedAtLocation then + AwaitingInput = false + break + end + if IsControlJustReleased(2, 38) then + local distBetweenTruckAndTrailer = #(GetEntityCoords(spawnedDeliveryTruck) - GetEntityCoords(spawnedTankerTrailer)) + if distBetweenTruckAndTrailer > 10.0 then + distBetweenTruckAndTrailer = nil + if not stopNotifyTemp then + QBCore.Functions.Notify(Lang:t("trailer_too_far"), 'error', 7500) + end + stopNotifyTemp = true + Wait(1000) + stopNotifyTemp = false + else + EndAwaitListener = true + local ped = PlayerPedId() + VehicleDelivered = true + -- Handle Vehicle Dropoff + -- Remove PolyZone -- + ReservePickupData.PolyZone:destroy() + ReservePickupData.PolyZone = nil + -- Get Ped Out of Vehicle if Inside -- + if IsPedInAnyVehicle(ped, true) and GetVehiclePedIsIn(ped, false) == spawnedDeliveryTruck then + TaskLeaveVehicle( + ped --[[ Ped ]], + spawnedDeliveryTruck --[[ Vehicle ]], + 1 --[[ flags | integer ]] + ) + Wait(5000) + end + + if Config.Ox.DrawText then + lib.hideTextUI() + else + exports[Config.Core]:HideText() + end + + -- Remove Vehicle -- + DeleteEntity(spawnedDeliveryTruck) + DeleteEntity(spawnedTankerTrailer) + -- Send Data to Server to Put Into Station -- + TriggerServerEvent('qb-fuel:station:server:fuelpickup:finished', ReservePickupData.location) + -- Remove Handler + RemoveEventHandler(locationSwapHandler) + AwaitingInput = false + CreatedEventHandler = false + ReservePickupData = nil + ReservePickupData = {} + -- Break Loop + break + end + end + end + end) + AwaitingInput = true + end + locationSwapHandler = AddEventHandler('qb-fuel:stations:updatelocation', function(location) + if location == nil or location ~= ReservePickupData.location then + hasArrivedAtLocation = false + if Config.Ox.DrawText then + lib.hideTextUI() + else + exports[Config.Core]:HideText() + end + -- Break Listener + EndAwaitListener = true + Wait(50) + EndAwaitListener = false + else + hasArrivedAtLocation = true + if Config.Ox.DrawText then + lib.showTextUI(Lang:t("draw_text_fuel_dropoff"), { + position = 'left-center' + }) + else + exports[Config.Core]:DrawText(Lang:t("draw_text_fuel_dropoff"), 'top') + end + -- Add Listner for Keypress + AwaitInput() + end + end) + end + end + end + end + end + end) + else + + end + end) + else + -- This is just a worst case scenario event, if the vehicles somehow do not spawn. + TriggerServerEvent('qb-fuel:station:server:fuelpickup:failed', location) + end + else + if Config.FuelDebug then + print("An error has occurred. The amountBought / finalReserveAmountAfterPurchase / location is nil: `qb-fuel:station:client:initiatefuelpickup`") + end + end + end) + + RegisterNetEvent('qb-fuel:stations:client:purchaselocation', function(data) + local location = data.location + local CitizenID = QBCore.Functions.GetPlayerData().citizenid + CanOpen = false + Wait(5) + QBCore.Functions.TriggerCallback('qb-fuel:server:locationpurchased', function(result) + if result then + if Config.FuelDebug then print("The Location: "..CurrentLocation.." is owned!") end + IsOwned = true + else + if Config.FuelDebug then print("The Location: "..CurrentLocation.." is not owned.") end + IsOwned = false + end + end, CurrentLocation) + Wait(Config.WaitTime) + + if not IsOwned then + TriggerServerEvent('qb-fuel:server:buyStation', location, CitizenID) + elseif IsOwned then + QBCore.Functions.Notify(Lang:t("station_already_owned"), 'error', 7500) + end + end) + + RegisterNetEvent('qb-fuel:stations:client:sellstation', function(data) + local location = data.location + local SalePrice = data.SalePrice + local CitizenID = QBCore.Functions.GetPlayerData().citizenid + CanSell = false + Wait(5) + QBCore.Functions.TriggerCallback('qb-fuel:server:isowner', function(result) + if result then + if Config.FuelDebug then print("The Location: "..location.." is owned by ID: "..CitizenID) end + CanSell = true + else + QBCore.Functions.Notify(Lang:t("station_not_owner"), 'error', 7500) + if Config.FuelDebug then print("The Location: "..location.." is not owned by ID: "..CitizenID) end + CanSell = false + end + end, location) + Wait(Config.WaitTime) + if CanSell then + if Config.FuelDebug then print("Attempting to sell for: $"..SalePrice) end + TriggerServerEvent('qb-fuel:stations:server:sellstation', location) + if Config.FuelDebug then print("Event Triggered") end + else + QBCore.Functions.Notify(Lang:t("station_cannot_sell"), 'error', 7500) + end + end) + + RegisterNetEvent('qb-fuel:stations:client:purchasereserves:final', function(location, price, amount) -- Menu, seens after selecting the "purchase reserves" option. + local location = location + local price = price + local amount = amount + CanOpen = false + Wait(5) + if Config.FuelDebug then print("checking ownership of "..location) end + QBCore.Functions.TriggerCallback('qb-fuel:server:isowner', function(result) + local CitizenID = QBCore.Functions.GetPlayerData().citizenid + if result then + if Config.FuelDebug then print("The Location: "..location.." is owned by ID: "..CitizenID) end + CanOpen = true + else + QBCore.Functions.Notify(Lang:t("station_not_owner"), 'error', 7500) + if Config.FuelDebug then print("The Location: "..location.." is not owned by ID: "..CitizenID) end + CanOpen = false + end + end, location) + Wait(Config.WaitTime) + if CanOpen then + if Config.FuelDebug then print("Price: "..price.."
Amount: "..amount.."
Location: "..location) end + if Config.Ox.Menu then + lib.registerContext({ + id = 'purchasereservesmenu', + title = Lang:t("menu_station_reserves_header")..Config.GasStations[location].label, + options = { + { + title = Lang:t("menu_station_reserves_purchase_header")..price..",-", + description = Lang:t("menu_station_reserves_purchase_footer")..price..",-!", + icon = "fas fa-usd", + arrow = false, -- puts arrow to the right + event = 'qb-fuel:stations:client:buyreserves', + args = { + location = location, + price = price, + amount = amount, + } + }, + { + title = Lang:t("menu_header_close"), + description = Lang:t("menu_ped_close_footer"), + icon = "fas fa-times-circle", + arrow = false, -- puts arrow to the right + onSelect = function() + lib.hideContext() + end, + }, + }, + }) + lib.showContext('purchasereservesmenu') + else + exports['qb-menu']:openMenu({ + { + header = Lang:t("menu_station_reserves_header")..Config.GasStations[location].label, + isMenuHeader = true, + icon = "fas fa-gas-pump", + }, + { + header = Lang:t("menu_station_reserves_purchase_header")..price, + txt = Lang:t("menu_station_reserves_purchase_footer")..price.."!", + icon = "fas fa-usd", + params = { + event = "qb-fuel:stations:client:buyreserves", + args = { + location = location, + price = price, + amount = amount, + }, + }, + }, + { + header = Lang:t("menu_header_close"), + txt = Lang:t("menu_station_reserves_cancel_footer"), + icon = "fas fa-times-circle", + params = { + event = "qb-menu:closeMenu", + } + }, + }) + end + else + if Config.FuelDebug then print("Not showing menu, as the player doesn't have proper permissions.") end + end + end) + + RegisterNetEvent('qb-fuel:stations:client:purchasereserves', function(data) + local CanOpen = false + local location = data.location + QBCore.Functions.TriggerCallback('qb-fuel:server:isowner', function(result) + local CitizenID = QBCore.Functions.GetPlayerData().citizenid + if result then + if Config.FuelDebug then print("The Location: "..CurrentLocation.." is owned by ID: "..CitizenID) end + CanOpen = true + else + QBCore.Functions.Notify(Lang:t("station_not_owner"), 'error', 7500) + if Config.FuelDebug then print("The Location: "..CurrentLocation.." is not owned by ID: "..CitizenID) end + CanOpen = false + end + end, location) + Wait(Config.WaitTime) + if CanOpen then + local bankmoney = QBCore.Functions.GetPlayerData().money['bank'] + if Config.FuelDebug then print("Showing Input for Reserves!") end + if Config.Ox.Input then + local reserves = lib.inputDialog('Køb reserve', { + { type = "input", label = 'Nuværrende pris', + default = Config.FuelReservesPrice .. ',- Per Liter', + disabled = true }, + { type = "input", label = 'Nuværrende reserve', + default = Currentreserveamount, + disabled = true }, + { type = "input", label = 'Nødvendig reserve', + default = Config.MaxFuelReserves - Currentreserveamount, + disabled = true }, + { type = "slider", label = 'Pris for fuld opfyldning: ' ..math.ceil(GlobalTax((Config.MaxFuelReserves - Currentreserveamount) * Config.FuelReservesPrice) + ((Config.MaxFuelReserves - Currentreserveamount) * Config.FuelReservesPrice)).. ',-', + default = Config.MaxFuelReserves - Currentreserveamount, + min = 0, + max = Config.MaxFuelReserves - Currentreserveamount + }, + }) + if not reserves then return end + reservesAmount = tonumber(reserves[4]) + if reserves then + if Config.FuelDebug then print("Attempting to buy reserves!") end + Wait(100) + local amount = reservesAmount + if not reservesAmount then QBCore.Functions.Notify(Lang:t("station_amount_invalid"), 'error', 7500) return end + Reservebuyamount = tonumber(reservesAmount) + if Reservebuyamount < 1 then QBCore.Functions.Notify(Lang:t("station_more_than_one"), 'error', 7500) return end + if (Reservebuyamount + Currentreserveamount) > Config.MaxFuelReserves then + QBCore.Functions.Notify(Lang:t("station_reserve_cannot_fit"), "error") + else + if math.ceil(GlobalTax(Reservebuyamount * Config.FuelReservesPrice) + (Reservebuyamount * Config.FuelReservesPrice)) <= bankmoney then + local price = math.ceil(GlobalTax(Reservebuyamount * Config.FuelReservesPrice) + (Reservebuyamount * Config.FuelReservesPrice)) + if Config.FuelDebug then print("Price: "..price) end + TriggerEvent("qb-fuel:stations:client:purchasereserves:final", location, price, amount) + + else + QBCore.Functions.Notify(Lang:t("not_enough_money_in_bank"), 'error', 7500) + end + end + end + else + local reserves = exports['qb-input']:ShowInput({ + header = Lang:t("input_purchase_reserves_header_1") .. Lang:t("input_purchase_reserves_header_2") .. Currentreserveamount.."/"..Config.MaxFuelReserves.. Lang:t("input_purchase_reserves_header_3") .. + math.ceil(GlobalTax((Config.MaxFuelReserves - Currentreserveamount) * Config.FuelReservesPrice) + ((Config.MaxFuelReserves - Currentreserveamount) * Config.FuelReservesPrice)) .. ",-", + submitText = Lang:t("input_purchase_reserves_submit_text"), + inputs = { { + type = 'number', + isRequired = true, + name = 'amount', + text = Lang:t("input_purchase_reserves_text") + }} + }) + if reserves then + if Config.FuelDebug then print("Attempting to buy reserves!") end + Wait(100) + local amount = reserves.amount + if not reserves.amount then QBCore.Functions.Notify(Lang:t("station_amount_invalid"), 'error', 7500) return end + Reservebuyamount = tonumber(reserves.amount) + if Reservebuyamount < 1 then QBCore.Functions.Notify(Lang:t("station_more_than_one"), 'error', 7500) return end + if (Reservebuyamount + Currentreserveamount) > Config.MaxFuelReserves then + QBCore.Functions.Notify(Lang:t("station_reserve_cannot_fit"), "error") + else + if math.ceil(GlobalTax(Reservebuyamount * Config.FuelReservesPrice) + (Reservebuyamount * Config.FuelReservesPrice)) <= bankmoney then + local price = math.ceil(GlobalTax(Reservebuyamount * Config.FuelReservesPrice) + (Reservebuyamount * Config.FuelReservesPrice)) + if Config.FuelDebug then print("Price: "..price) end + TriggerEvent("qb-fuel:stations:client:purchasereserves:final", location, price, amount) + + else + QBCore.Functions.Notify(Lang:t("not_enough_money_in_bank"), 'error', 7500) + end + end + end + end + end + end) + + RegisterNetEvent('qb-fuel:stations:client:changefuelprice', function(data) + CanOpen = false + local location = data.location + QBCore.Functions.TriggerCallback('qb-fuel:server:isowner', function(result) + local CitizenID = QBCore.Functions.GetPlayerData().citizenid + if result then + if Config.FuelDebug then print("The Location: "..CurrentLocation.." is owned by ID: "..CitizenID) end + CanOpen = true + else + QBCore.Functions.Notify(Lang:t("station_not_owner"), 'error', 7500) + if Config.FuelDebug then print("The Location: "..CurrentLocation.." is not owned by ID: "..CitizenID) end + CanOpen = false + end + end, location) + Wait(Config.WaitTime) + if CanOpen then + if Config.FuelDebug then print("Showing Input for Fuel Price Change!") end + if Config.Ox.Input then + local fuelprice = lib.inputDialog('Brændstofpriser', { + { type = "input", label = 'Nuværrende pris', + default = Comma_Value(StationFuelPrice) .. ',- Per Liter', + disabled = true }, + { type = "number", label = 'Indtast ny pris per liter', + default = StationFuelPrice, + min = Config.MinimumFuelPrice, + max = Config.MaxFuelPrice + }, + }) + if not fuelprice then return end + fuelPrice = tonumber(fuelprice[2]) + if fuelprice then + if Config.FuelDebug then print("Attempting to change fuel price!") end + Wait(100) + if not fuelPrice then QBCore.Functions.Notify(Lang:t("station_amount_invalid"), 'error', 7500) return end + NewFuelPrice = tonumber(fuelPrice) + if NewFuelPrice < Config.MinimumFuelPrice then QBCore.Functions.Notify(Lang:t("station_price_too_low"), 'error', 7500) return end + if NewFuelPrice > Config.MaxFuelPrice then + QBCore.Functions.Notify(Lang:t("station_price_too_high"), "error") + else + TriggerServerEvent("qb-fuel:station:server:updatefuelprice", NewFuelPrice, CurrentLocation) + end + end + else + local fuelprice = exports['qb-input']:ShowInput({ + header = Lang:t("input_alter_fuel_price_header_1")..StationFuelPrice..Lang:t("input_alter_fuel_price_header_2"), + submitText = Lang:t("input_alter_fuel_price_submit_text"), + inputs = { { + type = 'number', + isRequired = true, + name = 'price', + text = Lang:t("input_alter_fuel_price_submit_text") + }} + }) + if fuelprice then + if Config.FuelDebug then print("Attempting to change fuel price!") end + Wait(100) + if not fuelprice.price then QBCore.Functions.Notify(Lang:t("station_amount_invalid"), 'error', 7500) return end + NewFuelPrice = tonumber(fuelprice.price) + if NewFuelPrice < Config.MinimumFuelPrice then QBCore.Functions.Notify(Lang:t("station_price_too_low"), 'error', 7500) return end + if NewFuelPrice > Config.MaxFuelPrice then + QBCore.Functions.Notify(Lang:t("station_price_too_high"), "error") + else + TriggerServerEvent("qb-fuel:station:server:updatefuelprice", NewFuelPrice, CurrentLocation) + end + end + end + end + end) + + RegisterNetEvent('qb-fuel:stations:client:sellstation:menu', function(data) -- Menu, seen after selecting the Sell this Location option. + local location = data.location + local CitizenID = QBCore.Functions.GetPlayerData().citizenid + QBCore.Functions.TriggerCallback('qb-fuel:server:isowner', function(result) + if result then + if Config.FuelDebug then print("The Location: "..CurrentLocation.." is owned by ID: "..CitizenID) end + CanOpen = true + else + QBCore.Functions.Notify(Lang:t("station_not_owner"), 'error', 7500) + if Config.FuelDebug then print("The Location: "..CurrentLocation.." is not owned by ID: "..CitizenID) end + CanOpen = false + end + end, CurrentLocation) + Wait(Config.WaitTime) + if CanOpen then + local GasStationCost = Config.GasStations[location].cost + GlobalTax(Config.GasStations[location].cost) + local SalePrice = math.percent(Config.GasStationSellPercentage, GasStationCost) + if Config.Ox.Menu then + lib.registerContext({ + id = 'sellstationmenu', + title = Lang:t("menu_sell_station_header")..Config.GasStations[location].label, + options = { + { + title = Lang:t("menu_sell_station_header_accept"), + description = Lang:t("menu_sell_station_footer_accept")..Comma_Value(SalePrice)..",-", + icon = "fas fa-usd", + arrow = false, -- puts arrow to the right + event = 'qb-fuel:stations:client:sellstation', + args = { + location = location, + SalePrice = SalePrice, + } + }, + { + title = Lang:t("menu_header_close"), + description = Lang:t("menu_refuel_cancel"), + icon = "fas fa-times-circle", + arrow = false, -- puts arrow to the right + onSelect = function() + lib.hideContext() + end, + }, + }, + }) + lib.showContext('sellstationmenu') + TriggerServerEvent("qb-fuel:stations:server:stationsold", location) + else + exports['qb-menu']:openMenu({ + { + header = Lang:t("menu_sell_station_header")..Config.GasStations[location].label, + isMenuHeader = true, + icon = "fas fa-gas-pump", + }, + { + header = Lang:t("menu_sell_station_header_accept"), + txt = Lang:t("menu_sell_station_footer_accept")..SalePrice..",-", + icon = "fas fa-usd", + params = { + event = "qb-fuel:stations:client:sellstation", + args = { + location = location, + SalePrice = SalePrice, + } + }, + }, + { + header = Lang:t("menu_header_close"), + txt = Lang:t("menu_sell_station_footer_close"), + icon = "fas fa-times-circle", + params = { + event = "qb-menu:closeMenu", + } + }, + }) + TriggerServerEvent("qb-fuel:stations:server:stationsold", location) + end + end + end) + + RegisterNetEvent('qb-fuel:stations:client:changestationname', function() -- Menu for changing the label of the owned station. + CanOpen = false + QBCore.Functions.TriggerCallback('qb-fuel:server:isowner', function(result) + local CitizenID = QBCore.Functions.GetPlayerData().citizenid + if result then + if Config.FuelDebug then print("The Location: "..CurrentLocation.." is owned by ID: "..CitizenID) end + CanOpen = true + else + QBCore.Functions.Notify(Lang:t("station_not_owner"), 'error', 7500) + if Config.FuelDebug then print("The Location: "..CurrentLocation.." is not owned by ID: "..CitizenID) end + CanOpen = false + end + end, CurrentLocation) + Wait(Config.WaitTime) + if CanOpen then + if Config.FuelDebug then print("Showing Input for name Change!") end + if Config.Ox.Input then + local NewName = lib.inputDialog('Skift navn', { + { type = "input", label = 'Nuværrende navn', + default = Config.GasStations[CurrentLocation].label, + disabled = true }, + { type = "input", label = 'Indtast nyt navn', + placeholder = 'Nyt navn' + }, + }) + if not NewName then return end + NewNameName = NewName[2] + if NewName then + if Config.FuelDebug then print("Attempting to alter stations name!") end + if not NewNameName then QBCore.Functions.Notify(Lang:t("station_name_invalid"), 'error', 7500) return end + NewName = NewNameName + if type(NewName) ~= "string" then QBCore.Functions.Notify(Lang:t("station_name_invalid"), 'error') return end + if Config.ProfanityList[NewName] then QBCore.Functions.Notify(Lang:t("station_name_invalid"), 'error', 7500) + -- You can add logs for people that put prohibited words into the name changer if wanted, and here is where you would do it. + return + end + if string.len(NewName) > Config.NameChangeMaxChar then QBCore.Functions.Notify(Lang:t("station_name_too_long"), 'error') return end + if string.len(NewName) < Config.NameChangeMinChar then QBCore.Functions.Notify(Lang:t("station_name_too_short"), 'error') return end + Wait(100) + TriggerServerEvent("qb-fuel:station:server:updatelocationname", NewName, CurrentLocation) + end + else + local NewName = exports['qb-input']:ShowInput({ + header = Lang:t("input_change_name_header_1")..Config.GasStations[CurrentLocation].label..Lang:t("input_change_name_header_2"), + submitText = Lang:t("input_change_name_submit_text"), + inputs = { { + type = 'text', + isRequired = true, + name = 'newname', + text = Lang:t("input_change_name_text") + }} + }) + if NewName then + if Config.FuelDebug then print("Attempting to alter stations name!") end + if not NewName.newname then QBCore.Functions.Notify(Lang:t("station_name_invalid"), 'error', 7500) return end + NewName = NewName.newname + if type(NewName) ~= "string" then QBCore.Functions.Notify(Lang:t("station_name_invalid"), 'error') return end + if Config.ProfanityList[NewName] then QBCore.Functions.Notify(Lang:t("station_name_invalid"), 'error', 7500) + -- You can add logs for people that put prohibited words into the name changer if wanted, and here is where you would do it. + return + end + if string.len(NewName) > Config.NameChangeMaxChar then QBCore.Functions.Notify(Lang:t("station_name_too_long"), 'error') return end + if string.len(NewName) < Config.NameChangeMinChar then QBCore.Functions.Notify(Lang:t("station_name_too_short"), 'error') return end + Wait(100) + TriggerServerEvent("qb-fuel:station:server:updatelocationname", NewName, CurrentLocation) + end + end + end + end) + + RegisterNetEvent('qb-fuel:stations:client:managemenu', function(location) -- Menu, seen after selecting the Manage this Location Option. + location = CurrentLocation + QBCore.Functions.TriggerCallback('qb-fuel:server:isowner', function(result) + local CitizenID = QBCore.Functions.GetPlayerData().citizenid + if result then + if Config.FuelDebug then print("The Location: "..CurrentLocation.." is owned by ID: "..CitizenID) end + CanOpen = true + else + QBCore.Functions.Notify(Lang:t("station_not_owner"), 'error', 7500) + if Config.FuelDebug then print("The Location: "..CurrentLocation.." is not owned by ID: "..CitizenID) end + CanOpen = false + end + end, CurrentLocation) + UpdateStationInfo("all") + if Config.PlayerControlledFuelPrices then CanNotChangeFuelPrice = false else CanNotChangeFuelPrice = true end + Wait(5) + Wait(Config.WaitTime) + if CanOpen then + local GasStationCost = (Config.GasStations[location].cost + GlobalTax(Config.GasStations[location].cost)) + if Config.Ox.Menu then + lib.registerContext({ + id = 'stationmanagemenu', + title = Lang:t("menu_manage_header")..Config.GasStations[location].label, + options = { + { + title = Lang:t("menu_manage_reserves_header"), + description = 'Køb din reserve brændstof her!', + icon = "fas fa-info-circle", + arrow = true, -- puts arrow to the right + event = 'qb-fuel:stations:client:purchasereserves', + args = { + location = location, + }, + metadata = { + {label = 'Reserve tank: ', value = ReserveLevels..Lang:t("menu_manage_reserves_footer_1")..Config.MaxFuelReserves}, + }, + disabled = ReservesNotBuyable, + }, + { + title = Lang:t("menu_alter_fuel_price_header"), + description = "Jeg vil skifte prisen på brændstoffen!", + icon = "fas fa-usd", + arrow = false, -- puts arrow to the right + event = 'qb-fuel:stations:client:changefuelprice', + args = { + location = location, + }, + metadata = { + {label = 'Nuværrende pris: ', value = Comma_Value(StationFuelPrice)..",-"..Lang:t("input_alter_fuel_price_header_2")}, + }, + disabled = CanNotChangeFuelPrice, + }, + { + title = Lang:t("menu_manage_company_funds_header"), + description = Lang:t("menu_manage_company_funds_footer"), + icon = "fas fa-usd", + arrow = false, -- puts arrow to the right + event = 'qb-fuel:stations:client:managefunds' + }, + { + title = Lang:t("menu_manage_change_name_header"), + description = Lang:t("menu_manage_change_name_footer"), + icon = "fas fa-pen", + arrow = false, -- puts arrow to the right + event = 'qb-fuel:stations:client:changestationname', + disabled = not Config.GasStationNameChanges, + }, + { + title = Lang:t("menu_sell_station_header_accept"), + description = Lang:t("menu_manage_sell_station_footer")..Comma_Value(math.percent(Config.GasStationSellPercentage, GasStationCost))..",-", + icon = "fas fa-usd", + arrow = false, -- puts arrow to the right + event = 'qb-fuel:stations:client:sellstation:menu', + args = { + location = location, + }, + }, + { + title = Lang:t("menu_header_close"), + description = Lang:t("menu_refuel_cancel"), + icon = "fas fa-times-circle", + arrow = false, -- puts arrow to the right + onSelect = function() + lib.hideContext() + end, + }, + }, + }) + lib.showContext('stationmanagemenu') + else + exports['qb-menu']:openMenu({ + { + header = Lang:t("menu_manage_header")..Config.GasStations[location].label, + isMenuHeader = true, + icon = "fas fa-gas-pump", + }, + { + header = Lang:t("menu_manage_reserves_header"), + icon = "fas fa-info-circle", + isMenuHeader = true, + txt = ReserveLevels..Lang:t("menu_manage_reserves_footer_1")..Config.MaxFuelReserves..Lang:t("menu_manage_reserves_footer_2"), + }, + { + header = Lang:t("menu_manage_purchase_reserves_header"), + icon = "fas fa-usd", + txt = Lang:t("menu_manage_purchase_reserves_footer")..Config.FuelReservesPrice..Lang:t("menu_manage_purchase_reserves_footer_2") , + params = { + event = "qb-fuel:stations:client:purchasereserves", + args = { + location = location, + } + }, + disabled = ReservesNotBuyable, + }, + { + header = Lang:t("menu_alter_fuel_price_header"), + icon = "fas fa-usd", + txt = "Jeg vil skifte prisen på brændstof!
Lige nu koster det "..StationFuelPrice..",-"..Lang:t("input_alter_fuel_price_header_2") , + params = { + event = "qb-fuel:stations:client:changefuelprice", + args = { + location = location, + } + }, + disabled = CanNotChangeFuelPrice, + }, + { + header = Lang:t("menu_manage_company_funds_header"), + icon = "fas fa-usd", + txt = Lang:t("menu_manage_company_funds_footer"), + params = { + event = "qb-fuel:stations:client:managefunds", + }, + }, + { + header = Lang:t("menu_manage_change_name_header"), + icon = "fas fa-pen", + txt = Lang:t("menu_manage_change_name_footer"), + disabled = not Config.GasStationNameChanges, + params = { + event = "qb-fuel:stations:client:changestationname", + }, + }, + { + header = Lang:t("menu_sell_station_header_accept"), + txt = Lang:t("menu_manage_sell_station_footer")..math.percent(Config.GasStationSellPercentage, GasStationCost)..",-", + icon = "fas fa-usd", + params = { + event = "qb-fuel:stations:client:sellstation:menu", + args = { + location = location, + } + }, + }, + { + header = Lang:t("menu_header_close"), + txt = Lang:t("menu_manage_close"), + icon = "fas fa-times-circle", + params = { + event = "qb-menu:closeMenu", + } + }, + }) + end + end + end) + + RegisterNetEvent('qb-fuel:stations:client:managefunds', function(location) -- Menu, seen after selecting the Manage this Location Option. + QBCore.Functions.TriggerCallback('qb-fuel:server:isowner', function(result) + local CitizenID = QBCore.Functions.GetPlayerData().citizenid + if result then + if Config.FuelDebug then print("The Location: "..CurrentLocation.." is owned by ID: "..CitizenID) end + CanOpen = true + else + QBCore.Functions.Notify(Lang:t("station_not_owner"), 'error', 7500) + if Config.FuelDebug then print("The Location: "..CurrentLocation.." is not owned by ID: "..CitizenID) end + CanOpen = false + end + end, CurrentLocation) + UpdateStationInfo("all") + Wait(5) + Wait(Config.WaitTime) + if CanOpen then + + if Config.Ox.Menu then + lib.registerContext({ + id = 'managefundsmenu', + title = Lang:t("menu_manage_company_funds_header_2")..Config.GasStations[CurrentLocation].label, + options = { + { + title = Lang:t("menu_manage_company_funds_withdraw_header"), + description = Lang:t("menu_manage_company_funds_withdraw_footer"), + icon = "fas fa-arrow-left", + arrow = false, -- puts arrow to the right + event = 'qb-fuel:stations:client:WithdrawFunds', + args = { + location = location, + } + }, + { + title = Lang:t("menu_manage_company_funds_deposit_header"), + description = Lang:t("menu_manage_company_funds_deposit_footer"), + icon = "fas fa-arrow-right", + arrow = false, -- puts arrow to the right + event = 'qb-fuel:stations:client:DepositFunds', + args = { + location = location, + } + }, + { + title = Lang:t("menu_manage_company_funds_return_header"), + description = Lang:t("menu_manage_company_funds_return_footer"), + icon = "fas fa-circle-left", + arrow = false, -- puts arrow to the right + event = 'qb-fuel:stations:client:managemenu', + args = { + location = location, + } + }, + { + title = Lang:t("menu_header_close"), + description = Lang:t("menu_refuel_cancel"), + icon = "fas fa-times-circle", + arrow = false, -- puts arrow to the right + onSelect = function() + lib.hideContext() + end, + }, + }, + }) + lib.showContext('managefundsmenu') + else + exports['qb-menu']:openMenu({ + { + header = Lang:t("menu_manage_company_funds_header_2")..Config.GasStations[CurrentLocation].label, + isMenuHeader = true, + icon = "fas fa-gas-pump", + }, + { + header = Lang:t("menu_manage_company_funds_withdraw_header"), + icon = "fas fa-arrow-left", + txt = Lang:t("menu_manage_company_funds_withdraw_footer"), + params = { + event = "qb-fuel:stations:client:WithdrawFunds", + args = { + location = location, + } + }, + }, + { + header = Lang:t("menu_manage_company_funds_deposit_header"), + icon = "fas fa-arrow-right", + txt = Lang:t("menu_manage_company_funds_deposit_footer"), + params = { + event = "qb-fuel:stations:client:DepositFunds", + args = { + location = location, + } + }, + }, + { + header = Lang:t("menu_manage_company_funds_return_header"), + txt = Lang:t("menu_manage_company_funds_return_footer"), + icon = "fas fa-circle-left", + params = { + event = "qb-fuel:stations:client:managemenu", + args = { + location = location, + } + }, + }, + }) + end + end + end) + + RegisterNetEvent('qb-fuel:stations:client:WithdrawFunds', function(data) + if Config.FuelDebug then print("Triggered Event for: Withdraw!") end + CanOpen = false + local location = CurrentLocation + QBCore.Functions.TriggerCallback('qb-fuel:server:isowner', function(result) + local CitizenID = QBCore.Functions.GetPlayerData().citizenid + if result then + if Config.FuelDebug then print("The Location: "..CurrentLocation.." is owned by ID: "..CitizenID) end + CanOpen = true + else + QBCore.Functions.Notify(Lang:t("station_not_owner"), 'error', 7500) + if Config.FuelDebug then print("The Location: "..CurrentLocation.." is not owned by ID: "..CitizenID) end + CanOpen = false + end + end, CurrentLocation) + Wait(Config.WaitTime) + if CanOpen then + if Config.FuelDebug then print("Showing Input for Withdraw!") end + UpdateStationInfo("balance") + Wait(50) + if Config.Ox.Input then + local Withdraw = lib.inputDialog('Hæv kapitel', { + { type = "input", label = 'Nuværrende kapital', + default = Comma_Value(StationBalance)..",-", + disabled = true }, + { type = "number", label = 'Hæv beløb', + }, + }) + if not Withdraw then return end + WithdrawAmounts = tonumber(Withdraw[2]) + if Withdraw then + if Config.FuelDebug then print("Attempting to Withdraw!") end + Wait(100) + local amount = tonumber(WithdrawAmounts) + if not WithdrawAmounts then QBCore.Functions.Notify(Lang:t("station_amount_invalid"), 'error', 7500) return end + if amount < 1 then QBCore.Functions.Notify(Lang:t("station_withdraw_too_little"), 'error', 7500) return end + if amount > StationBalance then QBCore.Functions.Notify(Lang:t("station_withdraw_too_much"), 'error', 7500) return end + WithdrawAmount = tonumber(amount) + if (StationBalance - WithdrawAmount) < 0 then + QBCore.Functions.Notify(Lang:t("station_withdraw_too_much"), 'error', 7500) + else + TriggerServerEvent('qb-fuel:station:server:Withdraw', amount, location, StationBalance) + end + end + else + local Withdraw = exports['qb-input']:ShowInput({ + header = Lang:t("input_withdraw_funds_header") ..StationBalance..",-", + submitText = Lang:t("input_withdraw_submit_text"), + inputs = { { + type = 'number', + isRequired = true, + name = 'amount', + text = Lang:t("input_withdraw_text") + }} + }) + if Withdraw then + if Config.FuelDebug then print("Attempting to Withdraw!") end + Wait(100) + local amount = tonumber(Withdraw.amount) + if not Withdraw.amount then QBCore.Functions.Notify(Lang:t("station_amount_invalid"), 'error', 7500) return end + if amount < 1 then QBCore.Functions.Notify(Lang:t("station_withdraw_too_little"), 'error', 7500) return end + if amount > StationBalance then QBCore.Functions.Notify(Lang:t("station_withdraw_too_much"), 'error', 7500) return end + WithdrawAmount = tonumber(amount) + if (StationBalance - WithdrawAmount) < 0 then + QBCore.Functions.Notify(Lang:t("station_withdraw_too_much"), 'error', 7500) + else + TriggerServerEvent('qb-fuel:station:server:Withdraw', amount, location, StationBalance) + end + end + end + end + end) + + RegisterNetEvent('qb-fuel:stations:client:DepositFunds', function(data) + if Config.FuelDebug then print("Triggered Event for: Deposit!") end + CanOpen = false + local location = CurrentLocation + QBCore.Functions.TriggerCallback('qb-fuel:server:isowner', function(result) + local CitizenID = QBCore.Functions.GetPlayerData().citizenid + if result then + if Config.FuelDebug then print("The Location: "..CurrentLocation.." is owned by ID: "..CitizenID) end + CanOpen = true + else + QBCore.Functions.Notify(Lang:t("station_not_owner"), 'error', 7500) + if Config.FuelDebug then print("The Location: "..CurrentLocation.." is not owned by ID: "..CitizenID) end + CanOpen = false + end + end, CurrentLocation) + Wait(Config.WaitTime) + if CanOpen then + local bankmoney = QBCore.Functions.GetPlayerData().money['bank'] + if Config.FuelDebug then print("Showing Input for Deposit!") end + UpdateStationInfo("balance") + Wait(50) + if Config.Ox.Input then + local Deposit = lib.inputDialog('Indsæt kapital', { + { type = "input", label = 'Nuværrende kapital', + default = Comma_Value(StationBalance)..",-", + disabled = true }, + { type = "number", label = 'Indsæt beløb', + }, + }) + if not Deposit then return end + DepositAmounts = tonumber(Deposit[2]) + if Deposit then + if Config.FuelDebug then print("Attempting to Deposit!") end + Wait(100) + local amount = tonumber(DepositAmounts) + if not DepositAmounts then QBCore.Functions.Notify(Lang:t("station_amount_invalid"), 'error', 7500) return end + if amount < 1 then QBCore.Functions.Notify(Lang:t("station_deposit_too_little"), 'error', 7500) return end + DepositAmount = tonumber(amount) + if (DepositAmount) > bankmoney then + QBCore.Functions.Notify(Lang:t("station_deposity_too_much"), "error") + else + TriggerServerEvent('qb-fuel:station:server:Deposit', amount, location, StationBalance) + end + end + else + local Deposit = exports['qb-input']:ShowInput({ + header = Lang:t("input_deposit_funds_header") ..StationBalance..",-", + submitText = Lang:t("input_deposit_submit_text"), + inputs = { { + type = 'number', + isRequired = true, + name = 'amount', + text = Lang:t("input_deposit_text") + }} + }) + if Deposit then + if Config.FuelDebug then print("Attempting to Deposit!") end + Wait(100) + local amount = tonumber(Deposit.amount) + if not Deposit.amount then QBCore.Functions.Notify(Lang:t("station_amount_invalid"), 'error', 7500) return end + if amount < 1 then QBCore.Functions.Notify(Lang:t("station_deposit_too_little"), 'error', 7500) return end + DepositAmount = tonumber(amount) + if (DepositAmount) > bankmoney then + QBCore.Functions.Notify(Lang:t("station_deposity_too_much"), "error") + else + TriggerServerEvent('qb-fuel:station:server:Deposit', amount, location, StationBalance) + end + end + end + end + end) + + RegisterNetEvent('qb-fuel:stations:client:Shutoff', function(location) + TriggerServerEvent("qb-fuel:stations:server:Shutoff", location) + end) + + RegisterNetEvent('qb-fuel:stations:client:purchasemenu', function(location) -- Menu, seen after selecting the purchase this location option. + local bankmoney = QBCore.Functions.GetPlayerData().money['bank'] + local costofstation = Config.GasStations[location].cost + GlobalTax(Config.GasStations[location].cost) + + if Config.OneStationPerPerson == true then + QBCore.Functions.TriggerCallback('qb-fuel:server:doesPlayerOwnStation', function(result) + if result then + if Config.FuelDebug then print("Player already owns a station, so disallowing purchase.") end + PlayerOwnsAStation = true + else + if Config.FuelDebug then print("Player doesn't own a station, so continuing purchase checks.") end + PlayerOwnsAStation = false + end + end) + + Wait(Config.WaitTime) + + if PlayerOwnsAStation == true then + QBCore.Functions.Notify('You can only buy one station, and you already own one!', 'error') + return + end + end + + + if bankmoney < costofstation then + QBCore.Functions.Notify(Lang:t("not_enough_money_in_bank").." - "..costofstation..",-", 'error', 7500) return + end + + if Config.Ox.Menu then + lib.registerContext({ + id = 'purchasemenu', + title = Config.GasStations[location].label, + options = { + { + title = Lang:t("menu_purchase_station_confirm_header"), + description = 'Jeg vil godt købe denne tankstation!', + icon = "fas fa-usd", + arrow = true, -- puts arrow to the right + event = 'qb-fuel:stations:client:purchaselocation', + args = { + location = location, + }, + metadata = { + {label = 'Tankstationen koster ', value = Comma_Value(costofstation)..",- "..Lang:t("menu_purchase_station_header_2")}, + }, + }, + { + title = Lang:t("menu_header_close"), + description = Lang:t("menu_refuel_cancel"), + icon = "fas fa-times-circle", + arrow = false, -- puts arrow to the right + onSelect = function() + lib.hideContext() + end, + }, + }, + }) + lib.showContext('purchasemenu') + else + exports['qb-menu']:openMenu({ + { + header = Config.GasStations[location].label, + isMenuHeader = true, + icon = "fas fa-gas-pump", + }, + { + header = "", + icon = "fas fa-info-circle", + isMenuHeader = true, + txt = Lang:t("menu_purchase_station_header_1")..costofstation..Lang:t("menu_purchase_station_header_2"), + }, + { + header = Lang:t("menu_purchase_station_confirm_header"), + icon = "fas fa-check-circle", + txt = Lang:t("menu_purchase_station_confirm_footer")..costofstation..',-!' , + params = { + event = "qb-fuel:stations:client:purchaselocation", + args = { + location = location, + } + } + }, + { + header = Lang:t("menu_header_close"), + txt = Lang:t("menu_purchase_station_cancel_footer"), + icon = "fas fa-times-circle", + params = { + event = "qb-menu:closeMenu", + } + }, + }) + end + end) + + RegisterNetEvent('qb-fuel:stations:openmenu', function() -- Menu #1, the first menu you see. + DisablePurchase = true + DisableOwnerMenu = true + ShutOffDisabled = false + + QBCore.Functions.TriggerCallback('qb-fuel:server:locationpurchased', function(result) + if result then + if Config.FuelDebug then print("The Location: "..CurrentLocation.." is owned.") end + DisablePurchase = true + else + if Config.FuelDebug then print("The Location: "..CurrentLocation.." is not owned.") end + DisablePurchase = false + DisableOwnerMenu = true + end + end, CurrentLocation) + + QBCore.Functions.TriggerCallback('qb-fuel:server:isowner', function(result) + local CitizenID = QBCore.Functions.GetPlayerData().citizenid + if result then + if Config.FuelDebug then print("The Location: "..CurrentLocation.." is owned by ID: "..CitizenID) end + DisableOwnerMenu = false + else + if Config.FuelDebug then print("The Location: "..CurrentLocation.." is not owned by ID: "..CitizenID) end + DisableOwnerMenu = true + end + end, CurrentLocation) + + if Config.EmergencyShutOff then + QBCore.Functions.TriggerCallback('qb-fuel:server:checkshutoff', function(result) + if result == true then + PumpState = "lukket." + elseif result == false then + PumpState = "åbne." + else + PumpState = "nil" + end + + if Config.FuelDebug then print("The result from Callback: Config.GasStations["..CurrentLocation.."].shutoff = "..PumpState) end + end, CurrentLocation) + else + PumpState = "åbnet." + ShutOffDisabled = true + end + + Wait(Config.WaitTime) + + if Config.Ox.Menu then + lib.registerContext({ + id = 'stationmainmenu', + title = Config.GasStations[CurrentLocation].label, + options = { + { + title = Lang:t("menu_ped_manage_location_header"), + description = Lang:t("menu_ped_manage_location_footer"), + icon = "fas fa-gas-pump", + arrow = false, -- puts arrow to the right + event = 'qb-fuel:stations:client:managemenu', + args = CurrentLocation, + disabled = DisableOwnerMenu, + }, + { + title = Lang:t("menu_ped_purchase_location_header"), + description = Lang:t("menu_ped_purchase_location_footer"), + icon = "fas fa-usd", + arrow = false, -- puts arrow to the right + event = 'qb-fuel:stations:client:purchasemenu', + args = CurrentLocation, + disabled = DisablePurchase, + }, + { + title = Lang:t("menu_ped_emergency_shutoff_header"), + description = Lang:t("menu_ped_emergency_shutoff_footer")..PumpState, + icon = "fas fa-gas-pump", + arrow = false, -- puts arrow to the right + event = 'qb-fuel:stations:client:Shutoff', + args = CurrentLocation, + disabled = ShutOffDisabled, + }, + { + title = Lang:t("menu_header_close"), + description = Lang:t("menu_refuel_cancel"), + icon = "fas fa-times-circle", + arrow = false, -- puts arrow to the right + onSelect = function() + lib.hideContext() + end, + }, + }, + }) + lib.showContext('stationmainmenu') + else + exports['qb-menu']:openMenu({ + { + header = Config.GasStations[CurrentLocation].label, + isMenuHeader = true, + icon = "fas fa-gas-pump", + }, + { + header = Lang:t("menu_ped_manage_location_header"), + txt = Lang:t("menu_ped_manage_location_footer"), + icon = "fas fa-usd", + params = { + event = "qb-fuel:stations:client:managemenu", + args = CurrentLocation, + }, + disabled = DisableOwnerMenu, + }, + { + header = Lang:t("menu_ped_purchase_location_header"), + txt = Lang:t("menu_ped_purchase_location_footer"), + icon = "fas fa-usd", + params = { + event = "qb-fuel:stations:client:purchasemenu", + args = CurrentLocation, + }, + disabled = DisablePurchase, + }, + { + header = Lang:t("menu_ped_emergency_shutoff_header"), + txt = Lang:t("menu_ped_emergency_shutoff_footer")..PumpState, + icon = "fas fa-gas-pump", + params = { + event = "qb-fuel:stations:client:Shutoff", + args = CurrentLocation, + }, + disabled = ShutOffDisabled, + }, + { + header = Lang:t("menu_ped_close_header"), + txt = Lang:t("menu_ped_close_footer"), + icon = "fas fa-times-circle", + params = { + event = "qb-menu:closeMenu", + } + }, + }) + end + end) + + -- Threads + CreateThread(function() -- Spawn the Peds for Gas Stations when the resource starts. + SpawnGasStationPeds() + end) +end -- For Config.PlayerOwnedGasStationsEnabled check, don't remove! \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-fuel/client/utils.lua b/resources/[qb]/[qb_core]/qb-fuel/client/utils.lua new file mode 100644 index 0000000..9af547e --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-fuel/client/utils.lua @@ -0,0 +1,153 @@ +local QBCore = exports[Config.Core]:GetCoreObject() + +function GetFuel(vehicle) + return DecorGetFloat(vehicle, Config.FuelDecor) +end + +function SetFuel(vehicle, fuel) + if type(fuel) == 'number' and fuel >= 0 and fuel <= 100 then + SetVehicleFuelLevel(vehicle, fuel + 0.0) + DecorSetFloat(vehicle, Config.FuelDecor, GetVehicleFuelLevel(vehicle)) + end +end + +function LoadAnimDict(dict) + while (not HasAnimDictLoaded(dict)) do + RequestAnimDict(dict) + Wait(5) + end +end + +function GlobalTax(value) + if Config.GlobalTax < 0.1 then + return 0 + end + local tax = (value / 100 * Config.GlobalTax) + return tax +end + +function Comma_Value(amount) + local formatted = amount + while true do + formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2') + if (k==0) then + break + end + end + return formatted +end + +function math.percent(percent, maxvalue) + if tonumber(percent) and tonumber(maxvalue) then + return (maxvalue*percent)/100 + end + return false +end + +function Round(num, numDecimalPlaces) + local mult = 10^(numDecimalPlaces or 0) + return math.floor(num * mult + 0.5) / mult +end + +function GetCurrentVehicleType(vehicle) + if not vehicle then + vehicle = GetVehiclePedIsIn(PlayerPedId(), true) + end + if not vehicle then return false end + local vehiclename = GetEntityModel(vehicle) + for _, currentCar in pairs(Config.ElectricVehicles) do + if currentCar == vehiclename or joaat(currentCar) == vehiclename then + return 'electricvehicle' + end + end + return 'gasvehicle' +end + +function CreateBlip(coords, label) + local blip = AddBlipForCoord(coords) + local vehicle = GetCurrentVehicleType() + local electricbolt = Config.ElectricSprite -- Sprite + if vehicle == 'electricvehicle' then + SetBlipSprite(blip, electricbolt) -- This is where the fuel thing will get changed into the electric bolt instead of the pump. + SetBlipColour(blip, 5) + else + SetBlipSprite(blip, 361) + SetBlipColour(blip, 4) + end + SetBlipScale(blip, 0.6) + SetBlipDisplay(blip, 4) + SetBlipAsShortRange(blip, true) + BeginTextCommandSetBlipName("STRING") + AddTextComponentString(label) + EndTextCommandSetBlipName(blip) + return blip +end + +function GetClosestVehicle(coords) + local ped = PlayerPedId() + local vehicles = GetGamePool('CVehicle') + local closestDistance = -1 + local closestVehicle = -1 + if coords then + coords = type(coords) == 'table' and vec3(coords.x, coords.y, coords.z) or coords + else + coords = GetEntityCoords(ped) + end + for i = 1, #vehicles, 1 do + local vehicleCoords = GetEntityCoords(vehicles[i]) + local distance = #(vehicleCoords - coords) + if closestDistance == -1 or closestDistance > distance then + closestVehicle = vehicles[i] + closestDistance = distance + end + end + return closestVehicle, closestDistance +end + + +function IsPlayerNearVehicle() + if Config.FuelDebug then + print("Checking if player is near a vehicle!") + end + local vehicle = GetClosestVehicle() + local closestVehCoords = GetEntityCoords(vehicle) + if #(GetEntityCoords(PlayerPedId(), closestVehCoords)) > 3.0 then + return true + end + return false +end + +function IsVehicleBlacklisted(veh) + if Config.FuelDebug then print("checking if vehicle is blacklisted") end + if veh and veh ~= 0 then + veh = string.lower(GetDisplayNameFromVehicleModel(GetEntityModel(veh))) + if Config.FuelDebug then print(veh) end + -- Puts Vehicles In Blacklist if you have electric charging on. + if not Config.ElectricVehicleCharging then + for i = 1, #Config.ElectricVehicles, 1 do + local cur = Config.ElectricVehicles[i] + if cur == veh then + print("Vehicle: "..cur.." is in the Blacklist.") + return true + end + end + end + + for i = 1, #Config.NoFuelUsage, 1 do + local cur = Config.NoFuelUsage[i] + if cur == veh then + if Config.FuelDebug then + print("Vehicle: "..cur.." is in the Blacklist.") + end + -- If the veh equals a vehicle in the list then return true. + return true + end + end + -- Default False + if Config.FuelDebug then print("Vehicle is not blacklisted.") end + return false + else + if Config.FuelDebug then print("veh is nil!") end + return false + end +end \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-fuel/fxmanifest.lua b/resources/[qb]/[qb_core]/qb-fuel/fxmanifest.lua new file mode 100644 index 0000000..39492bc --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-fuel/fxmanifest.lua @@ -0,0 +1,51 @@ +fx_version 'cerulean' +game 'gta5' +author 'https://www.github.com/CodineDev' -- Base Refueling System: (https://github.com/InZidiuZ/LegacyFuel), other code by Codine (https://www.github.com/CodineDev). +description 'qb-fuel' +version '2.1.4' + +client_scripts { + '@PolyZone/client.lua', + 'client/fuel_cl.lua', + 'client/electric_cl.lua', + 'client/station_cl.lua', + 'client/utils.lua' +} + +server_scripts { + 'server/fuel_sv.lua', + 'server/station_sv.lua', + 'server/electric_sv.lua', + '@oxmysql/lib/MySQL.lua', +} + +shared_scripts { + 'shared/config.lua', + '@qb-core/shared/locale.lua', + '@ox_lib/init.lua', -- OX_Lib, only line this in if you have ox_lib and are using them. + 'locales/da.lua', -- English Locales +} + +exports { -- Call with exports['qb-fuel']:GetFuel or exports['qb-fuel']:SetFuel + 'GetFuel', + 'SetFuel' +} + +lua54 'yes' + +dependencies { -- Make sure these are started before qb-fuel in your server.cfg! + 'PolyZone', + 'interact-sound', + -- QB-Core Functionality (Input, Target, Menu) + 'qb-target', + 'qb-input', + 'qb-menu', + -- QBox | Overextended Functionalities (Input, Progressbar, Target, Menu etc.) + 'ox_lib', -- Ox Library + -- 'ox_target', +} + +data_file 'DLC_ITYP_REQUEST' 'stream/[electric_nozzle]/electric_nozzle_typ.ytyp' +data_file 'DLC_ITYP_REQUEST' 'stream/[electric_charger]/electric_charger_typ.ytyp' + +provide 'cdn-syphoning' -- This is used to override cdn-syphoning(https://github.com/CodineDev/cdn-syphoning) if you have it installed. If you don't have it installed, don't worry about this. If you do, we recommend removing it and using this instead. diff --git a/resources/[qb]/[qb_core]/qb-fuel/locales/da.lua b/resources/[qb]/[qb_core]/qb-fuel/locales/da.lua new file mode 100644 index 0000000..e880778 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-fuel/locales/da.lua @@ -0,0 +1,255 @@ +local Translations = { + -- Fuel + set_fuel_debug = "Sæt brændstof til:", + cancelled = "Annulleret.", + not_enough_money = "Du har ikke nok penge!", + not_enough_money_in_bank = "Du har ikke nok penge i banken!", + not_enough_money_in_cash = "Du har ikke nok penge i lommen!", + more_than_zero = "Du skal tanke mere end 0L!", + emergency_shutoff_active = "Pumperne er i øjeblikket slukket via nødstop-systemet.", + nozzle_cannot_reach = "Dyserne kan ikke nå så langt!", + station_no_fuel = "Denne station er løbet tør for brændstof!", + station_not_enough_fuel = "Stationen har ikke så meget brændstof!", + show_input_key_special = "Tryk [G] når du er tæt på køretøjet for at tanke det op!", + tank_cannot_fit = "Din tank kan ikke rumme dette!", + tank_already_full = "Dit køretøj er allerede fuldt!", + need_electric_charger = "Jeg har brug for en elektrisk oplader!", + cannot_refuel_inside = "Du kan ikke tanke når du sidder i bilen!", + + -- 2.1.2 -- Reserves Pickup --- + fuel_order_ready = "Din brændstofordre er klar til afhentning! Tag et kig på din GPS for at finde afhentnings-placeringen!", + draw_text_fuel_dropoff = "[E] Aflever lastbil", + fuel_pickup_success = "Dine reserver er blevet fyldt til: %sL", + fuel_pickup_failed = "Ron Oil har lige afleveret brændstof til din station!", + trailer_too_far = "Tankvogn er ikke tilsluttet lastbilen eller er for langt væk!", + + -- 2.1.0 + no_nozzle = "Du har ikke dysen!", + vehicle_is_damaged = "Køretøjet er for beskadiget til at kunne tankes!", + vehicle_too_far = "Du er for langt væk til at tanke dette køretøj!", + inside_vehicle = "Du kan ikke tanke når du sidder i bilen!", + you_are_discount_eligible = "Du får en "..Config.EmergencyServicesDiscount['discount'].."% discount hvis du går på arbejde!", + no_fuel = "Intet brændstof", + + -- Electric + electric_more_than_zero = "Du skal oplade mere end 0KW!", + electric_vehicle_not_electric = "Dit køretøj er ikke elektrisk!", + electric_no_nozzle = "Dit køretøj er ikke elektrisk!", + + -- Phone -- + electric_phone_header = "Elektrisk oplader", + electric_phone_notification = "Total pris på el-opladning: ", + fuel_phone_header = "Tankstation", + phone_notification = "Total pris: ", + phone_refund_payment_label = "Refundering ved tankstationen!", + + -- Stations + station_per_liter = " / Liter!", + station_already_owned = "Denne tankstation er allerede ejet!", + station_cannot_sell = "Du kan ikke sælge denne tankstation!", + station_sold_success = "Du har solgt tankstationen!", + station_not_owner = "Du ejer ikke denne tankstation!", + station_amount_invalid = "Beløbet er ugyldigt!", + station_more_than_one = "Du skal købe mere end 1L!", + station_price_too_high = "Denne pris er for høj!", + station_price_too_low = "Denne pris er for lav!", + station_name_invalid = "Dette navn er ugyldigt!", + station_name_too_long = "Navnet kan ikke være længere end "..Config.NameChangeMaxChar.." tegn.", + station_name_too_short = "Navnet skal være længere end "..Config.NameChangeMinChar.." tegn.", + station_withdraw_too_much = "Du kan ikke hæve mere end tankstationen har!", + station_withdraw_too_little = "Du kan ikke hæve mindre end 1,-", + station_success_withdrew_1 = "Du hævedet", + station_success_withdrew_2 = ",- fra tankstationens kapital!", -- Leave the space @ the front! + station_deposit_too_much = "Du kan ikke indsætte mere end du har!", + station_deposit_too_little = "Du kan ikke indsætte mindre end 1,-", + station_success_deposit_1 = "Du indskød", + station_success_deposit_2 = ",- i tankstationens kapital", -- Leave the space @ the front! + station_cannot_afford_deposit = "Du har ikke råd til at indsætte den mængde", + station_shutoff_success = "Du har ændret nødstop-ventilen for denne lokation!", + station_fuel_price_success = "Du ændrede prisen på brændstof til ", + station_reserve_cannot_fit = "Reserverne kan ikke rumme dette!", + station_reserves_over_max = "Du kan ikke købe denne mængde da det vil være større end det maksimale mængde på "..Config.MaxFuelReserves.." Liter", + station_name_change_success = "Du ændrede navnet til: ", -- Leave the space @ the end! + station_purchased_location_payment_label = "Køb tankstation: ", + station_sold_location_payment_label = "Du solgte tankstation: ", + station_withdraw_payment_label = "Hævede penge fra tankstations-kapital: ", + station_deposit_payment_label = "Indskød penge i tankstations-kapital: ", + -- All Progress Bars + prog_refueling_vehicle = "Tanker køretøj..", + prog_electric_charging = "Oplader..", + prog_jerry_can_refuel = "Genopfylder benzindunk...", + prog_syphoning = "Tapper brændstof..", + + -- Menus + + menu_header_cash = "Kontanter", + menu_header_bank = "Kreditkort", + menu_header_close = "Annuller", + menu_pay_with_cash = "Betal med kontanter. \nDu har: ", + menu_pay_with_bank = "Betal med kreditkort.", + menu_refuel_header = "Tankstation", + menu_refuel_accept = "Jeg vil gerne købe brændstof.", + menu_refuel_cancel = "Jeg vil faktisk ikke have brændstof mere.", + menu_pay_label_1 = "Brændstof @ ", + menu_pay_label_2 = ",- / L", + menu_header_jerry_can = "Benzindunk", + menu_header_refuel_jerry_can = "Genopfyld benzindunk", + menu_header_refuel_vehicle = "Genopfyld køretøj", + + menu_electric_cancel = "Jeg vil faktisk ikke oplade min bil mere.", + menu_electric_header = "Elektrisk oplader", + menu_electric_accept = "Jeg vil gerne betale for elektricitet.", + menu_electric_payment_label_1 = "Elektricitet @ ", + menu_electric_payment_label_2 = ",- / KW", + + + -- Station Menus + + menu_ped_manage_location_header = "Administrer lokation", + menu_ped_manage_location_footer = "Hvis du er ejeren, kan du administrere denne lokation.", + + menu_ped_purchase_location_header = "Køb lokation", + menu_ped_purchase_location_footer = "Hvis ingen ejer denne lokation, kan du købe den.", + + menu_ped_emergency_shutoff_header = "Manipuler nød-ventil", + menu_ped_emergency_shutoff_footer = "Sluk for brændstof i tilfælde af en nødsituation. \nPumperne er i øjeblikket ", + + menu_ped_close_header = "Stop samtale", + menu_ped_close_footer = "Jeg vil faktisk ikke snakke mere.", + + menu_station_reserves_header = "Køb reservebrændstof til ", + menu_station_reserves_purchase_header = "Køb reservebrændstof til: ", + menu_station_reserves_purchase_footer = "Ja, jeg vil gerne købe brændstof-reserver for ", + menu_station_reserves_cancel_footer = "Jeg har ikke brug for mere brændstof.", + + menu_purchase_station_header_1 = "Den samlede pris bliver: ", + menu_purchase_station_header_2 = ",- inklusiv skat.", + menu_purchase_station_confirm_header = "Bekræft", + menu_purchase_station_confirm_footer = "Jeg vil købe denne tankstation for ", + menu_purchase_station_cancel_footer = "Jeg vil faktisk ikke købe denne lokation. Prisen er vanvittig!", + + menu_sell_station_header = "Sælg ", + menu_sell_station_header_accept = "Sælg tankstation", + menu_sell_station_footer_accept = "Ja, jeg vil gerne sælge denne tankstation for ", + menu_sell_station_footer_close = "Jeg har ikke mere jeg vil diskutere", + + menu_manage_header = "Administreing af ", + menu_manage_reserves_header = "Brændstof-reserve \n", + menu_manage_reserves_footer_1 = " liter ud af ", + menu_manage_reserves_footer_2 = " liter \nDu kan købe mere forneden!", + + menu_manage_purchase_reserves_header = "Køb brændstof", + menu_manage_purchase_reserves_footer = "Jeg vil gerne købe mere brændstof for ", + menu_manage_purchase_reserves_footer_2 = ",- / L!", + + menu_alter_fuel_price_header = "Skfit brændstofpris", + menu_alter_fuel_price_footer_1 = "Jeg vil gerne ændre prisen på brændstof på min tankstation! \nI øjeblikket er prisen ", + + menu_manage_company_funds_header = "Administrer firmakapital", + menu_manage_company_funds_footer = "Jeg vil gerne administrere denne lokations kapital.", + menu_manage_company_funds_header_2 = "Firmakapital for ", + menu_manage_company_funds_withdraw_header = "Hæv kapital", + menu_manage_company_funds_withdraw_footer = "Hæv penge fra tankstationens kapital.", + menu_manage_company_funds_deposit_header = "Indsæt kapital", + menu_manage_company_funds_deposit_footer = "Indsæt penge på tankstationens konto.", + menu_manage_company_funds_return_header = "Tilbage", + menu_manage_company_funds_return_footer = "Jeg vil snakke om noget andet!", + + menu_manage_change_name_header = "Skift navn på tankstationen", + menu_manage_change_name_footer = "Jeg vil gerne ændre navnet på tankstationen.", + + menu_manage_sell_station_footer = "Sælg tankstation for ", + menu_manage_close = "Jeg har ikke mere at diskutere!", + + -- Jerry Can Menus + menu_jerry_can_purchase_header = "Køb benzindunk for ", + menu_jerry_can_footer_full_gas = "Din benzindunk er fuld!", + menu_jerry_can_footer_refuel_gas = "Genopfyld din benzindunk!", + menu_jerry_can_footer_use_gas = "Brug din benzindunk til at genopfylde køretøjet!", + menu_jerry_can_footer_no_gas = "Din benzindunk er tom!", + menu_jerry_can_footer_close = "Jeg har ikke brug for en benzindunk.", + menu_jerry_can_close = "Jeg har ikke lyst til at bruge denne her mere", + + + --Brændstof tyveri bliver ikke brugt, så jeg har ikke oversat det........ + -- Syphon Kit Menus + menu_syphon_kit_full = "Your Syphon Kit is full! It only fits " .. Config.SyphonKitCap .. "L!", + menu_syphon_vehicle_empty = "This vehicle's fuel tank is empty.", + menu_syphon_allowed = "Steal fuel from an unsuspecting victim!", + menu_syphon_refuel = "Put your stolen gasoline to use and refuel the vehicle!", + menu_syphon_empty = "Put your stolen gasoline to use and refuel the vehicle!", + menu_syphon_cancel = "I actually don't want to use this anymore. I've turned a new leaf!", + menu_syphon_header = "Syphon", + menu_syphon_refuel_header = "Refuel", + + + -- Input -- + input_select_refuel_header = "Vælg hvor meget brændstof du vil købe.", + input_refuel_submit = "Køb brændstof", + input_refuel_jerrycan_submit = "Genopfyld benzindunk", + input_max_fuel_footer_1 = "Op til ", + input_max_fuel_footer_2 = "L brændstof.", + input_insert_nozzle = "Sæt dysen i", -- Used for Target as well! + + input_purchase_reserves_header_1 = "Køb brændstof-reserver | Nuværende pris: ", + input_purchase_reserves_header_2 = Config.FuelReservesPrice .. ",- / Liter \nReserve: ", + input_purchase_reserves_header_3 = " Liter | Fuld genopfyldning koster: ", + input_purchase_reserves_submit_text = "Køb brændstof-reserver", + input_purchase_reserves_text = 'Køb brændstof-reserver.', + + input_alter_fuel_price_header_1 = "Skift brændstofpris \nNuværende pris: ", + input_alter_fuel_price_header_2 = ",- / Liter", + input_alter_fuel_price_submit_text = "Skift brændstofpris", + + input_change_name_header_1 = "Skift ", + input_change_name_header_2 = "'s navn.", + input_change_name_submit_text = "Gem navneændring", + input_change_name_text = "Nyt navn..", + + input_withdraw_funds_header = "Hæv kapital \nNuværende kapital: ", + input_withdraw_submit_text = "Hæv", + input_withdraw_text = "Hæv kapital", + + input_deposit_funds_header = "Indsæt kapital \nNuværende kapital: ", + input_deposit_submit_text = "Indsæt", + input_deposit_text = "Indsæt kapital", + + -- Target + grab_electric_nozzle = "Tag el-kabel", + insert_electric_nozzle = "Sæt el-kabel i", + grab_nozzle = "Tag dysen", + return_nozzle = "Returner dysen", + grab_special_nozzle = "Tag special dysen", + return_special_nozzle = "Returner special dysen", + buy_jerrycan = "Køb benzindunk", + station_talk_to_ped = "Snak med medarbejderen", + + -- Jerry Can + jerry_can_full = "Din benzindunk er fuld!", + jerry_can_refuel = "Genopfyld din benzindunk!", + jerry_can_not_enough_fuel = "Din benzindunk har ikke så meget brændstof!", + jerry_can_not_fit_fuel = "Din benzindunk kan ikke rumme så meget brændstof!", + jerry_can_success = "Du har genopfyldt din benzindunk!", + jerry_can_success_vehicle = "Du har genopfyldt køretøjet med din benzindunk!", + jerry_can_payment_label = "Købte benzindunk.", + + + -- Brændstof tyveri bliver ikke brugt, så jeg har ikke oversat det........ + -- Syphoning + syphon_success = "Successfully syphoned from vehicle!", + syphon_success_vehicle = "Successfully fueled the vehicle with the Syphon Kit!", + syphon_electric_vehicle = "This vehicle is electric!", + syphon_no_syphon_kit = "You need something to syphon gas with.", + syphon_inside_vehicle = "You cannot syphon from the inside of the vehicle!", + syphon_more_than_zero = "You have to steal more than 0L!", + syphon_kit_cannot_fit_1 = "You cannot syphon this much, your can won't fit it! You can only fit: ", + syphon_kit_cannot_fit_2 = " Liters.", + syphon_not_enough_gas = "You don't have enough gas to refuel that much!", + syphon_dispatch_string = "(10-90) - Gasoline Theft", +} + +Lang = Locale:new({ + phrases = Translations, + warnOnMissing = true, + fallbackLang = Lang, +}) \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-fuel/server/electric_sv.lua b/resources/[qb]/[qb_core]/qb-fuel/server/electric_sv.lua new file mode 100644 index 0000000..49d4f38 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-fuel/server/electric_sv.lua @@ -0,0 +1,64 @@ +-- Variables +local QBCore = exports[Config.Core]:GetCoreObject() + +-- Functions +local function GlobalTax(value) + local tax = (value / 100 * Config.GlobalTax) + return tax +end + +-- Events +RegisterNetEvent("qb-fuel:server:electric:OpenMenu", function(amount, inGasStation, hasWeapon, purchasetype, FuelPrice) + local src = source + if not src then print("SRC is nil!") return end + local player = QBCore.Functions.GetPlayer(src) + if not player then print("Player is nil!") return end + local FuelCost = amount*FuelPrice + local tax = GlobalTax(FuelCost) + local total = tonumber(FuelCost + tax) + if not amount then if Config.FuelDebug then print("Electric Recharge Amount is invalid!") end TriggerClientEvent('QBCore:Notify', src, Lang:t("electric_more_than_zero"), 'error') return end + Wait(50) + if inGasStation and not hasWeapon then + if Config.RenewedPhonePayment and purchasetype == "bank" then + TriggerClientEvent("qb-fuel:client:electric:phone:PayForFuel", src, amount) + else + if Config.Ox.Menu then + TriggerClientEvent('cdn-electric:client:OpenContextMenu', src, math.ceil(total), amount, purchasetype) + else + TriggerClientEvent('qb-menu:client:openMenu', src, { + { + header = Lang:t("menu_electric_header"), + isMenuHeader = true, + icon = "fas fa-bolt", + }, + { + header = "", + icon = "fas fa-info-circle", + isMenuHeader = true, + txt = Lang:t("menu_purchase_station_header_1")..math.ceil(total)..Lang:t("menu_purchase_station_header_2"), + }, + { + header = Lang:t("menu_purchase_station_confirm_header"), + icon = "fas fa-check-circle", + txt = Lang:t("menu_electric_accept"), + params = { + event = "qb-fuel:client:electric:ChargeVehicle", + args = { + fuelamounttotal = amount, + purchasetype = purchasetype, + } + } + }, + { + header = Lang:t("menu_header_close"), + txt = Lang:t("menu_electric_cancel"), + icon = "fas fa-times-circle", + params = { + event = "qb-menu:closeMenu", + } + }, + }) + end + end + end +end) \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-fuel/server/fuel_sv.lua b/resources/[qb]/[qb_core]/qb-fuel/server/fuel_sv.lua new file mode 100644 index 0000000..c43d404 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-fuel/server/fuel_sv.lua @@ -0,0 +1,213 @@ +-- Variables +local QBCore = exports[Config.Core]:GetCoreObject() + +-- Functions +local function GlobalTax(value) + local tax = (value / 100 * Config.GlobalTax) + return tax +end + +--- Events +if Config.RenewedPhonePayment then + RegisterNetEvent('qb-fuel:server:phone:givebackmoney', function(amount) + local src = source + local player = QBCore.Functions.GetPlayer(src) + player.Functions.AddMoney("bank", math.ceil(amount), Lang:t("phone_refund_payment_label")) + end) +end + +RegisterNetEvent("qb-fuel:server:OpenMenu", function(amount, inGasStation, hasWeapon, purchasetype, FuelPrice) + local src = source + if not src then return end + local player = QBCore.Functions.GetPlayer(src) + if not player then return end + if not amount then if Config.FuelDebug then print("Amount is invalid!") end TriggerClientEvent('QBCore:Notify', src, Lang:t("more_than_zero"), 'error') return end + local FuelCost = amount*FuelPrice + local tax = GlobalTax(FuelCost) + local total = tonumber(FuelCost + tax) + if inGasStation == true and not hasWeapon then + if Config.RenewedPhonePayment and purchasetype == "bank" then + TriggerClientEvent("qb-fuel:client:phone:PayForFuel", src, amount) + else + if Config.Ox.Menu then + if Config.FuelDebug then print("going to open the context menu (OX)") end + TriggerClientEvent('qb-fuel:client:OpenContextMenu', src, total, amount, purchasetype) + else + TriggerClientEvent('qb-menu:client:openMenu', src, { + { + header = Lang:t("menu_refuel_header"), + isMenuHeader = true, + icon = "fas fa-gas-pump", + }, + { + header = "", + icon = "fas fa-info-circle", + isMenuHeader = true, + txt = Lang:t("menu_purchase_station_header_1")..math.ceil(total)..Lang:t("menu_purchase_station_header_2") , + }, + { + header = Lang:t("menu_purchase_station_confirm_header"), + icon = "fas fa-check-circle", + txt = Lang:t("menu_refuel_accept"), + params = { + event = "qb-fuel:client:RefuelVehicle", + args = { + fuelamounttotal = amount, + purchasetype = purchasetype, + } + } + }, + { + header = Lang:t("menu_header_close"), + txt = Lang:t("menu_refuel_cancel"), + icon = "fas fa-times-circle", + params = { + event = "qb-menu:closeMenu", + } + }, + }) + end + end + end +end) + +RegisterNetEvent("qb-fuel:server:PayForFuel", function(amount, purchasetype, FuelPrice, electric) + local src = source + if not src then return end + local Player = QBCore.Functions.GetPlayer(src) + if not Player then return end + local total = math.ceil(amount) + if amount < 1 then + total = 0 + end + local moneyremovetype = purchasetype + if Config.FuelDebug then print("Player is attempting to purchase fuel with the money type: " ..moneyremovetype) end + if Config.FuelDebug then print("Attempting to charge client: $"..total.." for Fuel @ "..FuelPrice.." PER LITER | PER KW") end + if purchasetype == "bank" then + moneyremovetype = "bank" + elseif purchasetype == "cash" then + moneyremovetype = "cash" + end + local payString = Lang:t("menu_pay_label_1") ..FuelPrice..Lang:t("menu_pay_label_2") + if electric then payString = Lang:t("menu_electric_payment_label_1") ..FuelPrice..Lang:t("menu_electric_payment_label_2") end + Player.Functions.RemoveMoney(moneyremovetype, total, payString) +end) + +RegisterNetEvent("qb-fuel:server:purchase:jerrycan", function(purchasetype) + local src = source if not src then return end + local Player = QBCore.Functions.GetPlayer(src) if not Player then return end + local tax = GlobalTax(Config.JerryCanPrice) local total = math.ceil(Config.JerryCanPrice + tax) + local moneyremovetype = purchasetype + if purchasetype == "bank" then + moneyremovetype = "bank" + elseif purchasetype == "cash" then + moneyremovetype = "cash" + end + if Config.Ox.Inventory then + local info = {cdn_fuel = tostring(Config.JerryCanGas)} + exports.ox_inventory:AddItem(src, 'jerrycan', 1, info) + local hasItem = exports.ox_inventory:GetItem(src, 'jerrycan', info, 1) + if hasItem then + Player.Functions.RemoveMoney(moneyremovetype, total, Lang:t("jerry_can_payment_label")) + end + else + local info = {gasamount = Config.JerryCanGas} + if Player.Functions.AddItem("jerrycan", 1, false, info) then -- Dont remove money if AddItem() not possible! + TriggerClientEvent('inventory:client:ItemBox', src, QBCore.Shared.Items['jerrycan'], "add") + Player.Functions.RemoveMoney(moneyremovetype, total, Lang:t("jerry_can_payment_label")) + end + end +end) + +--- Jerry Can +if Config.UseJerryCan then + QBCore.Functions.CreateUseableItem("jerrycan", function(source, item) + local src = source + TriggerClientEvent('qb-fuel:jerrycan:refuelmenu', src, item) + end) +end + +--- Syphoning +if Config.UseSyphoning then + QBCore.Functions.CreateUseableItem("syphoningkit", function(source, item) + local src = source + if Config.Ox.Inventory then + if item.metadata.cdn_fuel == nil then + item.metadata.cdn_fuel = '0' + exports.ox_inventory:SetMetadata(src, item.slot, item.metadata) + end + end + TriggerClientEvent('cdn-syphoning:syphon:menu', src, item) + end) +end + +RegisterNetEvent('qb-fuel:info', function(type, amount, srcPlayerData, itemdata) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local srcPlayerData = srcPlayerData + local ItemName = itemdata.name + + if Config.Ox.Inventory then + if itemdata == "jerrycan" then + if amount < 1 or amount > Config.JerryCanCap then if Config.FuelDebug then print("Error, amount is invalid (< 1 or > "..Config.SyphonKitCap..")! Amount:" ..amount) end return end + elseif itemdata == "syphoningkit" then + if amount < 1 or amount > Config.SyphonKitCap then if Config.SyphonDebug then print("Error, amount is invalid (< 1 or > "..Config.SyphonKitCap..")! Amount:" ..amount) end return end + end + if ItemName ~= nil then + -- Ignore -- + itemdata.metadata = itemdata.metadata + itemdata.slot = itemdata.slot + if ItemName == 'jerrycan' then + local fuel_amount = tonumber(itemdata.metadata.cdn_fuel) + if type == "add" then + fuel_amount = fuel_amount + amount + itemdata.metadata.cdn_fuel = tostring(fuel_amount) + exports.ox_inventory:SetMetadata(src, itemdata.slot, itemdata.metadata) + elseif type == "remove" then + fuel_amount = fuel_amount - amount + itemdata.metadata.cdn_fuel = tostring(fuel_amount) + exports.ox_inventory:SetMetadata(src, itemdata.slot, itemdata.metadata) + else + if Config.FuelDebug then print("error, type is invalid!") end + end + elseif ItemName == 'syphoningkit' then + local fuel_amount = tonumber(itemdata.metadata.cdn_fuel) + if type == "add" then + fuel_amount = fuel_amount + amount + itemdata.metadata.cdn_fuel = tostring(fuel_amount) + exports.ox_inventory:SetMetadata(src, itemdata.slot, itemdata.metadata) + elseif type == "remove" then + fuel_amount = fuel_amount - amount + itemdata.metadata.cdn_fuel = tostring(fuel_amount) + exports.ox_inventory:SetMetadata(src, itemdata.slot, itemdata.metadata) + else + if Config.SyphonDebug then print("error, type is invalid!") end + end + end + else + if Config.FuelDebug then + print("ItemName is invalid!") + end + end + else + if itemdata.info.name == "jerrycan" then + if amount < 1 or amount > Config.JerryCanCap then if Config.FuelDebug then print("Error, amount is invalid (< 1 or > "..Config.SyphonKitCap..")! Amount:" ..amount) end return end + elseif itemdata.info.name == "syphoningkit" then + if amount < 1 or amount > Config.SyphonKitCap then if Config.SyphonDebug then print("Error, amount is invalid (< 1 or > "..Config.SyphonKitCap..")! Amount:" ..amount) end return end + end + + if type == "add" then + srcPlayerData.items[itemdata.slot].info.gasamount = srcPlayerData.items[itemdata.slot].info.gasamount + amount + Player.Functions.SetInventory(srcPlayerData.items) + elseif type == "remove" then + srcPlayerData.items[itemdata.slot].info.gasamount = srcPlayerData.items[itemdata.slot].info.gasamount - amount + Player.Functions.SetInventory(srcPlayerData.items) + else + if Config.SyphonDebug then print("error, type is invalid!") end + end + end +end) + +RegisterNetEvent('cdn-syphoning:callcops', function(coords) + TriggerClientEvent('cdn-syphoning:client:callcops', -1, coords) +end) diff --git a/resources/[qb]/[qb_core]/qb-fuel/server/station_sv.lua b/resources/[qb]/[qb_core]/qb-fuel/server/station_sv.lua new file mode 100644 index 0000000..b5e7e10 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-fuel/server/station_sv.lua @@ -0,0 +1,360 @@ +if Config.PlayerOwnedGasStationsEnabled then -- This is so Player Owned Gas Stations are a Config Option, instead of forced. Set this option in shared/config.lua! + + -- Variables + local QBCore = exports[Config.Core]:GetCoreObject() + local FuelPickupSent = {} -- This is in case of an issue with vehicles not spawning when picking up vehicles. + + -- Functions + local function GlobalTax(value) + local tax = (value / 100 * Config.GlobalTax) + return tax + end + + function math.percent(percent, maxvalue) + if tonumber(percent) and tonumber(maxvalue) then + return (maxvalue*percent)/100 + end + return false + end + + local function UpdateStationLabel(location, newLabel, src) + if not newLabel or newLabel == nil then + if Config.FuelDebug then print('Attempting to fetch label for Location #'..location) end + MySQL.Async.fetchAll('SELECT label FROM fuel_stations WHERE location = ?', {location}, function(result) + if result then + local data = result[1] + if data == nil then return end + local newLabel = data.label + TriggerClientEvent('qb-fuel:client:updatestationlabels', -1, location, newLabel) + else + if Config.FuelDebug then print('No Result! (UpdateStationLabel() line 29 station_sv.lua)') end + cb(false) + end + end) + else + if Config.FuelDebug then print(newLabel, location) end + MySQL.Async.execute('UPDATE fuel_stations SET label = ? WHERE `location` = ?', {newLabel, location}) + if src then + TriggerClientEvent('qb-fuel:client:updatestationlabels', src, location, newLabel) + else + TriggerClientEvent('qb-fuel:client:updatestationlabels', -1, location, newLabel) + end + end + end + + -- Events + RegisterNetEvent('qb-fuel:server:updatelocationlabels', function() + local src = source + local location = 0 + for _ in pairs(Config.GasStations) do + location = location + 1 + UpdateStationLabel(location, nil, src) + end + end) + + RegisterNetEvent('qb-fuel:server:buyStation', function(location, CitizenID) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local CostOfStation = Config.GasStations[location].cost + GlobalTax(Config.GasStations[location].cost) + if Player.Functions.RemoveMoney("bank", CostOfStation, Lang:t("station_purchased_location_payment_label")..Config.GasStations[location].label) then + MySQL.Async.execute('UPDATE fuel_stations SET owned = ? WHERE `location` = ?', {1, location}) + MySQL.Async.execute('UPDATE fuel_stations SET owner = ? WHERE `location` = ?', {CitizenID, location}) + end + end) + + RegisterNetEvent('qb-fuel:stations:server:sellstation', function(location) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local GasStationCost = Config.GasStations[location].cost + GlobalTax(Config.GasStations[location].cost) + local SalePrice = math.percent(Config.GasStationSellPercentage, GasStationCost) + if Player.Functions.AddMoney("bank", SalePrice, Lang:t("station_sold_location_payment_label")..Config.GasStations[location].label) then + MySQL.Async.execute('UPDATE fuel_stations SET owned = ? WHERE `location` = ?', {0, location}) + MySQL.Async.execute('UPDATE fuel_stations SET owner = ? WHERE `location` = ?', {0, location}) + TriggerClientEvent('QBCore:Notify', src, Lang:t("station_sold_success"), 'success') + + else + TriggerClientEvent('QBCore:Notify', src, Lang:t("station_cannot_sell"), 'error') + end + end) + + RegisterNetEvent('qb-fuel:station:server:Withdraw', function(amount, location, StationBalance) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local setamount = (StationBalance - amount) + if Config.FuelDebug then print("Attempting to withdraw $"..amount.." from Location #"..location.."'s Balance!") end + if amount > StationBalance then TriggerClientEvent('QBCore:Notify', src, Lang:t("station_withdraw_too_much"), 'success') return end + MySQL.Async.execute('UPDATE fuel_stations SET balance = ? WHERE `location` = ?', {setamount, location}) + Player.Functions.AddMoney("bank", amount, Lang:t("station_withdraw_payment_label")..Config.GasStations[location].label) + TriggerClientEvent('QBCore:Notify', src, Lang:t("station_success_withdrew_1")..amount..Lang:t("station_success_withdrew_2"), 'success') + end) + + RegisterNetEvent('qb-fuel:station:server:Deposit', function(amount, location, StationBalance) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local setamount = (StationBalance + amount) + if Config.FuelDebug then print("Attempting to deposit $"..amount.." to Location #"..location.."'s Balance!") end + if Player.Functions.RemoveMoney("bank", amount, Lang:t("station_deposit_payment_label")..Config.GasStations[location].label) then + MySQL.Async.execute('UPDATE fuel_stations SET balance = ? WHERE `location` = ?', {setamount, location}) + TriggerClientEvent('QBCore:Notify', src, Lang:t("station_success_deposit_1")..amount..Lang:t("station_success_deposit_2"), 'success') + else + TriggerClientEvent('QBCore:Notify', src, Lang:t("station_cannot_afford_deposit")..amount.."!", 'success') + end + end) + + RegisterNetEvent('qb-fuel:stations:server:Shutoff', function(location) + local src = source + if Config.FuelDebug then print("Toggling Emergency Shutoff Valves for Location #"..location) end + Config.GasStations[location].shutoff = not Config.GasStations[location].shutoff + Wait(5) + TriggerClientEvent('QBCore:Notify', src, Lang:t("station_shutoff_success"), 'success') + if Config.FuelDebug then print('Successfully altered the shutoff valve state for location #'..location..'!') end + if Config.FuelDebug then print(Config.GasStations[location].shutoff) end + end) + + RegisterNetEvent('qb-fuel:station:server:updatefuelprice', function(fuelprice, location) + local src = source + if Config.FuelDebug then print('Attempting to update Location #'..location.."'s Fuel Price to a new price: $"..fuelprice) end + MySQL.Async.execute('UPDATE fuel_stations SET fuelprice = ? WHERE `location` = ?', {fuelprice, location}) + TriggerClientEvent('QBCore:Notify', src, Lang:t("station_fuel_price_success")..fuelprice..",-"..Lang:t("station_per_liter"), 'success') + end) + + RegisterNetEvent('qb-fuel:station:server:updatereserves', function(reason, amount, currentlevel, location) + if reason == "remove" then + NewLevel = (currentlevel - amount) + elseif reason == "add" then + NewLevel = (currentlevel + amount) + else + if Config.FuelDebug then print("Reason is not a valid string! It should be 'add' or 'remove'!") end + end + if Config.FuelDebug then print('Attempting to '..reason..' '..amount..' to / from Location #'..location.."'s Reserves!") end + MySQL.Async.execute('UPDATE fuel_stations SET fuel = ? WHERE `location` = ?', {NewLevel, location}) + if Config.FuelDebug then print('Successfully executed the previous SQL Update!') end + end) + + RegisterNetEvent('qb-fuel:station:server:updatebalance', function(reason, amount, StationBalance, location, FuelPrice) + if Config.FuelDebug then print("Amount: "..amount) end + local Price = (FuelPrice * tonumber(amount)) + local StationGetAmount = math.floor(Config.StationFuelSalePercentage * Price) + if reason == "remove" then + NewBalance = (StationBalance - StationGetAmount) + elseif reason == "add" then + NewBalance = (StationBalance + StationGetAmount) + else + if Config.FuelDebug then print("Reason is not a valid string! It should be 'add' or 'remove'!") end + end + if Config.FuelDebug then print('Attempting to '..reason..' '..StationGetAmount..' to / from Location #'..location.."'s Balance!") end + MySQL.Async.execute('UPDATE fuel_stations SET balance = ? WHERE `location` = ?', {NewBalance, location}) + if Config.FuelDebug then print('Successfully executed the previous SQL Update!') end + end) + + + RegisterNetEvent('qb-fuel:stations:server:buyreserves', function(location, price, amount) + local location = location + local price = math.ceil(price) + local amount = amount + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local result = MySQL.Sync.fetchAll('SELECT * FROM fuel_stations WHERE `location` = ?', {location}) + if result then + if Config.FuelDebug then print("Result Fetched!") end + for k, v in pairs(result) do + local gasstationinfo = json.encode(v) + if Config.FuelDebug then print(gasstationinfo) print(v.fuel) end + if v.fuel + amount > Config.MaxFuelReserves then + ReserveBuyPossible = false + if Config.FuelDebug then print("Purchase is not possible, as reserves will be greater than the maximum amount!") end + TriggerClientEvent('QBCore:Notify', src, Lang:t("station_reserves_over_max"), 'error') + elseif v.fuel + amount <= Config.MaxFuelReserves then + ReserveBuyPossible = true + OldAmount = v.fuel + NewAmount = OldAmount + amount + if Config.FuelDebug then print("Purchase is possible, as reserves will be below or equal to the maximum amount!") end + else + if Config.FuelDebug then print('error fetching v.fuel') end + end + end + else + if Config.FuelDebug then print("No Result Fetched!!") end + end + if Config.FuelDebug then print("Attempting Sale Server Side for location: #"..location.." for Price: $"..price) end + if ReserveBuyPossible and Player.Functions.RemoveMoney("bank", price, "Purchased"..amount.."L of Reserves for: "..Config.GasStations[location].label.." @ $"..Config.FuelReservesPrice.." / L!") then + if not Config.OwnersPickupFuel then + MySQL.Async.execute('UPDATE fuel_stations SET fuel = ? WHERE `location` = ?', {NewAmount, location}) + if Config.FuelDebug then print("SQL Execute Update: fuel_station level to: "..NewAmount.. " Math: ("..amount.." + "..OldAmount.." = "..NewAmount) end + else + FuelPickupSent[location] = { + ['src'] = src, + ['refuelAmount'] = NewAmount, + ['amountBought'] = amount, + } + TriggerClientEvent('qb-fuel:station:client:initiatefuelpickup', src, amount, NewAmount, location) + if Config.FuelDebug then print("Initiating a Fuel Pickup for Location: "..location.." with for the amount of "..NewAmount.." | Triggered By: Source: "..src) end + end + + elseif ReserveBuyPossible then + TriggerClientEvent('QBCore:Notify', src, Lang:t("not_enough_money"), 'error') + end + end) + + RegisterNetEvent('qb-fuel:station:server:fuelpickup:failed', function(location) + local src = source + if location then + if FuelPickupSent[location] then + local cid = QBCore.Functions.GetPlayer(src).PlayerData.citizenid + MySQL.Async.execute('UPDATE fuel_stations SET fuel = ? WHERE `location` = ?', {FuelPickupSent[location]['refuelAmount'], location}) + TriggerClientEvent('QBCore:Notify', src, Lang:t("fuel_pickup_failed"), 'success') + -- This will print player information just in case someone figures out a way to exploit this. + print("User encountered an error with fuel pickup, so we are updating the fuel level anyways, and cancelling the pickup. SQL Execute Update: fuel_station level to: "..FuelPickupSent[location].refuelAmount.. " | Source: "..src.." | Citizen Id: "..cid..".") + FuelPickupSent[location] = nil + else + if Config.FuelDebug then + print("`qb-fuel:station:server:fuelpickup:failed` | FuelPickupSent[location] is not valid! Location: "..location) + end + -- They are probably exploiting in some way/shape/form. + end + end + end) + + RegisterNetEvent('qb-fuel:station:server:fuelpickup:finished', function(location) + local src = source + if location then + if FuelPickupSent[location] then + local cid = QBCore.Functions.GetPlayer(src).PlayerData.citizenid + MySQL.Async.execute('UPDATE fuel_stations SET fuel = ? WHERE `location` = ?', {FuelPickupSent[location].refuelAmount, location}) + TriggerClientEvent('QBCore:Notify', src, string.format(Lang:t("fuel_pickup_success"), tostring(tonumber(FuelPickupSent[location].refuelAmount))), 'success') + -- This will print player information just in case someone figures out a way to exploit this. + if Config.FuelDebug then + print("User successfully dropped off fuel truck, so we are updating the fuel level and clearing the pickup table. SQL Execute Update: fuel_station level to: "..FuelPickupSent[location].refuelAmount.. " | Source: "..src.." | Citizen Id: "..cid..".") + end + FuelPickupSent[location] = nil + else + if Config.FuelDebug then + print("FuelPickupSent[location] is not valid! Location: "..location) + end + -- They are probably exploiting in some way/shape/form. + end + end + end) + + RegisterNetEvent('qb-fuel:station:server:updatelocationname', function(newName, location) + local src = source + if Config.FuelDebug then print('Attempting to set name for Location #'..location..' to: '..newName) end + MySQL.Async.execute('UPDATE fuel_stations SET label = ? WHERE `location` = ?', {newName, location}) + if Config.FuelDebug then print('Successfully executed the previous SQL Update!') end + TriggerClientEvent('QBCore:Notify', src, Lang:t("station_name_change_success")..newName.."!", 'success') + TriggerClientEvent('qb-fuel:client:updatestationlabels', -1, location, newName) + end) + + -- Callbacks + QBCore.Functions.CreateCallback('qb-fuel:server:locationpurchased', function(source, cb, location) + if Config.FuelDebug then print("Working on it.") end + local result = MySQL.Sync.fetchAll('SELECT * FROM fuel_stations WHERE `location` = ?', {location}) + if result then + for k, v in pairs(result) do + local gasstationinfo = json.encode(v) + if Config.FuelDebug then print(gasstationinfo) end + local owned = false + if Config.FuelDebug then print(v.owned) end + if v.owned == 1 then + owned = true + if Config.FuelDebug then print("Owned Status: True") end + elseif v.owned == 0 then + owned = false + if Config.FuelDebug then print("Owned Status: False") end + else + if Config.FuelDebug then print("Owned State (v.owned ~= 1 or 0) It must be 1 or 0! 1 = True, 0 = False!") end + end + cb(owned) + end + else + if Config.FuelDebug then print("No Result Fetched!!") end + end + end) + + QBCore.Functions.CreateCallback('qb-fuel:server:doesPlayerOwnStation', function(source, cb) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local citizenid = Player.PlayerData.citizenid + if Config.FuelDebug then print("Checking if Player Already Owns Another Station...") end + local result = MySQL.Sync.fetchAll('SELECT * FROM fuel_stations WHERE `owner` = ?', {citizenid}) + local tableEmpty = next(result) == nil + if result and not tableEmpty then + if Config.FuelDebug then print("Player already owns another station!") print("Result: "..json.encode(result)) end + cb(true) + else + if Config.FuelDebug then print("No Result Sadge!") end + cb(false) + end + end) + + QBCore.Functions.CreateCallback('qb-fuel:server:isowner', function(source, cb, location) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local citizenid = Player.PlayerData.citizenid + if Config.FuelDebug then print("working on it.") end + local result = MySQL.Sync.fetchAll('SELECT * FROM fuel_stations WHERE `owner` = ? AND location = ?', {citizenid, location}) + if result then + if Config.FuelDebug then print("Got result!") print("Result: "..json.encode(result)) end + for _, v in pairs(result) do + if Config.FuelDebug then print("Owned State: "..v.owned) print("Owner: "..v.owner) end + if v.owner == citizenid and v.owned == 1 then + cb(true) if Config.FuelDebug then print(citizenid.." is the owner.. owner state == "..v.owned) end + else + cb(false) if Config.FuelDebug then print("The owner is: "..v.owner) end + end + end + else + if Config.FuelDebug then print("No Result Sadge!") end + cb(false) + end + end) + + QBCore.Functions.CreateCallback('qb-fuel:server:fetchinfo', function(source, cb, location) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if Config.FuelDebug then print("Fetching Information for Location: "..location) end + MySQL.Async.fetchAll('SELECT * FROM fuel_stations WHERE location = ?', {location}, function(result) + if result then + cb(result) + if Config.FuelDebug then print(json.encode(result)) end + else + cb(false) + end + end) + end) + + QBCore.Functions.CreateCallback('qb-fuel:server:checkshutoff', function(source, cb, location) + if Config.FuelDebug then print("Fetching Shutoff State for Location: "..location) end + cb(Config.GasStations[location].shutoff) + end) + + QBCore.Functions.CreateCallback('qb-fuel:server:fetchlabel', function(source, cb, location) + if Config.FuelDebug then print("Fetching Shutoff State for Location: "..location) end + MySQL.Async.fetchAll('SELECT label FROM fuel_stations WHERE location = ?', {location}, function(result) + if result then + cb(result) + if Config.FuelDebug then print(result) end + else + cb(false) + end + end) + end) + + -- Startup Process + local function Startup() + if Config.FuelDebug then print("Startup process...") end + local location = 0 + for value in ipairs(Config.GasStations) do + location = location + 1 + + UpdateStationLabel(location) + end + end + + AddEventHandler('onResourceStart', function(resource) + if resource == GetCurrentResourceName() then + Startup() + end + end) + +end -- For Config.PlayerOwnedGasStationsEnabled check, don't remove!\ \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-fuel/shared/config.lua b/resources/[qb]/[qb_core]/qb-fuel/shared/config.lua new file mode 100644 index 0000000..d557b97 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-fuel/shared/config.lua @@ -0,0 +1,1902 @@ +Config = {} +Config.FuelDebug = true -- Used for debugging, although there are not many areas in yet (Default: false) + Enables Setfuel Commands (0, 50, 100). +Config.PolyDebug = true -- Enables Polyzone Debugging to see PolyZones! +Config.ShowNearestGasStationOnly = true -- When enabled, only the nearest gas stations will be shown on the map. +Config.LeaveEngineRunning = false -- When true, the vehicle's engine will be left running upon exit if the player *HOLDS* F. +Config.VehicleBlowUp = true -- When true, there will be a configurable chance of the vehicle blowing up, if you fuel while the engine is on. +Config.BlowUpChance = 5 -- Percentage for Chance of Engine Explosion (Default: 5% or 5) +Config.CostMultiplier = 15 -- Amount to multiply 1 by. This indicates fuel price. (Default: $3.0/l or 3.0) +Config.GlobalTax = 25.0 -- The tax, in %, that people will be charged at the pump. (Default: 15% or 15.0) +Config.FuelNozzleExplosion = false -- When true, it enables the fuel pump exploding when players run away with the nozzle. Highly recommeded to be false. +Config.FuelDecor = "_FUEL_LEVEL" -- Do not touch! (Default: "_FUEL_LEVEL") +Config.RefuelTime = 600 -- Highly recommended to leave at 600. This value will be multiplied times the amount the player is fueling for the progress bar and cancellation logic! DON'T GO BELOW 250, performance WILL drop! +Config.FuelTargetExport = false -- DO NOT USE WITH OX_TARGET! This is only used to fix this qb-target issue: https://github.com/CodineDev/qb-fuel/issues/3.

If you don't have this issue and haven't installed this exports in qb-target, then this should be false. Otherwise there will be an error. + +-- 2.1.1 Update -- +Config.OwnersPickupFuel = true -- If an owner buys fuel, they will have to go pick it up at a configured location. +Config.PossibleDeliveryTrucks = { + "hauler", + "phantom", + -- "phantom3", -- This is an fast version of the normal phantom. + "packer", +} + +Config.DeliveryTruckSpawns = { -- https://i.imgur.com/VS22i8R.jpeg + ['trailer'] = vector4(1724.0, -1649.7, 112.57, 194.24), + ['truck'] = vector4(1727.08, -1664.01, 112.62, 189.62), + ['PolyZone'] = { + ['coords'] = { + vector2(1724.62, -1672.36), + vector2(1719.01, -1648.33), + vector2(1730.99, -1645.62), + vector2(1734.42, -1673.32), + }, + ['minz'] = 110.0, + ['maxz'] = 115.0, + } +} +-- 2.1.1 End + +-- 2.1.0 Update +Config.EmergencyServicesDiscount = { + ['enabled'] = true, -- Enables Emergency Services Getting a discount based on the value below for Refueling & Electricity Charging Cost + ['discount'] = 20, -- % Discount off of price. + ['emergency_vehicles_only'] = true, -- Only allows discounts to be applied to Emergency Vehicles + ['ondutyonly'] = true, -- Discount only applies while on duty. + ['job'] = { + "police", + "sasp", + "trooper", + "ambulance", + } +} +Config.Core = 'qb-core' -- Change this to your core resources (Ex: 'qbx-core' | 'qb-core'), must be qb based! +Config.Ox = { + Inventory = false, -- Uses OX_Inventory's metadata instead of QB-Inventory's. + Menu = false, -- Uses OX Libraries instead of qb-menu. + Input = false, -- Uses Ox Input Dialog instead of qb-input. + DrawText = false, -- Uses Ox DrawText instead of qb-core DrawText. + Progress = false -- Uses Ox ProgressBar instead of progressbar. +} +Config.TargetResource = "qb-target" -- Supported: { 'qb-target', 'ox_target'} -- Others must use the same format as QB-Target or manual configuration is required. +Config.PumpHose = true -- If true, it creates a hose from the pump to the nozzle the client is holding, to give it a more realistic feel. +Config.RopeType = { -- Options: 1-2-3-4-5; 1: Khaki Color, Kind of Thick, 2: Very Thick Khaki Rope, 3: Very Thick Black Rope, 4: Very Thin Black Rope, 5: Same as 3 + ['fuel'] = 3, + ['electric'] = 4, +} +Config.FaceTowardsVehicle = true -- Ped will turn towards the entity's boot bone for refueling, sometimes can result in incorrect nozzle placement when refueling. +Config.VehicleShutoffOnLowFuel = { -- If enabled, vehicles will turn off when the reach 0 fuel. This works well in conjuction with disallowing people to turn on a vehicle with 0 fuel. + ['shutOffLevel'] = 0, -- At this fuel level, the vehicle will shut off. Default: 0, Recommended: 0-5. + ['sounds'] = { + ['enabled'] = true, -- Are Sounds Enabled when vehicle has no fuel? + -- Find sound banks and sounds here: https://pastebin.com/A8Ny8AHZ. + ['audio_bank'] = "DLC_PILOT_ENGINE_FAILURE_SOUNDS", -- Audio Bank of Sound. + ['sound'] = "Landing_Tone", -- Sound Name in Audio Bank. + } +} + +-- 2.1.0 End + +-- Phone -- +Config.RenewedPhonePayment = false -- Enables use of Renewed-Phone Payment System and Notifications + +-- Syphoning -- +Config.UseSyphoning = false -- Follow the Syphoning Install Guide to enable this option! +Config.SyphonDebug = false -- Used for Debugging the syphon portion! +Config.SyphonKitCap = 50 -- Maximum amount (in L) the syphon kit can fit! +Config.SyphonPoliceCallChance = 25 -- Math.Random(1, 100) Default: 25% +Config.SyphonDispatchSystem = "ps-dispatch" -- Options: "ps-dispatch", "qb-dispatch", "qb-default" (just blips) or "custom" (Custom: you must configure yourself!) + +--- Jerry Can ----- +Config.UseJerryCan = true -- Enable the Jerry Can functionality. Will only work if properly installed. +Config.JerryCanCap = 20 -- Maximum amount (in L) the jerrycan can fit! (Default: 50L) +Config.JerryCanPrice = (Config.JerryCanCap * Config.CostMultiplier) -- The price of a jerry can, not including tax. +Config.JerryCanGas = 20 -- The amount of Gas that the Jerry Can you purchase comes with. This should not be bigger that your Config.JerryCanCap! + +-- Animations -- +Config.StealAnimDict = 'anim@amb@clubhouse@tutorial@bkr_tut_ig3@'-- Used for Syphoning +Config.StealAnim = 'machinic_loop_mechandplayer'-- Used for Syphoning +Config.JerryCanAnimDict = 'weapon@w_sp_jerrycan' -- Used for Syphoning & Jerry Can +Config.JerryCanAnim = 'fire' -- Used for Syphoning & Jerry Can +Config.RefuelAnimation = "gar_ig_5_filling_can" -- This is for refueling and charging. +Config.RefuelAnimationDictionary = "timetable@gardener@filling_can" -- This is for refueling and charging. + +--- Player Owned Gas (Gasoline) Ergonomic Refueling Stations (Poggers) --- +Config.PlayerOwnedGasStationsEnabled = true -- When true, peds will be located at all gas stations, and players will be able to talk with peds & purchase gas stations, having to manage fuel supplies. +Config.StationFuelSalePercentage = 0.65 -- % of sales that the station gets. If they sell 4 Liters of Gas for $16 (not including taxes), they will get 16*Config.StationFuelSalePercentage back from the sale. Treat this as tax, also, it balances the profit margins a bit. +Config.EmergencyShutOff = true -- When true, players can walk up to the ped and shut off the pumps at a gas station. While false, this option is disabled, because it can obviously be an issue. +Config.UnlimitedFuel = false -- When true, the fuel stations will not require refuelling by gas station owners, this is for the early stages of implementation. +Config.MaxFuelReserves = 100000 -- This is the maximum amount that the fuel station's reserves can hold. +Config.FuelReservesPrice = 2.0 -- This is the price of fuel reserves for gas station owners. +Config.GasStationSellPercentage = 50 -- This is the percentage that players will get of the gas stations price, when they sell a location! +Config.MinimumFuelPrice = 2 -- This is the minimum value you want to let players set their fuel prices to. +Config.MaxFuelPrice = 18 -- This is the maximum value you want to let players set their fuel prices to. +Config.PlayerControlledFuelPrices = true -- This gives you the option to disable people being able to control fuel prices. When true, players can control the fuel prices via to management menu for the location. +Config.GasStationNameChanges = true -- This gives you the option to disable people being able to change the name of their gas station, only recommended if it becomes a problem. +Config.NameChangeMinChar = 10 -- This is the minimum length that a Gas Station's name must be. +Config.NameChangeMaxChar = 25 -- This is the maximum length that a Gas Station's name must be. +Config.WaitTime = 400 -- This is the wait time after callbacks, if you are having issues with menus not popping up, or being greyed out, up this to around ~300, it is not recommended to go over ~750, as menus will get slower and more unresponsive the higher you go. (Fixes this issue: https://www.shorturl.at/eqS19) +Config.OneStationPerPerson = true -- This prevents players that already own one station from buying another, to prevent monopolies over Gas Stations. + +--- Electric Vehicles +Config.ElectricVehicleCharging = true -- When true, electric vehicles will actually consume resources and decrease 'Fuel / Battery' while driving. This means players will have to recharge their vehicle! +Config.ElectricChargingPrice = 4 -- Per "KW". This value is multiplied times the amount of electricity someone put into their vehicle, to constitute the final cost of the charge. Players whom own the gas station will not recieve the money from electric charging. +Config.ElectricVehicles = { -- The list of Electric Vehicles in the base game. You can add more if needed, use the Vehicle's Spawn Name + "surge", + "iwagen", + "voltic", + "voltic2", + "raiden", + "cyclone", + "tezeract", + "neon", + "omnisegt", + "iwagen", + "caddy", + "caddy2", + "caddy3", + "airtug", + "rcbandito", + "imorgon", + "dilettante", + "khamelion", +} +Config.ElectricSprite = 620 -- This is for when the player is in an electric charger, the blips with change to this sprite. (Sprite with a car with a bolt going through it: 620) +Config.ElectricChargerModel = true -- If you wish, you can set this to false to add your own props, or use a ymap for the props instead. + +-- Basic Configuration Settings +-- Turn on Config.FuelDebug and use this command to get the name for here: getVehNameForBlacklist +Config.NoFuelUsage = { -- This is for you to put vehicles that you don't want to use fuel. + "bmx", +} + +Config.Classes = { -- Class multipliers. If you want SUVs to use less fuel, you can change it to anything under 1.0, and vise versa. + [0] = 1.0, -- Compacts + [1] = 1.0, -- Sedans + [2] = 1.0, -- SUVs + [3] = 1.0, -- Coupes + [4] = 1.3, -- Muscle + [5] = 1.3, -- Sports Classics + [6] = 1.3, -- Sports + [7] = 1.4, -- Super + [8] = 0.9, -- Motorcycles + [9] = 1.2, -- Off-road + [10] = 0.7, -- Industrial + [11] = 0.7, -- Utility + [12] = 1.0, -- Vans + [13] = 0.0, -- Cycles + [14] = 1.0, -- Boats + [15] = 1.0, -- Helicopters + [16] = 1.0, -- Planes + [17] = 1.0, -- Service + [18] = 0.7, -- Emergency + [19] = 1.0, -- Military + [20] = 0.7, -- Commercial + [21] = 1.0, -- Trains +} + +Config.FuelUsage = { -- The left part is at percentage RPM, and the right is how much fuel (divided by 10) you want to remove from the tank every second + [1.0] = 1.3, + [0.9] = 1.1, + [0.8] = 0.9, + [0.7] = 0.8, + [0.6] = 0.7, + [0.5] = 0.5, + [0.4] = 0.3, + [0.3] = 0.2, + [0.2] = 0.1, + [0.1] = 0.1, + [0.0] = 0.0, +} + +Config.AirAndWaterVehicleFueling = { + ['enabled'] = true, + ['locations'] = { + -- MRPD Helipad + [1] = { + ['PolyZone'] = { + ['coords'] = { + vector2(439.96, -973.0), + vector2(458.09, -973.04), + vector2(458.26, -989.47), + vector2(439.58, -989.94), + }, + ['minmax'] = { + ['min'] = 40, + ['max'] = 50.0 + }, + }, + ['draw_text'] = "[G] Genopfyld helikopter", + ['type'] = 'air', + ['whitelist'] = { + ['enabled'] = true, + ['on_duty_only'] = true, + ['whitelisted_jobs'] = { + 'police', 'ambulance' + }, + }, + ['prop'] = { + ['model'] = 'prop_gas_pump_1d', + ['coords'] = vector4(442.08, -977.15, 42.69, 269.52), + } + }, + -- Pillbox Hospital + [2] = { + ['PolyZone'] = { + ['coords'] = { + vector2(340.46, -580.02), + vector2(351.11, -575.06), + vector2(360.2, -578.35), + vector2(364.99, -588.36), + vector2(361.57, -597.44), + vector2(351.71, -601.99), + vector2(342.19, -598.38), + vector2(337.23, -587.49), + }, + ['minmax'] = { + ['min'] = 72.50, + ['max'] = 78.50 + }, + }, + ['draw_text'] = "[G] Genopfyld helikopter", + ['type'] = 'air', + ['whitelist'] = { + ['enabled'] = true, + ['on_duty_only'] = true, + ['whitelisted_jobs'] = { + 'police', 'ambulance' + }, + }, + ['prop'] = { + ['model'] = 'prop_gas_pump_1d', + ['coords'] = vector4(362.65, -592.64, 73.16, 71.26), + } + }, + -- Cental Los Santos Medical Center + [3] = { + ['PolyZone'] = { + ['coords'] = { + vector2(287.81, -1454.52), + vector2(298.6, -1441.48), + vector2(325.74, -1464.21), + vector2(314.95, -1477.29), + }, + ['minmax'] = { + ['min'] = 43.00, + ['max'] = 50.50 + }, + }, + ['draw_text'] = "[G] Genopfyld helikopter", + ['type'] = 'air', + ['whitelist'] = { + ['enabled'] = true, + ['on_duty_only'] = true, + ['whitelisted_jobs'] = { + 'police', 'ambulance' + }, + }, + ['prop'] = { + ['model'] = 'prop_gas_pump_1d', + ['coords'] = vector4(301.12, -1465.61, 45.51, 321.3), + } + }, + -- Devin Weston Terminal + [4] = { + ['PolyZone'] = { + ['coords'] = { + vector2(-944.57, -2963.51), + vector2(-954.6, -2981.75), + vector2(-929.13, -2996.81), + vector2(-918.35, -2978.74), + }, + ['minmax'] = { + ['min'] = 11.00, + ['max'] = 19.50 + }, + }, + ['draw_text'] = "[G] Genopfyld luftfaretøj", + ['type'] = 'air', + ['whitelist'] = { + ['enabled'] = false, + ['on_duty_only'] = false, + ['whitelisted_jobs'] = { + 'police', 'ambulance' + }, + }, + ['prop'] = { + ['model'] = 'prop_gas_pump_1d', + ['coords'] = vector4(-923.12, -2976.81, 12.95, 149.55), + } + }, + -- Back Right Terminal + [5] = { + ['PolyZone'] = { + ['coords'] = { + vector2(-1658.47, -3109.69), + vector2(-1645.78, -3085.85), + vector2(-1664.28, -3074.94), + vector2(-1677.93, -3098.61), + }, + ['minmax'] = { + ['min'] = 12.00, + ['max'] = 19.50 + }, + }, + ['draw_text'] = "[G] Genopfyld luftfaretøj", + ['type'] = 'air', + ['whitelist'] = { + ['enabled'] = false, + ['on_duty_only'] = false, + ['whitelisted_jobs'] = { + 'police', 'ambulance' + }, + }, + ['prop'] = { + ['model'] = 'prop_gas_pump_1d', + ['coords'] = vector4(-1665.44, -3104.53, 12.94, 329.89), + } + }, + -- La Puerta Helicopter Pad #1 + [6] = { + ['PolyZone'] = { + ['coords'] = { + vector2(-701.34, -1441.48), + vector2(-728.05, -1473.15), + vector2(-712.1, -1486.4), + vector2(-685.58, -1454.86), + }, + ['minmax'] = { + ['min'] = 4.00, + ['max'] = 10.50 + }, + }, + ['draw_text'] = "[G] Genopfyld luftfaretøj", + ['type'] = 'air', + ['whitelist'] = { + ['enabled'] = false, + ['on_duty_only'] = false, + ['whitelisted_jobs'] = { + 'police', 'ambulance' + }, + }, + ['prop'] = { + ['model'] = 'prop_gas_pump_1d', + ['coords'] = vector4(-706.13, -1464.14, 4.04, 320.0), + } + }, + -- La Puerta Helicopter Pad #2 + [7] = { + ['PolyZone'] = { + ['coords'] = { + vector2(-777.17, -1446.61), + vector2(-761.78, -1459.59), + vector2(-739.92, -1433.25), + vector2(-755.4, -1420.29), + }, + ['minmax'] = { + ['min'] = 4.00, + ['max'] = 10.50 + }, + }, + ['draw_text'] = "[G] Genopfyld luftfaretøj", + ['type'] = 'air', + ['whitelist'] = { + ['enabled'] = false, + ['on_duty_only'] = false, + ['whitelisted_jobs'] = { + 'police', 'ambulance' + }, + }, + ['prop'] = { + ['model'] = 'prop_gas_pump_1d', + ['coords'] = vector4(-764.81, -1434.32, 4.06, 320.0), + } + }, + -- La Puerta Boat Dock #1 + [8] = { + ['PolyZone'] = { + ['coords'] = { + vector2(-793.1, -1482.94), + vector2(-786.39, -1500.85), + vector2(-809.39, -1508.94), + vector2(-817.48, -1491.62), + }, + ['minmax'] = { + ['min'] = -5.00, + ['max'] = 8.50 + }, + }, + ['draw_text'] = "[G] Genopfyld båd/jetski", + ['type'] = 'water', + ['whitelist'] = { + ['enabled'] = false, + ['on_duty_only'] = false, + ['whitelisted_jobs'] = { + 'police', 'ambulance' + }, + }, + ['prop'] = { + ['model'] = 'prop_gas_pump_1d', + ['coords'] = vector4(-805.9, -1496.68, 0.6, 200.00), + } + }, + -- Fort Zancudo Military Base Hangar + [9] = { + ['PolyZone'] = { + ['coords'] = { + vector2(-2145.24, 3291.63), + vector2(-2127.94, 3281.7), + vector2(-2139.37, 3260.35), + vector2(-2157.69, 3271.1), + }, + ['minmax'] = { + ['min'] = 30.00, + ['max'] = 37.50 + }, + }, + ['draw_text'] = "[G] Genopfyld luftfaretøj", + ['type'] = 'air', + ['whitelist'] = { + ['enabled'] = true, + ['on_duty_only'] = true, + ['whitelisted_jobs'] = { + 'police', 'ambulance' + }, + }, + ['prop'] = { + ['model'] = 'prop_gas_pump_1d', + ['coords'] = vector4(-2148.8, 3283.99, 31.81, 240.0), + } + }, + -- Paleto Bay Police Department + [10] = { + ['PolyZone'] = { + ['coords'] = { + vector2(-497.03, 5987.98), + vector2(-476.48, 6008.6), + vector2(-454.99, 5986.53), + vector2(-475.77, 5966.83), + }, + ['minmax'] = { + ['min'] = 30.00, + ['max'] = 37.50 + }, + }, + ['draw_text'] = "[G] Genopfyld luftfaretøj", + ['type'] = 'air', + ['whitelist'] = { + ['enabled'] = true, + ['on_duty_only'] = true, + ['whitelisted_jobs'] = { + 'police', 'ambulance' + }, + }, + ['prop'] = { + ['model'] = 'prop_gas_pump_1d', + ['coords'] = vector4(-486.22, 5977.65, 30.3, 315.4), + } + }, + -- Grapeseed Airfield + [11] = { + ['PolyZone'] = { + ['coords'] = { + vector2(2094.41, 4771.26), + vector2(2080.85, 4797.71), + vector2(2104.56, 4811.8), + vector2(2118.06, 4782.09), + }, + ['minmax'] = { + ['min'] = 40.00, + ['max'] = 47.50 + }, + }, + ['draw_text'] = "[G] Genopfyld luftfaretøj", + ['type'] = 'air', + ['whitelist'] = { + ['enabled'] = false, + ['on_duty_only'] = false, + ['whitelisted_jobs'] = { + 'police', 'ambulance' + }, + }, + ['prop'] = { + ['model'] = 'prop_gas_pump_1d', + ['coords'] = vector4(2101.82, 4776.8, 40.02, 21.41), + } + }, + -- Grapeseed Airfield + [12] = { + ['PolyZone'] = { + ['coords'] = { + vector2(1347.76, 4277.37), + vector2(1330.47, 4279.02), + vector2(1328.53, 4261.64), + vector2(1346.13, 4260.88), + }, + ['minmax'] = { + ['min'] = 28.00, + ['max'] = 37.50 + }, + }, + ['draw_text'] = "[G] Genopfyld båd/jetski", + ['type'] = 'water', + ['whitelist'] = { + ['enabled'] = false, + ['on_duty_only'] = false, + ['whitelisted_jobs'] = { + 'police', 'ambulance' + }, + }, + ['prop'] = { + ['model'] = 'prop_gas_pump_1d', + ['coords'] = vector4(1338.13, 4269.62, 30.5, 85.00), + } + }, + -- Bob Smith PD + [13] = { + ['PolyZone'] = { + ['coords'] = { + vector2(-1083.85, -837.07), + vector2(-1100.36, -849.84), + vector2(-1108.85, -839.11), + vector2(-1107.04, -837.76), + vector2(-1109.65, -834.04), + vector2(-1104.1, -829.69), + vector2(-1104.29, -829.07), + vector2(-1095.62, -822.42), + }, + ['minmax'] = { + ['min'] = 36.00, + ['max'] = 42.50 + }, + }, + ['draw_text'] = "[G] Genopfyld helikopter", + ['type'] = 'air', + ['whitelist'] = { + ['enabled'] = true, + ['on_duty_only'] = true, + ['whitelisted_jobs'] = { + 'police', 'ambulance' + }, + }, + ['prop'] = { + ['model'] = 'prop_gas_pump_1d', + ['coords'] = vector4(-1089.72, -830.6, 36.68, 129.00), + } + }, + -- Merryweather Helipad + [14] = { + ['PolyZone'] = { + ['coords'] = { + vector2(488.84, -3383.66), + vector2(489.23, -3356.98), + vector2(467.46, -3356.83), + vector2(467.58, -3383.62), + vector2(472.59, -3383.59), + vector2(472.63, -3382.13), + vector2(476.67, -3382.11), + vector2(476.8, -3383.94), + }, + ['minmax'] = { + ['min'] = 4.50, + ['max'] = 10.50 + }, + }, + ['draw_text'] = "[G] Genopfyld helikopter", + ['type'] = 'air', + ['whitelist'] = { + ['enabled'] = false, + ['on_duty_only'] = false, + ['whitelisted_jobs'] = { + 'police', 'ambulance' + }, + }, + ['prop'] = { + ['model'] = 'prop_gas_pump_1d', + ['coords'] = vector4(483.28, -3382.83, 5.07, 0.0), + } + }, + -- Airport Helipad #1 & #2 + [15] = { + ['PolyZone'] = { + ['coords'] = { + vector2(-1133.49, -2860.32), + vector2(-1143.33, -2877.61), + vector2(-1191.03, -2850.14), + vector2(-1180.98, -2832.84), + }, + ['minmax'] = { + ['min'] = 12.50, + ['max'] = 18.50 + }, + }, + ['draw_text'] = "[G] Genopfyld helikopter", + ['type'] = 'air', + ['whitelist'] = { + ['enabled'] = false, + ['on_duty_only'] = false, + ['whitelisted_jobs'] = { + 'police', 'ambulance' + }, + }, + ['prop'] = { + ['model'] = 'prop_gas_pump_1d', + ['coords'] = vector4(-1158.29, -2848.67, 12.95, 240.0), + } + }, + -- Airport Helipad #3 + [16] = { + ['PolyZone'] = { + ['coords'] = { + vector2(-1124.63, -2865.31), + vector2(-1134.74, -2882.56), + vector2(-1108.76, -2897.71), + vector2(-1099.04, -2880.39), + }, + ['minmax'] = { + ['min'] = 12.50, + ['max'] = 18.50 + }, + }, + ['draw_text'] = "[G] Genopfyld helikopter", + ['type'] = 'air', + ['whitelist'] = { + ['enabled'] = false, + ['on_duty_only'] = false, + ['whitelisted_jobs'] = { + 'police', 'ambulance' + }, + }, + ['prop'] = { + ['model'] = 'prop_gas_pump_1d', + ['coords'] = vector4(-1125.15, -2866.97, 12.95, 240.0), + } + }, + -- Sandy Shores Helipad + [17] = { + ['PolyZone'] = { + ['coords'] = { + vector2(1764.15, 3226.34), + vector2(1758.66, 3246.44), + vector2(1777.28, 3250.51), + vector2(1781.89, 3230.8), + }, + ['minmax'] = { + ['min'] = 40.50, + ['max'] = 47.50 + }, + }, + ['draw_text'] = "[G] Genopfyld helikopter", + ['type'] = 'air', + ['whitelist'] = { + ['enabled'] = false, + ['on_duty_only'] = false, + ['whitelisted_jobs'] = { + 'police', 'ambulance' + }, + }, + ['prop'] = { + ['model'] = 'prop_gas_pump_1d', + ['coords'] = vector4(1771.81, 3229.24, 41.51, 15.00), + } + }, + -- Sandy Shores Hangar + [18] = { + ['PolyZone'] = { + ['coords'] = { + vector2(1755.37, 3301.3), + vector2(1764.9, 3294.63), + vector2(1769.42, 3277.19), + vector2(1728.83, 3266.58), + vector2(1721.75, 3291.6), + }, + ['minmax'] = { + ['min'] = 40.00, + ['max'] = 47.50 + }, + }, + ['draw_text'] = "[G] Genopfyld luftfaretøj", + ['type'] = 'air', + ['whitelist'] = { + ['enabled'] = false, + ['on_duty_only'] = false, + ['whitelisted_jobs'] = { + 'police', 'ambulance' + }, + }, + ['prop'] = { + ['model'] = 'prop_gas_pump_1d', + ['coords'] = vector4(1748.31, 3297.08, 40.16, 15.0), + } + }, + -- La Mesa Landing Pad (Custom) + -- Does not work in conjunction with Gabz Trooper PD. + -- [19] = { + -- ['PolyZone'] = { + -- ['coords'] = { + -- vector2(830.66, -1378.54), + -- vector2(834.87, -1382.59), + -- vector2(834.81, -1388.5), + -- vector2(830.75, -1392.54), + -- vector2(824.96, -1392.58), + -- vector2(820.8, -1388.39), + -- vector2(820.84, -1382.65), + -- vector2(824.97, -1378.52) + -- }, + -- ['minmax'] = { + -- ['min'] = 35.67, + -- ['max'] = 38.67 + -- }, + -- }, + -- ['draw_text'] = "[G] Genopfyld luftfaretøj", + -- ['type'] = 'air', + -- ['whitelist'] = { + -- ['enabled'] = false, + -- ['on_duty_only'] = true, + -- ['whitelisted_jobs'] = { + -- 'police', 'ambulance', + -- } + -- }, + -- ['prop'] = { + -- ['model'] = 'prop_gas_pump_1c', + -- ['coords'] = vector4(827.55, -1378.57, 36.67, 1.11) + -- } + -- } + }, + ['refuel_button'] = 47, -- "G" Button for Draw Text. + ['nozzle_length'] = 20.0, -- The max distance you can go from the "Special Pump" before the nozzle in returned to the pump. + ['air_fuel_price'] = 10, -- Price Per Liter of Fuel for Air Vehicles (Emergency Services Discount Still Applies) + ['water_fuel_price'] = 4, -- Price Per Liter of Fuel for Water Vehicles (Emergency Services Discount Still Applies) +} + +Config.GasStations = { -- Configuration options for various gas station related things, including peds, coords and labels. + [1] = { + zones = { + vector2(176.89, -1538.26), + vector2(151.52, -1560.98), + vector2(168.56, -1577.65), + vector2(196.97, -1563.64) + }, + minz = 28.2, + maxz = 30.3, + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = 167.06, + y = -1553.56, + z = 28.26, + h = 220.44, + }, + electriccharger = nil, + electricchargercoords = vector4(175.9, -1546.65, 28.26, 224.29), + label = "Davis Avenue Ron", + }, + [2] = { + zones = { + vector2(-53.03, -1737.50), + vector2(-92.80, -1751.89), + vector2(-91.29, -1759.09), + vector2(-65.53, -1782.58), + vector2(-36.36, -1751.52) + }, + minz = 28.2, + maxz = 30.4, + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = -40.94, + y = -1751.7, + z = 28.42, + h = 140.72, + }, + electriccharger = nil, + electricchargercoords = vector4(-51.09, -1767.02, 28.26, 47.16), + label = "Grove Street LTD", + }, + [3] = { + zones = { + vector2(-543.94, -1218.18), + vector2(-533.71, -1191.67), + vector2(-500.00, -1204.55), + vector2(-521.97, -1232.58) + }, + minz = 17.4, + maxz = 21.04, + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = -531.2, + y = -1220.83, + z = 17.45, + h = 335.73, + }, + electriccharger = nil, + electricchargercoords = vector4(-514.06, -1216.25, 17.46, 66.29), + label = "Dutch London Xero", + }, + [4] = { + zones = { + vector2(-696.77, -948.94), + vector2(-739.47, -951.07), + vector2(-734.73, -906.5), + vector2(-711.0, -906.76), + vector2(-710.65, -903.27), + vector2(-696.82, -903.21), + }, + minz = 18.0, + maxz = 20.4, + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = -705.66, + y = -905.04, + z = 18.22, + h = 179.46, + }, + electriccharger = nil, + electricchargercoords = vector4(-704.64, -935.71, 18.21, 90.02), + label = "Little Seoul LTD", + }, + [5] = { + zones = { + vector2(243.18, -1281.82), + vector2(243.94, -1228.41), + vector2(299.62, -1228.03), + vector2(300.76, -1286.36) + }, + minz = 28.1, + maxz = 31.3, + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = 288.83, + y = -1267.01, + z = 28.44, + h = 93.81, + }, + electriccharger = nil, + electricchargercoords = vector4(279.79, -1237.35, 28.35, 181.07), + label = "Strawberry Ave Xero", + + }, + [6] = { + zones = { + vector2(798.48, -1017.05), + vector2(801.89, -1061.74), + vector2(847.73, -1063.26), + vector2(845.08, -1015.91) + }, + minz = 25.1, + maxz = 28.1, + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = 816.42, + y = -1040.51, + z = 25.75, + h = 2.07, + }, + electriccharger = nil, + electricchargercoords = vector4(834.27, -1028.7, 26.16, 88.39), + label = "Popular Street Ron", + }, + [7] = { + zones = { + vector2(1212.12, -1381.44), + vector2(1221.21, -1395.08), + vector2(1219.70, -1403.41), + vector2(1207.58, -1417.05), + vector2(1194.70, -1418.94), + vector2(1192.80, -1389.02) + }, + minz = 34.1, + maxz = 36.3, + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = 1211.13, + y = -1389.18, + z = 34.38, + h = 177.39, + }, + electriccharger = nil, + electricchargercoords = vector4(1194.41, -1394.44, 34.37, 270.3), + label = "Capital Blvd Ron", + }, + [8] = { + zones = { + vector2(1188.28, -306.38), + vector2(1145.24, -314.19), + vector2(1150.81, -346.52), + vector2(1195.44, -353.92), + vector2(1197.01, -340.55), + }, + minz = 67.1, + maxz = 70.7, + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = 1163.64, + y = -314.21, + z = 68.21, + h = 190.92, + }, + electriccharger = nil, + electricchargercoords = vector4(1168.38, -323.56, 68.3, 280.22), + label = "Mirror Park LTD", + }, + [9] = { + zones = { + vector2(650.76, 229.92), + vector2(599.24, 256.44), + vector2(598.48, 271.21), + vector2(610.61, 287.88), + vector2(634.85, 289.39), + vector2(664.77, 271.21) + }, + minz = 101.9, + maxz = 104.8, + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = 642.08, + y = 260.59, + z = 102.3, + h = 61.39, + }, + electriccharger = nil, + electricchargercoords = vector4(633.64, 247.22, 102.3, 60.29), + label = "Clinton Ave Globe Oil", + }, + [10] = { + zones = { + vector2(-1460.98, -276.89), + vector2(-1419.32, -237.12), + vector2(-1390.91, -270.45), + vector2(-1435.23, -305.68) + }, + minz = 45.0, + maxz = 47.3, + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = -1428.4, + y = -268.69, + z = 45.21, + h = 132.94, + }, + electriccharger = nil, + electricchargercoords = vector4(-1420.51, -278.76, 45.26, 137.35), + label = "North Rockford Ron", + }, + [11] = { + zones = { + vector2(-2135.61, -327.27), + vector2(-2134.85, -286.36), + vector2(-2051.52, -300.00), + vector2(-2054.55, -345.45), + vector2(-2081.82, -347.73), + vector2(-2113.64, -343.18) + }, + minz = 12.0, + maxz = 14.3, + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = -2074.28, + y = -327.22, + z = 12.32, + h = 132.94, + }, + electriccharger = nil, + electricchargercoords = vector4(-2080.61, -338.52, 12.26, 352.21), + label = "Great Ocean Xero", + }, + [12] = { + zones = { + vector2(-91.5, 6431.47), + vector2(-77.83, 6419.75), + vector2(-101.06, 6397.01), + vector2(-113.59, 6409.91) + }, + minz = 30.34, + maxz = 33.5, + pumpheightadd = 1.5, -- For Config.PumpHose + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = -93.02, + y = 6410.11, + z = 30.64, + h = 49.19, + }, + electriccharger = nil, + electricchargercoords =vector4(-98.12, 6403.39, 30.64, 141.49), + label = "Paleto Blvd Xero", + }, + [13] = { + zones = { + vector2(167.08, 6631.73), + vector2(176.47, 6640.66), + vector2(199.71, 6632.08), + vector2(202.3, 6597.25), + vector2(162.95, 6590.22), + vector2(158.64, 6610.64), + }, + minz = 30.7, + maxz = 33.4, + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = 170.44, + y = 6633.74, + z = 30.59, + h = 221.95, + }, + electriccharger = nil, + electricchargercoords = vector4(181.14, 6636.17, 30.61, 179.96), + label = "Paleto Ron", + }, + [14] = { + zones = { + vector2(1684.5, 6413.73), + vector2(1693.67, 6431.38), + vector2(1721.72, 6428.14), + vector2(1710.47, 6402.65) + }, + minz = 31.4, + maxz = 34.2, + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = 1698.62, + y = 6425.84, + z = 31.76, + h = 156.61, + }, + electriccharger = nil, + electricchargercoords = vector4(1714.14, 6425.44, 31.79, 155.94), + label = "Paleto Globe Oil", + }, + [15] = { + zones = { + vector2(1696.59, 4939.02), + vector2(1723.48, 4920.08), + vector2(1698.11, 4886.74), + vector2(1669.70, 4907.20), + vector2(1678.41, 4929.17) + }, + minz = 41.05, + maxz = 43.17, + pumpheightadd = 1.5, -- For Config.PumpHose + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = 1704.59, + y = 4917.5, + z = 41.06, + h = 52.16, + }, + electriccharger = nil, + electricchargercoords = vector4(1703.57, 4937.23, 41.08, 55.74), + label = "Grapeseed LTD", + }, + [16] = { + zones = { + vector2(1972.35, 3777.27), + vector2(1989.02, 3748.11), + vector2(2018.18, 3762.12), + vector2(2001.52, 3790.91) + }, + minz = 31.18, + maxz = 33.60, + pumpheightadd = 1.5, -- For Config.PumpHose + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = 2001.33, + y = 3779.87, + z = 31.18, + h = 211.44, + }, + electriccharger = nil, + electricchargercoords = vector4(1994.54, 3778.44, 31.18, 215.25), + label = "Sandy Shores Xero", + }, + [17] = { + zones = { + vector2(1774.24, 3308.71), + vector2(1752.65, 3345.83), + vector2(1784.47, 3357.95), + vector2(1808.71, 3321.21) + }, + minz = 39.0, + maxz = 44.6, + pumpheightadd = 1.5, -- For Config.PumpHose + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = 1776.57, + y = 3327.36, + z = 40.43, + h = 297.57, + }, + electriccharger = nil, + electricchargercoords = vector4(1770.86, 3337.97, 40.43, 301.1), + label = "Sandy Shores Globe Oil", + }, + [18] = { + zones = { + vector2(2671.21, 3290.53), + vector2(2649.62, 3254.55), + vector2(2682.95, 3237.50), + vector2(2703.79, 3275.38) + }, + minz = 54.24, + maxz = 56.4, + pumpheightadd = 1.5, -- For Config.PumpHose + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = 2673.98, + y = 3266.87, + z = 54.24, + h = 240.9, + }, + electriccharger = nil, + electricchargercoords = vector4(2690.25, 3265.62, 54.24, 58.98), + label = "Senora Freeway Xero", + }, + [19] = { + zones = { + vector2(1188.64, 2651.89), + vector2(1202.27, 2663.64), + vector2(1212.50, 2661.74), + vector2(1217.05, 2651.52), + vector2(1210.61, 2633.33), + vector2(1201.52, 2638.26) + }, + minz = 36.7, + maxz = 38.85, + pumpheightadd = 1.5, -- For Config.PumpHose + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = 1201.68, + y = 2655.24, + z = 36.85, + h = 322.97, + }, + electriccharger = nil, + electricchargercoords = vector4(1208.26, 2649.46, 36.85, 222.32), + label = "Harmony Globe Oil", + }, + [20] = { + zones = { + vector2(1026.14, 2669.70), + vector2(1028.03, 2640.91), + vector2(1058.33, 2640.53), + vector2(1055.30, 2668.94) + }, + minz = 38.24, + maxz = 40.55, + pumpheightadd = 1.5, -- For Config.PumpHose + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = 1039.44, + y = 2664.37, + z = 38.55, + h = 10.07, + }, + electriccharger = nil, + electricchargercoords = vector4(1033.32, 2662.91, 38.55, 95.38), + label = "Route 68 Globe Oil", + }, + [21] = { + zones = { + vector2(269.70, 2606.44), + vector2(275.38, 2585.23), + vector2(241.29, 2576.52), + vector2(235.23, 2609.09), + vector2(268.56, 2617.05) + }, + minz = 43.60, + maxz = 45.95, + pumpheightadd = 1.5, -- For Config.PumpHose + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = 265.89, + y = 2598.3, + z = 43.84, + h = 9.88, + }, + electriccharger = nil, + electricchargercoords = vector4(267.96, 2599.47, 43.69, 5.8), + label = "Route 68 Workshop Globe Oil", + }, + [22] = { + zones = { + vector2(46.59, 2795.45), + vector2(27.65, 2775.76), + vector2(49.24, 2754.55), + vector2(68.56, 2778.03) + }, + minz = 56.8, + maxz = 58.9, + pumpheightadd = 1.5, -- For Config.PumpHose + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = 46.53, + y = 2789.05, + z = 56.88, + h = 143.93, + }, + electriccharger = nil, + electricchargercoords = vector4(50.21, 2787.38, 56.88, 147.2), + label = "Route 68 Xero", + }, + [23] = { + zones = { + vector2(-2562.12, 2340.53), + vector2(-2560.98, 2299.62), + vector2(-2514.39, 2300.76), + vector2(-2516.29, 2314.02), + vector2(-2523.86, 2344.70) + }, + minz = 32.05, + maxz = 34.08, + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = -2544.04, + y = 2316.15, + z = 32.22, + h = 2.5, + }, + electriccharger = nil, + electricchargercoords = vector4(-2570.04, 2317.1, 32.22, 21.29), + label = "Route 68 Ron", + }, + [24] = { + zones = { + vector2(2545.08, 2601.14), + vector2(2556.06, 2573.11), + vector2(2545.83, 2568.56), + vector2(2531.06, 2601.14), + vector2(2540.91, 2599.24) + }, + minz = 36.94, + maxz = 38.94, + pumpheightadd = 1.5, -- For Config.PumpHose + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = 2545.02, + y = 2591.72, + z = 36.96, + h = 113.52, + }, + electriccharger = nil, + electricchargercoords = vector4(2545.81, 2586.18, 36.94, 83.74), + label = "Rex's Diner Globe Oil", + }, + [25] = { + zones = { + vector2(2540.15, 373.86), + vector2(2538.26, 345.83), + vector2(2592.80, 343.56), + vector2(2594.70, 369.70), + vector2(2557.58, 384.85) + }, + minz = 107.4, + maxz = 109.4, + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = 2559.36, + y = 373.68, + z = 107.62, + h = 272.2, + }, + electriccharger = nil, + electricchargercoords = vector4(2561.24, 357.3, 107.62, 266.65), + label = "Palmino Freeway Ron", + }, + [26] = { + zones = { + vector2(-1838.64, 787.12), + vector2(-1796.97, 821.97), + vector2(-1770.08, 797.73), + vector2(-1813.26, 762.50) + }, + minz = 136.64, + maxz = 139.9, + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = -1825.33, + y = 800.96, + z = 137.1, + h = 220.96, + }, + electriccharger = nil, + electricchargercoords = vector4(-1819.22, 798.51, 137.16, 315.13), + label = "North Rockford LTD", + }, + [27] = { + zones = { + vector2(-354.55, -1452.65), + vector2(-354.17, -1499.62), + vector2(-301.52, -1497.73), + vector2(-296.59, -1453.03) + }, + minz = 29.5, + maxz = 31.9, + pedmodel = "a_m_m_indian_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = -342.37, + y = -1482.97, + z = 29.71, + h = 273.47, + }, + electriccharger = nil, + electricchargercoords = vector4(-341.63, -1459.39, 29.76, 271.73), + label = "Alta Street Globe Oil", + }, + [28] = { -- Car Meet Location, Line In If Needed. + zones = { + vector2(968.98, -1754.89), + vector2(962.97, -1754.32), + vector2(963.62, -1746.29), + vector2(969.61, -1746.84) + }, + minz = 20.0, + maxz = 22.0, + pedmodel = "u_m_y_smugmech_01", + cost = 4500000, + shutoff = false, + pedcoords = { + x = 976.31, + y = -1746.9, + z = 20.03, + h = 177.72, + }, + electriccharger = nil, + electricchargercoords = vector4(971.98, -1746.81, 20.03, 177.17), + label = "H&O Exports", + }, + --[[ + [28] = { -- Gabz Ottos Autos Location, Line In If Needed. + zones = { + vector2(794.27795410156, -802.88677978516), + vector2(794.19073486328, -784.70434570313), + vector2(834.78155517578, -784.63250732422), + vector2(843.86151123047, -801.45819091797), + vector2(823.64239501953, -801.69488525391), + vector2(811.66571044922, -803.15899658203) + }, + minz = 26.0, + maxz = 27.0, + pedmodel = "a_m_m_indian_01", + cost = 100000, + shutoff = false, + pedcoords = { + x = 819.1, + y = -774.63, + z = 25.23, + h = 83.86, + }, + electriccharger = nil, + electricchargercoords = vector4(837.7554, -793.623, 25.23, 105.22), + label = "Ottos Autos Globe Oil", + }, + ]] + --[[ Example of a New Location + [29] = { + zones = { + https://skyrossm.github.io/PolyZoneCreator/ + Use this for a quick way to add a Gas Station, instead of doing it in game, make sure you included the entire area, including the ped and electric pumps if used. + }, + minz = 0, + maxz = 800.0, + pedmodel = "a_m_m_indian_01", -- This is the model of the ped that will be created for the management menu @ the gas station. + cost = 100000, -- This is the cost of the gas station for someone purchasing it, not including tax. + shutoff = false, -- Leave as false, this is for when someone turns off the pumps. + pedcoords = { -- Vector4, X, Y, Z & Heading. + x = -342.37, + y = -1482.97, + z = 29.71, + h = 273.47, + }, + electriccharger = nil, -- Leave this as nil. + electricchargercoords = vector4(-341.63, -1459.39, 29.76, 271.73), -- This is the location of the electric charger. + label = "Alta Street Globe Oil", -- This is the default label, before someone changes the name. + }, + ]] +} + +-- Profanity Dictionary from another source, used for stopping people from putting the words blacklisted as the name of their gas stations. -- + +Config.ProfanityList = { + "4r5e", + "5h1t", + "5hit", + "a55", + "anal", + "anus", + "ar5e", + "arrse", + "arse", + "ass", + "ass-fucker", + "asses", + "assfucker", + "assfukka", + "asshole", + "assholes", + "asswhole", + "a_s_s", + "b!tch", + "b00bs", + "b17ch", + "b1tch", + "ballbag", + "balls", + "ballsack", + "bastard", + "beastial", + "beastiality", + "bellend", + "bestial", + "bestiality", + "bi+ch", + "biatch", + "bitch", + "bitcher", + "bitchers", + "bitches", + "bitchin", + "bitching", + "bloody", + "blow job", + "blowjob", + "blowjobs", + "boiolas", + "bollock", + "bollok", + "boner", + "boob", + "boobs", + "booobs", + "boooobs", + "booooobs", + "booooooobs", + "breasts", + "buceta", + "bugger", + "bum", + "bunny fucker", + "butt", + "butthole", + "buttmuch", + "buttplug", + "c0ck", + "c0cksucker", + "carpet muncher", + "cawk", + "chink", + "cipa", + "cl1t", + "clit", + "clitoris", + "clits", + "cnut", + "cock", + "cock-sucker", + "cockface", + "cockhead", + "cockmunch", + "cockmuncher", + "cocks", + "cocksuck", + "cocksucked", + "cocksucker", + "cocksucking", + "cocksucks", + "cocksuka", + "cocksukka", + "cok", + "cokmuncher", + "coksucka", + "coon", + "cox", + "crap", + "cum", + "cummer", + "cumming", + "cums", + "cumshot", + "cunilingus", + "cunillingus", + "cunnilingus", + "cunt", + "cuntlick", + "cuntlicker", + "cuntlicking", + "cunts", + "cyalis", + "cyberfuc", + "cyberfuck", + "cyberfucked", + "cyberfucker", + "cyberfuckers", + "cyberfucking", + "d1ck", + "damn", + "dick", + "dickhead", + "dildo", + "dildos", + "dink", + "dinks", + "dirsa", + "dlck", + "dog-fucker", + "doggin", + "dogging", + "donkeyribber", + "doosh", + "duche", + "dyke", + "ejaculate", + "ejaculated", + "ejaculates", + "ejaculating", + "ejaculatings", + "ejaculation", + "ejakulate", + "f u c k", + "f u c k e r", + "f4nny", + "fag", + "fagging", + "faggitt", + "faggot", + "faggs", + "fagot", + "fagots", + "fags", + "fanny", + "fannyflaps", + "fannyfucker", + "fanyy", + "fatass", + "fcuk", + "fcuker", + "fcuking", + "feck", + "fecker", + "felching", + "fellate", + "fellatio", + "fingerfuck", + "fingerfucked", + "fingerfucker", + "fingerfuckers", + "fingerfucking", + "fingerfucks", + "fistfuck", + "fistfucked", + "fistfucker", + "fistfuckers", + "fistfucking", + "fistfuckings", + "fistfucks", + "flange", + "fook", + "fooker", + "fuck", + "fucka", + "fucked", + "fucker", + "fuckers", + "fuckhead", + "fuckheads", + "fuckin", + "fucking", + "fuckings", + "fuckingshitmotherfucker", + "fuckme", + "fucks", + "fuckwhit", + "fuckwit", + "fudge packer", + "fudgepacker", + "fuk", + "fuker", + "fukker", + "fukkin", + "fuks", + "fukwhit", + "fukwit", + "fux", + "fux0r", + "f_u_c_k", + "gangbang", + "gangbanged", + "gangbangs", + "gaylord", + "gaysex", + "goatse", + "God", + "god-dam", + "god-damned", + "goddamn", + "goddamned", + "hardcoresex", + "hell", + "heshe", + "hoar", + "hoare", + "hoer", + "homo", + "hore", + "horniest", + "horny", + "hotsex", + "jack-off", + "jackoff", + "jap", + "jerk-off", + "jism", + "jiz", + "jizm", + "jizz", + "kawk", + "knob", + "knobead", + "knobed", + "knobend", + "knobhead", + "knobjocky", + "knobjokey", + "kock", + "kondum", + "kondums", + "kum", + "kummer", + "kumming", + "kums", + "kunilingus", + "l3i+ch", + "l3itch", + "labia", + "lust", + "lusting", + "m0f0", + "m0fo", + "m45terbate", + "ma5terb8", + "ma5terbate", + "masochist", + "master-bate", + "masterb8", + "masterbat*", + "masterbat3", + "masterbate", + "masterbation", + "masterbations", + "masturbate", + "mo-fo", + "mof0", + "mofo", + "mothafuck", + "mothafucka", + "mothafuckas", + "mothafuckaz", + "mothafucked", + "mothafucker", + "mothafuckers", + "mothafuckin", + "mothafucking", + "mothafuckings", + "mothafucks", + "mother fucker", + "motherfuck", + "motherfucked", + "motherfucker", + "motherfuckers", + "motherfuckin", + "motherfucking", + "motherfuckings", + "motherfuckka", + "motherfucks", + "muff", + "mutha", + "muthafecker", + "muthafuckker", + "muther", + "mutherfucker", + "n1gga", + "n1gger", + "nazi", + "nigg3r", + "nigg4h", + "nigga", + "niggah", + "niggas", + "niggaz", + "nigger", + "niggers", + "nob", + "nob jokey", + "nobhead", + "nobjocky", + "nobjokey", + "numbnuts", + "nutsack", + "orgasim", + "orgasims", + "orgasm", + "orgasms", + "p0rn", + "pawn", + "pecker", + "penis", + "penisfucker", + "phonesex", + "phuck", + "phuk", + "phuked", + "phuking", + "phukked", + "phukking", + "phuks", + "phuq", + "pigfucker", + "pimpis", + "piss", + "pissed", + "pisser", + "pissers", + "pisses", + "pissflaps", + "pissin", + "pissing", + "pissoff", + "poop", + "porn", + "porno", + "pornography", + "pornos", + "prick", + "pricks", + "pron", + "pube", + "pusse", + "pussi", + "pussies", + "pussy", + "pussys", + "rectum", + "retard", + "rimjaw", + "rimming", + "s hit", + "s.o.b.", + "sadist", + "schlong", + "screwing", + "scroat", + "scrote", + "scrotum", + "semen", + "sex", + "sh!+", + "sh!t", + "sh1t", + "shag", + "shagger", + "shaggin", + "shagging", + "shemale", + "shi+", + "shit", + "shitdick", + "shite", + "shited", + "shitey", + "shitfuck", + "shitfull", + "shithead", + "shiting", + "shitings", + "shits", + "shitted", + "shitter", + "shitters", + "shitting", + "shittings", + "shitty", + "skank", + "slut", + "sluts", + "smegma", + "smut", + "snatch", + "son-of-a-bitch", + "spac", + "spunk", + "s_h_i_t", + "t1tt1e5", + "t1tties", + "teets", + "teez", + "testical", + "testicle", + "tit", + "titfuck", + "tits", + "titt", + "tittie5", + "tittiefucker", + "titties", + "tittyfuck", + "tittywank", + "titwank", + "tosser", + "turd", + "tw4t", + "twat", + "twathead", + "twatty", + "twunt", + "twunter", + "v14gra", + "v1gra", + "vagina", + "viagra", + "vulva", + "w00se", + "wang", + "wank", + "wanker", + "wanky", + "whoar", + "whore", + "willies", + "willy", + "xrated", + "xxx", +} diff --git a/resources/[qb]/[qb_core]/qb-fuel/stream/[electric_charger]/electric_charger.ydr b/resources/[qb]/[qb_core]/qb-fuel/stream/[electric_charger]/electric_charger.ydr new file mode 100644 index 0000000..20638b1 Binary files /dev/null and b/resources/[qb]/[qb_core]/qb-fuel/stream/[electric_charger]/electric_charger.ydr differ diff --git a/resources/[qb]/[qb_core]/qb-fuel/stream/[electric_charger]/electric_charger_typ.ytyp b/resources/[qb]/[qb_core]/qb-fuel/stream/[electric_charger]/electric_charger_typ.ytyp new file mode 100644 index 0000000..a406d93 Binary files /dev/null and b/resources/[qb]/[qb_core]/qb-fuel/stream/[electric_charger]/electric_charger_typ.ytyp differ diff --git a/resources/[qb]/[qb_core]/qb-fuel/stream/[electric_nozzle]/electric_nozzle.ydr b/resources/[qb]/[qb_core]/qb-fuel/stream/[electric_nozzle]/electric_nozzle.ydr new file mode 100644 index 0000000..0346a6f Binary files /dev/null and b/resources/[qb]/[qb_core]/qb-fuel/stream/[electric_nozzle]/electric_nozzle.ydr differ diff --git a/resources/[qb]/[qb_core]/qb-fuel/stream/[electric_nozzle]/electric_nozzle_typ.ytyp b/resources/[qb]/[qb_core]/qb-fuel/stream/[electric_nozzle]/electric_nozzle_typ.ytyp new file mode 100644 index 0000000..df1c743 Binary files /dev/null and b/resources/[qb]/[qb_core]/qb-fuel/stream/[electric_nozzle]/electric_nozzle_typ.ytyp differ diff --git a/resources/[qb]/[qb_core]/qb-fuel/version b/resources/[qb]/[qb_core]/qb-fuel/version new file mode 100644 index 0000000..7d2ed7c --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-fuel/version @@ -0,0 +1 @@ +2.1.4 diff --git a/resources/[qb]/[qb_core]/qb-idcard/LICENSE b/resources/[qb]/[qb_core]/qb-idcard/LICENSE new file mode 100644 index 0000000..b96ecd8 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-idcard/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 uyuyorum {um} + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/resources/[qb]/[qb_core]/qb-idcard/README.md b/resources/[qb]/[qb_core]/qb-idcard/README.md new file mode 100644 index 0000000..40af0ff --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-idcard/README.md @@ -0,0 +1,113 @@ +# qb-idcard +[![Github Alp1x](https://img.shields.io/badge/Github-alp1x-pink?style=for-the-badge&logo=github)](https://github.com/alp1x/) +[![Donate Tebex](https://img.shields.io/badge/Coffee-Donate-%23FFDD00?style=for-the-badge&logo=buymeacoffee)](https://uyuyorum.tebex.io/package/4721743) +[![License MIT](https://img.shields.io/badge/License-MIT-green?style=for-the-badge&logo=opensourceinitiative)](https://choosealicense.com/licenses/mit/) + + +Simple id card you can use for **[qb-core](https://github.com/qbcore-framework/qb-core)** + + +

+ + + + + +

+ +## How to use +### Let's delete the default CreateUseableItem codes ++ qb-inventory > server > main.lua > Find and Delete + ```lua +CreateUsableItem("driver_license", function(source, item) + local playerPed = GetPlayerPed(source) + local playerCoords = GetEntityCoords(playerPed) + local players = QBCore.Functions.GetPlayers() + for _, v in pairs(players) do + local targetPed = GetPlayerPed(v) + local dist = #(playerCoords - GetEntityCoords(targetPed)) + if dist < 3.0 then + TriggerClientEvent('chat:addMessage', v, { + template = '', + args = { + "Drivers License", + item.info.firstname, + item.info.lastname, + item.info.birthdate, + item.info.type + } + } + ) + end + end +end) + +CreateUsableItem("id_card", function(source, item) + local playerPed = GetPlayerPed(source) + local playerCoords = GetEntityCoords(playerPed) + local players = QBCore.Functions.GetPlayers() + for _, v in pairs(players) do + local targetPed = GetPlayerPed(v) + local dist = #(playerCoords - GetEntityCoords(targetPed)) + if dist < 3.0 then + local gender = "Man" + if item.info.gender == 1 then + gender = "Woman" + end + TriggerClientEvent('chat:addMessage', v, { + template = '', + args = { + "ID Card", + item.info.citizenid, + item.info.firstname, + item.info.lastname, + item.info.birthdate, + gender, + item.info.nationality + } + } + ) + end + end +end) + +``` +#### qb-core > shared > items.lua > add item policecard + +### inventory > server > main.lua > find QBCore.Commands.Add("giveitem" and add +```lua + elseif itemData["name"] == "weaponlicense" then + info.firstname = Player.PlayerData.charinfo.firstname + info.lastname = Player.PlayerData.charinfo.lastname + info.birthdate = Player.PlayerData.charinfo.birthdate + elseif itemData["name"] == "lawyerpass" then + info.firstname = Player.PlayerData.charinfo.firstname + info.lastname = Player.PlayerData.charinfo.lastname + info.birthdate = Player.PlayerData.charinfo.birthdate + elseif itemData["name"] == "policecard" then + info.firstname = Player.PlayerData.charinfo.firstname + info.lastname = Player.PlayerData.charinfo.lastname + info.birthdate = Player.PlayerData.charinfo.birthdate + info.gender = Player.PlayerData.charinfo.gender + info.nationality = Player.PlayerData.charinfo.nationality +``` + ++ server.cfg ```ensure qb-idcard or [qb] folder``` ++ use item {show nui} ++ hide nui key {config.js} + +## Changelog (v3.5) ++ Vue.js ++ Auto close config.js ++ Close key config.js ++ CSS cleaned ++ More flexible now + +## Changelog (v3) ++ Added config.js and language setting ++ More flexible now ++ Added police card and badge prop,animation + + +## License +[MIT](https://choosealicense.com/licenses/mit/) diff --git a/resources/[qb]/[qb_core]/qb-idcard/client/client.lua b/resources/[qb]/[qb_core]/qb-idcard/client/client.lua new file mode 100644 index 0000000..c1f03ca --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-idcard/client/client.lua @@ -0,0 +1,41 @@ +local QBCore = exports['qb-core']:GetCoreObject() +local openid = false + +RegisterNetEvent('qb-idcard:client:open', function(info,nui) + if not openid then + local gender = QBCore.Functions.GetPlayerData().charinfo.gender + SetNuiFocusKeepInput(true) + SetNuiFocus(true) + SendNUIMessage({nui = nui, information = info, gender = gender}) + openid = true + else + QBCore.Functions.Notify("Ikke muligt","error") + end +end) + +RegisterNetEvent('qb-idcard:client:policebadgeanim', function() + local ped = PlayerPedId() + local propname = "prop_fib_badge" + local x,y,z = table.unpack(GetEntityCoords(ped)) + local prop = CreateObject(GetHashKey(propname), x, y, z + 0.2, true, true, true) + local boneIndex = GetPedBoneIndex(ped, 28422) + loadAnimDict("paper_1_rcm_alt1-9") + AttachEntityToEntity(prop, ped, boneIndex, 0.12, 0.01, -0.06, -310.0, 10.0, 150.0, true, true, false, true, 1, true) + TaskPlayAnim(ped, "paper_1_rcm_alt1-9", "player_one_dual-9", 3.0, -1, -1, 50, 0, false, false, false) + Wait(3200) + DeleteEntity(prop) + ClearPedTasks(ped) +end) + +RegisterNUICallback("escape", function() + SetNuiFocusKeepInput(false) + SetNuiFocus(false) + openid = false +end) + +function loadAnimDict(dict) + while not HasAnimDictLoaded(dict) do + RequestAnimDict(dict) + Wait(5) + end +end diff --git a/resources/[qb]/[qb_core]/qb-idcard/config.js b/resources/[qb]/[qb_core]/qb-idcard/config.js new file mode 100644 index 0000000..254ab28 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-idcard/config.js @@ -0,0 +1,42 @@ +QB = []; + +QB.AutoClose = { + close: true, // or true + time: 3000, +}; + +QB.CloseKey = 'Backspace'; + +QB.Idcardlang = { + header: "Los Santos", + langlast: "Fornavn", + langfirst: "Efternavn", + langdob: "Fødselsdato", + langsex: "Køn", + langnat: "Nationalitet" +}; + +QB.Idcard = { + header: "Identitet", + background: "#ebf7fd" +}; + +QB.Driverlicense = { + header: "Kørekort", + background: "#febbbb" +}; + +QB.Weaponlicense = { + header: "Våben licens", + background: "#c7ffe5" +}; + +QB.Lawyerpass = { + header: "Advokat licens", + background: "#f9c491" +}; + +QB.Policeidcard = { + header: "Politi ID kort", + background: "#73b2ff" +}; \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-idcard/fxmanifest.lua b/resources/[qb]/[qb_core]/qb-idcard/fxmanifest.lua new file mode 100644 index 0000000..713df05 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-idcard/fxmanifest.lua @@ -0,0 +1,16 @@ +fx_version 'cerulean' +game 'gta5' + +version 'V1.0' + +ui_page 'ui/ui.html' +client_script 'client/client.lua' +server_script 'server/server.lua' + +files { + 'config.js', + 'ui/ui.html', + 'ui/assets/css/*.css', + 'ui/assets/js/*.js', + 'ui/assets/img/*.png', +} diff --git a/resources/[qb]/[qb_core]/qb-idcard/server/server.lua b/resources/[qb]/[qb_core]/qb-idcard/server/server.lua new file mode 100644 index 0000000..91e2209 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-idcard/server/server.lua @@ -0,0 +1,50 @@ +local QBCore = exports['qb-core']:GetCoreObject() + +local ShowId = function(source, item, nui) + local src = source + local found = false + local character = QBCore.Functions.GetPlayer(src) + local PlayerPed = GetPlayerPed(src) + local PlayerCoords = GetEntityCoords(PlayerPed) + local info = { + ['name'] = item.info.firstname, + ['lastname'] = item.info.lastname, + ['gender'] = item.info.gender, + ['dob'] = item.info.birthdate, + ['nationality'] = item.info.nationality, + ['type'] = item.info.type, + ['image'] = item.info.image, + } + for k, v in pairs(QBCore.Functions.GetPlayers()) do + local TargetPed = GetPlayerPed(v) + local dist = #(PlayerCoords - GetEntityCoords(TargetPed)) + if dist < 3.0 and PlayerPed ~= TargetPed then + TriggerClientEvent('QBCore:Notify', src, "Du viser dit ID") + TriggerClientEvent('qb-idcard:client:open', v, info, nui) + found = true + break + end + end + if not found then TriggerClientEvent('qb-idcard:client:open', src, info, nui) end + if nui == 'policecard' then TriggerClientEvent('qb-idcard:client:policebadgeanim', src) end +end + +QBCore.Functions.CreateUseableItem("id_card", function(source,item) + ShowId(source, item, 'idcard') +end) + +QBCore.Functions.CreateUseableItem("driver_license", function(source,item) + ShowId(source, item, 'driverlicense') +end) + +QBCore.Functions.CreateUseableItem("weaponlicense", function(source,item) + ShowId(source, item, 'weaponlicense') +end) + +QBCore.Functions.CreateUseableItem("lawyerpass", function(source,item) + ShowId(source, item, 'lawyerpass') +end) + +QBCore.Functions.CreateUseableItem("policecard", function(source,item) + ShowId(source, item, 'policecard') +end) diff --git a/resources/[qb]/[qb_core]/qb-idcard/stream/prop_fib_badge+hidr.ytd b/resources/[qb]/[qb_core]/qb-idcard/stream/prop_fib_badge+hidr.ytd new file mode 100644 index 0000000..726b84e Binary files /dev/null and b/resources/[qb]/[qb_core]/qb-idcard/stream/prop_fib_badge+hidr.ytd differ diff --git a/resources/[qb]/[qb_core]/qb-idcard/stream/prop_fib_badge.ydr b/resources/[qb]/[qb_core]/qb-idcard/stream/prop_fib_badge.ydr new file mode 100644 index 0000000..54782fa Binary files /dev/null and b/resources/[qb]/[qb_core]/qb-idcard/stream/prop_fib_badge.ydr differ diff --git a/resources/[qb]/[qb_core]/qb-idcard/ui/assets/css/style.css b/resources/[qb]/[qb_core]/qb-idcard/ui/assets/css/style.css new file mode 100644 index 0000000..0ee83ca --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-idcard/ui/assets/css/style.css @@ -0,0 +1,23 @@ +@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap"); +:root { --font: "Poppins", sans-serif; --zero: 0; --rel: relative; --abs: absolute; } +body { margin: var(--zero); padding: var(--zero); font-family: var(--font); text-transform: uppercase; user-select: none; overflow: hidden;} +.qb-idcard {position: var(--abs);width: 45vh;height: 26vh;right: 3vh;top: 25vh;border: 0.4vh solid rgb(255 255 255 / 16%);border-radius: 1.3vh;} +.qb-idcard .header { text-align: center; font-size: 2vh; font-weight: 600; width: 50vh; } +.qb-idcard .header img {width: 3.5vh;margin-left: -1.2vh;margin-top: 1vh;} +.qb-idcard .header span:first-of-type {position: var(--rel);top: -1vh;left: -3vh;} +.qb-idcard .header span {position: var(--rel);top: -1vh;left: 2vh;} +.qb-idcard .icon { position: absolute; width: 15vh; height: 15vh; left: 3vh; } +.qb-idcard .icon #seximg {width: 13vh;border: 0.1vh solid rgb(0 0 0 / 19%);border-radius: 1vh;margin-top: 2vh;} +.qb-idcard .text { position: absolute; width: 25vh; height: 25vh; left: 14vh; top: 4vh; } +.qb-idcard .text ul li { margin: 0.3vh; list-style-type: none; font-size: 1vh; text-align: right; font-weight: 900; border-bottom: 0.1vh solid rgba(0, 0, 0, 0.07); } +.qb-idcard .text ul span.info { font-size: 1vh; font-weight: 800; padding: 2vh; } +.slidein { animation: 0.2s ease 0s 1 normal forwards running slidein; } +.slideout { animation: 0.3s ease 0s 1 normal forwards running slideout; } +@keyframes slidein { + 0% { right: 7vh; } + 100% { right: 1vh; } +} +@keyframes slideout { + 0% { right: 1vh; } + 100% { right: 7vh; } +} diff --git a/resources/[qb]/[qb_core]/qb-idcard/ui/assets/img/female.png b/resources/[qb]/[qb_core]/qb-idcard/ui/assets/img/female.png new file mode 100644 index 0000000..189bd8e Binary files /dev/null and b/resources/[qb]/[qb_core]/qb-idcard/ui/assets/img/female.png differ diff --git a/resources/[qb]/[qb_core]/qb-idcard/ui/assets/img/icon.png b/resources/[qb]/[qb_core]/qb-idcard/ui/assets/img/icon.png new file mode 100644 index 0000000..d99a21a Binary files /dev/null and b/resources/[qb]/[qb_core]/qb-idcard/ui/assets/img/icon.png differ diff --git a/resources/[qb]/[qb_core]/qb-idcard/ui/assets/img/male.png b/resources/[qb]/[qb_core]/qb-idcard/ui/assets/img/male.png new file mode 100644 index 0000000..82686e7 Binary files /dev/null and b/resources/[qb]/[qb_core]/qb-idcard/ui/assets/img/male.png differ diff --git a/resources/[qb]/[qb_core]/qb-idcard/ui/assets/js/app.js b/resources/[qb]/[qb_core]/qb-idcard/ui/assets/js/app.js new file mode 100644 index 0000000..e0f46ad --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-idcard/ui/assets/js/app.js @@ -0,0 +1,106 @@ +const idcard = Vue.createApp({ + data() { + return { + langheader: QB.Idcardlang.header, + langlast: QB.Idcardlang.langlast, + langfirst: QB.Idcardlang.langfirst, + langsex: QB.Idcardlang.langsex, + langdob: QB.Idcardlang.langdob, + langnat: QB.Idcardlang.langnat, + background: '', + picture: '', + langheader2: '', + Name: '', + Lastname: '', + Dob: '', + Nationality: '', + Sex: '', + show: false, + nui: false, + disabled: false + } + }, + methods: { + eventHandler: function(event) { + let data = event.data; + this.Name = data.information.name; + this.Lastname = data.information.lastname; + this.Dob = data.information.dob; + this.Nationality = data.information.nationality; + this.langsex = QB.Idcardlang.langsex; + this.langnat = QB.Idcardlang.langnat; + if (data.gender == 0) { + this.Sex = 'Mand' + this.picture = 'male.png' + } + else if (data.gender == 1) { + this.Sex = 'Kvinde' + this.picture = 'female.png' + } + else { + this.Sex = 'Intet' + this.picture = 'icon.png' + } + this.disabled = false; + this.nui = true; + this.show = false; + switch (data.nui) { + case 'idcard': + this.langheader2 = QB.Idcard.header; + this.background = QB.Idcard.background; + break; + case 'driverlicense': + this.langheader2 = QB.Driverlicense.header; + this.background = QB.Driverlicense.background; + this.langsex = 'Type'; + this.Sex = 'B kørekort'; + this.langnat= '✔️ Kørekort'; + break; + case 'weaponlicense': + this.show = true; + this.langheader2 = QB.Weaponlicense.header; + this.background = QB.Weaponlicense.background; + break; + case 'lawyerpass': + this.show = true; + this.langheader2 = QB.Lawyerpass.header; + this.background = QB.Lawyerpass.background; + break; + case 'policecard': + this.langheader2 = QB.Policeidcard.header; + this.background = QB.Policeidcard.background; + break; + } + if (QB.AutoClose.close) { + setTimeout(() => { + this.postMessage('escape') + this.disabled = true; + this.nui = false; + }, QB.AutoClose.time) + } + }, + postMessage: function(data) { + fetch(`https://${GetParentResourceName()}/${data}`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + }) + }, + keyupHandler: function(e) { + if (e.key == QB.CloseKey) { + this.postMessage('escape') + this.disabled = true; + setTimeout(() => { + this.nui = false; + }, 500) + } + }, + }, + mounted(){ + window.addEventListener('message',this.eventHandler); + document.addEventListener('keyup', this.keyupHandler); + }, + beforeUnmount() { + window.removeEventListener('message', this.eventHandler); + document.removeEventListener('keyup', this.keyupHandler); + }, +}).mount('#card') diff --git a/resources/[qb]/[qb_core]/qb-idcard/ui/ui.html b/resources/[qb]/[qb_core]/qb-idcard/ui/ui.html new file mode 100644 index 0000000..6369ada --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-idcard/ui/ui.html @@ -0,0 +1,39 @@ + + + + + UM qb-idcard + + + + +
+
+
+ {{langheader}} + + {{langheader2}} +
+
+ +
+
+
    +
  • {{langlast}}
  • + {{Lastname}} +
  • {{langfirst}}
  • + {{Name}} +
  • {{langdob}}
  • + {{Dob}} +
  • {{langsex}}
  • + {{Sex}} +
  • {{langnat}}
  • + {{Nationality}} +
+
+
+
+ + + + diff --git a/resources/[qb]/[qb_core]/qb-input/LICENSE b/resources/[qb]/[qb_core]/qb-input/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-input/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/resources/[qb]/[qb_core]/qb-input/README.md b/resources/[qb]/[qb_core]/qb-input/README.md new file mode 100644 index 0000000..8005c56 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-input/README.md @@ -0,0 +1,90 @@ +# qb-input + +NUI input system for QBCore + +This is a modified version of **[NH Keyboard](https://forum.cfx.re/t/no-longer-supported-standalone-nerohiro-s-keyboard-dynamic-nui-keyboard-input/2506326)** by **[NeroHiro](https://github.com/nerohiro)** + +## Example + +Here is an example commant which will create a basic form and will print out it's inputs: + +```lua +RegisterCommand('testinput', function() + local dialog = exports['qb-input']:ShowInput({ + header = "Test", + submitText = "Bill", + inputs = { + { + text = "Citizen ID (#)", -- text you want to be displayed as a place holder + name = "citizenid", -- name of the input should be unique otherwise it might override + type = "text", -- type of the input + isRequired = true -- Optional [accepted values: true | false] but will submit the form if no value is inputted + }, + { + text = "Bill Price ($)", -- text you want to be displayed as a place holder + name = "billprice", -- name of the input should be unique otherwise it might override + type = "number", -- type of the input - number will not allow non-number characters in the field so only accepts 0-9 + isRequired = false -- Optional [accepted values: true | false] but will submit the form if no value is inputted + }, + { + text = "Bill Type", -- text you want to be displayed as a input header + name = "billtype", -- name of the input should be unique otherwise it might override + type = "radio", -- type of the input - Radio is useful for "or" options e.g; billtype = Cash OR Bill OR bank + options = { -- The options (in this case for a radio) you want displayed, more than 6 is not recommended + { value = "bill", text = "Bill" }, -- Options MUST include a value and a text option + { value = "cash", text = "Cash" }, -- Options MUST include a value and a text option + { value = "bank", text = "Bank" } -- Options MUST include a value and a text option + } + }, + { + text = "Include Tax?", -- text you want to be displayed as a input header + name = "taxincl", -- name of the input should be unique otherwise it might override + type = "checkbox", -- type of the input - Check is useful for "AND" options e.g; taxincle = gst AND business AND othertax + options = { -- The options (in this case for a check) you want displayed, more than 6 is not recommended + { value = "gst", text = "10% incl." }, -- Options MUST include a value and a text option + { value = "business", text = "35% incl." }, -- Options MUST include a value and a text option + { value = "othertax", text = "15% incl." } -- Options MUST include a value and a text option + } + }, + { + text = "Some Select", -- text you want to be displayed as a input header + name = "someselect", -- name of the input should be unique otherwise it might override + type = "select", -- type of the input - Select is useful for 3+ amount of "or" options e.g; someselect = none OR other OR other2 OR other3...etc + options = { -- Select drop down options, the first option will by default be selected + { value = "none", text = "None" }, -- Options MUST include a value and a text option + { value = "other", text = "Other" }, -- Options MUST include a value and a text option + { value = "other2", text = "Other2" }, -- Options MUST include a value and a text option + { value = "other3", text = "Other3" }, -- Options MUST include a value and a text option + { value = "other4", text = "Other4" }, -- Options MUST include a value and a text option + { value = "other5", text = "Other5" }, -- Options MUST include a value and a text option + { value = "other6", text = "Other6" }, -- Options MUST include a value and a text option + } + } + }, + }) + + if dialog ~= nil then + for k,v in pairs(dialog) do + print(k .. " : " .. v) + end + end +end, false) +``` + +## License + + QBCore Framework + Copyright (C) 2021 Joshua Eger + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see diff --git a/resources/[qb]/[qb_core]/qb-input/client/config.lua b/resources/[qb]/[qb_core]/qb-input/client/config.lua new file mode 100644 index 0000000..e282ecd --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-input/client/config.lua @@ -0,0 +1,3 @@ +Config = {} + +Config.Style = "default" diff --git a/resources/[qb]/[qb_core]/qb-input/client/main.lua b/resources/[qb]/[qb_core]/qb-input/client/main.lua new file mode 100644 index 0000000..5de7b87 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-input/client/main.lua @@ -0,0 +1,33 @@ +local properties = nil + +RegisterNUICallback("buttonSubmit", function(data, cb) + SetNuiFocus(false) + properties:resolve(data.data) + properties = nil + cb("ok") +end) + +RegisterNUICallback("closeMenu", function(data, cb) + SetNuiFocus(false) + properties:resolve(nil) + properties = nil + cb("ok") +end) + +function ShowInput(data) + Wait(150) + if not data then return end + if properties then return end + + properties = promise.new() + + SetNuiFocus(true, true) + SendNUIMessage({ + action = "OPEN_MENU", + data = data + }) + + return Citizen.Await(properties) +end + +exports("ShowInput", ShowInput) diff --git a/resources/[qb]/[qb_core]/qb-input/fxmanifest.lua b/resources/[qb]/[qb_core]/qb-input/fxmanifest.lua new file mode 100644 index 0000000..58bd8e0 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-input/fxmanifest.lua @@ -0,0 +1,16 @@ +fx_version 'cerulean' +game 'gta5' + +description 'QB-Input' +version '0.0.1' + +client_script 'client/main.lua' +ui_page 'html/index.html' + +files { + 'html/index.html', + 'html/style.css', + 'html/script.js' +} + +lua54 'yes' \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-input/html/index.html b/resources/[qb]/[qb_core]/qb-input/html/index.html new file mode 100644 index 0000000..d18d599 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-input/html/index.html @@ -0,0 +1,17 @@ + + + + + + QB INPUT + + + + + +
+
+
+
+ + \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-input/html/script.js b/resources/[qb]/[qb_core]/qb-input/html/script.js new file mode 100644 index 0000000..e5532bc --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-input/html/script.js @@ -0,0 +1,192 @@ +let formInputs = {}; + +const OpenMenu = (data) => { + if (data == null || data == "") { + console.log("No data detected"); + return null; + } + + $(`.main-wrapper`).fadeIn(0); + + let form = [ + "
", + `
${ data.header != null ? data.header : "Titel" }
`, + ]; + + data.inputs.forEach((item, index) => { + switch (item.type) { + case "text": + form.push(renderTextInput(item)); + break; + case "password": + form.push(renderPasswordInput(item)); + break; + case "number": + form.push(renderNumberInput(item)); + break; + case "radio": + form.push(renderRadioInput(item)); + break; + case "select": + form.push(renderSelectInput(item)); + break; + case "checkbox": + form.push(renderCheckboxInput(item)); + break; + default: + form.push(`
${item.text}
`); + } + }); + form.push( + `` + ); + + form.push("
"); + + $(".main-wrapper").html(form.join(" ")); + + $("#qb-input-form").on("change", function (event) { + formInputs[$(event.target).attr("name")] = $(event.target).val(); + }); + + $("#qb-input-form").on("submit", async function (event) { + if (event != null) { + event.preventDefault(); + } + let formData = $("#qb-input-form").serializeArray(); + + formData.forEach((item, index) => { + formInputs[item.name] = item.value; + }); + + await $.post( + `https://${GetParentResourceName()}/buttonSubmit`, + JSON.stringify({ data: formInputs }) + ); + CloseMenu(); + }); +}; + +const renderTextInput = (item) => { + const { text, name } = item; + formInputs[name] = item.default ? item.default : ""; + const isRequired = item.isRequired == "true" || item.isRequired ? "required" : ""; + const defaultValue = item.default ? `value="${item.default}"` : "" + + return ` `; +}; +const renderPasswordInput = (item) => { + const { text, name } = item; + formInputs[name] = item.default ? item.default : ""; + const isRequired = item.isRequired == "true" || item.isRequired ? "required" : ""; + const defaultValue = item.default ? `value="${item.default}"` : "" + + return ` `; +}; +const renderNumberInput = (item) => { + try { + const { text, name } = item; + formInputs[name] = item.default ? item.default : ""; + const isRequired = item.isRequired == "true" || item.isRequired ? "required" : ""; + const defaultValue = item.default ? `value="${item.default}"` : "" + + return ``; + } catch (err) { + console.log(err); + return ""; + } +}; + +const renderRadioInput = (item) => { + const { options, name, text } = item; + formInputs[name] = options[0].value; + + let div = `
${text}
`; + div += '
'; + options.forEach((option, index) => { + div += ``; + }); + + div += "
"; + div += "
"; + return div; +}; + +const renderCheckboxInput = (item) => { + const { options, name, text } = item; + formInputs[name] = options[0].value; + + let div = `
${text}
`; + div += '
'; + + options.forEach((option, index) => { + div += ``; + formInputs[option.value] = option.checked ? 'true' : 'false'; + }); + + div += "
"; + div += "
"; + return div; +}; + +const renderSelectInput = (item) => { + const { options, name, text } = item; + let div = `
${text}
`; + + div += `"; + return div; +}; + +const CloseMenu = () => { + $(`.main-wrapper`).fadeOut(0); + $("#qb-input-form").remove(); + formInputs = {}; +}; + +const CancelMenu = () => { + $.post(`https://${GetParentResourceName()}/closeMenu`); + return CloseMenu(); +}; + +window.addEventListener("message", (event) => { + const data = event.data; + const info = data.data; + const action = data.action; + switch (action) { + case "OPEN_MENU": + return OpenMenu(info); + case "CLOSE_MENU": + return CloseMenu(); + default: + return; + } +}); + +document.onkeyup = function (event) { + const charCode = event.key; + if (charCode == "Escape") { + CancelMenu(); + } else if (charCode == "Enter") { + SubmitData(); + } +}; + +// IDK why this is a thing ? what if they misclick? +$(document).click(function (event) { + var $target = $(event.target); + if ( + !$target.closest(".main-wrapper").length && + $(".main-wrapper").is(":visible") + ) { + CancelMenu(); + } +}); diff --git a/resources/[qb]/[qb_core]/qb-input/html/style.css b/resources/[qb]/[qb_core]/qb-input/html/style.css new file mode 100644 index 0000000..076005b --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-input/html/style.css @@ -0,0 +1,245 @@ +@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;500&display=swap"); +/* width */ +::-webkit-scrollbar { + width: 10px; + } + + /* Track */ + ::-webkit-scrollbar-track { + box-shadow: inset 0 0 5px grey; + border-radius: 10px; + } + + /* Handle */ + ::-webkit-scrollbar-thumb { + background: #42020291; +} +:root { + --heading-bg: #000000a8; + border-radius: 10px; + --form-wrapper-bg: #020000a8; + --element-bg: #00000098; + --btn-bg: #000000a8; + --btn-hover-bg: #03000c86; +} + +* { + padding: 0; + margin: 0; + font-family: "Poppins", sans-serif !important; + font-weight: 300; + color: white; +} + +.root-wrapper { + width: 100%; + height: 100vh; + background: transparent !important; + display: flex; + justify-content: center; + align-items: center; + opacity: 1; +} + +html, +body { + background: transparent !important; +} + +.main-wrapper { + min-width: 300px; + max-width: 500px; + width: 15%; + position: relative; + overflow: hidden; + border-radius: 15px; + padding: 1.5rem; + display: none; + background-color: var(--form-wrapper-bg); +} + +.heading { + display: flex; + justify-content: center; + overflow: wrap; + margin-bottom: 5px; + background-color: var(--heading-bg); + border-radius: 5px; + color: white; + font-size: 0.9rem; + font-weight: 400; + padding: 0.45rem; +} + +.heading h1 { + font-size: 18px; + position: relative; + font-weight: 500; +} + +.form { + width: 100%; + height: auto; + min-height: 10%; + max-height: 70%; + margin: auto; + display: flex; + flex-direction: column; +} + +.form label { + margin-top: 24px; +} + +.form-control[type="number"] { + appearance: none; + -webkit-appearance: none; +} + +.form-control[type="text"], +.form-control[type="number"], +.form-control[type="password"] { + border: none; + font-size: 14px; + border-radius: 5px; + margin: 5px 0px; + background-color: var(--element-bg); + height: 50px; + text-align: center; + width: 100%; + transition-property: color, background-color; + transition-duration: 0.1s, 0.3s; + transition-timing-function: linear, ease-in; +} + +.form-control[type="text"]:focus, +.form-control[type="number"]:focus, +.form-control[type="password"]:focus { + transition-property: color, background-color; + transition-duration: 0.1s, 0.3s; + transition-timing-function: linear, ease-in; +} + +.form-control[type="text"]:hover, +.form-control[type="number"]:hover, +.form-control[type="password"]:hover { + background-color: #3A8DA6; +} + +.form-input-group { + background-color: var(--element-bg); + margin-top: 5px; + margin-bottom: 10px; + padding: 5px; + border-radius: 5px; + transition-property: color, background-color; + transition-duration: 0.1s, 0.3s; + transition-timing-function: linear, ease-in; +} + +.form-input-group:hover { + background-color: #3A8DA6; + } + +.input-group { + display: flex; + flex-flow: row wrap; + justify-content: space-evenly; +} + +.input-group-chk { + display: flex; + flex-flow: column wrap; + align-content: center; + justify-content: center; +} + +.input-group label { + color: rgba(255, 255, 255, 0.65) !important; +} + +.input-group label:checked { + color: rgba(255, 255, 255, 1) !important; +} + +.form-group-title { + width: 100%; + text-align: center; +} + +.select-title { + background-color: var(--element-bg); + margin-top: 5px; + padding: 5px; + border-radius: 2px; + text-align: center; +} + +.form-select:focus-visible { + outline: unset; +} + +.form-select { + width: 100%; + background-color: var(--element-bg); + margin-top: 2.5px; + margin-bottom: 10px; + padding: 5px; + border-radius: 2px; + border: none; + height: 50px; +} + +.form-select:active, +.form-select:focus { + width: 100%; + background-color: var(--element-bg); + margin-top: 5px; + margin-bottom: 10px; + padding: 5px; + border-radius: 2px; + border: none; +} + +.form-select option:hover { + background-color: var(--element-bg); + padding: 1rem 0; + border-radius: 2px; + border: none; + appearance: none; +} + +::placeholder { + opacity: 100%; + color: white; +} + +.background { + width: auto; + height: auto; + position: absolute; + top: 0; + left: 0; + background-color: var(--element-bg); + z-index: -99; + display: none; +} + +.btn { + width: 100%; + height: 40px; + font-size: 16px; + background-color: var(--btn-bg); + /* box-shadow: 0rem 0rem 0.1rem 0.02rem #000000; */ + border: none; + font-weight: 600; + border-radius: 8px; + transition-property: color, background-color; + transition-duration: 0.1s, 0.3s; + transition-timing-function: linear, ease-in; + margin-top: 5px; +} + +.btn:hover { + background-color: var(--btn-hover-bg); +} \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-input/html/styles/default.css b/resources/[qb]/[qb_core]/qb-input/html/styles/default.css new file mode 100644 index 0000000..a41787a --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-input/html/styles/default.css @@ -0,0 +1,237 @@ +@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;500&display=swap"); + +* { + padding: 0; + margin: 0; + font-family: "Poppins", sans-serif !important; + font-weight: 300; + color: white; +} + +.root-wrapper { + width: 30vw; + height: auto; + top: 31.2%; + left: 62.5%; + transform: translateY(-50%) translateX(-50%); + position: absolute; + overflow: hidden; + background: transparent !important; +} + +html, +body { + background: transparent !important; +} + +/* width */ +::-webkit-scrollbar { + width: 10px; + } + + /* Track */ + ::-webkit-scrollbar-track { + box-shadow: inset 0 0 5px grey; + border-radius: 10px; + } + + /* Handle */ + ::-webkit-scrollbar-thumb { + background: #171717e6; + border-radius: 10px; +} + +.main-wrapper { + margin: auto; + margin-top: 64.2%; + width: 44.5%; + height: 80vh; + position: relative; + overflow: auto; + border-radius: 5%; + padding: 1.5rem; + display: none; +} + +.heading { + display: flex; + justify-content: center; + overflow: wrap; + margin-bottom: 5px; + background: rgba(23, 23, 23, 90%); + box-shadow: 0rem 0rem 0.1rem 0.05rem #000000; + border-radius: 5px; + color: white; + font-size: 0.9rem; + font-weight: 400; + padding: 0.45rem; +} + +.heading h1 { + font-size: 18px; + position: relative; + font-weight: 500; +} + +.form { + width: 100%; + height: auto; + min-height: 10%; + max-height: 70%; + margin: auto; + display: flex; + flex-direction: column; +} + +.form label { + margin-top: 24px; +} + +.form-control[type="number"] { + appearance: none; + -webkit-appearance: none; +} +.form-control[type="text"], +.form-control[type="number"], +.form-control[type="password"] { + border: none; + font-size: 14px; + border-radius: 5px; + margin: 5px 0px; + background: rgba(23, 23, 23, 90%); + height: 50px; + text-align: center; + width: 100%; +} + +.form-control[type="text"]:focus, +.form-control[type="number"]:focus, +.form-control[type="password"]:focus { + transition-property: box-shadow; + transition-duration: 0.25s; + transition-timing-function: linear, ease-in; +} + +.form-input-group { + background: rgba(23, 23, 23, 90%); + box-shadow: 0rem 0rem 0.1rem 0.05rem #000000; + margin-top: 5px; + margin-bottom: 10px; + padding: 5px; + border-radius: 5px; +} + +.input-group { + display: flex; + flex-flow: row wrap; + justify-content: space-evenly; +} + +.input-group-chk { + display: flex; + flex-flow: column wrap; + align-content: center; + justify-content: center; +} + +.input-group label { + color: rgba(255, 255, 255, 0.65) !important; +} + +.input-group label:checked { + color: rgba(255, 255, 255, 1) !important; +} + +.form-group-title { + width: 100%; + text-align: center; +} + +.select-title { + background: rgba(23, 23, 23, 90%); + box-shadow: 0rem 0rem 0.1rem 0.05rem #000000; + margin: 5px 0px; + padding: 5px; + border-radius: 5px; + text-align: center; +} + +.form-select { + width: 100%; + background: rgba(23, 23, 23, 90%); + box-shadow: 0rem 0rem 0.1rem 0.05rem #000000; + margin-top: 2.5px; + margin-bottom: 10px; + padding: 5px; + border-radius: 5px; + border: none; + height: 50px; +} + +.form-select:active, +.form-select:focus { + width: 100%; + background: rgba(23, 23, 23, 90%); + box-shadow: 0rem 0rem 0.1rem 0.05rem #000000; + margin-top: 2.5px; + margin-bottom: 10px; + padding: 5px; + border-radius: 5px; + border: none; +} + +.form-select:focus-visible { + outline: unset; +} + +.form-select option:hover { + background: rgba(23, 23, 23, 90%); + box-shadow: 0rem 0rem 0.1rem 0.05rem #000000; + padding: 1rem 0; + border-radius: 5px; + border: none; + appearance: none; +} + +::placeholder { + opacity: 65%; + color: white; +} + +.background { + width: auto; + height: auto; + position: absolute; + top: 0; + left: 0; + background-color: rgba(0, 0, 0, 0.452); + z-index: -99; + display: none; +} + +.btn { + width: 100%; + height: 50px; + font-size: 16px; + background: rgba(23, 23, 23, 90%); + box-shadow: 0rem 0rem 0.1rem 0.05rem #000000; + border: none; + font-weight: 500; + border-radius: 5px; + transition-property: color, background-color; + transition-duration: 0.1s, 0.3s; + transition-timing-function: linear, ease-in; + margin-top: 5px; +} + +.btn:hover { + background-color: #000000a1; +} + +.label { + background: rgba(23, 23, 23, 90%); + padding: 10px; + border-radius:5px; + font-size: 14px; + margin: 5px 0px; +} diff --git a/resources/[qb]/[qb_core]/qb-keyminigame/LICENSE b/resources/[qb]/[qb_core]/qb-keyminigame/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-keyminigame/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/resources/[qb]/[qb_core]/qb-keyminigame/README.md b/resources/[qb]/[qb_core]/qb-keyminigame/README.md new file mode 100644 index 0000000..c080314 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-keyminigame/README.md @@ -0,0 +1,20 @@ +# qb-keyminigame +Keys Mini-Game For QB-Core + +# License + + QBCore Framework + Copyright (C) 2021 Joshua Eger + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see diff --git a/resources/[qb]/[qb_core]/qb-keyminigame/client/main.lua b/resources/[qb]/[qb_core]/qb-keyminigame/client/main.lua new file mode 100644 index 0000000..ee4e6b2 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-keyminigame/client/main.lua @@ -0,0 +1,24 @@ +KeyMinigameCallback = {} + +RegisterNetEvent('qb-keyminigame:show', function() + SendNUIMessage({ + action = "ShowMinigame" + }) + SetNuiFocus(true, false) +end) + +RegisterNetEvent('qb-keyminigame:start', function(callback) + KeyMinigameCallback = callback + SendNUIMessage({ + action = "StartMinigame" + }) +end) + +RegisterNUICallback('callback', function(data, cb) + KeyMinigameCallback(data.faults) + SendNUIMessage({ + action = "HideMinigame" + }) + SetNuiFocus(false, false) + cb('ok') +end) diff --git a/resources/[qb]/[qb_core]/qb-keyminigame/fxmanifest.lua b/resources/[qb]/[qb_core]/qb-keyminigame/fxmanifest.lua new file mode 100644 index 0000000..00fb86f --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-keyminigame/fxmanifest.lua @@ -0,0 +1,17 @@ +fx_version 'cerulean' +game 'gta5' + +description 'QB-KeyMiniGame' +version '1.2.0' + +ui_page 'html/index.html' + +client_script 'client/main.lua' + +files { + 'html/index.html', + 'html/app.js', + 'html/style.css', +} + +lua54 'yes' diff --git a/resources/[qb]/[qb_core]/qb-keyminigame/html/app.js b/resources/[qb]/[qb_core]/qb-keyminigame/html/app.js new file mode 100644 index 0000000..a5428a7 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-keyminigame/html/app.js @@ -0,0 +1,212 @@ +var WrongKeyCount = 0; +var CurrentKey = 0; +var Key = 0; +var KeyPressed = false; +var CanPress = false; +var TotalPresses = 10; +var SkippedPress = true; + +$(document).ready(function(){ + window.addEventListener('message', function(event){ + var Data = event.data; + + if (Data.action == "ShowMinigame") { + ShowMinigame() + } else if (Data.action == "HideMinigame") { + HideMinigame() + } else if (Data.action == "StartMinigame") { + StartMinigame() + } + }); +}); + +function ShowMinigame() { + $(".container").fadeIn(300); +} + +function HideMinigame() { + $(".container").fadeOut(300); +} + +var Patterns = []; +Patterns[0] = [39, 38, 37, 38, 39, 40, 37, 38, 40, 39]; +Patterns[1] = [38, 38, 37, 38, 39, 40, 37, 38, 40, 39]; +Patterns[2] = [40, 38, 37, 38, 39, 40, 37, 38, 40, 39]; +Patterns[3] = [37, 38, 37, 38, 39, 40, 37, 38, 40, 39]; +Patterns[4] = [39, 38, 37, 38, 39, 40, 37, 38, 40, 39]; +Patterns[5] = [38, 38, 37, 38, 39, 40, 37, 38, 40, 39]; +Patterns[6] = [40, 38, 37, 38, 39, 40, 37, 38, 40, 39]; +Patterns[7] = [38, 38, 37, 38, 39, 40, 37, 38, 40, 39]; +Patterns[8] = [40, 38, 37, 38, 39, 40, 37, 38, 40, 39]; +Patterns[9] = [37, 38, 37, 38, 39, 40, 37, 38, 40, 39]; +Patterns[10] = [39, 38, 37, 38, 39, 40, 37, 38, 40, 39]; + +var Keys = []; +Keys["38"] = "arrowup"; +Keys["37"] = "arrowleft"; +Keys["40"] = "arrowdown"; +Keys["39"] = "arrowright"; + +$(document).on('keydown', function() { + switch(event.keyCode) { + case 38: + if (Key == 38) { + if (CanPress) { + $(".minigame-container").find("[data-key='"+Keys[Key]+"']").css({"background-color":"green"}); + KeyPressed = true; + CanPress = false; + } else { + if (!KeyPressed) { + WrongKeyCount = WrongKeyCount + 1; + $(".minigame-container").find("[data-key='"+Keys[Key]+"']").css({"background-color":"red"}); + KeyPressed = true; + CanPress = false; + setTimeout(function(){ + $(".minigame-container").find("[data-key='"+Keys[Key]+"']").css({"background-color":"white"}); + }, 300); + } + } + } else { + if (!KeyPressed) { + WrongKeyCount = WrongKeyCount + 1; + $(".minigame-container").find("[data-key='"+Keys[Key]+"']").css({"background-color":"red"}); + KeyPressed = true; + CanPress = false; + setTimeout(function(){ + $(".minigame-container").find("[data-key='"+Keys[Key]+"']").css({"background-color":"white"}); + }, 300); + } + } + break; + case 37: + if (Key == 37) { + if (CanPress) { + $(".minigame-container").find("[data-key='"+Keys[Key]+"']").css({"background-color":"green"}); + KeyPressed = true; + CanPress = false; + } else { + if (!KeyPressed) { + WrongKeyCount = WrongKeyCount + 1; + $(".minigame-container").find("[data-key='"+Keys[Key]+"']").css({"background-color":"red"}); + KeyPressed = true; + CanPress = false; + setTimeout(function(){ + $(".minigame-container").find("[data-key='"+Keys[Key]+"']").css({"background-color":"white"}); + }, 300); + } + } + } else { + if (!KeyPressed) { + WrongKeyCount = WrongKeyCount + 1; + $(".minigame-container").find("[data-key='"+Keys[Key]+"']").css({"background-color":"red"}); + KeyPressed = true; + CanPress = false; + setTimeout(function(){ + $(".minigame-container").find("[data-key='"+Keys[Key]+"']").css({"background-color":"white"}); + }, 300); + } + } + break; + case 40: + if (Key == 40) { + if (CanPress) { + $(".minigame-container").find("[data-key='"+Keys[Key]+"']").css({"background-color":"green"}); + KeyPressed = true; + CanPress = false; + } else { + if (!KeyPressed) { + WrongKeyCount = WrongKeyCount + 1; + $(".minigame-container").find("[data-key='"+Keys[Key]+"']").css({"background-color":"red"}); + KeyPressed = true; + CanPress = false; + setTimeout(function(){ + $(".minigame-container").find("[data-key='"+Keys[Key]+"']").css({"background-color":"white"}); + }, 300); + } + } + } else { + if (!KeyPressed) { + WrongKeyCount = WrongKeyCount + 1; + $(".minigame-container").find("[data-key='"+Keys[Key]+"']").css({"background-color":"red"}); + KeyPressed = true; + CanPress = false; + setTimeout(function(){ + $(".minigame-container").find("[data-key='"+Keys[Key]+"']").css({"background-color":"white"}); + }, 300); + } + } + break; + case 39: + if (Key == 39) { + if (CanPress) { + $(".minigame-container").find("[data-key='"+Keys[Key]+"']").css({"background-color":"green"}); + KeyPressed = true; + CanPress = false; + } else { + if (!KeyPressed) { + WrongKeyCount = WrongKeyCount + 1; + $(".minigame-container").find("[data-key='"+Keys[Key]+"']").css({"background-color":"red"}); + KeyPressed = true; + CanPress = false; + setTimeout(function(){ + $(".minigame-container").find("[data-key='"+Keys[Key]+"']").css({"background-color":"white"}); + }, 300); + } + } + } else { + if (!KeyPressed) { + WrongKeyCount = WrongKeyCount + 1; + $(".minigame-container").find("[data-key='"+Keys[Key]+"']").css({"background-color":"red"}); + KeyPressed = true; + CanPress = false; + setTimeout(function(){ + $(".minigame-container").find("[data-key='"+Keys[Key]+"']").css({"background-color":"white"}); + }, 300); + } + } + break; + } +}); + +function timer(ms) { + return new Promise(res => setTimeout(res, ms)); +} + +async function StartMinigame() { + await timer(1000); + var Pattern = Math.ceil((Math.random() * 10)); + for (var i = 1; i < TotalPresses; i++) { + Key = Patterns[Pattern][CurrentKey]; + CurrentKey = CurrentKey + 1 + + if (!KeyPressed && CurrentKey != 1) { + WrongKeyCount = WrongKeyCount + 1; + } + + KeyPressed = false; + + $(".minigame-container").find("[data-key='"+Keys[Key]+"']").css({"background-color":"orange"}); + CanPress = true; + setTimeout(function(){ + $(".minigame-container").find("[data-key='"+Keys[Key]+"']").css({"background-color":"white"}); + CanPress = false; + }, 500); + + if ((CurrentKey + 1) == TotalPresses) { + $(".minigame-container").find("[data-key='"+Keys[Key]+"']").css({"background-color":"white"}); + $.post('https://qb-keyminigame/callback', JSON.stringify({ + faults: WrongKeyCount, + }), function(status){ + if (status == "ok") { + console.log('Minigame Ended!'); + } + }); + WrongKeyCount = 0; + CurrentKey = 0; + Key = 0; + KeyPressed = false; + TotalPresses = 10; + } + await timer(1500); + } +} \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-keyminigame/html/index.html b/resources/[qb]/[qb_core]/qb-keyminigame/html/index.html new file mode 100644 index 0000000..06d06ed --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-keyminigame/html/index.html @@ -0,0 +1,22 @@ + + + + + + QB Key Minigame + + + + + +
+
+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-keyminigame/html/style.css b/resources/[qb]/[qb_core]/qb-keyminigame/html/style.css new file mode 100644 index 0000000..cd348b7 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-keyminigame/html/style.css @@ -0,0 +1,56 @@ +body { + margin: 0; + padding: 0; +} + +.container { + display: none; + height: 100vh; + background-color: rgba(0, 0, 0, 0.219); +} + +.minigame-container { + position: absolute; + width: 30vh; + height: 30vh; + bottom: 10vh; + /* background-color: rgb(255, 60, 60); */ + margin: 0 auto; + left: 0; + right: 0; +} + +.key { + position: absolute; + background-color: white; + width: 6vh; + height: 6vh; + text-align: center; + line-height: 6vh; + color: black; + font-family: sans-serif; + font-size: 2.2vh; + font-weight: bold; + border-radius: .7vh; + box-shadow: 0 0 0 #000; +} + +#w { + left: 12.5vh; + top: 6vh; +} + +#a { + left: 5.5vh; + top: 13vh; +} + +#s { + left: 12.5vh; + top: 13vh; +} + +#d { + left: 19.5vh; + top: 13vh; +} \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-lapraces/LICENSE b/resources/[qb]/[qb_core]/qb-lapraces/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-lapraces/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/resources/[qb]/[qb_core]/qb-lapraces/README.md b/resources/[qb]/[qb_core]/qb-lapraces/README.md new file mode 100644 index 0000000..0c74e78 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-lapraces/README.md @@ -0,0 +1,20 @@ +# qb-lapraces +Race Track Creation For QB-Core + +# License + + QBCore Framework + Copyright (C) 2021 Joshua Eger + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see diff --git a/resources/[qb]/[qb_core]/qb-lapraces/client/main.lua b/resources/[qb]/[qb_core]/qb-lapraces/client/main.lua new file mode 100644 index 0000000..2a0668f --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-lapraces/client/main.lua @@ -0,0 +1,859 @@ +local QBCore = exports['qb-core']:GetCoreObject() +local Countdown = 10 +local ToFarCountdown = 10 +local FinishedUITimeout = false + +local RaceData = { + InCreator = false, + InRace = false, + ClosestCheckpoint = 0, +} + +local CreatorData = { + RaceName = nil, + Checkpoints = {}, + TireDistance = 3.0, + ConfirmDelete = false, +} + +local CurrentRaceData = { + RaceId = nil, + RaceName = nil, + Checkpoints = {}, + Started = false, + CurrentCheckpoint = nil, + TotalLaps = 0, + Lap = 0, +} + +-- Handlers + +AddEventHandler('onResourceStop', function(resource) + if resource == GetCurrentResourceName() then + for k, _ in pairs(CreatorData.Checkpoints) do + if CreatorData.Checkpoints[k].pileleft ~= nil then + local coords = CreatorData.Checkpoints[k].offset.right + local Obj = GetClosestObjectOfType(coords.x, coords.y, coords.z, 5.0, `prop_offroad_tyres02`, 0, 0, 0) + DeleteObject(Obj) + ClearAreaOfObjects(coords.x, coords.y, coords.z, 50.0, 0) + CreatorData.Checkpoints[k].pileright = nil + end + if CreatorData.Checkpoints[k].pileright ~= nil then + local coords = CreatorData.Checkpoints[k].offset.right + local Obj = GetClosestObjectOfType(coords.x, coords.y, coords.z, 5.0, `prop_offroad_tyres02`, 0, 0, 0) + DeleteObject(Obj) + ClearAreaOfObjects(coords.x, coords.y, coords.z, 50.0, 0) + CreatorData.Checkpoints[k].pileright = nil + end + end + + for k, _ in pairs(CurrentRaceData.Checkpoints) do + if CurrentRaceData.Checkpoints[k] ~= nil then + if CurrentRaceData.Checkpoints[k].pileleft ~= nil then + local coords = CurrentRaceData.Checkpoints[k].offset.right + local Obj = GetClosestObjectOfType(coords.x, coords.y, coords.z, 5.0, `prop_offroad_tyres02`, 0, 0, 0) + DeleteObject(Obj) + ClearAreaOfObjects(coords.x, coords.y, coords.z, 50.0, 0) + CurrentRaceData.Checkpoints[k].pileright = nil + end + if CurrentRaceData.Checkpoints[k].pileright ~= nil then + local coords = CurrentRaceData.Checkpoints[k].offset.right + local Obj = GetClosestObjectOfType(coords.x, coords.y, coords.z, 5.0, `prop_offroad_tyres02`, 0, 0, 0) + DeleteObject(Obj) + ClearAreaOfObjects(coords.x, coords.y, coords.z, 50.0, 0) + CurrentRaceData.Checkpoints[k].pileright = nil + end + end + end + end +end) + +-- Functions + +local function DrawText3Ds(x, y, z, text) + SetTextScale(0.35, 0.35) + SetTextFont(4) + SetTextProportional(1) + SetTextColour(255, 255, 255, 215) + SetTextEntry("STRING") + SetTextCentre(true) + AddTextComponentString(text) + SetDrawOrigin(x,y,z, 0) + DrawText(0.0, 0.0) + local factor = (string.len(text)) / 370 + DrawRect(0.0, 0.0+0.0125, 0.017+ factor, 0.03, 0, 0, 0, 75) + ClearDrawOrigin() +end + +local function GetClosestCheckpoint() + local pos = GetEntityCoords(PlayerPedId(), true) + local current = nil + local dist = nil + for id, _ in pairs(CreatorData.Checkpoints) do + if current ~= nil then + if #(pos - vector3(CreatorData.Checkpoints[id].coords.x, CreatorData.Checkpoints[id].coords.y, CreatorData.Checkpoints[id].coords.z)) < dist then + current = id + dist = #(pos - vector3(CreatorData.Checkpoints[id].coords.x, CreatorData.Checkpoints[id].coords.y, CreatorData.Checkpoints[id].coords.z)) + end + else + dist = #(pos - vector3(CreatorData.Checkpoints[id].coords.x, CreatorData.Checkpoints[id].coords.y, CreatorData.Checkpoints[id].coords.z)) + current = id + end + end + RaceData.ClosestCheckpoint = current +end + +local function CreatorUI() + CreateThread(function() + while true do + if RaceData.InCreator then + SendNUIMessage({ + action = "Update", + type = "creator", + data = CreatorData, + racedata = RaceData, + active = true, + }) + else + SendNUIMessage({ + action = "Update", + type = "creator", + data = CreatorData, + racedata = RaceData, + active = false, + }) + break + end + Wait(200) + end + end) +end + +local function DeleteCheckpoint() + local NewCheckpoints = {} + if RaceData.ClosestCheckpoint ~= 0 then + if CreatorData.Checkpoints[RaceData.ClosestCheckpoint] ~= nil then + if CreatorData.Checkpoints[RaceData.ClosestCheckpoint].blip ~= nil then + RemoveBlip(CreatorData.Checkpoints[RaceData.ClosestCheckpoint].blip) + CreatorData.Checkpoints[RaceData.ClosestCheckpoint].blip = nil + end + if CreatorData.Checkpoints[RaceData.ClosestCheckpoint].pileleft ~= nil then + local coords = CreatorData.Checkpoints[RaceData.ClosestCheckpoint].offset.left + local Obj = GetClosestObjectOfType(coords.x, coords.y, coords.z, 5.0, `prop_offroad_tyres02`, 0, 0, 0) + DeleteObject(Obj) + ClearAreaOfObjects(coords.x, coords.y, coords.z, 50.0, 0) + CreatorData.Checkpoints[RaceData.ClosestCheckpoint].pileleft = nil + end + if CreatorData.Checkpoints[RaceData.ClosestCheckpoint].pileright ~= nil then + local coords = CreatorData.Checkpoints[RaceData.ClosestCheckpoint].offset.right + local Obj = GetClosestObjectOfType(coords.x, coords.y, coords.z, 5.0, `prop_offroad_tyres02`, 0, 0, 0) + DeleteObject(Obj) + ClearAreaOfObjects(coords.x, coords.y, coords.z, 50.0, 0) + CreatorData.Checkpoints[RaceData.ClosestCheckpoint].pileright = nil + end + + for id, data in pairs(CreatorData.Checkpoints) do + if id ~= RaceData.ClosestCheckpoint then + NewCheckpoints[#NewCheckpoints+1] = data + end + end + CreatorData.Checkpoints = NewCheckpoints + else + QBCore.Functions.Notify('Du kan ikke bevæge dig for hurtigt', 'error') + end + else + QBCore.Functions.Notify('Du kan ikke bevæge dig for hurtigt', 'error') + end +end + +local function SaveRace() + local RaceDistance = 0 + + for k, v in pairs(CreatorData.Checkpoints) do + if k + 1 <= #CreatorData.Checkpoints then + local checkpointdistance = #(vector3(v.coords.x, v.coords.y, v.coords.z) - vector3(CreatorData.Checkpoints[k + 1].coords.x, CreatorData.Checkpoints[k + 1].coords.y, CreatorData.Checkpoints[k + 1].coords.z)) + RaceDistance = RaceDistance + checkpointdistance + end + end + + CreatorData.RaceDistance = RaceDistance + + TriggerServerEvent('qb-lapraces:server:SaveRace', CreatorData) + + QBCore.Functions.Notify('Race: '..CreatorData.RaceName..' blev gemt!', 'success') + + for id,_ in pairs(CreatorData.Checkpoints) do + if CreatorData.Checkpoints[id].blip ~= nil then + RemoveBlip(CreatorData.Checkpoints[id].blip) + CreatorData.Checkpoints[id].blip = nil + end + if CreatorData.Checkpoints[id] ~= nil then + if CreatorData.Checkpoints[id].pileleft ~= nil then + local coords = CreatorData.Checkpoints[id].offset.left + local Obj = GetClosestObjectOfType(coords.x, coords.y, coords.z, 5.0, `prop_offroad_tyres02`, 0, 0, 0) + DeleteObject(Obj) + ClearAreaOfObjects(coords.x, coords.y, coords.z, 50.0, 0) + CreatorData.Checkpoints[id].pileleft = nil + end + if CreatorData.Checkpoints[id].pileright ~= nil then + local coords = CreatorData.Checkpoints[id].offset.right + local Obj = GetClosestObjectOfType(coords.x, coords.y, coords.z, 5.0, `prop_offroad_tyres02`, 0, 0, 0) + DeleteObject(Obj) + ClearAreaOfObjects(coords.x, coords.y, coords.z, 50.0, 0) + CreatorData.Checkpoints[id].pileright = nil + end + end + end + + RaceData.InCreator = false + CreatorData.RaceName = nil + CreatorData.Checkpoints = {} +end + +local function AddCheckpoint() + local PlayerPed = PlayerPedId() + local PlayerPos = GetEntityCoords(PlayerPed) + local PlayerVeh = GetVehiclePedIsIn(PlayerPed) + local Offset = { + left = { + x = (GetOffsetFromEntityInWorldCoords(PlayerVeh, -CreatorData.TireDistance, 0.0, 0.0)).x, + y = (GetOffsetFromEntityInWorldCoords(PlayerVeh, -CreatorData.TireDistance, 0.0, 0.0)).y, + z = (GetOffsetFromEntityInWorldCoords(PlayerVeh, -CreatorData.TireDistance, 0.0, 0.0)).z, + }, + right = { + x = (GetOffsetFromEntityInWorldCoords(PlayerVeh, CreatorData.TireDistance, 0.0, 0.0)).x, + y = (GetOffsetFromEntityInWorldCoords(PlayerVeh, CreatorData.TireDistance, 0.0, 0.0)).y, + z = (GetOffsetFromEntityInWorldCoords(PlayerVeh, CreatorData.TireDistance, 0.0, 0.0)).z, + } + } + + CreatorData.Checkpoints[#CreatorData.Checkpoints+1] = { + coords = { + x = PlayerPos.x, + y = PlayerPos.y, + z = PlayerPos.z, + }, + offset = Offset, + } + + + for id, CheckpointData in pairs(CreatorData.Checkpoints) do + if CheckpointData.blip ~= nil then + RemoveBlip(CheckpointData.blip) + end + + CheckpointData.blip = AddBlipForCoord(CheckpointData.coords.x, CheckpointData.coords.y, CheckpointData.coords.z) + + SetBlipSprite(CheckpointData.blip, 1) + SetBlipDisplay(CheckpointData.blip, 4) + SetBlipScale(CheckpointData.blip, 0.8) + SetBlipAsShortRange(CheckpointData.blip, true) + SetBlipColour(CheckpointData.blip, 26) + ShowNumberOnBlip(CheckpointData.blip, id) + SetBlipShowCone(CheckpointData.blip, false) + BeginTextCommandSetBlipName("STRING") + AddTextComponentSubstringPlayerName("Checkpoint: "..id) + EndTextCommandSetBlipName(CheckpointData.blip) + end +end + +local function CreatorLoop() + CreateThread(function() + while RaceData.InCreator do + local PlayerPed = PlayerPedId() + local PlayerVeh = GetVehiclePedIsIn(PlayerPed) + + if PlayerVeh ~= 0 then + if IsControlJustPressed(0, 161) or IsDisabledControlJustPressed(0, 161) then + AddCheckpoint() + end + + if IsControlJustPressed(0, 162) or IsDisabledControlJustPressed(0, 162) then + if CreatorData.Checkpoints ~= nil and next(CreatorData.Checkpoints) ~= nil then + DeleteCheckpoint() + else + QBCore.Functions.Notify('Du har ikke placeret nogle checkpoints endnu...', 'error') + end + end + + if IsControlJustPressed(0, 311) or IsDisabledControlJustPressed(0, 311) then + if CreatorData.Checkpoints ~= nil and #CreatorData.Checkpoints >= 2 then + SaveRace() + else + QBCore.Functions.Notify('Du skal have mindst 10 checkpoints', 'error') + end + end + + if IsControlJustPressed(0, 40) or IsDisabledControlJustPressed(0, 40) then + if CreatorData.TireDistance + 1.0 ~= 16.0 then + CreatorData.TireDistance = CreatorData.TireDistance + 1.0 + else + QBCore.Functions.Notify('Du kan ikke gå højere end 15', 'error') + end + end + + if IsControlJustPressed(0, 39) or IsDisabledControlJustPressed(0, 39) then + if CreatorData.TireDistance - 1.0 ~= 1.0 then + CreatorData.TireDistance = CreatorData.TireDistance - 1.0 + else + QBCore.Functions.Notify('Du kan ikke gå lavere end 2', 'error') + end + end + else + local coords = GetEntityCoords(PlayerPedId()) + DrawText3Ds(coords.x, coords.y, coords.z, 'Du skal være i et køretøj') + end + + if IsControlJustPressed(0, 163) or IsDisabledControlJustPressed(0, 163) then + if not CreatorData.ConfirmDelete then + CreatorData.ConfirmDelete = true + QBCore.Functions.Notify('Tryk [9] igen for at bekræfte', 'error', 5000) + else + for _, CheckpointData in pairs(CreatorData.Checkpoints) do + if CheckpointData.blip ~= nil then + RemoveBlip(CheckpointData.blip) + end + end + + for id,_ in pairs(CreatorData.Checkpoints) do + if CreatorData.Checkpoints[id].pileleft ~= nil then + local coords = CreatorData.Checkpoints[id].offset.left + local Obj = GetClosestObjectOfType(coords.x, coords.y, coords.z, 8.0, `prop_offroad_tyres02`, 0, 0, 0) + DeleteObject(Obj) + ClearAreaOfObjects(coords.x, coords.y, coords.z, 50.0, 0) + CreatorData.Checkpoints[id].pileleft = nil + end + + if CreatorData.Checkpoints[id].pileright ~= nil then + local coords = CreatorData.Checkpoints[id].offset.right + local Obj = GetClosestObjectOfType(coords.x, coords.y, coords.z, 8.0, `prop_offroad_tyres02`, 0, 0, 0) + DeleteObject(Obj) + ClearAreaOfObjects(coords.x, coords.y, coords.z, 50.0, 0) + CreatorData.Checkpoints[id].pileright = nil + end + end + + RaceData.InCreator = false + CreatorData.RaceName = nil + CreatorData.Checkpoints = {} + QBCore.Functions.Notify('Race-editor annulleret', 'error') + CreatorData.ConfirmDelete = false + end + end + Wait(3) + end + end) +end + +local function RaceUI() + CreateThread(function() + while true do + if CurrentRaceData.Checkpoints ~= nil and next(CurrentRaceData.Checkpoints) ~= nil then + if CurrentRaceData.Started then + CurrentRaceData.RaceTime = CurrentRaceData.RaceTime + 1 + CurrentRaceData.TotalTime = CurrentRaceData.TotalTime + 1 + end + SendNUIMessage({ + action = "Update", + type = "race", + data = { + CurrentCheckpoint = CurrentRaceData.CurrentCheckpoint, + TotalCheckpoints = #CurrentRaceData.Checkpoints, + TotalLaps = CurrentRaceData.TotalLaps, + CurrentLap = CurrentRaceData.Lap, + RaceStarted = CurrentRaceData.Started, + RaceName = CurrentRaceData.RaceName, + Time = CurrentRaceData.RaceTime, + TotalTime = CurrentRaceData.TotalTime, + BestLap = CurrentRaceData.BestLap, + }, + racedata = RaceData, + active = true, + }) + else + if not FinishedUITimeout then + FinishedUITimeout = true + SetTimeout(10000, function() + FinishedUITimeout = false + SendNUIMessage({ + action = "Update", + type = "race", + data = {}, + racedata = RaceData, + active = false, + }) + end) + end + break + end + Wait(12) + end + end) +end + +local function SetupRace(sRaceData, Laps) + RaceData.RaceId = sRaceData.RaceId + CurrentRaceData = { + RaceId = sRaceData.RaceId, + Creator = sRaceData.Creator, + RaceName = sRaceData.RaceName, + Checkpoints = sRaceData.Checkpoints, + Started = false, + CurrentCheckpoint = 1, + TotalLaps = Laps, + Lap = 1, + RaceTime = 0, + TotalTime = 0, + BestLap = 0, + Racers = {} + } + + for k, v in pairs(CurrentRaceData.Checkpoints) do + ClearAreaOfObjects(v.offset.left.x, v.offset.left.y, v.offset.left.z, 50.0, 0) + CurrentRaceData.Checkpoints[k].pileleft = CreateObject(`prop_offroad_tyres02`, v.offset.left.x, v.offset.left.y, v.offset.left.z, 0, 0, 0) + PlaceObjectOnGroundProperly(CurrentRaceData.Checkpoints[k].pileleft) + FreezeEntityPosition(CurrentRaceData.Checkpoints[k].pileleft, 1) + SetEntityAsMissionEntity(CurrentRaceData.Checkpoints[k].pileleft, 1, 1) + + ClearAreaOfObjects(v.offset.right.x, v.offset.right.y, v.offset.right.z, 50.0, 0) + CurrentRaceData.Checkpoints[k].pileright = CreateObject(`prop_offroad_tyres02`, v.offset.right.x, v.offset.right.y, v.offset.right.z, 0, 0, 0) + PlaceObjectOnGroundProperly(CurrentRaceData.Checkpoints[k].pileright) + FreezeEntityPosition(CurrentRaceData.Checkpoints[k].pileright, 1) + SetEntityAsMissionEntity(CurrentRaceData.Checkpoints[k].pileright, 1, 1) + + CurrentRaceData.Checkpoints[k].blip = AddBlipForCoord(v.coords.x, v.coords.y, v.coords.z) + SetBlipSprite(CurrentRaceData.Checkpoints[k].blip, 1) + SetBlipDisplay(CurrentRaceData.Checkpoints[k].blip, 4) + SetBlipScale(CurrentRaceData.Checkpoints[k].blip, 0.6) + SetBlipAsShortRange(CurrentRaceData.Checkpoints[k].blip, true) + SetBlipColour(CurrentRaceData.Checkpoints[k].blip, 26) + ShowNumberOnBlip(CurrentRaceData.Checkpoints[k].blip, k) + BeginTextCommandSetBlipName("STRING") + AddTextComponentSubstringPlayerName("Checkpoint: "..k) + EndTextCommandSetBlipName(CurrentRaceData.Checkpoints[k].blip) + end + + RaceUI() +end + +local function showNonLoopParticle(dict, particleName, coords, scale) + RequestNamedPtfxAsset(dict) + while not HasNamedPtfxAssetLoaded(dict) do + Wait(0) + end + UseParticleFxAssetNextCall(dict) + local particleHandle = StartParticleFxLoopedAtCoord(particleName, coords.x, coords.y, coords.z, 0.0, 0.0, 0.0, scale, false, false, false) + SetParticleFxLoopedColour(particleHandle, 0, 255, 0 ,0) + return particleHandle +end + +local function DoPilePfx() + if CurrentRaceData.Checkpoints[CurrentRaceData.CurrentCheckpoint] ~= nil then + local Timeout = 500 + local Size = 2.0 + local left = showNonLoopParticle('core', 'ent_sht_flame', CurrentRaceData.Checkpoints[CurrentRaceData.CurrentCheckpoint].offset.left, Size) + local right = showNonLoopParticle('core', 'ent_sht_flame', CurrentRaceData.Checkpoints[CurrentRaceData.CurrentCheckpoint].offset.right, Size) + + SetTimeout(Timeout, function() + StopParticleFxLooped(left, false) + StopParticleFxLooped(right, false) + end) + end +end + +local function SetupPiles() + for k, v in pairs(CreatorData.Checkpoints) do + if CreatorData.Checkpoints[k].pileleft == nil then + ClearAreaOfObjects(v.offset.left.x, v.offset.left.y, v.offset.left.z, 50.0, 0) + CreatorData.Checkpoints[k].pileleft = CreateObject(`prop_offroad_tyres02`, v.offset.left.x, v.offset.left.y, v.offset.left.z, 0, 0, 0) + PlaceObjectOnGroundProperly(CreatorData.Checkpoints[k].pileleft) + FreezeEntityPosition(CreatorData.Checkpoints[k].pileleft, 1) + SetEntityAsMissionEntity(CreatorData.Checkpoints[k].pileleft, 1, 1) + end + + if CreatorData.Checkpoints[k].pileright == nil then + ClearAreaOfObjects(v.offset.right.x, v.offset.right.y, v.offset.right.z, 50.0, 0) + CreatorData.Checkpoints[k].pileright = CreateObject(`prop_offroad_tyres02`, v.offset.right.x, v.offset.right.y, v.offset.right.z, 0, 0, 0) + PlaceObjectOnGroundProperly(CreatorData.Checkpoints[k].pileright) + FreezeEntityPosition(CreatorData.Checkpoints[k].pileleft, 1) + SetEntityAsMissionEntity(CreatorData.Checkpoints[k].pileleft, 1, 1) + end + end +end + +local function GetMaxDistance(OffsetCoords) + local Distance = #(vector3(OffsetCoords.left.x, OffsetCoords.left.y, OffsetCoords.left.z) - vector3(OffsetCoords.right.x, OffsetCoords.right.y, OffsetCoords.right.z)) + local Retval = 7.5 + if Distance > 20.0 then + Retval = 12.5 + end + return Retval +end + +local function SecondsToClock(seconds) + seconds = tonumber(seconds) + local retval + if seconds <= 0 then + retval = "00:00:00"; + else + local hours = string.format("%02.f", math.floor(seconds/3600)); + local mins = string.format("%02.f", math.floor(seconds/60 - (hours*60))); + local secs = string.format("%02.f", math.floor(seconds - hours*3600 - mins *60)); + retval = hours..":"..mins..":"..secs + end + return retval +end + +local function FinishRace() + TriggerServerEvent('qb-lapraces:server:FinishPlayer', CurrentRaceData, CurrentRaceData.TotalTime, CurrentRaceData.TotalLaps, CurrentRaceData.BestLap) + if CurrentRaceData.BestLap ~= 0 then + QBCore.Functions.Notify('Racet blev færdiggjort på '..SecondsToClock(CurrentRaceData.TotalTime)..', med bedste lap: '..SecondsToClock(CurrentRaceData.BestLap)) + else + QBCore.Functions.Notify('Racet blev færdiggjort på '..SecondsToClock(CurrentRaceData.TotalTime)) + end + for k, _ in pairs(CurrentRaceData.Checkpoints) do + if CurrentRaceData.Checkpoints[k].blip ~= nil then + RemoveBlip(CurrentRaceData.Checkpoints[k].blip) + CurrentRaceData.Checkpoints[k].blip = nil + end + if CurrentRaceData.Checkpoints[k].pileleft ~= nil then + local coords = CurrentRaceData.Checkpoints[k].offset.left + local Obj = GetClosestObjectOfType(coords.x, coords.y, coords.z, 5.0, `prop_offroad_tyres02`, 0, 0, 0) + DeleteObject(Obj) + ClearAreaOfObjects(coords.x, coords.y, coords.z, 50.0, 0) + CurrentRaceData.Checkpoints[k].pileleft = nil + end + if CurrentRaceData.Checkpoints[k].pileright ~= nil then + local coords = CurrentRaceData.Checkpoints[k].offset.right + local Obj = GetClosestObjectOfType(coords.x, coords.y, coords.z, 5.0, `prop_offroad_tyres02`, 0, 0, 0) + DeleteObject(Obj) + ClearAreaOfObjects(coords.x, coords.y, coords.z, 50.0, 0) + CurrentRaceData.Checkpoints[k].pileright = nil + end + end + CurrentRaceData.RaceName = nil + CurrentRaceData.Checkpoints = {} + CurrentRaceData.Started = false + CurrentRaceData.CurrentCheckpoint = 0 + CurrentRaceData.TotalLaps = 0 + CurrentRaceData.Lap = 0 + CurrentRaceData.RaceTime = 0 + CurrentRaceData.TotalTime = 0 + CurrentRaceData.BestLap = 0 + CurrentRaceData.RaceId = nil + RaceData.InRace = false +end + +local function Info() + local PlayerPed = PlayerPedId() + local plyVeh = GetVehiclePedIsIn(PlayerPed, false) + local IsDriver = GetPedInVehicleSeat(plyVeh, -1) == PlayerPed + local returnValue = plyVeh ~= 0 and plyVeh ~= nil and IsDriver + return returnValue, plyVeh +end + +local function IsInRace() + local retval = false + if RaceData.InRace then + retval = true + end + return retval +end + +local function IsInEditor() + local retval = false + if RaceData.InCreator then + retval = true + end + return retval +end + +exports('IsInEditor', IsInEditor) +exports('IsInRace', IsInRace) + +-- Events + +RegisterNetEvent('qb-lapraces:client:StartRaceEditor', function(RaceName) + if not RaceData.InCreator then + CreatorData.RaceName = RaceName + RaceData.InCreator = true + CreatorUI() + CreatorLoop() + else + QBCore.Functions.Notify('Du er allerede igang med at lave et løb.', 'error') + end +end) + +RegisterNetEvent('qb-lapraces:client:UpdateRaceRacerData', function(RaceId, aRaceData) + if (CurrentRaceData.RaceId ~= nil) and CurrentRaceData.RaceId == RaceId then + CurrentRaceData.Racers = aRaceData.Racers + end +end) + +RegisterNetEvent('qb-lapraces:client:JoinRace', function(Data, Laps) + if not RaceData.InRace then + RaceData.InRace = true + SetupRace(Data, Laps) + TriggerServerEvent('qb-lapraces:server:UpdateRaceState', CurrentRaceData.RaceId, false, true) + else + QBCore.Functions.Notify('Du er allerede i et løb.', 'error') + end +end) + +RegisterNetEvent('qb-lapraces:client:LeaveRace', function(_) + QBCore.Functions.Notify('You have completed the race!') + for k, _ in pairs(CurrentRaceData.Checkpoints) do + if CurrentRaceData.Checkpoints[k].blip ~= nil then + RemoveBlip(CurrentRaceData.Checkpoints[k].blip) + CurrentRaceData.Checkpoints[k].blip = nil + end + if CurrentRaceData.Checkpoints[k].pileleft ~= nil then + local coords = CurrentRaceData.Checkpoints[k].offset.left + local Obj = GetClosestObjectOfType(coords.x, coords.y, coords.z, 5.0, `prop_offroad_tyres02`, 0, 0, 0) + DeleteObject(Obj) + ClearAreaOfObjects(coords.x, coords.y, coords.z, 50.0, 0) + CurrentRaceData.Checkpoints[k].pileleft = nil + end + if CurrentRaceData.Checkpoints[k].pileright ~= nil then + local coords = CurrentRaceData.Checkpoints[k].offset.right + local Obj = GetClosestObjectOfType(coords.x, coords.y, coords.z, 5.0, `prop_offroad_tyres02`, 0, 0, 0) + DeleteObject(Obj) + ClearAreaOfObjects(coords.x, coords.y, coords.z, 50.0, 0) + CurrentRaceData.Checkpoints[k].pileright = nil + end + end + CurrentRaceData.RaceName = nil + CurrentRaceData.Checkpoints = {} + CurrentRaceData.Started = false + CurrentRaceData.CurrentCheckpoint = 0 + CurrentRaceData.TotalLaps = 0 + CurrentRaceData.Lap = 0 + CurrentRaceData.RaceTime = 0 + CurrentRaceData.TotalTime = 0 + CurrentRaceData.BestLap = 0 + CurrentRaceData.RaceId = nil + RaceData.InRace = false + FreezeEntityPosition(GetVehiclePedIsIn(PlayerPedId(), false), false) +end) + +RegisterNetEvent('qb-lapraces:client:RaceCountdown', function() + TriggerServerEvent('qb-lapraces:server:UpdateRaceState', CurrentRaceData.RaceId, true, false) + if CurrentRaceData.RaceId ~= nil then + while Countdown ~= 0 do + if CurrentRaceData.RaceName ~= nil then + if Countdown == 10 then + QBCore.Functions.Notify('Løbet starter om 10 sekunder!', 'error', 2500) + PlaySound(-1, "slow", "SHORT_PLAYER_SWITCH_SOUND_SET", 0, 0, 1) + elseif Countdown <= 5 then + QBCore.Functions.Notify(Countdown, 'error', 500) + PlaySound(-1, "slow", "SHORT_PLAYER_SWITCH_SOUND_SET", 0, 0, 1) + end + Countdown = Countdown - 1 + FreezeEntityPosition(GetVehiclePedIsIn(PlayerPedId(), true), true) + else + break + end + Wait(1000) + end + if CurrentRaceData.RaceName ~= nil then + SetNewWaypoint(CurrentRaceData.Checkpoints[CurrentRaceData.CurrentCheckpoint + 1].coords.x, CurrentRaceData.Checkpoints[CurrentRaceData.CurrentCheckpoint + 1].coords.y) + QBCore.Functions.Notify('GO!', 'success', 1000) + SetBlipScale(CurrentRaceData.Checkpoints[CurrentRaceData.CurrentCheckpoint + 1].blip, 1.0) + FreezeEntityPosition(GetVehiclePedIsIn(PlayerPedId(), true), false) + DoPilePfx() + CurrentRaceData.Started = true + Countdown = 10 + else + FreezeEntityPosition(GetVehiclePedIsIn(PlayerPedId(), true), false) + Countdown = 10 + end + else + QBCore.Functions.Notify('Du er ikke i noget løb...', 'error') + end +end) + +RegisterNetEvent('qb-lapraces:client:PlayerFinishs', function(RaceId, Place, FinisherData) + if CurrentRaceData.RaceId ~= nil then + if CurrentRaceData.RaceId == RaceId then + QBCore.Functions.Notify(FinisherData.PlayerData.charinfo.firstname..' sluttede på: '..Place..". pladsen", 'error', 3500) + end + end +end) + +RegisterNetEvent('qb-lapraces:client:WaitingDistanceCheck', function() + Wait(1000) + CreateThread(function() + while true do + if not CurrentRaceData.Started then + local ped = PlayerPedId() + local pos = GetEntityCoords(ped) + if CurrentRaceData.Checkpoints[1] ~= nil then + local cpcoords = CurrentRaceData.Checkpoints[1].coords + local dist = #(pos - vector3(cpcoords.x, cpcoords.y, cpcoords.z)) + if dist > 115.0 then + if ToFarCountdown ~= 0 then + ToFarCountdown = ToFarCountdown - 1 + QBCore.Functions.Notify('Kør tilbage til start, ellers bliver du smidt ud om: '..ToFarCountdown..'s', 'error', 500) + else + TriggerServerEvent('qb-lapraces:server:LeaveRace', CurrentRaceData) + ToFarCountdown = 10 + break + end + Wait(1000) + else + if ToFarCountdown ~= 10 then + ToFarCountdown = 10 + end + end + end + else + break + end + Wait(3) + end + end) +end) + +-- Threads + +CreateThread(function() + while true do + if RaceData.InCreator then + local PlayerPed = PlayerPedId() + local PlayerVeh = GetVehiclePedIsIn(PlayerPed) + + if PlayerVeh ~= 0 then + local Offset = { + left = { + x = (GetOffsetFromEntityInWorldCoords(PlayerVeh, -CreatorData.TireDistance, 0.0, 0.0)).x, + y = (GetOffsetFromEntityInWorldCoords(PlayerVeh, -CreatorData.TireDistance, 0.0, 0.0)).y, + z = (GetOffsetFromEntityInWorldCoords(PlayerVeh, -CreatorData.TireDistance, 0.0, 0.0)).z, + }, + right = { + x = (GetOffsetFromEntityInWorldCoords(PlayerVeh, CreatorData.TireDistance, 0.0, 0.0)).x, + y = (GetOffsetFromEntityInWorldCoords(PlayerVeh, CreatorData.TireDistance, 0.0, 0.0)).y, + z = (GetOffsetFromEntityInWorldCoords(PlayerVeh, CreatorData.TireDistance, 0.0, 0.0)).z, + } + } + + DrawText3Ds(Offset.left.x, Offset.left.y, Offset.left.z, 'Checkpoint L') + DrawText3Ds(Offset.right.x, Offset.right.y, Offset.right.z, 'Checkpoint R') + end + end + Wait(3) + end +end) + +CreateThread(function() + while true do + + local ped = PlayerPedId() + local pos = GetEntityCoords(ped) + + if CurrentRaceData.RaceName ~= nil then + if CurrentRaceData.Started then + local cp + if CurrentRaceData.CurrentCheckpoint + 1 > #CurrentRaceData.Checkpoints then + cp = 1 + else + cp = CurrentRaceData.CurrentCheckpoint + 1 + end + local data = CurrentRaceData.Checkpoints[cp] + local CheckpointDistance = #(pos - vector3(data.coords.x, data.coords.y, data.coords.z)) + local MaxDistance = GetMaxDistance(CurrentRaceData.Checkpoints[cp].offset) + + if CheckpointDistance < MaxDistance then + if CurrentRaceData.TotalLaps == 0 then + if CurrentRaceData.CurrentCheckpoint + 1 < #CurrentRaceData.Checkpoints then + CurrentRaceData.CurrentCheckpoint = CurrentRaceData.CurrentCheckpoint + 1 + SetNewWaypoint(CurrentRaceData.Checkpoints[CurrentRaceData.CurrentCheckpoint + 1].coords.x, CurrentRaceData.Checkpoints[CurrentRaceData.CurrentCheckpoint + 1].coords.y) + TriggerServerEvent('qb-lapraces:server:UpdateRacerData', CurrentRaceData.RaceId, CurrentRaceData.CurrentCheckpoint, CurrentRaceData.Lap, false) + DoPilePfx() + PlaySound(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", 0, 0, 1) + SetBlipScale(CurrentRaceData.Checkpoints[CurrentRaceData.CurrentCheckpoint].blip, 0.6) + SetBlipScale(CurrentRaceData.Checkpoints[CurrentRaceData.CurrentCheckpoint + 1].blip, 1.0) + else + DoPilePfx() + PlaySound(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", 0, 0, 1) + CurrentRaceData.CurrentCheckpoint = CurrentRaceData.CurrentCheckpoint + 1 + TriggerServerEvent('qb-lapraces:server:UpdateRacerData', CurrentRaceData.RaceId, CurrentRaceData.CurrentCheckpoint, CurrentRaceData.Lap, true) + FinishRace() + end + else + if CurrentRaceData.CurrentCheckpoint + 1 > #CurrentRaceData.Checkpoints then + if CurrentRaceData.Lap + 1 > CurrentRaceData.TotalLaps then + DoPilePfx() + PlaySound(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", 0, 0, 1) + CurrentRaceData.CurrentCheckpoint = CurrentRaceData.CurrentCheckpoint + 1 + TriggerServerEvent('qb-lapraces:server:UpdateRacerData', CurrentRaceData.RaceId, CurrentRaceData.CurrentCheckpoint, CurrentRaceData.Lap, true) + FinishRace() + else + DoPilePfx() + PlaySound(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", 0, 0, 1) + if CurrentRaceData.RaceTime < CurrentRaceData.BestLap then + CurrentRaceData.BestLap = CurrentRaceData.RaceTime + elseif CurrentRaceData.BestLap == 0 then + CurrentRaceData.BestLap = CurrentRaceData.RaceTime + end + CurrentRaceData.RaceTime = 0 + CurrentRaceData.Lap = CurrentRaceData.Lap + 1 + CurrentRaceData.CurrentCheckpoint = 1 + SetNewWaypoint(CurrentRaceData.Checkpoints[CurrentRaceData.CurrentCheckpoint + 1].coords.x, CurrentRaceData.Checkpoints[CurrentRaceData.CurrentCheckpoint + 1].coords.y) + TriggerServerEvent('qb-lapraces:server:UpdateRacerData', CurrentRaceData.RaceId, CurrentRaceData.CurrentCheckpoint, CurrentRaceData.Lap, false) + end + else + CurrentRaceData.CurrentCheckpoint = CurrentRaceData.CurrentCheckpoint + 1 + if CurrentRaceData.CurrentCheckpoint ~= #CurrentRaceData.Checkpoints then + SetNewWaypoint(CurrentRaceData.Checkpoints[CurrentRaceData.CurrentCheckpoint + 1].coords.x, CurrentRaceData.Checkpoints[CurrentRaceData.CurrentCheckpoint + 1].coords.y) + TriggerServerEvent('qb-lapraces:server:UpdateRacerData', CurrentRaceData.RaceId, CurrentRaceData.CurrentCheckpoint, CurrentRaceData.Lap, false) + SetBlipScale(CurrentRaceData.Checkpoints[CurrentRaceData.CurrentCheckpoint].blip, 0.6) + SetBlipScale(CurrentRaceData.Checkpoints[CurrentRaceData.CurrentCheckpoint + 1].blip, 1.0) + else + SetNewWaypoint(CurrentRaceData.Checkpoints[1].coords.x, CurrentRaceData.Checkpoints[1].coords.y) + TriggerServerEvent('qb-lapraces:server:UpdateRacerData', CurrentRaceData.RaceId, CurrentRaceData.CurrentCheckpoint, CurrentRaceData.Lap, false) + SetBlipScale(CurrentRaceData.Checkpoints[#CurrentRaceData.Checkpoints].blip, 0.6) + SetBlipScale(CurrentRaceData.Checkpoints[1].blip, 1.0) + end + DoPilePfx() + PlaySound(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", 0, 0, 1) + end + end + end + else + local data = CurrentRaceData.Checkpoints[CurrentRaceData.CurrentCheckpoint] + -- DrawText3Ds(data.coords.x, data.coords.y, data.coords.z, 'Ga hier staan') + DrawMarker(4, data.coords.x, data.coords.y, data.coords.z + 1.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.9, 1.5, 1.5, 255, 255, 255, 255, 0, 1, 0, 0, 0, 0, 0) + end + else + Wait(1000) + end + + Wait(3) + end +end) + +CreateThread(function() + while true do + if RaceData.InCreator then + GetClosestCheckpoint() + SetupPiles() + end + Wait(1000) + end +end) + +CreateThread(function() + while true do + local Driver, plyVeh = Info() + if Driver then + if GetVehicleCurrentGear(plyVeh) < 3 and GetVehicleCurrentRpm(plyVeh) == 1.0 and math.ceil(GetEntitySpeed(plyVeh) * 2.236936) > 50 then + while GetVehicleCurrentRpm(plyVeh) > 0.6 do + SetVehicleCurrentRpm(plyVeh, 0.3) + Wait(1) + end + Wait(800) + end + end + Wait(500) + end +end) diff --git a/resources/[qb]/[qb_core]/qb-lapraces/config.lua b/resources/[qb]/[qb_core]/qb-lapraces/config.lua new file mode 100644 index 0000000..fa18979 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-lapraces/config.lua @@ -0,0 +1,10 @@ +Config = Config or {} + +Config.WhitelistedCreators = { + "DZD19858", + "FSK23277", + "FUD52312", + "RKS92226", +} + +Config.RaceSetupAllowed = true diff --git a/resources/[qb]/[qb_core]/qb-lapraces/fxmanifest.lua b/resources/[qb]/[qb_core]/qb-lapraces/fxmanifest.lua new file mode 100644 index 0000000..e0f9f7a --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-lapraces/fxmanifest.lua @@ -0,0 +1,25 @@ +fx_version 'cerulean' +game 'gta5' + +description 'QB-LapRaces' +version '1.2.0' + +ui_page 'html/index.html' + +shared_script 'config.lua' + +client_script 'client/main.lua' + +server_scripts { + '@oxmysql/lib/MySQL.lua', + 'server/main.lua' +} + +files { + 'html/*.html', + 'html/*.css', + 'html/*.js', + 'html/img/*' +} + +lua54 'yes' diff --git a/resources/[qb]/[qb_core]/qb-lapraces/html/index.html b/resources/[qb]/[qb_core]/qb-lapraces/html/index.html new file mode 100644 index 0000000..3536086 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-lapraces/html/index.html @@ -0,0 +1,35 @@ + + + + + + QB Lapraces + + + + +
+
+
+ Race: Test Race + Checkpoints: 1 / ? + 7 - Add Checkpoint + + + 9 - Cancel Editor + K - Save Race +
+ +
+ Race: Test Race + Checkpoints: 1 / 1 + Lap: 1 / 1 + Current Lap: + Best Lap: + Total Time: +
+
+
+ + + \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-lapraces/html/script.js b/resources/[qb]/[qb_core]/qb-lapraces/html/script.js new file mode 100644 index 0000000..4871a64 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-lapraces/html/script.js @@ -0,0 +1,92 @@ +var CreatorActive = false; +var RaceActive = false; + +$(document).ready(function(){ + window.addEventListener('message', function(event){ + var data = event.data; + + if (data.action == "Update") { + UpdateUI(data.type, data); + } + }); +}); + +function secondsTimeSpanToHMS(s) { + var h = Math.floor(s/3600); //Get whole hours + s -= h*3600; + var m = Math.floor(s/60); //Get remaining minutes + s -= m*60; + return h+":"+(m < 10 ? '0'+m : m)+":"+(s < 10 ? '0'+s : s); //zero padding on minutes and seconds +} + +function UpdateUI(type, data) { + if (type == "creator") { + if (data.active) { + if (!CreatorActive) { + CreatorActive = true; + $(".editor").fadeIn(300); + $("#editor-racename").html('Race: ' + data.data.RaceName); + $("#editor-checkpoints").html('Checkpoints: ' + data.data.Checkpoints.length + ' / ?'); + $("#editor-keys-tiredistance").html('+ ] / - [ - Dæk distance ['+data.data.TireDistance+'.0]'); + if (data.racedata.ClosestCheckpoint !== undefined && data.racedata.ClosestCheckpoint !== 0) { + $("#editor-keys-delete").html('8 - Slet checkpoints [' + data.racedata.ClosestCheckpoint + ']'); + } else { + $("#editor-keys-delete").html(""); + } + } else { + $("#editor-racename").html('Race: ' + data.data.RaceName); + $("#editor-checkpoints").html('Checkpoints: ' + data.data.Checkpoints.length + ' / ?'); + $("#editor-keys-tiredistance").html('+ ] / - [ - Dæk distance ['+data.data.TireDistance+'.0]'); + if (data.racedata.ClosestCheckpoint !== undefined && data.racedata.ClosestCheckpoint !== 0) { + $("#editor-keys-delete").html('8 - Slet checkpoints [' + data.racedata.ClosestCheckpoint + ']'); + } else { + $("#editor-keys-delete").html(""); + } + } + } else { + CreatorActive = false; + $(".editor").fadeOut(300); + } + } else if (type == "race") { + if (data.active) { + if (!RaceActive) { + RaceActive = true; + $(".editor").hide(); + $(".race").fadeIn(300); + $("#race-racename").html('Race: ' + data.data.RaceName); + $("#race-checkpoints").html('Checkpoint: ' + data.data.CurrentCheckpoint + ' / ' + data.data.TotalCheckpoints); + if (data.data.TotalLaps == 0) { + $("#race-lap").html('Runde: Sprint'); + } else { + $("#race-lap").html('Runde: ' + data.data.CurrentLap + ' / ' + data.data.TotalLaps); + } + $("#race-time").html('Rundetid: ' + secondsTimeSpanToHMS(data.data.Time)); + if (data.data.BestLap !== 0) { + $("#race-besttime").html('Bedste runde: ' + secondsTimeSpanToHMS(data.data.BestLap)); + } else { + $("#race-besttime").html('Bedste runde: N/A'); + } + $("#race-totaltime").html('Samlet tid: ' + secondsTimeSpanToHMS(data.data.TotalTime)); + } else { + $("#race-racename").html('Race: ' + data.data.RaceName); + $("#race-checkpoints").html('Checkpoint: ' + data.data.CurrentCheckpoint + ' / ' + data.data.TotalCheckpoints); + if (data.data.TotalLaps == 0) { + $("#race-lap").html('Runde: Sprint'); + } else { + $("#race-lap").html('Runde: ' + data.data.CurrentLap + ' / ' + data.data.TotalLaps); + } + $("#race-time").html('Rundetid: ' + secondsTimeSpanToHMS(data.data.Time)); + if (data.data.BestLap !== 0) { + $("#race-besttime").html('Bedste runde: ' + secondsTimeSpanToHMS(data.data.BestLap)); + } else { + $("#race-besttime").html('Bedste runde: N/A'); + } + $("#race-totaltime").html('Samlet tid: ' + secondsTimeSpanToHMS(data.data.TotalTime)); + } + } else { + RaceActive = false; + $(".editor").hide(); + $(".race").fadeOut(300); + } + } +} \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-lapraces/html/style.css b/resources/[qb]/[qb_core]/qb-lapraces/html/style.css new file mode 100644 index 0000000..8c3973b --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-lapraces/html/style.css @@ -0,0 +1,164 @@ +@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200&display=swap'); + +body { + margin: 0; + padding: 0; +} + +:root { + --main-font-size: 2vh; +} + +.container { + height: 100vh; + opacity: 1.0; +} + +.ui { + position: absolute; + width: 30vh; + height: 20vh; + bottom: 23vh; + border-radius: 1vh; + left: 2.7vh; +} + +.editor { + display: none; +} + +#editor-racename { + position: absolute; + left: 2vh; + top: 2vh; + font-family: 'Poppins', sans-serif; + font-size: var(--main-font-size); + color: white; + text-shadow: 1px 1px 0px #000000; +} + +#editor-checkpoints { + position: absolute; + left: 2vh; + top: 5vh; + font-family: 'Poppins', sans-serif; + font-size: var(--main-font-size); + color: white; + text-shadow: 1px 1px 0px #000000; +} + +#editor-keys-tiredistance { + position: absolute; + left: 2vh; + top: 8vh; + font-family: 'Poppins', sans-serif; + font-size: var(--main-font-size); + color: white; + text-shadow: 1px 1px 0px #000000; +} + +#editor-keys-add { + position: absolute; + left: 2vh; + top: 11vh; + font-family: 'Poppins', sans-serif; + font-size: var(--main-font-size); + color: white; + text-shadow: 1px 1px 0px #000000; +} + +#editor-keys-delete { + position: absolute; + left: 2vh; + top: 14vh; + font-family: 'Poppins', sans-serif; + font-size: var(--main-font-size); + color: white; + text-shadow: 1px 1px 0px #000000; +} + +#editor-keys-cancel { + position: absolute; + left: 2vh; + top: 17vh; + font-family: 'Poppins', sans-serif; + font-size: var(--main-font-size); + color: white; + text-shadow: 1px 1px 0px #000000; +} + +#editor-keys-save { + position: absolute; + left: 2vh; + top: 20vh; + font-family: 'Poppins', sans-serif; + font-size: var(--main-font-size); + color: white; + text-shadow: 1px 1px 0px #000000; +} + +/* RACE */ + +.race { + display: none; +} + +#race-racename { + position: absolute; + left: 2vh; + top: 2vh; + font-family: 'Poppins', sans-serif; + font-size: var(--main-font-size); + color: white; + text-shadow: 1px 1px 0px #000000; +} + +#race-checkpoints { + position: absolute; + left: 2vh; + top: 5vh; + font-family: 'Poppins', sans-serif; + font-size: var(--main-font-size); + color: white; + text-shadow: 1px 1px 0px #000000; +} + +#race-lap { + position: absolute; + left: 2vh; + top: 8vh; + font-family: 'Poppins', sans-serif; + font-size: var(--main-font-size); + color: white; + text-shadow: 1px 1px 0px #000000; +} + +#race-time { + position: absolute; + left: 2vh; + top: 11vh; + font-family: 'Poppins', sans-serif; + font-size: var(--main-font-size); + color: white; + text-shadow: 1px 1px 0px #000000; +} + +#race-besttime { + position: absolute; + left: 2vh; + top: 14vh; + font-family: 'Poppins', sans-serif; + font-size: var(--main-font-size); + color: white; + text-shadow: 1px 1px 0px #000000; +} + +#race-totaltime { + position: absolute; + left: 2vh; + top: 17vh; + font-family: 'Poppins', sans-serif; + font-size: var(--main-font-size); + color: white; + text-shadow: 1px 1px 0px #000000; +} diff --git a/resources/[qb]/[qb_core]/qb-lapraces/qb-lapraces.sql b/resources/[qb]/[qb_core]/qb-lapraces/qb-lapraces.sql new file mode 100644 index 0000000..7a92d5b --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-lapraces/qb-lapraces.sql @@ -0,0 +1,10 @@ +CREATE TABLE IF NOT EXISTS `lapraces` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(50) DEFAULT NULL, + `checkpoints` text DEFAULT NULL, + `records` text DEFAULT NULL, + `creator` varchar(50) DEFAULT NULL, + `distance` int(11) DEFAULT NULL, + `raceid` varchar(50) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=35 DEFAULT CHARSET=utf8mb4; diff --git a/resources/[qb]/[qb_core]/qb-lapraces/server/main.lua b/resources/[qb]/[qb_core]/qb-lapraces/server/main.lua new file mode 100644 index 0000000..38a2bd4 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-lapraces/server/main.lua @@ -0,0 +1,599 @@ +local QBCore = exports['qb-core']:GetCoreObject() +local Races = {} +local AvailableRaces = {} +local LastRaces = {} +local NotFinished = {} + +-- Functions + +local function SecondsToClock(seconds) + seconds = tonumber(seconds) + local retval + if seconds <= 0 then + retval = "00:00:00"; + else + local hours = string.format("%02.f", math.floor(seconds / 3600)); + local mins = string.format("%02.f", math.floor(seconds / 60 - (hours * 60))); + local secs = string.format("%02.f", math.floor(seconds - hours * 3600 - mins * 60)); + retval = hours .. ":" .. mins .. ":" .. secs + end + return retval +end + +local function IsWhitelisted(CitizenId) + local retval = false + for _, cid in pairs(Config.WhitelistedCreators) do + if cid == CitizenId then + retval = true + break + end + end + local Player = QBCore.Functions.GetPlayerByCitizenId(CitizenId) + local Perms = QBCore.Functions.GetPermission(Player.PlayerData.source) + if Perms == "admin" or Perms == "god" then + retval = true + end + return retval +end + +local function IsNameAvailable(RaceName) + local retval = true + for RaceId, _ in pairs(Races) do + if Races[RaceId].RaceName == RaceName then + retval = false + break + end + end + return retval +end + +local function HasOpenedRace(CitizenId) + local retval = false + for _, v in pairs(AvailableRaces) do + if v.SetupCitizenId == CitizenId then + retval = true + end + end + return retval +end + +local function GetOpenedRaceKey(RaceId) + local retval = nil + for k, v in pairs(AvailableRaces) do + if v.RaceId == RaceId then + retval = k + break + end + end + return retval +end + +local function GetCurrentRace(MyCitizenId) + local retval = nil + for RaceId, _ in pairs(Races) do + for cid, _ in pairs(Races[RaceId].Racers) do + if cid == MyCitizenId then + retval = RaceId + break + end + end + end + return retval +end + +local function GetRaceId(name) + local retval = nil + for k, v in pairs(Races) do + if v.RaceName == name then + retval = k + break + end + end + return retval +end + +local function GenerateRaceId() + local RaceId = "LR-" .. math.random(1111, 9999) + while Races[RaceId] ~= nil do + RaceId = "LR-" .. math.random(1111, 9999) + end + return RaceId +end + +-- Events + +RegisterNetEvent('qb-lapraces:server:FinishPlayer', function(RaceData, TotalTime, TotalLaps, BestLap) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local AvailableKey = GetOpenedRaceKey(RaceData.RaceId) + local PlayersFinished = 0 + local AmountOfRacers = 0 + for _, v in pairs(Races[RaceData.RaceId].Racers) do + if v.Finished then + PlayersFinished = PlayersFinished + 1 + end + AmountOfRacers = AmountOfRacers + 1 + end + local BLap + if TotalLaps < 2 then + BLap = TotalTime + else + BLap = BestLap + end + if LastRaces[RaceData.RaceId] ~= nil then + LastRaces[RaceData.RaceId][#LastRaces[RaceData.RaceId]+1] = { + TotalTime = TotalTime, + BestLap = BLap, + Holder = { + [1] = Player.PlayerData.charinfo.firstname, + [2] = Player.PlayerData.charinfo.lastname + } + } + else + LastRaces[RaceData.RaceId] = {} + LastRaces[RaceData.RaceId][#LastRaces[RaceData.RaceId]+1] = { + TotalTime = TotalTime, + BestLap = BLap, + Holder = { + [1] = Player.PlayerData.charinfo.firstname, + [2] = Player.PlayerData.charinfo.lastname + } + } + end + if Races[RaceData.RaceId].Records ~= nil and next(Races[RaceData.RaceId].Records) ~= nil then + if BLap < Races[RaceData.RaceId].Records.Time then + Races[RaceData.RaceId].Records = { + Time = BLap, + Holder = { + [1] = Player.PlayerData.charinfo.firstname, + [2] = Player.PlayerData.charinfo.lastname + } + } + MySQL.update('UPDATE lapraces SET records = ? WHERE raceid = ?', + {json.encode(Races[RaceData.RaceId].Records), RaceData.RaceId}) + TriggerClientEvent('qb-phone:client:RaceNotify', src, 'Du fik hurtigste tid på ' .. RaceData.RaceName .. + ' med en tid på: ' .. SecondsToClock(BLap) .. '!') + end + else + Races[RaceData.RaceId].Records = { + Time = BLap, + Holder = { + [1] = Player.PlayerData.charinfo.firstname, + [2] = Player.PlayerData.charinfo.lastname + } + } + MySQL.update('UPDATE lapraces SET records = ? WHERE raceid = ?', + {json.encode(Races[RaceData.RaceId].Records), RaceData.RaceId}) + TriggerClientEvent('qb-phone:client:RaceNotify', src, 'Du fik hurtigste tid på ' .. RaceData.RaceName .. + ' med en tid på: ' .. SecondsToClock(BLap) .. '!') + end + AvailableRaces[AvailableKey].RaceData = Races[RaceData.RaceId] + TriggerClientEvent('qb-lapraces:client:PlayerFinishs', -1, RaceData.RaceId, PlayersFinished, Player) + if PlayersFinished == AmountOfRacers then + if NotFinished ~= nil and next(NotFinished) ~= nil and NotFinished[RaceData.RaceId] ~= nil and + next(NotFinished[RaceData.RaceId]) ~= nil then + for _, v in pairs(NotFinished[RaceData.RaceId]) do + LastRaces[RaceData.RaceId][#LastRaces[RaceData.RaceId]+1] = { + TotalTime = v.TotalTime, + BestLap = v.BestLap, + Holder = { + [1] = v.Holder[1], + [2] = v.Holder[2] + } + } + end + end + Races[RaceData.RaceId].LastLeaderboard = LastRaces[RaceData.RaceId] + Races[RaceData.RaceId].Racers = {} + Races[RaceData.RaceId].Started = false + Races[RaceData.RaceId].Waiting = false + table.remove(AvailableRaces, AvailableKey) + LastRaces[RaceData.RaceId] = nil + NotFinished[RaceData.RaceId] = nil + end + TriggerClientEvent('qb-phone:client:UpdateLapraces', -1) +end) + +RegisterNetEvent('qb-lapraces:server:CreateLapRace', function(RaceName) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + + if IsWhitelisted(Player.PlayerData.citizenid) then + if IsNameAvailable(RaceName) then + TriggerClientEvent('qb-lapraces:client:StartRaceEditor', source, RaceName) + else + TriggerClientEvent('QBCore:Notify', source, 'Et løb med dette navn findes allerede.', 'error') + end + else + TriggerClientEvent('QBCore:Notify', source, 'Du har ikke tilladelse til at oprette et løb', 'error') + end +end) + +RegisterNetEvent('qb-lapraces:server:JoinRace', function(RaceData) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local RaceId = RaceData.RaceId + local AvailableKey = GetOpenedRaceKey(RaceId) + local CurrentRace = GetCurrentRace(Player.PlayerData.citizenid) + if CurrentRace ~= nil then + local AmountOfRacers = 0 + local PreviousRaceKey = GetOpenedRaceKey(CurrentRace) + for _, _ in pairs(Races[CurrentRace].Racers) do + AmountOfRacers = AmountOfRacers + 1 + end + Races[CurrentRace].Racers[Player.PlayerData.citizenid] = nil + if (AmountOfRacers - 1) == 0 then + Races[CurrentRace].Racers = {} + Races[CurrentRace].Started = false + Races[CurrentRace].Waiting = false + table.remove(AvailableRaces, PreviousRaceKey) + TriggerClientEvent('QBCore:Notify', src, 'Du var alene i løbet, så det blev stoppet.', 'error') + TriggerClientEvent('qb-lapraces:client:LeaveRace', src, Races[CurrentRace]) + else + AvailableRaces[PreviousRaceKey].RaceData = Races[CurrentRace] + TriggerClientEvent('qb-lapraces:client:LeaveRace', src, Races[CurrentRace]) + end + TriggerClientEvent('qb-phone:client:UpdateLapraces', -1) + end + Races[RaceId].Waiting = true + Races[RaceId].Racers[Player.PlayerData.citizenid] = { + Checkpoint = 0, + Lap = 1, + Finished = false + } + AvailableRaces[AvailableKey].RaceData = Races[RaceId] + TriggerClientEvent('qb-lapraces:client:JoinRace', src, Races[RaceId], AvailableRaces[AvailableKey].Laps) + TriggerClientEvent('qb-phone:client:UpdateLapraces', -1) + local creatorsource = QBCore.Functions.GetPlayerByCitizenId(AvailableRaces[AvailableKey].SetupCitizenId).PlayerData + .source + if creatorsource ~= Player.PlayerData.source then + TriggerClientEvent('qb-phone:client:RaceNotify', creatorsource, + string.sub(Player.PlayerData.charinfo.firstname, 1, 1) .. ' ' .. Player.PlayerData.charinfo.lastname .. + ' joinede løbet!') + end +end) + +RegisterNetEvent('qb-lapraces:server:LeaveRace', function(RaceData) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local RaceName + if RaceData.RaceData ~= nil then + RaceName = RaceData.RaceData.RaceName + else + RaceName = RaceData.RaceName + end + local RaceId = GetRaceId(RaceName) + local AvailableKey = GetOpenedRaceKey(RaceData.RaceId) + local creatorsource = QBCore.Functions.GetPlayerByCitizenId(AvailableRaces[AvailableKey].SetupCitizenId).PlayerData + .source + if creatorsource ~= Player.PlayerData.source then + TriggerClientEvent('qb-phone:client:RaceNotify', creatorsource, + string.sub(Player.PlayerData.charinfo.firstname, 1, 1) .. ' ' .. Player.PlayerData.charinfo.lastname .. + ' forlod løbet!') + end + local AmountOfRacers = 0 + for _, _ in pairs(Races[RaceData.RaceId].Racers) do + AmountOfRacers = AmountOfRacers + 1 + end + if NotFinished[RaceData.RaceId] ~= nil then + NotFinished[RaceData.RaceId][#NotFinished[RaceData.RaceId]+1] = { + TotalTime = "DNF", + BestLap = "DNF", + Holder = { + [1] = Player.PlayerData.charinfo.firstname, + [2] = Player.PlayerData.charinfo.lastname + } + } + else + NotFinished[RaceData.RaceId] = {} + NotFinished[RaceData.RaceId][#NotFinished[RaceData.RaceId]+1] = { + TotalTime = "DNF", + BestLap = "DNF", + Holder = { + [1] = Player.PlayerData.charinfo.firstname, + [2] = Player.PlayerData.charinfo.lastname + } + } + end + Races[RaceId].Racers[Player.PlayerData.citizenid] = nil + if (AmountOfRacers - 1) == 0 then + if NotFinished ~= nil and next(NotFinished) ~= nil and NotFinished[RaceId] ~= nil and next(NotFinished[RaceId]) ~= + nil then + for _, v in pairs(NotFinished[RaceId]) do + if LastRaces[RaceId] ~= nil then + LastRaces[RaceId][#LastRaces[RaceId]+1] = { + TotalTime = v.TotalTime, + BestLap = v.BestLap, + Holder = { + [1] = v.Holder[1], + [2] = v.Holder[2] + } + } + else + LastRaces[RaceId] = {} + LastRaces[RaceId][#LastRaces[RaceId]+1] = { + TotalTime = v.TotalTime, + BestLap = v.BestLap, + Holder = { + [1] = v.Holder[1], + [2] = v.Holder[2] + } + } + end + end + end + Races[RaceId].LastLeaderboard = LastRaces[RaceId] + Races[RaceId].Racers = {} + Races[RaceId].Started = false + Races[RaceId].Waiting = false + table.remove(AvailableRaces, AvailableKey) + TriggerClientEvent('QBCore:Notify', src, 'Du var alene i løbet, så det blev stoppet.', 'error') + TriggerClientEvent('qb-lapraces:client:LeaveRace', src, Races[RaceId]) + LastRaces[RaceId] = nil + NotFinished[RaceId] = nil + else + AvailableRaces[AvailableKey].RaceData = Races[RaceId] + TriggerClientEvent('qb-lapraces:client:LeaveRace', src, Races[RaceId]) + end + TriggerClientEvent('qb-phone:client:UpdateLapraces', -1) +end) + +RegisterNetEvent('qb-lapraces:server:SetupRace', function(RaceId, Laps) + local Player = QBCore.Functions.GetPlayer(source) + if Races[RaceId] ~= nil then + if not Races[RaceId].Waiting then + if not Races[RaceId].Started then + Races[RaceId].Waiting = true + AvailableRaces[#AvailableRaces+1] = { + RaceData = Races[RaceId], + Laps = Laps, + RaceId = RaceId, + SetupCitizenId = Player.PlayerData.citizenid + } + TriggerClientEvent('qb-phone:client:UpdateLapraces', -1) + SetTimeout(5 * 60 * 1000, function() + if Races[RaceId].Waiting then + local AvailableKey = GetOpenedRaceKey(RaceId) + for cid, _ in pairs(Races[RaceId].Racers) do + local RacerData = QBCore.Functions.GetPlayerByCitizenId(cid) + if RacerData ~= nil then + TriggerClientEvent('qb-lapraces:client:LeaveRace', RacerData.PlayerData.source, + Races[RaceId]) + end + end + table.remove(AvailableRaces, AvailableKey) + Races[RaceId].LastLeaderboard = {} + Races[RaceId].Racers = {} + Races[RaceId].Started = false + Races[RaceId].Waiting = false + LastRaces[RaceId] = nil + TriggerClientEvent('qb-phone:client:UpdateLapraces', -1) + end + end) + else + TriggerClientEvent('QBCore:Notify', source, 'Løber er allerede igang.', 'error') + end + else + TriggerClientEvent('QBCore:Notify', source, 'Løber er allerede igang.', 'error') + end + else + TriggerClientEvent('QBCore:Notify', source, 'Løbet findes ikke.', 'error') + end +end) + +RegisterNetEvent('qb-lapraces:server:CancelRace', function(raceId) + local src = source + local Player = QBCore.Functions.GetPlayer(source) + local AvailableKey = GetOpenedRaceKey(raceId) + + TriggerClientEvent('QBCore:Notify', src, 'Stopper løbet: ' .. raceId, 'error') + + if AvailableKey ~= nil then + if AvailableRaces[AvailableKey].SetupCitizenId == Player.PlayerData.citizenid then + for cid, _ in pairs(Races[raceId].Racers) do + local RacerData = QBCore.Functions.GetPlayerByCitizenId(cid) + if RacerData ~= nil then + TriggerClientEvent('qb-lapraces:client:LeaveRace', RacerData.PlayerData.source, Races[raceId]) + end + end + + table.remove(AvailableRaces, AvailableKey) + Races[raceId].LastLeaderboard = {} + Races[raceId].Racers = {} + Races[raceId].Started = false + Races[raceId].Waiting = false + LastRaces[raceId] = nil + TriggerClientEvent('qb-phone:client:UpdateLapraces', -1) + end + else + TriggerClientEvent('QBCore:Notify', src, 'Løbet er ikke åbent: ' .. raceId, 'error') + end +end) + +RegisterNetEvent('qb-lapraces:server:UpdateRaceState', function(RaceId, Started, Waiting) + Races[RaceId].Waiting = Waiting + Races[RaceId].Started = Started +end) + +RegisterNetEvent('qb-lapraces:server:UpdateRacerData', function(RaceId, Checkpoint, Lap, Finished) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local CitizenId = Player.PlayerData.citizenid + + Races[RaceId].Racers[CitizenId].Checkpoint = Checkpoint + Races[RaceId].Racers[CitizenId].Lap = Lap + Races[RaceId].Racers[CitizenId].Finished = Finished + + TriggerClientEvent('qb-lapraces:client:UpdateRaceRacerData', -1, RaceId, Races[RaceId]) +end) + +RegisterNetEvent('qb-lapraces:server:StartRace', function(RaceId) + local src = source + local MyPlayer = QBCore.Functions.GetPlayer(src) + local AvailableKey = GetOpenedRaceKey(RaceId) + + if RaceId ~= nil then + if AvailableRaces[AvailableKey].SetupCitizenId == MyPlayer.PlayerData.citizenid then + AvailableRaces[AvailableKey].RaceData.Started = true + AvailableRaces[AvailableKey].RaceData.Waiting = false + for CitizenId, _ in pairs(Races[RaceId].Racers) do + local Player = QBCore.Functions.GetPlayerByCitizenId(CitizenId) + if Player ~= nil then + TriggerClientEvent('qb-lapraces:client:RaceCountdown', Player.PlayerData.source) + end + end + TriggerClientEvent('qb-phone:client:UpdateLapraces', -1) + else + TriggerClientEvent('QBCore:Notify', src, 'Du er ikke løbslederen...', 'error') + end + else + TriggerClientEvent('QBCore:Notify', src, 'Du er ikke med i løbet...', 'error') + end +end) + +RegisterNetEvent('qb-lapraces:server:SaveRace', function(RaceData) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local RaceId = GenerateRaceId() + local Checkpoints = {} + for k, v in pairs(RaceData.Checkpoints) do + Checkpoints[k] = { + offset = v.offset, + coords = v.coords + } + end + Races[RaceId] = { + RaceName = RaceData.RaceName, + Checkpoints = Checkpoints, + Records = {}, + Creator = Player.PlayerData.citizenid, + RaceId = RaceId, + Started = false, + Waiting = false, + Distance = math.ceil(RaceData.RaceDistance), + Racers = {}, + LastLeaderboard = {} + } + MySQL.insert('INSERT INTO lapraces (name, checkpoints, creator, distance, raceid) VALUES (?, ?, ?, ?, ?)', + {RaceData.RaceName, json.encode(Checkpoints), Player.PlayerData.citizenid, RaceData.RaceDistance, + GenerateRaceId()}) +end) + +-- Callbacks + +QBCore.Functions.CreateCallback('qb-lapraces:server:GetRacingLeaderboards', function(_, cb) + cb(Races) +end) + +QBCore.Functions.CreateCallback('qb-lapraces:server:GetRaces', function(_, cb) + cb(AvailableRaces) +end) + +QBCore.Functions.CreateCallback('qb-lapraces:server:GetListedRaces', function(_, cb) + cb(Races) +end) + +QBCore.Functions.CreateCallback('qb-lapraces:server:GetRacingData', function(_, cb, RaceId) + cb(Races[RaceId]) +end) + +QBCore.Functions.CreateCallback('qb-lapraces:server:HasCreatedRace', function(source, cb) + cb(HasOpenedRace(QBCore.Functions.GetPlayer(source).PlayerData.citizenid)) +end) + +QBCore.Functions.CreateCallback('qb-lapraces:server:IsAuthorizedToCreateRaces', function(source, cb, TrackName) + cb(IsWhitelisted(QBCore.Functions.GetPlayer(source).PlayerData.citizenid), IsNameAvailable(TrackName)) +end) + +QBCore.Functions.CreateCallback('qb-lapraces:server:CanRaceSetup', function(_, cb) + cb(Config.RaceSetupAllowed) +end) + +QBCore.Functions.CreateCallback('qb-lapraces:server:GetTrackData', function(_, cb, RaceId) + local result = MySQL.query.await('SELECT * FROM players WHERE citizenid = ?', {Races[RaceId].Creator}) + if result[1] ~= nil then + result[1].charinfo = json.decode(result[1].charinfo) + cb(Races[RaceId], result[1]) + else + cb(Races[RaceId], { + charinfo = { + firstname = "Ukendt", + lastname = "Navn" + } + }) + end +end) + +-- Commands + +QBCore.Commands.Add("cancelrace", "Annuller løb..", {}, false, function(source, args) + local Player = QBCore.Functions.GetPlayer(source) + + if IsWhitelisted(Player.PlayerData.citizenid) then + local RaceName = table.concat(args, " ") + if RaceName ~= nil then + local RaceId = GetRaceId(RaceName) + if Races[RaceId].Started then + local AvailableKey = GetOpenedRaceKey(RaceId) + for cid, _ in pairs(Races[RaceId].Racers) do + local RacerData = QBCore.Functions.GetPlayerByCitizenId(cid) + if RacerData ~= nil then + TriggerClientEvent('qb-lapraces:client:LeaveRace', RacerData.PlayerData.source, Races[RaceId]) + end + end + table.remove(AvailableRaces, AvailableKey) + Races[RaceId].LastLeaderboard = {} + Races[RaceId].Racers = {} + Races[RaceId].Started = false + Races[RaceId].Waiting = false + LastRaces[RaceId] = nil + TriggerClientEvent('qb-phone:client:UpdateLapraces', -1) + else + TriggerClientEvent('QBCore:Notify', source, 'Løbet er ikke startet.', 'error') + end + end + else + TriggerClientEvent('QBCore:Notify', source, 'Det har du ikke tilladelse til.', 'error') + end +end) + +QBCore.Commands.Add("togglesetup", "Tillad/Forbyd løbstilladelse", {}, false, function(source, _) + local Player = QBCore.Functions.GetPlayer(source) + + if IsWhitelisted(Player.PlayerData.citizenid) then + Config.RaceSetupAllowed = not Config.RaceSetupAllowed + if not Config.RaceSetupAllowed then + TriggerClientEvent('QBCore:Notify', source, 'Der kan ikke laves flere løb!', 'error') + else + TriggerClientEvent('QBCore:Notify', source, 'Der kan nu laves løb igen!', 'success') + end + else + TriggerClientEvent('QBCore:Notify', source, 'Det har du ikke tilladelse til.', 'error') + end +end) + +-- Threads + +CreateThread(function() + local races = MySQL.query.await('SELECT * FROM lapraces', {}) + if races[1] ~= nil then + for _, v in pairs(races) do + local Records = {} + if v.records ~= nil then + Records = json.decode(v.records) + end + Races[v.raceid] = { + RaceName = v.name, + Checkpoints = json.decode(v.checkpoints), + Records = Records, + Creator = v.creator, + RaceId = v.raceid, + Started = false, + Waiting = false, + Distance = v.distance, + LastLeaderboard = {}, + Racers = {} + } + end + end +end) diff --git a/resources/[qb]/[qb_core]/qb-lock/README.md b/resources/[qb]/[qb_core]/qb-lock/README.md new file mode 100644 index 0000000..40192b6 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-lock/README.md @@ -0,0 +1,26 @@ +# qb-lockpick + NoPixel Based Lockpick for QBFramework + + +# Template +exports['qb-lock']:StartLockPickCircle(amount, time, function(success) + +# Example useage + +RegisterCommand("lpgame", function() + local time = math.random(7,10) + local circles = math.random(2,4) + local success = exports['qb-lock']:StartLockPickCircle(circles, time, success) + print(success) + if success then + print("WIN") + else + print("FAIL") + end +end) + +# Amount of time to spin and amount of time to trigger are currently held within the js I am trying to export it to lua +# Amount and Time now work, but functioning success now doesn't go over to the export. +# Fixed the Lockpick now ready for use, enjoy! + +CREDITS TO https://github.com/Tex27 [Tex#9999] for the UI Update diff --git a/resources/[qb]/[qb_core]/qb-lock/client/client.lua b/resources/[qb]/[qb_core]/qb-lock/client/client.lua new file mode 100644 index 0000000..191be31 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-lock/client/client.lua @@ -0,0 +1,44 @@ +local QBCore = exports['qb-core']:GetCoreObject() +local Result = nil +local NUI_status = false + +RegisterNetEvent('kwk-lockpick:client:openLockpick', function(callback, circles) + lockpickCallback = callback + exports['qb-lock']:StartLockPickCircle(total,circles) +end) + +function StartLockPickCircle(circles, seconds, callback) + Result = nil + print(circles, "This be the lock") + NUI_status = true + SendNUIMessage({ + action = 'start', + value = circles, + time = seconds, + }) + while NUI_status do + Wait(5) + SetNuiFocus(NUI_status, false) + end + Wait(100) + SetNuiFocus(false, false) + lockpickCallback = callback + return Result +end + +RegisterNUICallback('fail', function() + ClearPedTasks(PlayerPedId()) + Result = false + Wait(100) + NUI_status = false + --print('fail') +end) + +RegisterNUICallback('success', function() + Result = true + Wait(100) + NUI_status = false + SetNuiFocus(false, false) + print(Result) + return Result +end) diff --git a/resources/[qb]/[qb_core]/qb-lock/fxmanifest.lua b/resources/[qb]/[qb_core]/qb-lock/fxmanifest.lua new file mode 100644 index 0000000..2b602e1 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-lock/fxmanifest.lua @@ -0,0 +1,21 @@ +fx_version 'cerulean' +games { 'gta5' } + +version 'V1.0' + +client_scripts { + 'client/*.lua', +} + +ui_page { + 'html/index.html', +} +files { + 'html/index.html', + 'html/*.css', + 'html/*.js', +} + +exports { + "StartLockPickCircle" +} \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-lock/html/index.html b/resources/[qb]/[qb_core]/qb-lock/html/index.html new file mode 100644 index 0000000..d21ceec --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-lock/html/index.html @@ -0,0 +1,17 @@ + + + + + Lockpicks Minigame + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-lock/html/main.css b/resources/[qb]/[qb_core]/qb-lock/html/main.css new file mode 100644 index 0000000..3ecadd0 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-lock/html/main.css @@ -0,0 +1,15 @@ +body{ + background-color:transparent; +} + +#canvas { + position: absolute; + margin: 50px auto; + top: 50%; + left:50%; + transform: translate(-50%,-50%); + overflow: visible; + height: 250px; + width: 250px; + color:#822ff6 +} diff --git a/resources/[qb]/[qb_core]/qb-lock/html/scripts.js b/resources/[qb]/[qb_core]/qb-lock/html/scripts.js new file mode 100644 index 0000000..08a1bd2 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-lock/html/scripts.js @@ -0,0 +1,158 @@ +let canvas = document.getElementById("canvas"); +let ctx = canvas.getContext("2d"); + +let W = canvas.width; +let H = canvas.height; +let degrees = 0; +let new_degrees = 0; +let time = 0; +let color = "#ff0000"; +let txtcolor = "#ffffff"; +let bgcolor = "#404b58"; +let bgcolor2 = "#41a491"; +let bgcolor3 = "#00ff00"; +let key_to_press; +let g_start, g_end; +let animation_loop; + + +let needed = 4; +let streak = 0; + + +function getRandomInt(min, max) { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min + 1) + min); //The maximum is inclusive and the minimum is inclusive +} + +function init() { + // Clear the canvas every time a chart is drawn + ctx.clearRect(0,0,W,H); + + // Background 360 degree arc + ctx.beginPath(); + ctx.strokeStyle = bgcolor; + ctx.lineWidth = 20; + ctx.arc(W / 2, H / 2, 100, 0, Math.PI * 2, false); + ctx.stroke(); + + // Green zone + ctx.beginPath(); + ctx.strokeStyle = correct === true? bgcolor3 : bgcolor2; + ctx.lineWidth = 20; + ctx.arc(W / 2, H / 2, 100, g_start - 90 * Math.PI / 180, g_end - 90 * Math.PI / 180, false); + ctx.stroke(); + + // Angle in radians = angle in degrees * PI / 180 + let radians = degrees * Math.PI / 180; + ctx.beginPath(); + ctx.strokeStyle = color; + ctx.lineWidth = 40; + ctx.arc(W / 2, H / 2, 90, radians - 0.1 - 90 * Math.PI / 180, radians - 90 * Math.PI / 180, false); + ctx.stroke(); +// alterar para 2 se n der o 0.01 + // Adding the key_to_press + ctx.fillStyle = txtcolor; + ctx.font = "100px sans-serif"; + let text_width = ctx.measureText(key_to_press).width; + ctx.fillText(key_to_press, W / 2 - text_width / 2, H / 2 + 35); +} + +function draw(time) { + if (typeof animation_loop !== undefined) clearInterval(animation_loop); + + g_start = getRandomInt(20,40) / 10; + g_end = getRandomInt(5,10) / 10; + g_end = g_start + g_end; + + degrees = 0; + new_degrees = 360; + + key_to_press = ''+getRandomInt(1,4); + + time = time; + + animation_loop = setInterval(animate_to, time); +} + +function animate_to() { + if (degrees >= new_degrees) { + wrong(); + return; + } + + degrees+=2; + init(); +} + +function correct(){ + streak += 1; + if (streak == needed) { + clearInterval(animation_loop) + endGame(true) + }else{ + draw(time); + }; +} + +function wrong(){ + clearInterval(animation_loop); + endGame(false); +} + +document.addEventListener("keydown", function(ev) { + let key_pressed = ev.key; + let valid_keys = ['1','2','3','4']; + if( valid_keys.includes(key_pressed) ){ + if( key_pressed === key_to_press ){ + let d_start = (180 / Math.PI) * g_start; + let d_end = (180 / Math.PI) * g_end; + if( degrees < d_start ){ + wrong(); + }else if( degrees > d_end ){ + wrong(); + }else{ + correct(); + } + }else{ + wrong(); + } + } +}); + +function startGame(time){ + $('#canvas').show(); + draw(time); + } + + function endGame(status){ + $('#canvas').hide(); + var xhr = new XMLHttpRequest(); + let u = "fail"; + if(status) + u = "success"; + xhr.open("POST", `https://qb-lock/${u}`, true); + xhr.setRequestHeader('Content-Type', 'application/json'); + xhr.send(JSON.stringify({})); + streak = 0; + needed = 4; + } + + window.addEventListener("message", (event) => { + if(event.data.action == "start"){ + if(event.data.value != null ){ + needed = event.data.value + }else{ + needed = 4 + } + if(event.data.time != null ){ + time = event.data.time + }else{ + time = 2 + } + console.log(event.data.time) + startGame(time) + } + }) + diff --git a/resources/[qb]/[qb_core]/qb-management/.github/ISSUE_TEMPLATE/bug_report.md b/resources/[qb]/[qb_core]/qb-management/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..62f702f --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-management/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,32 @@ +--- +name: Bug report +about: Create a report to help us improve or fix something +title: "[BUG]" +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. A stranger to qbcore should be able to read your bug report and understand how to reproduce it themselves and understand how the feature should work normally. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Use this item '....' (item's name from shared.lua if applicable) +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Questions (please complete the following information):** + - When you last updated: [e.g. last week] + - Are you using custom resource? which ones? [e.g. zdiscord, qb-target] + - Have you renamed `qb-` to something custom? [e.g. yes/no] + +**Additional context** +Add any other context about the problem here. diff --git a/resources/[qb]/[qb_core]/qb-management/.github/ISSUE_TEMPLATE/feature-request.md b/resources/[qb]/[qb_core]/qb-management/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 0000000..9e9bf3e --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-management/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,20 @@ +--- +name: Feature Request +about: Suggest an idea for QBCore +title: "[SUGGESTION]" +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. + +**Describe the feature you'd like** +A clear and concise description of what you want to happen. and with as much detail as possible how it would function in your opinion. Please try to keep it unique. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered for people to have in mind just in case the main idea isn't liked but a derivative is. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/resources/[qb]/[qb_core]/qb-management/.github/auto_assign.yml b/resources/[qb]/[qb_core]/qb-management/.github/auto_assign.yml new file mode 100644 index 0000000..a4a8078 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-management/.github/auto_assign.yml @@ -0,0 +1,17 @@ +# Set to true to add reviewers to pull requests +addReviewers: true + +# Set to true to add assignees to pull requests +addAssignees: author + +# A list of reviewers to be added to pull requests (GitHub user name) +reviewers: + - /maintenance + +# A list of keywords to be skipped the process that add reviewers if pull requests include it +skipKeywords: + - wip + +# A number of reviewers added to the pull request +# Set 0 to add all the reviewers (default: 0) +numberOfReviewers: 0 diff --git a/resources/[qb]/[qb_core]/qb-management/.github/contributing.md b/resources/[qb]/[qb_core]/qb-management/.github/contributing.md new file mode 100644 index 0000000..21fb806 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-management/.github/contributing.md @@ -0,0 +1,201 @@ +# Contributing to QBCore + +First of all, thank you for taking the time to contribute! + +These guidelines will help you help us in the best way possible regardless of your skill level. We ask that you try to read everything related to the way you'd like to contribute and try and use your best judgement for anything not covered. + +### Table of Contents + +[Code of Conduct](#code-of-conduct) + +[I don't want to read this whole thing, I just have a question!!!](#i-dont-want-to-read-this-whole-thing-i-just-have-a-question) + +[How Can I Contribute?](#how-can-i-contribute) + * [Reporting Bugs](#reporting-bugs) + * [Suggesting Features / Enhancements](#suggesting-features--enhancements) + * [Your First Code Contribution](#your-first-code-contribution) + * [Pull Requests](#pull-requests) + +[Styleguides](#styleguides) + * [Git Commit Messages](#git-commit-messages) + * [Lua Styleguide](#lua-styleguide) + * [JavaScript Styleguide](#javascript-styleguide) + + + +## Code of Conduct + +- Refrain from using languages other than English. +- Refrain from discussing any politically charged or inflammatory topics. +- Uphold mature conversations and respect each other; excessive profanity, hate speech or any kind of harassment will not be tolerated. +- No advertising of any kind. +- Follow these guidelines. +- Do not mention members of github unless a question is directed at them and can't be answered by anyone else. +- Do not mention any of the development team for any reason. We will read things as we get to them. + +## I don't want to read this whole thing I just have a question!!! + +> **Note:** Please don't file an issue to ask a question. You'll get faster results by using the resources below. + +* [QBCore Website](https://qbcore.org) +* [QBCore Discord](https://discord.gg/qbcore) +* [FiveM Discord - #qbcore channel](https://discord.gg/fivem) + + + + + + + + + + +## How Can I Contribute? + +### Reporting Bugs + +The easiest way to contribute for most people is just to report bugs you find cause if nobody reports it there's a chance we'll never know it exists and then we'll never fix it. + +Before creating bug reports, please check [this list](#before-submitting-a-bug-report) as you might find out that you don't need to create one. When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report). Fill out the bug-report template with the information it asks for helps us resolve issues faster. + +> **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one. + +#### Before Submitting A Bug Report + +* **Check the docs** There's a chance what you see as a bug might just work differently than you expect and if you think it could work better consider a feature enhancement report instead. +* **Search the [discord](https://discord.gg/qbcore)** to see if anyone else has run into the issue and see if it was solved through user error or code changes. (if the code change isn't pending a PR and you know what you're doing consider submitting one following [Pull Requests](#pull-requests) ) +* **Determine which resource the problem should be reported in**. If the bug is related to the inventory for example report this bug under qb-inventory rather than under qb-core or some other resource. +* **Perform a [cursory search](https://github.com/search?q=+is%3Aissue+user%3Aqbcore-framework)** to see if the problem has already been reported. If it has **and the issue is still open**, add a comment to the existing issue instead of opening a new one. + +#### How Do I Submit A (Good) Bug Report? + +Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). After you've determined which resource your bug is related to, create an issue on that repository and provide the following information by filling in bug-report template. + +Explain the problem and include additional details to help maintainers reproduce the problem: + +* **Use a clear and descriptive title** for the issue to identify the problem. +* **Describe the exact steps which reproduce the problem** in as many details as possible. +* **Provide specific examples to demonstrate the steps**. If something happened with only a specific group or single item but not others, specify that. +* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior. +* **Explain which behavior you expected to see instead and why.** +* **Include screenshots** which show the specific bug in action or before and after. +* **If the problem wasn't triggered by a specific action**, describe what you were doing before the problem happened and share more information using the guidelines below. + +Provide more context by answering these questions if possible: + +* **Did the problem start happening recently** (e.g. after updating to a new version of QBCore?) or was this always a problem? +* If the problem started happening recently, **can you reproduce the problem in an older version of QBCore?** What's the most recent commit in which the problem doesn't happen? +* **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens. + +Include details about your setup: + +* **When was your QBCore last updated?** +* **What OS is the server running on**? +* **Which *extra* resources do you have installed?** + + +--- + + +### Suggesting Features / Enhancements + +This section guides you through submitting an enhancement suggestion for QBCore, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion. + +Before creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-a-good-enhancement-suggestion). Fill in feature request template, including the steps that you imagine you would take if the feature you're requesting existed. + +#### Before Submitting An Enhancement Suggestion + +* **Make sure it doesn't already exist.** Sounds silly, but there's a lot of features built in to qbcore that people don't realize so take a look through the docs and stuff to make sure it's not already there. +* **Check if there's already PR which provides that enhancement.** +* **Determine which resource the enhancement should be suggested in.** if it fits with another resource suggest it in that resource. if it would be it's own resource suggest it in the main qb-core repository. +* **Perform a [cursory search](https://github.com/search?q=+is%3Aissue+user%3Aqbcore-framework)** to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. + +#### How Do I Submit A (Good) Enhancement Suggestion? + +Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com/features/issues/). After you've determined which resource your enhancement suggestion is related to, create an issue on that repository and provide the following information: + +* **Use a clear and descriptive title** for the issue to identify the suggestion. +* **Provide a step-by-step description of the suggested enhancement** in as many details as possible. +* **Provide specific examples to demonstrate the steps**. Include copy/pasteable snippets which you use in those examples, as [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). +* **Describe the current behavior** and **explain which behavior you expected to see instead** and why. +* **Include screenshots and animated GIFs** which help you demonstrate the steps or point out the part of QBCore which the suggestion is related to. +* **Explain why this enhancement would be useful.** +* **Be creative and unique.** Stealing ideas from popular servers 1:1 detail isn't going to get accepted. + + +--- + + + +### Your First Code Contribution + +Unsure where to begin contributing to QBCore? You can start by looking through these `beginner` and `help-wanted` issues. + + + +--- + + +### Pull Requests + +The process described here has several goals: + +- Maintain QBCore's quality. +- Fix problems that are important to users. +- Engage the community in working toward the best possible QBCore. +- Enable a sustainable system for QBCore's maintainers to review contributions. + +Please follow these steps to have your contribution considered by the maintainers: + +1. Follow all instructions in The Pull Request template. +2. Follow the [styleguides](#styleguides). +3. Await review by the reviewer(s). + +While the prerequisites above must be satisfied prior to having your pull request reviewed, the reviewer(s) may ask you to complete additional design work, tests, or other changes before your pull request can be ultimately accepted. + + +--- + +## Styleguides + +### Git Commit Messages + +* Limit the first line to 72 characters or less. +* Reference issues and pull requests liberally after the first line. +* Consider starting the commit message with an applicable emoji: + * :art: `:art:` when improving the format/structure of the code + * :racehorse: `:racehorse:` when improving performance + * :memo: `:memo:` when writing docs + * :bug: `:bug:` when fixing a bug + * :fire: `:fire:` when removing code or files + * :white_check_mark: `:white_check_mark:` when adding tests + * :lock: `:lock:` when dealing with security + * :arrow_up: `:arrow_up:` when upgrading dependencies + * :arrow_down: `:arrow_down:` when downgrading dependencies + * :shirt: `:shirt:` when removing linter warnings + +### Lua Styleguide + +All lua code should be done using all the best practices of proper lua using the easiest to read yet fastest/most optimized methods of execution. + +- Use 4 Space indentation +- Aim for lua 5.4 (include `lua54 'yes'` in the fxmanifest.lua) +- Use `PlayerPedId()` instead of `GetPlayerPed(-1)` +- Use `#(vector3 - vector3)` instead of `GetDistanceBetweenCoords()` +- Don't create unnecessary threads. always try to find a better method of triggering events +- Don't repeat yourself.. if you're using the same operations in many different places convert them into a function with flexible variables +- For distance checking loops set longer waits if you're outside of a range +- Job specific loops should only run for players with that job, don't waste cycles +- When possible don't trust the client, esspecially with transactions +- Balance security and optimizations +- [Consider this Lua Performance guide](https://springrts.com/wiki/Lua_Performance) +- Use local varriables everywhere possible +- Make use of config options where it makes sense making features optional or customizable +- Instead of `table.insert(myTable, "Value")` use `myTable[#myTable + 1] = "Value"` +- Instead of `table.insert(ages, "bob", 30)` use `ages["bob"] = 30` + + +### JavaScript Styleguide + +- Use 4 Space indentation +- Don't repeat yourself.. if you're using the same operations in many different places convert them into a function with flexible variables. diff --git a/resources/[qb]/[qb_core]/qb-management/.github/pull_request_template.md b/resources/[qb]/[qb_core]/qb-management/.github/pull_request_template.md new file mode 100644 index 0000000..000f0f9 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-management/.github/pull_request_template.md @@ -0,0 +1,10 @@ +**Describe Pull request** +First, make sure you've read and are following the contribution guidelines and style guide and your code reflects that. +Write up a clear and concise description of what your pull request adds or fixes and if it's an added feature explain why you think it should be included in the core. + +If your PR is to fix an issue mention that issue here + +**Questions (please complete the following information):** +- Have you personally loaded this code into an updated qbcore project and checked all it's functionality? [yes/no] (Be honest) +- Does your code fit the style guidelines? [yes/no] +- Does your PR fit the contribution guidelines? [yes/no] diff --git a/resources/[qb]/[qb_core]/qb-management/.github/workflows/lint.yml b/resources/[qb]/[qb_core]/qb-management/.github/workflows/lint.yml new file mode 100644 index 0000000..fb74fd6 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-management/.github/workflows/lint.yml @@ -0,0 +1,23 @@ +name: Lint +on: [push, pull_request_target] +jobs: + lint: + name: Lint Resource + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + ref: ${{ github.event.pull_request.head.sha }} + - name: Lint + uses: iLLeniumStudios/fivem-lua-lint-action@v2 + with: + capture: "junit.xml" + args: "-t --formatter JUnit" + extra_libs: mysql+polyzone+qblocales + - name: Generate Lint Report + if: always() + uses: mikepenz/action-junit-report@v3 + with: + report_paths: "**/junit.xml" + check_name: Linting Report + fail_on_failure: false \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-management/LICENSE b/resources/[qb]/[qb_core]/qb-management/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-management/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/resources/[qb]/[qb_core]/qb-management/README.md b/resources/[qb]/[qb_core]/qb-management/README.md new file mode 100644 index 0000000..1727e51 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-management/README.md @@ -0,0 +1,45 @@ +# qb-management + +New qb-bossmenu / qb-gangmenu converted into one resource using qb-menu and qb-input, with SQL support for society funds! + +## Dependencies +- [qb-core](https://github.com/qbcore-framework/qb-core) +- [qb-smallresources](https://github.com/qbcore-framework/qb-smallresources) (For the Logs) +- [qb-input](https://github.com/qbcore-framework/qb-input) +- [qb-menu](https://github.com/qbcore-framework/qb-menu) +- [qb-inventory](https://github.com/qbcore-framework/qb-inventory) +- [qb-clothing](https://github.com/qbcore-framework/qb-clothing) + +## Screenshots +![image](https://i.imgur.com/9yiQZDX.png) +![image](https://i.imgur.com/MRMWeqX.png) + +## Installation +### Manual +- Download the script and put it in the `[qb]` directory. +- IF NEW SERVER: Import `qb-management.sql` in your database +- IF EXISTING SERVER: Import `qb-management_upgrade.sql` in your database +- Edit config.lua with coords +- Restart Script / Server + +## ATTENTION +### YOU NEED TO CREATE A ROW IN DATABASE WITH NAME OF SOCIETY IN MANAGEMENT_FUNDS TABLE IF YOU HAVE CUSTOM JOBS / GANGS +![database](https://i.imgur.com/6cd3NLU.png) + +# License + + QBCore Framework + Copyright (C) 2021 Joshua Eger + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see diff --git a/resources/[qb]/[qb_core]/qb-management/client/cl_boss.lua b/resources/[qb]/[qb_core]/qb-management/client/cl_boss.lua new file mode 100644 index 0000000..f82c4a7 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-management/client/cl_boss.lua @@ -0,0 +1,387 @@ +local QBCore = exports['qb-core']:GetCoreObject() +local PlayerJob = QBCore.Functions.GetPlayerData().job +local shownBossMenu = false +local DynamicMenuItems = {} + +-- UTIL +local function CloseMenuFull() + exports['qb-menu']:closeMenu() + exports['qb-core']:HideText() + shownBossMenu = false +end + +local function comma_value(amount) + local formatted = amount + while true do + local k + formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1.%2') + if (k == 0) then + break + end + end + return formatted +end + +local function AddBossMenuItem(data, id) + local menuID = id or (#DynamicMenuItems + 1) + DynamicMenuItems[menuID] = deepcopy(data) + return menuID +end + +exports("AddBossMenuItem", AddBossMenuItem) + +local function RemoveBossMenuItem(id) + DynamicMenuItems[id] = nil +end + +exports("RemoveBossMenuItem", RemoveBossMenuItem) + +AddEventHandler('onResourceStart', function(resource) + if resource == GetCurrentResourceName() then + PlayerJob = QBCore.Functions.GetPlayerData().job + end +end) + +RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() + PlayerJob = QBCore.Functions.GetPlayerData().job +end) + +RegisterNetEvent('QBCore:Client:OnJobUpdate', function(JobInfo) + PlayerJob = JobInfo +end) + +RegisterNetEvent('qb-bossmenu:client:OpenMenu', function() + if not PlayerJob.name or not PlayerJob.isboss then return end + + local bossMenu = { + { + header = Lang:t("headers.bsm").. string.upper(PlayerJob.label), + icon = "fa-solid fa-circle-info", + isMenuHeader = true, + }, + { + header = Lang:t("body.manage"), + txt = Lang:t("body.managed"), + icon = "fa-solid fa-list", + params = { + event = "qb-bossmenu:client:employeelist", + } + }, + { + header = Lang:t("body.hire"), + txt = Lang:t("body.hired"), + icon = "fa-solid fa-hand-holding", + params = { + event = "qb-bossmenu:client:HireMenu", + } + }, + { + header = Lang:t("body.storage"), + txt = Lang:t("body.storaged"), + icon = "fa-solid fa-box-open", + params = { + event = "qb-bossmenu:client:Stash", + } + }, + { + header = Lang:t("body.outfits"), + txt = Lang:t("body.outfitsd"), + icon = "fa-solid fa-shirt", + params = { + event = "qb-bossmenu:client:Wardrobe", + } + }, + { + header = Lang:t("body.money"), + txt = Lang:t("body.moneyd"), + icon = "fa-solid fa-sack-dollar", + params = { + event = "qb-bossmenu:client:SocietyMenu", + } + }, + } + + for _, v in pairs(DynamicMenuItems) do + bossMenu[#bossMenu + 1] = v + end + + bossMenu[#bossMenu + 1] = { + header = Lang:t("body.exit"), + icon = "fa-solid fa-angle-left", + params = { + event = "qb-menu:closeMenu", + } + } + + exports['qb-menu']:openMenu(bossMenu) +end) + +RegisterNetEvent('qb-bossmenu:client:employeelist', function() + local EmployeesMenu = { + { + header = Lang:t("body.mempl").. string.upper(PlayerJob.label), + isMenuHeader = true, + icon = "fa-solid fa-circle-info", + }, + } + QBCore.Functions.TriggerCallback('qb-bossmenu:server:GetEmployees', function(cb) + for _, v in pairs(cb) do + EmployeesMenu[#EmployeesMenu + 1] = { + header = v.name, + txt = v.grade.name, + icon = "fa-solid fa-circle-user", + params = { + event = "qb-bossmenu:client:ManageEmployee", + args = { + player = v, + work = PlayerJob + } + } + } + end + EmployeesMenu[#EmployeesMenu + 1] = { + header = Lang:t("body.return"), + icon = "fa-solid fa-angle-left", + params = { + event = "qb-bossmenu:client:OpenMenu", + } + } + exports['qb-menu']:openMenu(EmployeesMenu) + end, PlayerJob.name) +end) + +RegisterNetEvent('qb-bossmenu:client:ManageEmployee', function(data) + local EmployeeMenu = { + { + header = Lang:t("body.mngpl").. data.player.name .. " - " .. string.upper(PlayerJob.label), + isMenuHeader = true, + icon = "fa-solid fa-circle-info" + }, + } + for k, v in pairs(QBCore.Shared.Jobs[data.work.name].grades) do + EmployeeMenu[#EmployeeMenu + 1] = { + header = v.name, + txt = Lang:t("body.grade") .. k, + params = { + isServer = true, + event = "qb-bossmenu:server:GradeUpdate", + icon = "fa-solid fa-file-pen", + args = { + cid = data.player.empSource, + grade = tonumber(k), + gradename = v.name + } + } + } + end + EmployeeMenu[#EmployeeMenu + 1] = { + header = Lang:t("body.fireemp"), + icon = "fa-solid fa-user-large-slash", + params = { + isServer = true, + event = "qb-bossmenu:server:FireEmployee", + args = data.player.empSource + } + } + EmployeeMenu[#EmployeeMenu + 1] = { + header = Lang:t("body.return"), + icon = "fa-solid fa-angle-left", + params = { + event = "qb-bossmenu:client:OpenMenu", + } + } + exports['qb-menu']:openMenu(EmployeeMenu) +end) + +RegisterNetEvent('qb-bossmenu:client:Stash', function() + TriggerServerEvent("inventory:server:OpenInventory", "stash", "boss_" .. PlayerJob.name, { + maxweight = 4000000, + slots = 25, + }) + TriggerEvent("inventory:client:SetCurrentStash", "boss_" .. PlayerJob.name) +end) + +RegisterNetEvent('qb-bossmenu:client:Wardrobe', function() + TriggerEvent('qb-clothing:client:openOutfitMenu') +end) + +RegisterNetEvent('qb-bossmenu:client:HireMenu', function() + local HireMenu = { + { + header = Lang:t("body.hireemp").. string.upper(PlayerJob.label), + isMenuHeader = true, + icon = "fa-solid fa-circle-info", + }, + } + QBCore.Functions.TriggerCallback('qb-bossmenu:getplayers', function(players) + for _, v in pairs(players) do + if v and v ~= PlayerId() then + HireMenu[#HireMenu + 1] = { + header = v.name, + txt = Lang:t("body.cid").. v.citizenid .. " - ID: " .. v.sourceplayer, + icon = "fa-solid fa-user-check", + params = { + isServer = true, + event = "qb-bossmenu:server:HireEmployee", + args = v.sourceplayer + } + } + end + end + HireMenu[#HireMenu + 1] = { + header = Lang:t("body.return"), + icon = "fa-solid fa-angle-left", + params = { + event = "qb-bossmenu:client:OpenMenu", + } + } + exports['qb-menu']:openMenu(HireMenu) + end) +end) + +RegisterNetEvent('qb-bossmenu:client:SocietyMenu', function() + QBCore.Functions.TriggerCallback('qb-bossmenu:server:GetAccount', function(cb) + local SocietyMenu = { + { + header = Lang:t("body.balance").. comma_value(cb) .. ",- - " .. string.upper(PlayerJob.label), + isMenuHeader = true, + icon = "fa-solid fa-circle-info", + }, + { + header = Lang:t("body.deposit"), + icon = "fa-solid fa-money-bill-transfer", + txt = Lang:t("body.depositd"), + params = { + event = "qb-bossmenu:client:SocetyDeposit", + args = comma_value(cb) + } + }, + { + header = Lang:t("body.withdraw"), + icon = "fa-solid fa-money-bill-transfer", + txt = Lang:t("body.withdrawd"), + params = { + event = "qb-bossmenu:client:SocetyWithDraw", + args = comma_value(cb) + } + }, + { + header = Lang:t("body.return"), + icon = "fa-solid fa-angle-left", + params = { + event = "qb-bossmenu:client:OpenMenu", + } + }, + } + exports['qb-menu']:openMenu(SocietyMenu) + end, PlayerJob.name) +end) + +RegisterNetEvent('qb-bossmenu:client:SocetyDeposit', function(money) + local deposit = exports['qb-input']:ShowInput({ + header = Lang:t("body.depositm").. money..",-", + submitText = Lang:t("body.submit"), + inputs = { + { + type = 'number', + isRequired = true, + name = 'amount', + text = Lang:t("body.amount") + } + } + }) + if deposit then + if not deposit.amount then return end + TriggerServerEvent("qb-bossmenu:server:depositMoney", tonumber(deposit.amount)) + end +end) + +RegisterNetEvent('qb-bossmenu:client:SocetyWithDraw', function(money) + local withdraw = exports['qb-input']:ShowInput({ + header = Lang:t("body.withdrawm").. money..",-", + submitText = Lang:t("body.submit"), + inputs = { + { + type = 'number', + isRequired = true, + name = 'amount', + text = Lang:t("body.amount") + } + } + }) + if withdraw then + if not withdraw.amount then return end + TriggerServerEvent("qb-bossmenu:server:withdrawMoney", tonumber(withdraw.amount)) + end +end) + +-- MAIN THREAD +CreateThread(function() + if Config.UseTarget then + for job, zones in pairs(Config.BossMenuZones) do + for index, data in ipairs(zones) do + exports['qb-target']:AddBoxZone(job.."-BossMenu-"..index, data.coords, data.length, data.width, { + name = job.."-BossMenu-"..index, + heading = data.heading, + debugPoly = false, + minZ = data.minZ, + maxZ = data.maxZ, + }, { + options = { + { + type = "client", + event = "qb-bossmenu:client:OpenMenu", + icon = "fas fa-sign-in-alt", + label = Lang:t("target.label"), + canInteract = function() return job == PlayerJob.name and PlayerJob.isboss end, + }, + }, + distance = 2.5 + }) + end + end + else + while true do + local wait = 2500 + local pos = GetEntityCoords(PlayerPedId()) + local inRangeBoss = false + local nearBossmenu = false + if PlayerJob then + wait = 0 + for k, menus in pairs(Config.BossMenus) do + for _, coords in ipairs(menus) do + if k == PlayerJob.name and PlayerJob.isboss then + if #(pos - coords) < 5.0 then + inRangeBoss = true + if #(pos - coords) <= 1.5 then + nearBossmenu = true + if not shownBossMenu then + exports['qb-core']:DrawText(Lang:t("drawtext.label"), 'top') + shownBossMenu = true + end + if IsControlJustReleased(0, 38) then + exports['qb-core']:HideText() + TriggerEvent("qb-bossmenu:client:OpenMenu") + end + end + + if not nearBossmenu and shownBossMenu then + CloseMenuFull() + shownBossMenu = false + end + end + end + end + end + if not inRangeBoss then + Wait(1500) + if shownBossMenu then + CloseMenuFull() + shownBossMenu = false + end + end + end + Wait(wait) + end + end +end) diff --git a/resources/[qb]/[qb_core]/qb-management/client/cl_config.lua b/resources/[qb]/[qb_core]/qb-management/client/cl_config.lua new file mode 100644 index 0000000..f0e3f25 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-management/client/cl_config.lua @@ -0,0 +1,123 @@ +-- Zones for Menues +Config = Config or {} + +Config.UseTarget = GetConvar('UseTarget', 'false') == 'true' -- Use qb-target interactions (don't change this, go to your server.cfg and add `setr UseTarget true` to use this and just that from true to false or the other way around) + +Config.BossMenus = { + ['police'] = { + vector3(441.67, -974.96, 35.96), + vector3(447.85, -990.88, 31.52), + }, + ['ambulance'] = { + vector3(335.46, -594.52, 43.28), + }, + ['tequilala'] = { + vector3(0.00, 0.00, 0.00), + }, + ['whitewidow'] = { + vector3(0.00, 0.00, 0.00), + }, + ['vanilla'] = { + vector3(129.68, -1280.64, 30.33), + }, + ['burgershot'] = { + vector3(-1188.11, -904.54, 14.74), + }, + ['beanmachine'] = { + vector3(126.24, -1034.34, 30.22), + }, + ['uwu'] = { + vector3(-578.0, -1067.08, 27.52), + }, + ['cardealer'] = { + vector3(-27.47, -1108.07, 28.17), + }, + ['mechanic'] = { + vector3(-228.26, -1328.26, 31.93), + }, + ['dominos'] = { + vector3(541.36, 114.08, 96.57), + }, + ['popeyes'] = { + vector3(179.1, -1456.12, 29.13), + }, + ['cookies'] = { + vector3(-934.1, -1168.34, 5.11), + }, + ['realestate'] = { + vector3(-79.69, -801.96, 243.2) + }, +} + +Config.BossMenuZones = { + ['police'] = { + { coords = vector3(441.67, -974.96, 35.96), length = 1, width = 2, heading = 90, minZ = 35.00, maxZ = 36.73 }, + { coords = vector3(447.85, -990.88, 31.52), length = 1.35, width = 1.45, heading = 190.13, minZ = 30.00, maxZ = 31.73 }, + }, + ['ambulance'] = { + { coords = vector3(311.5, -583.41, 44.18), length = 1.2, width = 0.6, heading = 252.47, minZ = 43.13, maxZ = 45.73 }, + }, + ['tequilala'] = { + { coords = vector3(0.00, 0.00, 0.00), length = 0.6, width = 1.0, heading = 44.76, minZ = 81.943, maxZ = 83.74 }, + }, + ['whitewidow'] = { + { coords = vector3(0.00, 0.00, 0.00), length = 1.0, width = 3.4, heading = 327.0, minZ = 73.17, maxZ = 353.1}, + }, + ['vanilla'] = { + { coords = vector3(129.68, -1280.64, 30.33), length = 2.4, width = 1.05, heading = 116.54, minZ = 30.07, maxZ = 31.67 }, + }, + ['burgershot'] = { + { coords = vector3(-1188.11, -904.54, 14.74), length = 1.15, width = 2.6, heading = 307.28, minZ = 13.74, maxZ = 15.74 }, + }, + ['beanmachine'] = { + { coords = vector3(126.24, -1034.34, 30.22), length = 1.15, width = 2.6, heading = 76.08, minZ = 29.74, maxZ = 31.74 }, + }, + ['uwu'] = { + { coords = vector3(-578.0, -1067.08, 27.52), length = 1.15, width = 2.6, heading = 198.04, minZ = 26.74, maxZ = 28.74 }, + }, + ['cardealer'] = { + { coords = vector3(-27.47, -1108.07, 28.17), length = 1.15, width = 2.6, heading = 233.61, minZ = 26.74, maxZ = 28.74 }, + }, + ['mechanic'] = { + { coords = vector3(-228.26, -1328.26, 31.93), length = 1.15, width = 2.6, heading = 264.69, minZ = 30.74, maxZ = 32.74 }, + }, + ['dominos'] = { + { coords = vector3(541.36, 114.08, 96.57), length = 1.15, width = 2.6, heading = 264.69, minZ = 95.74, maxZ = 97.74 }, + }, + ['popeyes'] = { + { coords = vector3(179.1, -1456.12, 29.13), length = 1.15, width = 2.6, heading = 264.69, minZ = 28.74, maxZ = 29.74 }, + }, + ['cookies'] = { + {coords = vector3(-934.1, -1168.34, 5.11), length = 1.15, width = 2.6, heading = 353.0, minZ = 4.11, maxZ = 5.99}, + }, + ['realestate'] = { + {coords = vector3(-79.69, -801.96, 243.2), length = 1.15, width = 2.6, heading = 250.62, minZ = 241.2, maxZ = 244.2}, + }, + +} + +Config.GangMenus = { + ['lostmc'] = { + vector3(0, 0, 0), + }, + ['ballas'] = { + vector3(0, 0, 0), + }, + ['vagos'] = { + vector3(0, 0, 0), + }, + ['cartel'] = { + vector3(0, 0, 0), + }, + ['families'] = { + vector3(0, 0, 0), + }, +} + +Config.GangMenuZones = { + --[[ + ['gangname'] = { + { coords = vector3(0.0, 0.0, 0.0), length = 0.0, width = 0.0, heading = 0.0, minZ = 0.0, maxZ = 0.0 }, + }, + ]] +} diff --git a/resources/[qb]/[qb_core]/qb-management/client/cl_gang.lua b/resources/[qb]/[qb_core]/qb-management/client/cl_gang.lua new file mode 100644 index 0000000..e8b0a20 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-management/client/cl_gang.lua @@ -0,0 +1,389 @@ +local QBCore = exports['qb-core']:GetCoreObject() +local PlayerGang = QBCore.Functions.GetPlayerData().gang +local shownGangMenu = false +local DynamicMenuItems = {} + +-- UTIL +local function CloseMenuFullGang() + exports['qb-menu']:closeMenu() + exports['qb-core']:HideText() + shownGangMenu = false +end + +local function comma_valueGang(amount) + local formatted = amount + while true do + local k + formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1.%2') + if (k == 0) then + break + end + end + return formatted +end + +--//Events +AddEventHandler('onResourceStart', function(resource)--if you restart the resource + if resource == GetCurrentResourceName() then + Wait(200) + PlayerGang = QBCore.Functions.GetPlayerData().gang + end +end) + +RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() + PlayerGang = QBCore.Functions.GetPlayerData().gang +end) + +RegisterNetEvent('QBCore:Client:OnGangUpdate', function(InfoGang) + PlayerGang = InfoGang +end) + +RegisterNetEvent('qb-gangmenu:client:Stash', function() + TriggerServerEvent("inventory:server:OpenInventory", "stash", "boss_" .. PlayerGang.name, { + maxweight = 4000000, + slots = 100, + }) + TriggerEvent("inventory:client:SetCurrentStash", "boss_" .. PlayerGang.name) +end) + +RegisterNetEvent('qb-gangmenu:client:Warbobe', function() + TriggerEvent('qb-clothing:client:openOutfitMenu') +end) + +local function AddGangMenuItem(data, id) + local menuID = id or (#DynamicMenuItems + 1) + DynamicMenuItems[menuID] = deepcopy(data) + return menuID +end + +exports("AddGangMenuItem", AddGangMenuItem) + +local function RemoveGangMenuItem(id) + DynamicMenuItems[id] = nil +end + +exports("RemoveGangMenuItem", RemoveGangMenuItem) + +RegisterNetEvent('qb-gangmenu:client:OpenMenu', function() + shownGangMenu = true + local gangMenu = { + { + header = Lang:t("headersgang.bsm").. string.upper(PlayerGang.label), + icon = "fa-solid fa-circle-info", + isMenuHeader = true, + }, + { + header = Lang:t("bodygang.manage"), + txt = Lang:t("bodygang.managed"), + icon = "fa-solid fa-list", + params = { + event = "qb-gangmenu:client:ManageGang", + } + }, + { + header = Lang:t("bodygang.hire"), + txt = Lang:t("bodygang.hired"), + icon = "fa-solid fa-hand-holding", + params = { + event = "qb-gangmenu:client:HireMembers", + } + }, + { + header = Lang:t("bodygang.storage"), + txt = Lang:t("bodygang.storaged"), + icon = "fa-solid fa-box-open", + params = { + event = "qb-gangmenu:client:Stash", + } + }, + { + header = Lang:t("bodygang.outfits"), + txt = Lang:t("bodygang.outfitsd"), + icon = "fa-solid fa-shirt", + params = { + event = "qb-gangmenu:client:Warbobe", + } + }, + { + header = Lang:t("bodygang.money"), + txt = Lang:t("bodygang.moneyd"), + icon = "fa-solid fa-sack-dollar", + params = { + event = "qb-gangmenu:client:SocietyMenu", + } + }, + } + + for _, v in pairs(DynamicMenuItems) do + gangMenu[#gangMenu + 1] = v + end + + gangMenu[#gangMenu + 1] = { + header = Lang:t("bodygang.exit"), + icon = "fa-solid fa-angle-left", + params = { + event = "qb-menu:closeMenu", + } + } + + exports['qb-menu']:openMenu(gangMenu) +end) + +RegisterNetEvent('qb-gangmenu:client:ManageGang', function() + local GangMembersMenu = { + { + header = Lang:t("bodygang.mempl").. string.upper(PlayerGang.label), + icon = "fa-solid fa-circle-info", + isMenuHeader = true, + }, + } + QBCore.Functions.TriggerCallback('qb-gangmenu:server:GetEmployees', function(cb) + for _, v in pairs(cb) do + GangMembersMenu[#GangMembersMenu + 1] = { + header = v.name, + txt = v.grade.name, + icon = "fa-solid fa-circle-user", + params = { + event = "qb-gangmenu:lient:ManageMember", + args = { + player = v, + work = PlayerGang + } + } + } + end + GangMembersMenu[#GangMembersMenu + 1] = { + header = Lang:t("bodygang.return"), + icon = "fa-solid fa-angle-left", + params = { + event = "qb-gangmenu:client:OpenMenu", + } + } + exports['qb-menu']:openMenu(GangMembersMenu) + end, PlayerGang.name) +end) + +RegisterNetEvent('qb-gangmenu:lient:ManageMember', function(data) + local MemberMenu = { + { + header = Lang:t("bodygang.mngpl").. data.player.name .. " - " .. string.upper(PlayerGang.label), + isMenuHeader = true, + icon = "fa-solid fa-circle-info", + }, + } + for k, v in pairs(QBCore.Shared.Gangs[data.work.name].grades) do + MemberMenu[#MemberMenu + 1] = { + header = v.name, + txt = Lang:t("bodygang.grade") .. k, + params = { + isServer = true, + event = "qb-gangmenu:server:GradeUpdate", + icon = "fa-solid fa-file-pen", + args = { + cid = data.player.empSource, + grade = tonumber(k), + gradename = v.name + } + } + } + end + MemberMenu[#MemberMenu + 1] = { + header = Lang:t("bodygang.fireemp"), + icon = "fa-solid fa-user-large-slash", + params = { + isServer = true, + event = "qb-gangmenu:server:FireMember", + args = data.player.empSource + } + } + MemberMenu[#MemberMenu + 1] = { + header = Lang:t("bodygang.return"), + icon = "fa-solid fa-angle-left", + params = { + event = "qb-gangmenu:client:ManageGang", + } + } + exports['qb-menu']:openMenu(MemberMenu) +end) + +RegisterNetEvent('qb-gangmenu:client:HireMembers', function() + local HireMembersMenu = { + { + header = Lang:t("bodygang.hireemp") .. string.upper(PlayerGang.label), + isMenuHeader = true, + icon = "fa-solid fa-circle-info", + }, + } + QBCore.Functions.TriggerCallback('qb-gangmenu:getplayers', function(players) + for _, v in pairs(players) do + if v and v ~= PlayerId() then + HireMembersMenu[#HireMembersMenu + 1] = { + header = v.name, + txt = Lang:t("bodygang.cid") .. v.citizenid .. " - ID: " .. v.sourceplayer, + icon = "fa-solid fa-user-check", + params = { + isServer = true, + event = "qb-gangmenu:server:HireMember", + args = v.sourceplayer + } + } + end + end + HireMembersMenu[#HireMembersMenu + 1] = { + header = Lang:t("bodygang.return"), + icon = "fa-solid fa-angle-left", + params = { + event = "qb-gangmenu:client:OpenMenu", + } + } + exports['qb-menu']:openMenu(HireMembersMenu) + end) +end) + +RegisterNetEvent('qb-gangmenu:client:SocietyMenu', function() + QBCore.Functions.TriggerCallback('qb-gangmenu:server:GetAccount', function(cb) + local SocietyMenu = { + { + header = Lang:t("bodygang.balance").. comma_valueGang(cb) .. ",- - " .. string.upper(PlayerGang.label), + isMenuHeader = true, + icon = "fa-solid fa-circle-info", + }, + { + header = Lang:t("bodygang.deposit"), + icon = "fa-solid fa-money-bill-transfer", + txt = Lang:t("bodygang.depositd"), + params = { + event = "qb-gangmenu:client:SocietyDeposit", + args = comma_valueGang(cb) + } + }, + { + header = Lang:t("bodygang.withdraw"), + icon = "fa-solid fa-money-bill-transfer", + txt = Lang:t("bodygang.withdrawd"), + params = { + event = "qb-gangmenu:client:SocietyWithdraw", + args = comma_valueGang(cb) + } + }, + { + header = Lang:t("bodygang.return"), + icon = "fa-solid fa-angle-left", + params = { + event = "qb-gangmenu:client:OpenMenu", + } + }, + } + exports['qb-menu']:openMenu(SocietyMenu) + end, PlayerGang.name) +end) + +RegisterNetEvent('qb-gangmenu:client:SocietyDeposit', function(saldoattuale) + local deposit = exports['qb-input']:ShowInput({ + header = Lang:t("bodygang.depositm").. saldoattuale..",-", + submitText = Lang:t("bodygang.submit"), + inputs = { + { + type = 'number', + isRequired = true, + name = 'amount', + text = Lang:t("bodygang.amount") + } + } + }) + if deposit then + if not deposit.amount then return end + TriggerServerEvent("qb-gangmenu:server:depositMoney", tonumber(deposit.amount)) + end +end) + +RegisterNetEvent('qb-gangmenu:client:SocietyWithdraw', function(saldoattuale) + local withdraw = exports['qb-input']:ShowInput({ + header = Lang:t("bodygang.withdrawm").. saldoattuale..",-", + submitText = Lang:t("bodygang.submit"), + inputs = { + { + type = 'number', + isRequired = true, + name = 'amount', + text = Lang:t("bodygang.amount") + } + } + }) + if withdraw then + if not withdraw.amount then return end + TriggerServerEvent("qb-gangmenu:server:withdrawMoney", tonumber(withdraw.amount)) + end +end) + +-- MAIN THREAD + +CreateThread(function() + if Config.UseTarget then + for gang, zones in pairs(Config.GangMenuZones) do + for index, data in ipairs(zones) do + exports['qb-target']:AddBoxZone(gang.."-GangMenu"..index, data.coords, data.length, data.width, { + name = gang.."-GangMenu"..index, + heading = data.heading, + -- debugPoly = true, + minZ = data.minZ, + maxZ = data.maxZ, + }, { + options = { + { + type = "client", + event = "qb-gangmenu:client:OpenMenu", + icon = "fas fa-sign-in-alt", + label = Lang:t("targetgang.label"), + canInteract = function() return gang == PlayerGang.name and PlayerGang.isboss end, + }, + }, + distance = 2.5 + }) + end + end + else + while true do + local wait = 2500 + local pos = GetEntityCoords(PlayerPedId()) + local inRangeGang = false + local nearGangmenu = false + if PlayerGang then + wait = 0 + for k, menus in pairs(Config.GangMenus) do + for _, coords in ipairs(menus) do + if k == PlayerGang.name and PlayerGang.isboss then + if #(pos - coords) < 5.0 then + inRangeGang = true + if #(pos - coords) <= 1.5 then + nearGangmenu = true + if not shownGangMenu then + exports['qb-core']:DrawText(Lang:t("drawtextgang.label"), 'top') + end + + if IsControlJustReleased(0, 38) then + exports['qb-core']:HideText() + TriggerEvent("qb-gangmenu:client:OpenMenu") + end + end + + if not nearGangmenu and shownGangMenu then + CloseMenuFullGang() + shownGangMenu = false + end + end + end + end + end + if not inRangeGang then + Wait(1500) + if shownGangMenu then + CloseMenuFullGang() + shownGangMenu = false + end + end + end + Wait(wait) + end + end +end) diff --git a/resources/[qb]/[qb_core]/qb-management/client/cl_utils.lua b/resources/[qb]/[qb_core]/qb-management/client/cl_utils.lua new file mode 100644 index 0000000..7131489 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-management/client/cl_utils.lua @@ -0,0 +1,20 @@ +function deepcopy(orig, copies) + copies = copies or {} + local orig_type = type(orig) + local copy + if orig_type == 'table' then + if copies[orig] then + copy = copies[orig] + else + copy = {} + copies[orig] = copy + for orig_key, orig_value in next, orig, nil do + copy[deepcopy(orig_key, copies)] = deepcopy(orig_value, copies) + end + setmetatable(copy, deepcopy(getmetatable(orig), copies)) + end + else -- number, string, boolean, etc + copy = orig + end + return copy +end diff --git a/resources/[qb]/[qb_core]/qb-management/fxmanifest.lua b/resources/[qb]/[qb_core]/qb-management/fxmanifest.lua new file mode 100644 index 0000000..4a39caa --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-management/fxmanifest.lua @@ -0,0 +1,30 @@ +fx_version 'cerulean' +game 'gta5' + +description 'qb-bossmenu' +version '2.1.2' + +shared_scripts { + '@qb-core/shared/locale.lua', + 'locales/da.lua', +} + +client_scripts { + 'client/*.lua' +} + +server_scripts { + '@oxmysql/lib/MySQL.lua', + 'server/*.lua' +} + +server_exports { + 'AddMoney', + 'AddGangMoney', + 'RemoveMoney', + 'RemoveGangMoney', + 'GetAccount', + 'GetGangAccount', +} + +lua54 'yes' diff --git a/resources/[qb]/[qb_core]/qb-management/locales/da.lua b/resources/[qb]/[qb_core]/qb-management/locales/da.lua new file mode 100644 index 0000000..de18320 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-management/locales/da.lua @@ -0,0 +1,84 @@ +local Translations = { + headers = { + ['bsm'] = 'Ledelsesmenu - ', + }, + body = { + ['manage'] = 'Administrer ansatte', + ['managed'] = 'Se din ansatteliste', + ['hire'] = 'Ansæt ansatte', + ['hired'] = 'Ansæt civile i nærheden', + ['storage'] = 'Lageradgang', + ['storaged'] = 'Åben lager', + ['outfits'] = 'Tøj', + ['outfitsd'] = 'Se gemte outfits', + ['money'] = 'Pengehåndtering', + ['moneyd'] = 'Se din virksomhedsbalance', + ['mempl'] = 'Administrer ansatte - ', + ['mngpl'] = 'Administrer ', + ['grade'] = 'Grad: ', + ['fireemp'] = 'Fyr ansat', + ['hireemp'] = 'Ansæt ansatte - ', + ['cid'] = 'Borger ID: ', + ['balance'] = 'Balance: ', + ['deposit'] = 'Indsæt', + ['depositd'] = 'Indsæt penge på kontoen', + ['withdraw'] = 'Hæv', + ['withdrawd'] = 'Hæv penge fra kontoen', + ['depositm'] = 'Indsæt penge
Tilgængelig balance: ', + ['withdrawm'] = 'Hæv penge
Tilgængelig balance: ', + ['submit'] = 'Bekræft', + ['amount'] = 'Beløb', + ['return'] = 'Retur', + ['exit'] = 'Retur', + }, + drawtext = { + ['label'] = '[E] Åben ledelsesmenu', + }, + target = { + ['label'] = 'Ledelsesmenu', + }, + headersgang = { + ['bsm'] = 'Bande administrering - ', + }, + bodygang = { + ['manage'] = 'Administrer Bandemedlemmer', + ['managed'] = 'Rekrutter eller Fyr Bandemedlemmer', + ['hire'] = 'Rekrutter Bandemedlemmer', + ['hired'] = 'Rekrutter Bandemedlemmer', + ['storage'] = 'Lageradgang', + ['storaged'] = 'Åben Bande Lager', + ['outfits'] = 'Tøj', + ['outfitsd'] = 'Skift Tøj', + ['money'] = 'Pengehåndtering', + ['moneyd'] = 'Se din Bande Balance', + ['mempl'] = 'Administrer Bandemedlemmer - ', + ['mngpl'] = 'Administrer ', + ['grade'] = 'Grad: ', + ['fireemp'] = 'Fyr', + ['hireemp'] = 'Rekrutter Bandemedlemmer - ', + ['cid'] = 'Borger ID: ', + ['balance'] = 'Balance: ', + ['deposit'] = 'Indsæt', + ['depositd'] = 'Indsæt penge på kontoen', + ['withdraw'] = 'Hæv', + ['withdrawd'] = 'Hæv penge fra kontoen', + ['depositm'] = 'Indsæt penge
Tilgængelig balance: $', + ['withdrawm'] = 'Hæv penge
Tilgængelig balance: $', + ['submit'] = 'Bekræft', + ['amount'] = 'Beløb', + ['return'] = 'Retur', + ['exit'] = 'Retur', + }, + drawtextgang = { + ['label'] = '[E] Åben Bande administrering', + }, + targetgang = { + ['label'] = 'Bande menu', + } +} + +Lang = Locale:new({ + phrases = Translations, + warnOnMissing = true, + fallbackLang = Lang, +}) \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-management/qb-management.sql b/resources/[qb]/[qb_core]/qb-management/qb-management.sql new file mode 100644 index 0000000..67b3f85 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-management/qb-management.sql @@ -0,0 +1,24 @@ +-- Use this if you are installing a new DB/Server (these are the default QB-Management jobs/gangs) +CREATE TABLE IF NOT EXISTS `management_funds` ( +`id` INT(11) NOT NULL AUTO_INCREMENT, +`job_name` VARCHAR(50) NOT NULL, +`amount` INT(100) NOT NULL, +`type` ENUM('boss','gang') NOT NULL DEFAULT 'boss', +PRIMARY KEY (`id`), +UNIQUE KEY `job_name` (`job_name`), +KEY `type` (`type`) +); + +INSERT INTO `management_funds` (`job_name`, `amount`, `type`) VALUES +('police', 0, 'boss'), +('ambulance', 0, 'boss'), +('realestate', 0, 'boss'), +('taxi', 0, 'boss'), +('cardealer', 0, 'boss'), +('mechanic', 0, 'boss'), +('lostmc', 0, 'gang'), +('ballas', 0, 'gang'), +('vagos', 0, 'gang'), +('cartel', 0, 'gang'), +('families', 0, 'gang'), +('triads', 0, 'gang'); diff --git a/resources/[qb]/[qb_core]/qb-management/qb-management_upgrade.sql b/resources/[qb]/[qb_core]/qb-management/qb-management_upgrade.sql new file mode 100644 index 0000000..c290494 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-management/qb-management_upgrade.sql @@ -0,0 +1,61 @@ +-- Use this if you are upgrading an existing DB/Server (this will copy your current bossmenu and gangmenu data to QB-Management jobs/gangs) +CREATE TABLE IF NOT EXISTS `management_funds` ( +`id` INT(11) NOT NULL AUTO_INCREMENT, +`job_name` VARCHAR(50) NOT NULL, +`amount` INT(100) NOT NULL, +`type` ENUM('boss','gang') NOT NULL DEFAULT 'boss', +PRIMARY KEY (`id`), +UNIQUE KEY `job_name` (`job_name`), +KEY `type` (`type`) +); + +-- First we do boss menu (THIS WILL PULL YOUR CURRENT bossmenu DATA AND ADD IT TO THE NEWLY CREATED TABLE) +INSERT INTO `management_funds` (`job_name`, `amount`, `type`) +SELECT job_name, amount, 'boss' FROM bossmenu; + + +-- [[ You can remove all of this if you want, this is just to confirm everything moved over correctly ]]-- +SET @totalbossmenu = (SELECT COUNT(*) FROM `bossmenu`); +SET @managemenu_boss = (SELECT COUNT(*) FROM `management_funds` WHERE type = 'boss'); + +SELECT CONCAT("TOTAL BOSS MENU: ", @totalbossmenu); +SELECT CONCAT("TOTAL MANAGE MENU: ", @managemenu_boss); + +-- Check that all of boss menu moved; +DELIMITER $ +BEGIN NOT ATOMIC +IF @totalbossmenu = @managemenu_boss THEN + SELECT CONCAT("CONGRATS, BOSS FULLY MOVED [You can delete bossmenu if you desire]", " "); +ELSE + SELECT CONCAT("UT OH, SOMETHING DID NOT WORK (BOSS) [Check what got moved and what didn't manually]", " "); +END IF; +END $ +DELIMITER ; +-- [[ end confirming all of bossmenu moved correctly ]]-- + + +-- Lastly we do the gang menu (THIS WILL PULL YOUR CURRENT gangmenu DATA AND ADD IT TO THE NEWLY CREATED TABLE) +INSERT INTO `management_funds` (`job_name`, `amount`, `type`) +SELECT job_name, amount, 'gang' FROM gangmenu; + + +-- [[ You can remove all of this if you want, this is just to confirm everything moved over correctly ]]-- +SET @totalgangmenu = (SELECT COUNT(*) FROM `gangmenu`); +SET @managemenu_gang = (SELECT COUNT(*) FROM `management_funds` WHERE type = 'gang'); + +SELECT CONCAT("TOTAL GANG MENU: ", @totalgangmenu); +SELECT CONCAT("TOTAL MANAGE GANG MENU: ", @managemenu_gang); + +-- Check that all of gang menu moved; +DELIMITER $ +BEGIN NOT ATOMIC +IF @totalgangmenu = @managemenu_gang THEN + SELECT CONCAT("CONGRATS, GANG FULLY MOVED [You can delete gangmenu if you desire]", " "); +ELSE + SELECT CONCAT("UT OH, SOMETHING DID NOT WORK (GANG) [Check what got moved and what didn't manually]", " "); +END IF; +END $ +DELIMITER ; +-- [[ end confirming all of gangmenu moved correctly ]]-- + + diff --git a/resources/[qb]/[qb_core]/qb-management/server/sv_boss.lua b/resources/[qb]/[qb_core]/qb-management/server/sv_boss.lua new file mode 100644 index 0000000..519839f --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-management/server/sv_boss.lua @@ -0,0 +1,268 @@ +local QBCore = exports['qb-core']:GetCoreObject() +local Accounts = {} + +function ExploitBan(id, reason) + MySQL.insert('INSERT INTO bans (name, license, discord, ip, reason, expire, bannedby) VALUES (?, ?, ?, ?, ?, ?, ?)', { + GetPlayerName(id), + QBCore.Functions.GetIdentifier(id, 'license'), + QBCore.Functions.GetIdentifier(id, 'discord'), + '0.0.0.0', + reason, + 2147483647, + 'qb-management' + }) + TriggerEvent('qb-log:server:CreateLog', 'bans', 'Player Banned', 'red', string.format('%s was banned by %s for %s', GetPlayerName(id), 'qb-management', reason), true) + DropPlayer(id, 'You were permanently banned by the server for: Exploiting - QB-Management') +end + +function GetAccount(account) + return Accounts[account] or 0 +end + +function AddMoney(account, amount) + if not Accounts[account] then + Accounts[account] = 0 + end + + Accounts[account] = Accounts[account] + amount + MySQL.insert('INSERT INTO management_funds (job_name, amount, type) VALUES (:job_name, :amount, :type) ON DUPLICATE KEY UPDATE amount = :amount', + { + ['job_name'] = account, + ['amount'] = Accounts[account], + ['type'] = 'boss' + }) +end + +function RemoveMoney(account, amount) + local isRemoved = false + if amount > 0 then + if not Accounts[account] then + Accounts[account] = 0 + end + + if Accounts[account] >= amount then + Accounts[account] = Accounts[account] - amount + isRemoved = true + end + + MySQL.update('UPDATE management_funds SET amount = ? WHERE job_name = ? and type = "boss"', { Accounts[account], account }) + end + return isRemoved +end + +MySQL.ready(function () + local bossmenu = MySQL.query.await('SELECT job_name,amount FROM management_funds WHERE type = "boss"', {}) + if not bossmenu then return end + + for _,v in ipairs(bossmenu) do + Accounts[v.job_name] = v.amount + end +end) + +RegisterNetEvent("qb-bossmenu:server:withdrawMoney", function(amount) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + + if not Player.PlayerData.job.isboss then ExploitBan(src, 'withdrawMoney Exploiting') return end + + local job = Player.PlayerData.job.name + if RemoveMoney(job, amount) then + Player.Functions.AddMoney("cash", amount, 'Boss menu withdraw') + TriggerEvent('qb-log:server:CreateLog', 'bossmenu', 'Withdraw Money', "blue", Player.PlayerData.name.. "Withdrawal $" .. amount .. ' (' .. job .. ')', false) + TriggerClientEvent('QBCore:Notify', src, "Du hævede " ..amount..",-", "success") + else + TriggerClientEvent('QBCore:Notify', src, "Der er ikke nok kapital.", "error") + end + + TriggerClientEvent('qb-bossmenu:client:OpenMenu', src) +end) + +RegisterNetEvent("qb-bossmenu:server:removeAccountMoney", function(account, amount) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + + if RemoveMoney(account, amount) then + Player.Functions.AddMoney("cash", amount, 'Boss menu withdraw') + TriggerEvent('qb-log:server:CreateLog', 'bossmenu', 'Payment', "blue", Player.PlayerData.name.. "Paid $" .. amount .. ' (' .. account .. ')', false) + TriggerClientEvent('QBCore:Notify', src, "Dit firma har betalt " ..amount..",-", "success") + else + TriggerClientEvent('QBCore:Notify', src, "Dit firma har ikke nok kapital!", "error") + end +end) + +RegisterNetEvent("qb-bossmenu:server:depositMoney", function(amount) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + + if not Player.PlayerData.job.isboss then ExploitBan(src, 'depositMoney Exploiting') return end + + if Player.Functions.RemoveMoney("cash", amount) then + local job = Player.PlayerData.job.name + AddMoney(job, amount) + TriggerEvent('qb-log:server:CreateLog', 'bossmenu', 'Deposit Money', "blue", Player.PlayerData.name.. "Deposit $" .. amount .. ' (' .. job .. ')', false) + TriggerClientEvent('QBCore:Notify', src, "Du indsatte " ..amount..",-", "success") + else + TriggerClientEvent('QBCore:Notify', src, "Du har ikke nok penge til dette!", "error") + end + + TriggerClientEvent('qb-bossmenu:client:OpenMenu', src) +end) + +QBCore.Functions.CreateCallback('qb-bossmenu:server:GetAccount', function(_, cb, jobname) + local result = GetAccount(jobname) + cb(result) +end) + +-- Get Employees +QBCore.Functions.CreateCallback('qb-bossmenu:server:GetEmployees', function(source, cb, jobname) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + + if not Player.PlayerData.job.isboss then ExploitBan(src, 'GetEmployees Exploiting') return end + + local employees = {} + + local players = MySQL.query.await("SELECT * FROM `players` WHERE `job` LIKE '%".. jobname .."%'", {}) + + if players[1] ~= nil then + for _, value in pairs(players) do + local isOnline = QBCore.Functions.GetPlayerByCitizenId(value.citizenid) + + if isOnline and isOnline.PlayerData.job.name == jobname then + employees[#employees+1] = { + empSource = isOnline.PlayerData.citizenid, + grade = isOnline.PlayerData.job.grade, + isboss = isOnline.PlayerData.job.isboss, + name = '🟢 ' .. isOnline.PlayerData.charinfo.firstname .. ' ' .. isOnline.PlayerData.charinfo.lastname + } + elseif value.job.name == jobname then + employees[#employees+1] = { + empSource = value.citizenid, + grade = value.job.grade, + isboss = value.job.isboss, + name = '❌ ' .. value.charinfo.firstname .. ' ' .. value.charinfo.lastname + } + end + end + table.sort(employees, function(a, b) + return a.grade.level > b.grade.level + end) + end + cb(employees) +end) + +-- Grade Change +RegisterNetEvent('qb-bossmenu:server:GradeUpdate', function(data) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local Employee = QBCore.Functions.GetPlayerByCitizenId(data.cid) + + if not Player.PlayerData.job.isboss then ExploitBan(src, 'GradeUpdate Exploiting') return end + if Player.PlayerData.citizenid == Employee.PlayerData.citizenid then TriggerClientEvent('QBCore:Notify', src, "Du kan ikke administrere dig selv!", "error") return end + if data.grade >= Player.PlayerData.job.grade.level then TriggerClientEvent('QBCore:Notify', src, "Du kan ikke forfremme til denne stilling!", "error") return end + + if Employee then + if Employee.Functions.SetJob(Player.PlayerData.job.name, data.grade) then + if data.grade > Employee.PlayerData.job.grade.level then + TriggerClientEvent('QBCore:Notify', src, "Forfremmet!", "success") + TriggerClientEvent('QBCore:Notify', Employee.PlayerData.source, "Du blev forfremmet til " ..data.gradename..".", "success") + else + TriggerClientEvent('QBCore:Notify', src, "Degraderet!", "success") + TriggerClientEvent('QBCore:Notify', Employee.PlayerData.source, "Du blev degraderet til " ..data.gradename..".", "error") + end + else + TriggerClientEvent('QBCore:Notify', src, "Forfremmelses-grad findes ikke.", "error") + end + else + TriggerClientEvent('QBCore:Notify', src, "Personen er ikke online.", "error") + end + TriggerClientEvent('qb-bossmenu:client:OpenMenu', src) +end) + +-- Fire Employee +RegisterNetEvent('qb-bossmenu:server:FireEmployee', function(target) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local Employee = QBCore.Functions.GetPlayerByCitizenId(target) + + if not Player.PlayerData.job.isboss then ExploitBan(src, 'FireEmployee Exploiting') return end + if Player.PlayerData.citizenid == Employee.PlayerData.citizenid then TriggerClientEvent('QBCore:Notify', src, "Du kan ikke fyre dig selv..", "error") return end + + if Employee then + if Employee.PlayerData.job.grade.level > Player.PlayerData.job.grade.level then TriggerClientEvent('QBCore:Notify', src, "Du kan ikke fyre denne person!", "error") return end + + if Employee.Functions.SetJob("unemployed", '0') then + TriggerEvent("qb-log:server:CreateLog", "bossmenu", "Job Fire", "red", Player.PlayerData.charinfo.firstname .. " " .. Player.PlayerData.charinfo.lastname .. ' fyrede ' .. Employee.PlayerData.charinfo.firstname .. " " .. EmployeePlayerData.charinfo.lastname .. " (" .. Player.PlayerData.job.name .. ")", false) + TriggerClientEvent('QBCore:Notify', src, "Ansat fyret!", "success") + TriggerClientEvent('QBCore:Notify', Employee.PlayerData.source , "Du blev fyret.", "error") + else + TriggerClientEvent('QBCore:Notify', src, "Fejl..", "error") + end + else + local player = MySQL.query.await('SELECT * FROM players WHERE citizenid = ? LIMIT 1', { target }) + if player[1] ~= nil then + Employee = player[1] + Employee.job = json.decode(Employee.job) + if Employee.job.grade.level > Player.PlayerData.job.grade.level then TriggerClientEvent('QBCore:Notify', src, "Du kan ikke fyre denne person!", "error") return end + local job = {} + job.name = "unemployed" + job.label = "Civil" + job.payment = QBCore.Shared.Jobs[job.name].grades['0'].payment or 500 + job.onduty = true + job.isboss = false + job.grade = {} + job.grade.name = "Arbejdsløs" + job.grade.level = 0 + MySQL.update('UPDATE players SET job = ? WHERE citizenid = ?', { json.encode(job), target }) + TriggerClientEvent('QBCore:Notify', src, "Ansat fyret!", "success") + TriggerEvent("qb-log:server:CreateLog", "bossmenu", "Job Fire", "red", Player.PlayerData.charinfo.firstname .. " " .. Player.PlayerData.charinfo.lastname .. ' fyrede ' .. Employee.PlayerData.charinfo.firstname .. " " .. Employee.PlayerData.charinfo.lastname .. " (" .. Player.PlayerData.job.name .. ")", false) + else + TriggerClientEvent('QBCore:Notify', src, "Personen er ikke online.", "error") + end + end + TriggerClientEvent('qb-bossmenu:client:OpenMenu', src) +end) + +-- Recruit Player +RegisterNetEvent('qb-bossmenu:server:HireEmployee', function(recruit) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local Target = QBCore.Functions.GetPlayer(recruit) + + if not Player.PlayerData.job.isboss then ExploitBan(src, 'HireEmployee Exploiting') return end + + if Target and Target.Functions.SetJob(Player.PlayerData.job.name, 0) then + TriggerClientEvent('QBCore:Notify', src, "Du ansatte " .. (Target.PlayerData.charinfo.firstname .. ' ' .. Target.PlayerData.charinfo.lastname) .. " som " .. Player.PlayerData.job.grade.name .. "", "success") + TriggerClientEvent('QBCore:Notify', Target.PlayerData.source , "Du blev ansat som " .. Player.PlayerData.job.grade.name .. "", "success") + TriggerEvent('qb-log:server:CreateLog', 'bossmenu', 'Recruit', "lightgreen", (Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname).. " ansatte " .. (Target.PlayerData.charinfo.firstname .. ' ' .. Target.PlayerData.charinfo.lastname) .. ' (' .. Player.PlayerData.job.name .. ' | '.. Player.PlayerData.job.grade.name ..')', false) + end + TriggerClientEvent('qb-bossmenu:client:OpenMenu', src) +end) + +-- Get closest player sv +QBCore.Functions.CreateCallback('qb-bossmenu:getplayers', function(source, cb) + local src = source + local players = {} + local PlayerPed = GetPlayerPed(src) + local pCoords = GetEntityCoords(PlayerPed) + for _, v in pairs(QBCore.Functions.GetPlayers()) do + local targetped = GetPlayerPed(v) + local tCoords = GetEntityCoords(targetped) + local dist = #(pCoords - tCoords) + if PlayerPed ~= targetped and dist < 10 then + local ped = QBCore.Functions.GetPlayer(v) + players[#players+1] = { + id = v, + coords = GetEntityCoords(targetped), + name = ped.PlayerData.charinfo.firstname .. " " .. ped.PlayerData.charinfo.lastname, + citizenid = ped.PlayerData.citizenid, + sources = GetPlayerPed(ped.PlayerData.source), + sourceplayer = ped.PlayerData.source + } + end + end + table.sort(players, function(a, b) + return a.name < b.name + end) + cb(players) +end) diff --git a/resources/[qb]/[qb_core]/qb-management/server/sv_gang.lua b/resources/[qb]/[qb_core]/qb-management/server/sv_gang.lua new file mode 100644 index 0000000..0152f91 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-management/server/sv_gang.lua @@ -0,0 +1,233 @@ +local QBCore = exports['qb-core']:GetCoreObject() +local GangAccounts = {} + +function GetGangAccount(account) + return GangAccounts[account] or 0 +end + +function AddGangMoney(account, amount) + if not GangAccounts[account] then + GangAccounts[account] = 0 + end + + GangAccounts[account] = GangAccounts[account] + amount + MySQL.insert('INSERT INTO management_funds (job_name, amount, type) VALUES (:job_name, :amount, :type) ON DUPLICATE KEY UPDATE amount = :amount', + { + ['job_name'] = account, + ['amount'] = GangAccounts[account], + ['type'] = 'gang' + }) +end + +function RemoveGangMoney(account, amount) + local isRemoved = false + if amount > 0 then + if not GangAccounts[account] then + GangAccounts[account] = 0 + end + + if GangAccounts[account] >= amount then + GangAccounts[account] = GangAccounts[account] - amount + isRemoved = true + end + + MySQL.update('UPDATE management_funds SET amount = ? WHERE job_name = ? and type = "gang"', { GangAccounts[account], account }) + end + return isRemoved +end + +MySQL.ready(function () + local gangmenu = MySQL.query.await('SELECT job_name,amount FROM management_funds WHERE type = "gang"', {}) + if not gangmenu then return end + + for _,v in ipairs(gangmenu) do + GangAccounts[v.job_name] = v.amount + end +end) + +RegisterNetEvent("qb-gangmenu:server:withdrawMoney", function(amount) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + + if not Player.PlayerData.gang.isboss then ExploitBan(src, 'withdrawMoney Exploiting') return end + + local gang = Player.PlayerData.gang.name + if RemoveGangMoney(gang, amount) then + Player.Functions.AddMoney("cash", amount, 'Gang menu withdraw') + TriggerEvent('qb-log:server:CreateLog', 'gangmenu', 'Withdraw Money', 'yellow', Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname .. ' successfully withdrew ' .. amount .. ',- (' .. gang .. ')', false) + TriggerClientEvent('QBCore:Notify', src, "Du hævede " ..amount..",-", "success") + else + TriggerClientEvent('QBCore:Notify', src, "Der er ikke nok penge i banden til dette!", "error") + end + + TriggerClientEvent('qb-gangmenu:client:OpenMenu', src) +end) + +RegisterNetEvent("qb-gangmenu:server:depositMoney", function(amount) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + + if not Player.PlayerData.gang.isboss then ExploitBan(src, 'depositMoney Exploiting') return end + + if Player.Functions.RemoveMoney("cash", amount) then + local gang = Player.PlayerData.gang.name + AddGangMoney(gang, amount) + TriggerEvent('qb-log:server:CreateLog', 'gangmenu', 'Deposit Money', 'yellow', Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname .. ' successfully deposited ' .. amount .. ',- (' .. gang .. ')', false) + TriggerClientEvent('QBCore:Notify', src, "Du indsatte " ..amount..",-", "success") + else + TriggerClientEvent('QBCore:Notify', src, "Du har ikke nok penge til dette!", "error") + end + + TriggerClientEvent('qb-gangmenu:client:OpenMenu', src) +end) + +QBCore.Functions.CreateCallback('qb-gangmenu:server:GetAccount', function(_, cb, GangName) + local gangmoney = GetGangAccount(GangName) + cb(gangmoney) +end) + +-- Get Employees +QBCore.Functions.CreateCallback('qb-gangmenu:server:GetEmployees', function(source, cb, gangname) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + + if not Player.PlayerData.gang.isboss then ExploitBan(src, 'GetEmployees Exploiting') return end + + local employees = {} + local players = MySQL.query.await("SELECT * FROM `players` WHERE `gang` LIKE '%".. gangname .."%'", {}) + if players[1] ~= nil then + for _, value in pairs(players) do + local isOnline = QBCore.Functions.GetPlayerByCitizenId(value.citizenid) + + if isOnline then + employees[#employees+1] = { + empSource = isOnline.PlayerData.citizenid, + grade = isOnline.PlayerData.gang.grade, + isboss = isOnline.PlayerData.gang.isboss, + name = '🟢' .. isOnline.PlayerData.charinfo.firstname .. ' ' .. isOnline.PlayerData.charinfo.lastname + } + else + employees[#employees+1] = { + empSource = value.citizenid, + grade = json.decode(value.gang).grade, + isboss = json.decode(value.gang).isboss, + name = '❌' .. json.decode(value.charinfo).firstname .. ' ' .. json.decode(value.charinfo).lastname + } + end + end + end + cb(employees) +end) + +-- Grade Change +RegisterNetEvent('qb-gangmenu:server:GradeUpdate', function(data) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local Employee = QBCore.Functions.GetPlayerByCitizenId(data.cid) + + + if not Player.PlayerData.gang.isboss then ExploitBan(src, 'GradeUpdate Exploiting') return end + if Player.PlayerData.citizenid == Employee.PlayerData.citizenid then TriggerClientEvent('QBCore:Notify', src, "Du kan ikke ændre din egen stilling!", "error") return end + if data.grade > Player.PlayerData.gang.grade.level then TriggerClientEvent('QBCore:Notify', src, "Du kan ikke forfremme til denne stilling", "error") return end + + if Employee then + + if Employee.Functions.SetGang(Player.PlayerData.gang.name, data.grade) then + TriggerClientEvent('QBCore:Notify', src, "Forfremmet!", "success") + TriggerClientEvent('QBCore:Notify', Employee.PlayerData.source, "Du blev forfremmet til " ..data.gradename..".", "success") + else + TriggerClientEvent('QBCore:Notify', src, "Den grad findes ikke.", "error") + end + else + TriggerClientEvent('QBCore:Notify', src, "Personen er ikke online.", "error") + end + TriggerClientEvent('qb-gangmenu:client:OpenMenu', src) +end) + +-- Fire Member +RegisterNetEvent('qb-gangmenu:server:FireMember', function(target) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local Employee = QBCore.Functions.GetPlayerByCitizenId(target) + + if not Player.PlayerData.gang.isboss then ExploitBan(src, 'FireEmployee Exploiting') return end + if Player.PlayerData.citizenid == target then TriggerClientEvent('QBCore:Notify', src, "Du kan ikke fjerne dig selv fra banden.", "error") return end + + if Employee then + if Employee.PlayerData.gang.grade.level > Player.PlayerData.gang.grade.level then TriggerClientEvent('QBCore:Notify', src, "Du kan ikke bortvise denne person!", "error") return end + + if Employee.Functions.SetGang("none", '0') then + TriggerEvent("qb-log:server:CreateLog", "gangmenu", "Gang Fire", "orange", Player.PlayerData.charinfo.firstname .. " " .. Player.PlayerData.charinfo.lastname .. ' bortviste ' .. Employee.PlayerData.charinfo.firstname .. " " .. Employee.PlayerData.charinfo.lastname .. "(" .. Player.PlayerData.gang.name .. ")", false) + TriggerClientEvent('QBCore:Notify', src, "Bandemedlem bortvist!", "success") + TriggerClientEvent('QBCore:Notify', Employee.PlayerData.source , "Du blev forvist fra banden!", "error") + else + TriggerClientEvent('QBCore:Notify', src, "Fejl.", "error") + end + else + local player = MySQL.query.await('SELECT * FROM players WHERE citizenid = ? LIMIT 1', {target}) + if player[1] ~= nil then + Employee = player[1] + Employee.gang = json.decode(Employee.gang) + if Employee.gang.grade.level > Player.PlayerData.job.grade.level then TriggerClientEvent('QBCore:Notify', src, "Du kan ikke bortvise denne person!", "error") return end + local gang = {} + gang.name = "Ingen" + gang.label = "Ingen affiliering" + gang.payment = 0 + gang.onduty = true + gang.isboss = false + gang.grade = {} + gang.grade.name = nil + gang.grade.level = 0 + MySQL.update('UPDATE players SET gang = ? WHERE citizenid = ?', {json.encode(gang), target}) + TriggerClientEvent('QBCore:Notify', src, "Bandemedlem bortvist!", "success") + TriggerEvent("qb-log:server:CreateLog", "gangmenu", "Gang Fire", "orange", Player.PlayerData.charinfo.firstname .. " " .. Player.PlayerData.charinfo.lastname .. ' bortviste ' .. Employee.PlayerData.charinfo.firstname .. " " .. Employee.PlayerData.charinfo.lastname .. " (" .. Player.PlayerData.gang.name .. ")", false) + else + TriggerClientEvent('QBCore:Notify', src, "Personen er ikke i byen.", "error") + end + end + TriggerClientEvent('qb-gangmenu:client:OpenMenu', src) +end) + +-- Recruit Player +RegisterNetEvent('qb-gangmenu:server:HireMember', function(recruit) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local Target = QBCore.Functions.GetPlayer(recruit) + + if not Player.PlayerData.gang.isboss then ExploitBan(src, 'HireEmployee Exploiting') return end + + if Target and Target.Functions.SetGang(Player.PlayerData.gang.name, 0) then + TriggerClientEvent('QBCore:Notify', src, "Du hvervede " .. (Target.PlayerData.charinfo.firstname .. ' ' .. Target.PlayerData.charinfo.lastname) .. " som " .. Player.PlayerData.gang.label .. "", "success") + TriggerClientEvent('QBCore:Notify', Target.PlayerData.source , "Du blev hvervet som " .. Player.PlayerData.gang.label .. "", "success") + TriggerEvent('qb-log:server:CreateLog', 'gangmenu', 'Recruit', 'yellow', (Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname).. ' hvervede ' .. Target.PlayerData.charinfo.firstname .. ' ' .. Target.PlayerData.charinfo.lastname .. ' (' .. Player.PlayerData.gang.name .. ')', false) + end + TriggerClientEvent('qb-gangmenu:client:OpenMenu', src) +end) + +-- Get closest player sv +QBCore.Functions.CreateCallback('qb-gangmenu:getplayers', function(source, cb) + local src = source + local players = {} + local PlayerPed = GetPlayerPed(src) + local pCoords = GetEntityCoords(PlayerPed) + for _, v in pairs(QBCore.Functions.GetPlayers()) do + local targetped = GetPlayerPed(v) + local tCoords = GetEntityCoords(targetped) + local dist = #(pCoords - tCoords) + if PlayerPed ~= targetped and dist < 10 then + local ped = QBCore.Functions.GetPlayer(v) + players[#players+1] = { + id = v, + coords = GetEntityCoords(targetped), + name = ped.PlayerData.charinfo.firstname .. " " .. ped.PlayerData.charinfo.lastname, + citizenid = ped.PlayerData.citizenid, + sources = GetPlayerPed(ped.PlayerData.source), + sourceplayer = ped.PlayerData.source + } + end + end + table.sort(players, function(a, b) + return a.name < b.name + end) + cb(players) +end) diff --git a/resources/[qb]/[qb_core]/qb-menu/LICENSE b/resources/[qb]/[qb_core]/qb-menu/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-menu/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/resources/[qb]/[qb_core]/qb-menu/README.md b/resources/[qb]/[qb_core]/qb-menu/README.md new file mode 100644 index 0000000..611243a --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-menu/README.md @@ -0,0 +1,84 @@ +# qb-menu +Menu System for the QBCore Framework + +This is a modified version of **[NH Context](https://forum.cfx.re/t/no-longer-supported-standalone-nerohiro-s-context-menu-dynamic-event-firing-menu/2564083)** by **[NeroHiro](https://github.com/nerohiro)** + +--[[ +EXAMPLE MENU +--]] + +``` +RegisterCommand("qbmenutest", function(source, args, raw) + openMenu({ + { + header = "Main Title", + isMenuHeader = true, -- Set to true to make a nonclickable title + }, + { + header = "Sub Menu Button", + txt = "This goes to a sub menu", + params = { + event = "qb-menu:client:testMenu2", + args = { + number = 1, + } + } + }, + { + header = "Sub Menu Button", + txt = "This goes to a sub menu", + disabled = true, + -- hidden = true, -- doesnt create this at all if set to true + params = { + event = "qb-menu:client:testMenu2", + args = { + number = 1, + } + } + }, + }) +end) +``` +``` +RegisterNetEvent('qb-menu:client:testMenu2', function(data) + local number = data.number + openMenu({ + { + header = "< Go Back", + }, + { + header = "Number: "..number, + txt = "Other", + params = { + event = "qb-menu:client:testButton", + args = { + message = "This was called by clicking this button" + } + } + }, + }) +end) +``` +``` +RegisterNetEvent('qb-menu:client:testButton', function(data) + TriggerEvent('QBCore:Notify', data.message) +end) +``` + +# License + + QBCore Framework + Copyright (C) 2021 Joshua Eger + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see diff --git a/resources/[qb]/[qb_core]/qb-menu/client/main.lua b/resources/[qb]/[qb_core]/qb-menu/client/main.lua new file mode 100644 index 0000000..ec04f64 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-menu/client/main.lua @@ -0,0 +1,111 @@ +local QBCore = exports['qb-core']:GetCoreObject() + +RegisterNetEvent('QBCore:Client:UpdateObject', function() QBCore = exports['qb-core']:GetCoreObject() end) + +local headerShown = false +local sendData = nil + +-- Functions + +local function sortData(data, skipfirst) + local header = data[1] + local tempData = data + if skipfirst then table.remove(tempData,1) end + table.sort(tempData, function(a,b) return a.header < b.header end) + if skipfirst then table.insert(tempData,1,header) end + return tempData +end + +local function openMenu(data, sort, skipFirst) + if not data or not next(data) then return end + if sort then data = sortData(data, skipFirst) end + for _,v in pairs(data) do + if v["icon"] then + if QBCore.Shared.Items[tostring(v["icon"])] then + if not string.find(QBCore.Shared.Items[tostring(v["icon"])].image, "//") and not string.find(v["icon"], "//") then + v["icon"] = "nui://qb-inventory/html/images/"..QBCore.Shared.Items[tostring(v["icon"])].image + end + end + end + end + SetNuiFocus(true, true) + headerShown = false + sendData = data + SendNUIMessage({ + action = 'OPEN_MENU', + config = Config, + data = table.clone(data) + }) +end + +local function closeMenu() + sendData = nil + headerShown = false + SetNuiFocus(false) + SendNUIMessage({ + action = 'CLOSE_MENU', + config = Config + }) +end + +local function showHeader(data) + if not data or not next(data) then return end + headerShown = true + sendData = data + SendNUIMessage({ + action = 'SHOW_HEADER', + config = Config, + data = table.clone(data) + }) +end + +-- Events + +RegisterNetEvent('qb-menu:client:openMenu', function(data, sort, skipFirst) openMenu(data, sort, skipFirst) end) + +RegisterNetEvent('qb-menu:client:closeMenu', function() closeMenu() end) + +-- NUI Callbacks + +RegisterNUICallback('clickedButton', function(option, cb) + if headerShown then headerShown = false end + PlaySoundFrontend(-1, 'Highlight_Cancel', 'DLC_HEIST_PLANNING_BOARD_SOUNDS', 1) + SetNuiFocus(false) + if sendData then + local data = sendData[tonumber(option)] + sendData = nil + if data then + if data.params.event then + if data.params.isServer then + TriggerServerEvent(data.params.event, data.params.args) + elseif data.params.isCommand then + ExecuteCommand(data.params.event) + elseif data.params.isQBCommand then + TriggerServerEvent('QBCore:CallCommand', data.params.event, data.params.args) + elseif data.params.isAction then + data.params.event(data.params.args) + else + TriggerEvent(data.params.event, data.params.args) + end + end + end + end + cb('ok') +end) + +RegisterNUICallback('closeMenu', function(_, cb) + headerShown = false + sendData = nil + SetNuiFocus(false) + cb('ok') + TriggerEvent("qb-menu:client:menuClosed") +end) + +-- Command and Keymapping +RegisterCommand('playerfocus', function() if headerShown then SetNuiFocus(true, true) end end) +RegisterKeyMapping('playerFocus', 'Sæt menu-focus', 'keyboard', 'LMENU') + +-- Exports +exports('openMenu', openMenu) +exports('closeMenu', closeMenu) +exports('showHeader', showHeader) \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-menu/fxmanifest.lua b/resources/[qb]/[qb_core]/qb-menu/fxmanifest.lua new file mode 100644 index 0000000..7980bdf --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-menu/fxmanifest.lua @@ -0,0 +1,19 @@ +fx_version 'cerulean' +game 'gta5' + +description 'QB-Menu' +version '1.2.0' + +shared_scripts { 'shared/*' } + +client_script 'client/main.lua' + +ui_page 'html/index.html' + +files { + 'html/index.html', + 'html/script.js', + 'html/style.css' +} + +lua54 'yes' diff --git a/resources/[qb]/[qb_core]/qb-menu/html/index.html b/resources/[qb]/[qb_core]/qb-menu/html/index.html new file mode 100644 index 0000000..61bd4c1 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-menu/html/index.html @@ -0,0 +1,20 @@ + + + + + + QB Menu + + + + + + + + + +
+
+
+ + \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-menu/html/script.js b/resources/[qb]/[qb_core]/qb-menu/html/script.js new file mode 100644 index 0000000..aabd5af --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-menu/html/script.js @@ -0,0 +1,87 @@ +let buttonParams = []; + +const openMenu = (data = null) => { + let html = ""; + $('#container').css('display', 'initial'); + data.forEach((item, index) => { + if(!item.hidden) { + let header = item.header; + let message = item.txt || item.text; + let isMenuHeader = item.isMenuHeader; + let isDisabled = item.disabled; + let icon = item.icon; + html += getButtonRender(header, message, index, isMenuHeader, isDisabled, icon); + if (item.params) buttonParams[index] = item.params; + } + }); + + $("#buttons").html(html); + + $('.button').click(function() { + const target = $(this) + if (!target.hasClass('title') && !target.hasClass('disabled')) { + postData(target.attr('id')); + } + }); +}; + +const getButtonRender = (header, message = null, id, isMenuHeader, isDisabled, icon) => { + return ` +
+
+
+
${header}
+ ${message ? `
${message}
` : ""} +
+
+ `; +}; + +const closeMenu = () => { + $("#buttons").html(" "); + buttonParams = []; + $('#container').css('display', 'none'); +}; + +const postData = (id) => { + $.post(`https://${GetParentResourceName()}/clickedButton`, JSON.stringify(parseInt(id) + 1)); + return closeMenu(); +}; + +const cancelMenu = () => { + $.post(`https://${GetParentResourceName()}/closeMenu`); + return closeMenu(); +}; + +const SetStyle = (data) => { + document.querySelector(':root').style.setProperty('--primary', data.Style.primary); + document.querySelector(':root').style.setProperty('--secondary', data.Style.secondary); + document.querySelector(':root').style.setProperty('--background', data.Style.background); + document.querySelector(':root').style.setProperty('--borderSecondary', data.Style.borderSecondary); + document.querySelector(':root').style.setProperty('--borderPrimary', data.Style.borderPrimary); + document.querySelector(':root').style.setProperty('--unselectable', data.Style.unselectable); + document.querySelector(':root').style.setProperty('--unselectableBorder', data.Style.unselectableBorder); +} + +window.addEventListener("message", (event) => { + const data = event.data; + const buttons = data.data; + const action = data.action; + SetStyle(event.data.config); + switch (action) { + case "OPEN_MENU": + case "SHOW_HEADER": + return openMenu(buttons); + case "CLOSE_MENU": + return closeMenu(); + default: + return; + } +}); + +document.onkeyup = function (event) { + const charCode = event.key; + if (charCode == "Escape") { + cancelMenu(); + } +}; diff --git a/resources/[qb]/[qb_core]/qb-menu/html/style.css b/resources/[qb]/[qb_core]/qb-menu/html/style.css new file mode 100644 index 0000000..ff3e7cc --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-menu/html/style.css @@ -0,0 +1,189 @@ +/* Nice font for button from Google Fonts */ +@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;500&display=swap"); + +* { + padding: 0; + margin: 0; + font-family: "Poppins", sans-serif; + font-weight: 300; +} + +:root { + --primary: transparent; + --secondary: transparent; + --background: transparent; + --borderSecondary: transparent; + --borderPrimary: transparent; + --unselectable: transparent; + --unselectableBorder: transparent; +} + +@media (width: 3840px) and (height: 2160px) { + #container { + position: absolute; + font-size: 25px !important; + height: auto; + top: 20%; + right: 20%; + border-radius: 5px; + background: transparent !important; + } + + #buttons { + font-size: 25px !important; + max-height: 75vh; + width: 300px; + overflow-x: none; + overflow-y: auto; + padding: 10px; + } + + div > .text { + flex-direction: column; + font-size: 25px !important; + overflow: hidden; + } + + div > .header { + width: 100%; + max-width: 100%; + display: flex; + align-items: center; + position: relative; + justify-content: left; + overflow: wrap; + color: white; + font-size: 25px !important; + font-weight: 400; + overflow: hidden; + } +} + +/* width */ +::-webkit-scrollbar { + width: 10px; +} + +/* Track */ +::-webkit-scrollbar-track { + box-shadow: inset 0 0 5px grey; + border-radius: 10px; +} + +/* Handle */ +::-webkit-scrollbar-thumb { + background: #171717e6; + border-radius: 10px; +} + +#container { + display: none; + position: absolute; + height: auto; + top: 20%; + right: 20%; + border-radius: 5px; + background: var(--background); +} +.button { + cursor: pointer; + display: flex; + flex-direction: row !important; + gap: 10px; +} +.title { + cursor: default; + gap: 10px; + display: flex; + flex-direction: row !important; +} + +#buttons { + max-height: 75vh; + width: 300px; + overflow-x: none; + overflow-y: auto; + padding: 10px; +} + +html, +body { + background: transparent !important; +} + +.button { + width: auto; + height: 10%; + background: var(--secondary); + border: 0.1vw solid var(--borderSecondary); + color: white; + margin: auto; + position: relative; + top: 10%; + margin-top: 0.5rem; + overflow: hidden; + padding: 0.45rem; + border-radius: 0.15rem; + display: flex; + flex-direction: column; + cursor: pointer; +} + +.icon { + display: flex; + align-items: center; + position: relative; + justify-content: left; +} + +.button:hover { + background: var(--primary); + border: 0.1vw solid var(--borderPrimary); +} + +.title { + width: auto; + height: 10%; + background: var(--unselectable); + border: 0.1vw solid var(--unselectableBorder); + color: white; + margin: auto; + position: relative; + top: 10%; + margin-top: 0.5rem; + overflow: hidden; + padding: 0.45rem; + border-radius: 0.15rem; + display: flex; + flex-direction: column; +} + +.title > div.column > div.header { + text-decoration: underline !important; +} + +.disabled { + background: var(--unselectable); + border: 0.1vw solid var(--unselectableBorder); + cursor: default; +} + +div > .text { + flex-direction: column; + font-size: 0.75rem; + overflow: hidden; +} + +div > .header { + width: 100%; + max-width: 100%; + display: flex; + align-items: center; + position: relative; + justify-content: left; + overflow: wrap; + color: white; + font-size: 0.9rem; + font-weight: 400; + overflow: hidden; +} \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-menu/shared/config.lua b/resources/[qb]/[qb_core]/qb-menu/shared/config.lua new file mode 100644 index 0000000..d63b258 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-menu/shared/config.lua @@ -0,0 +1,11 @@ +Config = { } + +Config.Style = { + primary = 'linear-gradient(270deg, rgba(255, 0, 102, 0) 0%, rgb(84 255 231 / 90%) 98.22%)', + secondary = 'linear-gradient(270deg, rgba(79, 79, 79, 0) 0%, rgba(105, 105, 105, 0.384) 98.22%)', + background = 'rgba(27, 27, 27, 0.7)', + borderSecondary = 'rgba(123, 144, 183, 0.3)', + borderPrimary = '#2EF5C1', + unselectable = 'linear-gradient(270deg, rgba(255, 0, 102, 0) 0%, rgba(255, 0, 102, 0.9) 98.22%)', + unselectableBorder = '#F93E3E' +} \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-policehub/.gitignore b/resources/[qb]/[qb_core]/qb-policehub/.gitignore new file mode 100644 index 0000000..723ef36 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-policehub/.gitignore @@ -0,0 +1 @@ +.idea \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-policehub/README.md b/resources/[qb]/[qb_core]/qb-policehub/README.md new file mode 100644 index 0000000..18705fc --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-policehub/README.md @@ -0,0 +1,8 @@ +

qb-policehub

+

Police list of all police officers with ther grade, callsigns, status, duty every officer have two buttons one to get their location and the other to join the radio channel they are in there is an panic button which the every can press when in emergency +there is a count beside the panic button shows the count of the officers the settings button to change callsign

+
+ +
+ +

This resource made by khdev store using FiveM React and Lua Boilerplate

diff --git a/resources/[qb]/[qb_core]/qb-policehub/client/client.lua b/resources/[qb]/[qb_core]/qb-policehub/client/client.lua new file mode 100644 index 0000000..b702d0c --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-policehub/client/client.lua @@ -0,0 +1,201 @@ +local QBCore = exports['qb-core']:GetCoreObject() +local menu_opened = false + +local function toggleNuiFrame(shouldShow) + SetNuiFocus(shouldShow, shouldShow) + SendReactMessage('setVisible', shouldShow) +end + +local function toggleNuiFrameNoFocus(shouldShow) + SendReactMessage('setVisible', shouldShow) +end + +RegisterCommand('policehub', function(source, args) + if args[1] == "0" then + if not menu_opened then + menu_opened = true + toggleNuiFrame(true) + SetNuiFocus(true, true) + TriggerServerEvent('qb-policehub:get:officers') + else + menu_opened = false + toggleNuiFrame(false) + SetNuiFocus(false,false) + end + else + if not menu_opened then + menu_opened = true + toggleNuiFrameNoFocus(true) + SetNuiFocus(true, true) + TriggerServerEvent('qb-policehub:get:officers') + else + menu_opened = false + toggleNuiFrameNoFocus(false) + SetNuiFocus(false, false) + end + end +end) + +RegisterNUICallback('hideFrame', function(_, cb) + toggleNuiFrame(false) + TriggerEvent('qb-policehub:close:all') + menu_opened = false + cb({}) +end) + +RegisterNetEvent('qb-policehub:close:all') +AddEventHandler('qb-policehub:close:all', function() + SendReactMessage('closeall', {}) +end) + +local cops = 0 +RegisterNetEvent('qb-policehub:send:officers') +AddEventHandler('qb-policehub:send:officers', function(users, cops) + SendReactMessage('open', { users = users, count = tostring(cops) }) + cops = users.count +end) + +RegisterNUICallback('Get-officers-count', function(data, cb) + QBCore.Functions.TriggerCallback('qb-policehub:officers:count', function(count2) + local retData = { count = count2 } + cb(retData) + end) +end) + +local officerlocationblip = nil +local officerlocationblipwave = nil + +RegisterNUICallback('Get-officer-location', function(data, cb) + local NumberData = tonumber(data.id) + local NameData = data.name + local GradeData = data.grade + local playerIdx = GetPlayerFromServerId(NumberData) + local ped = GetPlayerPed(playerIdx) + local PlayerCoords = GetEntityCoords(ped) + if DoesBlipExist(officerlocationblip) then + RemoveBlip(officerlocationblip) + officerlocationblip = nil + RemoveBlip(officerlocationblipwave) + officerlocationblipwave = nil + Citizen.Wait(1000) + -- wave blip + officerlocationblipwave = AddBlipForCoord(PlayerCoords.x, PlayerCoords.y, PlayerCoords.z) + SetBlipSprite(officerlocationblipwave, 161) + SetBlipColour(officerlocationblipwave, 1) + SetBlipAsShortRange(officerlocationblipwave, true) + -- circle blip + officerlocationblip = AddBlipForCoord(PlayerCoords.x, PlayerCoords.y, PlayerCoords.z) + SetBlipSprite(officerlocationblip, 774) + SetBlipDisplay(officerlocationblip, 4) + SetBlipScale(officerlocationblip, 1.0) + SetBlipColour(officerlocationblip, 1) + SetBlipAlpha(officerlocationblip, 255) + SetBlipAsShortRange(officerlocationblip, true) + BeginTextCommandSetBlipName("STRING") + AddTextComponentString("Placering af: "..GradeData.." "..NameData.."") + EndTextCommandSetBlipName(officerlocationblip) + QBCore.Functions.Notify("Betjent "..GradeData.." "..NameData.."'s lokation fundet", "success", 5000) + Citizen.Wait(20000) + RemoveBlip(officerlocationblip) + officerlocationblip = nil + RemoveBlip(officerlocationblipwave) + officerlocationblipwave = nil + QBCore.Functions.Notify("Mistede lokation af "..GradeData.." "..NameData.."", "error", 5000) + else + -- wave blip + officerlocationblipwave = AddBlipForCoord(PlayerCoords.x, PlayerCoords.y, PlayerCoords.z) + SetBlipSprite(officerlocationblipwave, 161) + SetBlipColour(officerlocationblipwave, 1) + SetBlipAsShortRange(officerlocationblipwave, true) + -- cricle blip + officerlocationblip = AddBlipForCoord(PlayerCoords.x, PlayerCoords.y, PlayerCoords.z) + SetBlipSprite(officerlocationblip, 774) + SetBlipDisplay(officerlocationblip, 4) + SetBlipScale(officerlocationblip, 1.0) + SetBlipColour(officerlocationblip, 1) + SetBlipAlpha(officerlocationblip, 255) + SetBlipAsShortRange(officerlocationblip, true) + BeginTextCommandSetBlipName("STRING") + AddTextComponentString("Placering af: "..GradeData.." "..NameData.."") + EndTextCommandSetBlipName(officerlocationblip) + QBCore.Functions.Notify("Betjent "..GradeData.." "..NameData.."'s lokation fundet", "success", 5000) + Citizen.Wait(20000) + RemoveBlip(officerlocationblip) + officerlocationblip = nil + RemoveBlip(officerlocationblipwave) + officerlocationblipwave = nil + QBCore.Functions.Notify("Mistede lokation af "..GradeData.." "..NameData.."", "error", 5000) + end +end) + +RegisterNUICallback("Join-Officer-radio", function(data) + local playerid = data.id + local playername = data.name + local playergrade = data.grade + local channel = Player(playerid).state['radioChannel'] + if channel ~= 0 then + exports["pma-voice"]:setRadioChannel(channel) + QBCore.Functions.Notify("Du tilkoblede "..playergrade.." "..playername.."'s kanal", "success", 5000) + else + QBCore.Functions.Notify(""..playergrade.." "..playername.." er ikke i en kanal", "error", 5000) + end +end) + +RegisterNUICallback("Use-Panic-Button", function() + local playerid = QBCore.Functions.GetPlayerData().source + local playergrade = QBCore.Functions.GetPlayerData().job.grade.name + local playername = QBCore.Functions.GetPlayerData().charinfo.firstname .." ".. QBCore.Functions.GetPlayerData().charinfo.lastname + local NumberData = tonumber(playerid) + local playerIdx = GetPlayerFromServerId(NumberData) + local playerped = GetPlayerPed(playerIdx) + local playerCoords = GetEntityCoords(playerped) + TriggerServerEvent('qb-policehub:SendPanicToPlayers', playerCoords, playergrade, playername, playerped) +end) + +local PanicBlipData = nil + +RegisterNetEvent('qb-policehub:SetPanicBlip') +AddEventHandler('qb-policehub:SetPanicBlip', function(PlayerCoords) + if DoesBlipExist(PanicBlipData) then + RemoveBlip(PanicBlipData) + PanicBlipData = nil + Citizen.Wait(1000) + -- wave blip + PanicBlipData = AddBlipForCoord(PlayerCoords.x, PlayerCoords.y, PlayerCoords.z) + SetBlipSprite(PanicBlipData, 161) + SetBlipColour(PanicBlipData, 1) + SetBlipAsShortRange(PanicBlipData, true) + Citizen.Wait(20000) + RemoveBlip(PanicBlipData) + PanicBlipData = nil + else + -- wave blip + PanicBlipData = AddBlipForCoord(PlayerCoords.x, PlayerCoords.y, PlayerCoords.z) + SetBlipSprite(PanicBlipData, 161) + SetBlipColour(PanicBlipData, 1) + SetBlipAsShortRange(PanicBlipData, true) + Citizen.Wait(20000) + RemoveBlip(PanicBlipData) + PanicBlipData = nil + end +end) + +RegisterNUICallback("Change-Officer-Callsign", function(data, cb) + local playerid = data.id + local playernewcallsign = data.callsign + TriggerServerEvent('qb-policehub:officer:change:callsign', playerid, playernewcallsign) + cb({}) +end) + +CreateThread(function() + while true do + if IsPauseMenuActive() then + if menu_opened then + toggleNuiFrame(false) + TriggerEvent('qb-policehub:close:all') + menu_opened = false + end + end + Wait(500) + end +end) \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-policehub/client/utils.lua b/resources/[qb]/[qb_core]/qb-policehub/client/utils.lua new file mode 100644 index 0000000..d78b5f4 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-policehub/client/utils.lua @@ -0,0 +1,30 @@ +--- A simple wrapper around SendNUIMessage that you can use to +--- dispatch actions to the React frame. +--- +---@param action string The action you wish to target +---@param data any The data you wish to send along with this action +function SendReactMessage(action, data) + SendNUIMessage({ + action = action, + data = data + }) +end + +local currentResourceName = GetCurrentResourceName() + +local debugIsEnabled = GetConvarInt(('%s-debugMode'):format(currentResourceName), 0) == 1 + +--- A simple debug print function that is dependent on a convar +--- will output a nice prettfied message if debugMode is on +function debugPrint(...) + if not debugIsEnabled then return end + local args = { ... } + + local appendStr = '' + for _, v in ipairs(args) do + appendStr = appendStr .. ' ' .. tostring(v) + end + local msgTemplate = '^3[%s]^0%s' + local finalMsg = msgTemplate:format(currentResourceName, appendStr) + print(finalMsg) +end diff --git a/resources/[qb]/[qb_core]/qb-policehub/fxmanifest.lua b/resources/[qb]/[qb_core]/qb-policehub/fxmanifest.lua new file mode 100644 index 0000000..047d45d --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-policehub/fxmanifest.lua @@ -0,0 +1,19 @@ +fx_version 'cerulean' + +version 'V1.0' +lua54 'yes' + +games { + "gta5", + "rdr3" +} + +ui_page 'web/build/index.html' + +client_script "client/**/*" +server_script "server/**/*" + +files { + 'web/build/index.html', + 'web/build/**/*' +} \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-policehub/server/server.lua b/resources/[qb]/[qb_core]/qb-policehub/server/server.lua new file mode 100644 index 0000000..016c26f --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-policehub/server/server.lua @@ -0,0 +1,76 @@ +local QBCore = exports['qb-core']:GetCoreObject() + +QBCore.Functions.CreateCallback('qb-policehub:officers:count', function(source, cb) + local count = 0 + local policelist = {} + for k, v in pairs(QBCore.Functions.GetPlayers()) do + local Player = QBCore.Functions.GetPlayer(v) + if Player.PlayerData.job.name == "police" and Player.PlayerData.job then + count = count + 1 + end + end + cb(count) +end) + +QBCore.Functions.CreateCallback('qb-policehub:officer:location', function(source, cb) + local count = 0 + local policelist = {} + for k, v in pairs(QBCore.Functions.GetPlayers()) do + local Player = QBCore.Functions.GetPlayer(v) + if Player.PlayerData.job.name == "police" and Player.PlayerData.job then + count = count + 1 + end + end + cb(count) +end) + +RegisterServerEvent('qb-policehub:get:officers') +AddEventHandler('qb-policehub:get:officers', function() + local src = source + local cops = 0 + local data = {} + for k, v in pairs(QBCore.Functions.GetPlayers()) do + local Player = QBCore.Functions.GetPlayer(v) + local vehicle = GetVehiclePedIsIn(GetPlayerPed(Player.PlayerData.source), false) + if Player.PlayerData.job.name == "police"then + cops = cops + 1 + local vehicleClass = GetVehicleType(vehicle) + if vehicleClass == nil then + vehiclestatus = 1 --"OnFoot" + elseif vehicleClass == "bike" then + vehiclestatus = 2 --"Cycles" + elseif vehicleClass == "boat" then + vehiclestatus = 3 --"Boats" + elseif vehicleClass == "heli" then + vehiclestatus = 4 --"Helicopters" + elseif vehicleClass == "plane" then + vehiclestatus = 5 --"Planes" + else + vehiclestatus = 6 --"Cars" + end + table.insert(data, {id = Player.PlayerData.source, name = Player.PlayerData.charinfo.firstname .. " " .. Player.PlayerData.charinfo.lastname, grade = Player.PlayerData.job.grade.name, count = cops, duty = Player.PlayerData.job.onduty, callsign = Player.PlayerData.metadata["callsign"], status = vehiclestatus}) + end + end + TriggerClientEvent('qb-policehub:send:officers', src, data, cops) +end) + +RegisterServerEvent('qb-policehub:SendPanicToPlayers') +AddEventHandler('qb-policehub:SendPanicToPlayers', function(coords, grade, name) + local src = source + for k, v in pairs(QBCore.Functions.GetPlayers()) do + local Player = QBCore.Functions.GetPlayer(v) + local Player2 = QBCore.Functions.GetPlayer(id) + if Player.PlayerData.job.name == "police" then + TriggerClientEvent("QBCore:Notify", Player.PlayerData.source, ""..grade.." "..name.." aktiverede nødknappen", "error", 15000) + TriggerClientEvent("QBCore:Notify", Player.PlayerData.source, "ALLE BETJENTE TIL "..grade.." "..name..", GPS'en er sat til deres placering.", "error", 15000) + TriggerClientEvent("qb-policehub:SetPanicBlip", Player.PlayerData.source, coords) + end + end +end) + +RegisterServerEvent('qb-policehub:officer:change:callsign') +AddEventHandler('qb-policehub:officer:change:callsign', function(id, callsign) + local Player = QBCore.Functions.GetPlayer(id) + Player.Functions.SetMetaData("callsign", callsign) + TriggerClientEvent("QBCore:Notify", Player.PlayerData.source, "Du skiftede dit kaldetegn", "success", 5000) +end) \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-policehub/web/build/asset-manifest.json b/resources/[qb]/[qb_core]/qb-policehub/web/build/asset-manifest.json new file mode 100644 index 0000000..ed13595 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-policehub/web/build/asset-manifest.json @@ -0,0 +1,13 @@ +{ + "files": { + "main.css": "/web/build/static/css/main.f771c7c4.css", + "main.js": "/web/build/static/js/main.d8302197.js", + "index.html": "/web/build/index.html", + "main.f771c7c4.css.map": "/web/build/static/css/main.f771c7c4.css.map", + "main.d8302197.js.map": "/web/build/static/js/main.d8302197.js.map" + }, + "entrypoints": [ + "static/css/main.f771c7c4.css", + "static/js/main.d8302197.js" + ] +} \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-policehub/web/build/index.html b/resources/[qb]/[qb_core]/qb-policehub/web/build/index.html new file mode 100644 index 0000000..637797e --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-policehub/web/build/index.html @@ -0,0 +1,21 @@ + + + + + + + + NUI React Boilerplate + + + + + +
+ + + + + + + \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-policehub/web/build/static/css/main.f771c7c4.css b/resources/[qb]/[qb_core]/qb-policehub/web/build/static/css/main.f771c7c4.css new file mode 100644 index 0000000..aebdb05 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-policehub/web/build/static/css/main.f771c7c4.css @@ -0,0 +1,6 @@ +@charset "UTF-8";@import url(https://fonts.googleapis.com/css2?family=Changa&display=swap);body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;height:100vh}#root{height:100%}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}*{font-family:Changa,sans-serif}#root,:root,body{background-color:initial!important}.list{background-color:rgba(0,0,0,.17);border-radius:5px;display:flex;flex-wrap:wrap;height:85%;left:85%;position:absolute;top:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);width:20%}.header-area{height:3%;position:relative;width:100%}.colums-row{grid-column-gap:2px;border-bottom:3px solid #b90000;text-align:center}.colums-col-title{background-color:#242424;color:#fff;text-align:left}.colums-col,.colums-col-icon{background-color:#242424;color:#fff;vertical-align:middle}.colums-col-icon{font-size:15px}.players-area{height:calc(96% - 20px);overflow-y:scroll;position:relative;white-space:nowrap;width:100%}.colums-row-2{grid-column-gap:2px;font-size:14.5px;font-weight:lighter;margin-bottom:2px;text-align:center}.colums-col-title-2{background-color:#242424;color:#fff;overflow-x:scroll;text-align:left}.colums-col-2,.colums-col-icon-2{background-color:#242424;color:#fff;vertical-align:middle}.colums-col-icon-2{font-size:15px}::-webkit-scrollbar{height:3px;width:0}::-webkit-scrollbar-track{background:#242424}::-webkit-scrollbar-thumb,::-webkit-scrollbar-thumb:hover{background:#3e3e3e}#player-loc,#player-radio{cursor:pointer}.popover-content{font-size:15px}.popover-arrow{display:none!important}.popover-body{background-color:#242424;border-bottom-left-radius:2px!important;border-bottom-right-radius:2px!important}.popover-body,.popover-header{-webkit-animation:fadeIn 30s!important;animation:fadeIn 30s!important;color:#fff!important}.popover-header{border-bottom:3px solid #b90000!important;border-top-left-radius:2px!important;border-top-right-radius:2px!important;padding:.5rem 6rem!important}.modal-content,.popover-header{background-color:#242424!important}.modal-content{color:#fff!important}.btn-close{--bs-btn-close-bg:url(https://api.iconify.design/ic:outline-close.svg?color=%23ffffff)!important;background:transparent var(--bs-btn-close-bg) center/1.5em auto no-repeat!important}.modal-header{border-bottom:1px solid red!important}.modal-footer{border-top:1px solid red!important}/*! + * Bootstrap v5.3.0-alpha3 (https://getbootstrap.com/) + * Copyright 2011-2023 The Bootstrap Authors + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */:root,[data-bs-theme=light]{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-black:#000;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-primary-text-emphasis:#052c65;--bs-secondary-text-emphasis:#2b2f32;--bs-success-text-emphasis:#0a3622;--bs-info-text-emphasis:#055160;--bs-warning-text-emphasis:#664d03;--bs-danger-text-emphasis:#58151c;--bs-light-text-emphasis:#495057;--bs-dark-text-emphasis:#495057;--bs-primary-bg-subtle:#cfe2ff;--bs-secondary-bg-subtle:#e2e3e5;--bs-success-bg-subtle:#d1e7dd;--bs-info-bg-subtle:#cff4fc;--bs-warning-bg-subtle:#fff3cd;--bs-danger-bg-subtle:#f8d7da;--bs-light-bg-subtle:#fcfcfd;--bs-dark-bg-subtle:#ced4da;--bs-primary-border-subtle:#9ec5fe;--bs-secondary-border-subtle:#c4c8cb;--bs-success-border-subtle:#a3cfbb;--bs-info-border-subtle:#9eeaf9;--bs-warning-border-subtle:#ffe69c;--bs-danger-border-subtle:#f1aeb5;--bs-light-border-subtle:#e9ecef;--bs-dark-border-subtle:#adb5bd;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans","Liberation Sans",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg,hsla(0,0%,100%,.15),hsla(0,0%,100%,0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-color-rgb:33,37,41;--bs-body-bg:#fff;--bs-body-bg-rgb:255,255,255;--bs-emphasis-color:#000;--bs-emphasis-color-rgb:0,0,0;--bs-secondary-color:rgba(33,37,41,.75);--bs-secondary-color-rgb:33,37,41;--bs-secondary-bg:#e9ecef;--bs-secondary-bg-rgb:233,236,239;--bs-tertiary-color:rgba(33,37,41,.5);--bs-tertiary-color-rgb:33,37,41;--bs-tertiary-bg:#f8f9fa;--bs-tertiary-bg-rgb:248,249,250;--bs-link-color:#0d6efd;--bs-link-color-rgb:13,110,253;--bs-link-decoration:underline;--bs-link-hover-color:#0a58ca;--bs-link-hover-color-rgb:10,88,202;--bs-code-color:#d63384;--bs-highlight-bg:#fff3cd;--bs-border-width:1px;--bs-border-style:solid;--bs-border-color:#dee2e6;--bs-border-color-translucent:rgba(0,0,0,.175);--bs-border-radius:0.375rem;--bs-border-radius-sm:0.25rem;--bs-border-radius-lg:0.5rem;--bs-border-radius-xl:1rem;--bs-border-radius-xxl:2rem;--bs-border-radius-2xl:var(--bs-border-radius-xxl);--bs-border-radius-pill:50rem;--bs-box-shadow:0 0.5rem 1rem rgba(0,0,0,.15);--bs-box-shadow-sm:0 0.125rem 0.25rem rgba(0,0,0,.075);--bs-box-shadow-lg:0 1rem 3rem rgba(0,0,0,.175);--bs-box-shadow-inset:inset 0 1px 2px rgba(0,0,0,.075);--bs-focus-ring-width:0.25rem;--bs-focus-ring-opacity:0.25;--bs-focus-ring-color:rgba(13,110,253,.25);--bs-form-valid-color:#198754;--bs-form-valid-border-color:#198754;--bs-form-invalid-color:#dc3545;--bs-form-invalid-border-color:#dc3545}[data-bs-theme=dark]{--bs-body-color:#adb5bd;--bs-body-color-rgb:173,181,189;--bs-body-bg:#212529;--bs-body-bg-rgb:33,37,41;--bs-emphasis-color:#fff;--bs-emphasis-color-rgb:255,255,255;--bs-secondary-color:rgba(173,181,189,.75);--bs-secondary-color-rgb:173,181,189;--bs-secondary-bg:#343a40;--bs-secondary-bg-rgb:52,58,64;--bs-tertiary-color:rgba(173,181,189,.5);--bs-tertiary-color-rgb:173,181,189;--bs-tertiary-bg:#2b3035;--bs-tertiary-bg-rgb:43,48,53;--bs-primary-text-emphasis:#6ea8fe;--bs-secondary-text-emphasis:#a7acb1;--bs-success-text-emphasis:#75b798;--bs-info-text-emphasis:#6edff6;--bs-warning-text-emphasis:#ffda6a;--bs-danger-text-emphasis:#ea868f;--bs-light-text-emphasis:#f8f9fa;--bs-dark-text-emphasis:#dee2e6;--bs-primary-bg-subtle:#031633;--bs-secondary-bg-subtle:#161719;--bs-success-bg-subtle:#051b11;--bs-info-bg-subtle:#032830;--bs-warning-bg-subtle:#332701;--bs-danger-bg-subtle:#2c0b0e;--bs-light-bg-subtle:#343a40;--bs-dark-bg-subtle:#1a1d20;--bs-primary-border-subtle:#084298;--bs-secondary-border-subtle:#41464b;--bs-success-border-subtle:#0f5132;--bs-info-border-subtle:#087990;--bs-warning-border-subtle:#997404;--bs-danger-border-subtle:#842029;--bs-light-border-subtle:#495057;--bs-dark-border-subtle:#343a40;--bs-link-color:#6ea8fe;--bs-link-hover-color:#8bb9fe;--bs-link-color-rgb:110,168,254;--bs-link-hover-color-rgb:139,185,254;--bs-code-color:#e685b5;--bs-border-color:#495057;--bs-border-color-translucent:hsla(0,0%,100%,.15);--bs-form-valid-color:#75b798;--bs-form-valid-border-color:#75b798;--bs-form-invalid-color:#ea868f;--bs-form-invalid-border-color:#ea868f;color-scheme:dark}*,:after,:before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent;background-color:#fff;background-color:var(--bs-body-bg);color:#212529;color:var(--bs-body-color);font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica Neue,Noto Sans,Liberation Sans,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-family:var(--bs-body-font-family);font-size:1rem;font-size:var(--bs-body-font-size);font-weight:400;font-weight:var(--bs-body-font-weight);line-height:1.5;line-height:var(--bs-body-line-height);margin:0;text-align:var(--bs-body-text-align)}hr{border:0;border-top:1px solid;border-top:var(--bs-border-width) solid;color:inherit;margin:1rem 0;opacity:.25}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{color:inherit;color:var(--bs-heading-color,inherit);font-weight:500;line-height:1.2;margin-bottom:.5rem;margin-top:0}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-bottom:1rem;margin-top:0}abbr[title]{cursor:help;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{font-style:normal;line-height:inherit;margin-bottom:1rem}ol,ul{padding-left:2rem}dl,ol,ul{margin-bottom:1rem;margin-top:0}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{background-color:#fff3cd;background-color:var(--bs-highlight-bg);padding:.1875em}sub,sup{font-size:.75em;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,1));text-decoration:underline}a:hover{--bs-link-color-rgb:var(--bs-link-hover-color-rgb)}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-family:var(--bs-font-monospace);font-size:1em}pre{display:block;font-size:.875em;margin-bottom:1rem;margin-top:0;overflow:auto}pre code{color:inherit;font-size:inherit;word-break:normal}code{word-wrap:break-word;color:#d63384;color:var(--bs-code-color);font-size:.875em}a>code{color:inherit}kbd{background-color:#212529;background-color:var(--bs-body-color);border-radius:.25rem;color:#fff;color:var(--bs-body-bg);font-size:.875em;padding:.1875rem .375rem}kbd kbd{font-size:1em;padding:0}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{border-collapse:collapse;caption-side:bottom}caption{color:rgba(33,37,41,.75);color:var(--bs-secondary-color);padding-bottom:.5rem;padding-top:.5rem;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border:0 solid;border-color:inherit}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator{display:none!important}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{border-style:none;padding:0}textarea{resize:vertical}fieldset{border:0;margin:0;min-width:0;padding:0}legend{float:left;font-size:calc(1.275rem + .3vw);line-height:inherit;margin-bottom:.5rem;padding:0;width:100%}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}::file-selector-button{-webkit-appearance:button;font:inherit}output{display:inline-block}iframe{border:0}summary{cursor:pointer;display:list-item}progress{vertical-align:initial}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-inline,.list-unstyled{list-style:none;padding-left:0}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{font-size:1.25rem;margin-bottom:1rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{color:#6c757d;font-size:.875em;margin-bottom:1rem;margin-top:-1rem}.blockquote-footer:before{content:"— "}.img-fluid,.img-thumbnail{height:auto;max-width:100%}.img-thumbnail{background-color:#fff;background-color:var(--bs-body-bg);border:1px solid #dee2e6;border:var(--bs-border-width) solid var(--bs-border-color);border-radius:.375rem;border-radius:var(--bs-border-radius);padding:.25rem}.figure{display:inline-block}.figure-img{line-height:1;margin-bottom:.5rem}.figure-caption{color:rgba(33,37,41,.75);color:var(--bs-secondary-color);font-size:.875em}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{--bs-gutter-x:1.5rem;--bs-gutter-y:0;margin-left:auto;margin-right:auto;padding-left:calc(var(--bs-gutter-x)*.5);padding-right:calc(var(--bs-gutter-x)*.5);width:100%}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}:root{--bs-breakpoint-xs:0;--bs-breakpoint-sm:576px;--bs-breakpoint-md:768px;--bs-breakpoint-lg:992px;--bs-breakpoint-xl:1200px;--bs-breakpoint-xxl:1400px}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-left:calc(var(--bs-gutter-x)*-.5);margin-right:calc(var(--bs-gutter-x)*-.5);margin-top:calc(var(--bs-gutter-y)*-1)}.row>*{flex-shrink:0;margin-top:var(--bs-gutter-y);max-width:100%;padding-left:calc(var(--bs-gutter-x)*.5);padding-right:calc(var(--bs-gutter-x)*.5);width:100%}.col{flex:1 0}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm{flex:1 0}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md{flex:1 0}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg{flex:1 0}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl{flex:1 0}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl{flex:1 0}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-color:var(--bs-body-color);--bs-table-bg:transparent;--bs-table-border-color:var(--bs-border-color);--bs-table-accent-bg:transparent;--bs-table-striped-color:var(--bs-body-color);--bs-table-striped-bg:rgba(0,0,0,.05);--bs-table-active-color:var(--bs-body-color);--bs-table-active-bg:rgba(0,0,0,.1);--bs-table-hover-color:var(--bs-body-color);--bs-table-hover-bg:rgba(0,0,0,.075);border-color:var(--bs-table-border-color);color:var(--bs-table-color);margin-bottom:1rem;vertical-align:top;width:100%}.table>:not(caption)>*>*{background-color:var(--bs-table-bg);border-bottom-width:1px;border-bottom-width:var(--bs-border-width);box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg);padding:.5rem}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table-group-divider{border-top:2px solid;border-top:calc(var(--bs-border-width)*2) solid}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem}.table-bordered>:not(caption)>*{border-width:1px 0;border-bottom-width:var(--bs-border-width);border-right-width:0;border-top-width:var(--bs-border-width)}.table-bordered>:not(caption)>*>*{border-width:0 1px;border-bottom-width:0;border-left-width:var(--bs-border-width);border-right-width:var(--bs-border-width);border-top-width:0}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped-columns>:not(caption)>tr>:nth-child(2n),.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-accent-bg:var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg:var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover>*{--bs-table-accent-bg:var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-color:#000;--bs-table-bg:#cfe2ff;--bs-table-border-color:#bacbe6;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000}.table-primary,.table-secondary{border-color:var(--bs-table-border-color);color:var(--bs-table-color)}.table-secondary{--bs-table-color:#000;--bs-table-bg:#e2e3e5;--bs-table-border-color:#cbccce;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000}.table-success{--bs-table-color:#000;--bs-table-bg:#d1e7dd;--bs-table-border-color:#bcd0c7;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000}.table-info,.table-success{border-color:var(--bs-table-border-color);color:var(--bs-table-color)}.table-info{--bs-table-color:#000;--bs-table-bg:#cff4fc;--bs-table-border-color:#badce3;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000}.table-warning{--bs-table-color:#000;--bs-table-bg:#fff3cd;--bs-table-border-color:#e6dbb9;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000}.table-danger,.table-warning{border-color:var(--bs-table-border-color);color:var(--bs-table-color)}.table-danger{--bs-table-color:#000;--bs-table-bg:#f8d7da;--bs-table-border-color:#dfc2c4;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000}.table-light{--bs-table-color:#000;--bs-table-bg:#f8f9fa;--bs-table-border-color:#dfe0e1;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000}.table-dark,.table-light{border-color:var(--bs-table-border-color);color:var(--bs-table-color)}.table-dark{--bs-table-color:#fff;--bs-table-bg:#212529;--bs-table-border-color:#373b3e;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff}.table-responsive{-webkit-overflow-scrolling:touch;overflow-x:auto}@media (max-width:575.98px){.table-responsive-sm{-webkit-overflow-scrolling:touch;overflow-x:auto}}@media (max-width:767.98px){.table-responsive-md{-webkit-overflow-scrolling:touch;overflow-x:auto}}@media (max-width:991.98px){.table-responsive-lg{-webkit-overflow-scrolling:touch;overflow-x:auto}}@media (max-width:1199.98px){.table-responsive-xl{-webkit-overflow-scrolling:touch;overflow-x:auto}}@media (max-width:1399.98px){.table-responsive-xxl{-webkit-overflow-scrolling:touch;overflow-x:auto}}.form-label{margin-bottom:.5rem}.col-form-label{font-size:inherit;line-height:1.5;margin-bottom:0;padding-bottom:calc(.375rem + 1px);padding-bottom:calc(.375rem + var(--bs-border-width));padding-top:calc(.375rem + 1px);padding-top:calc(.375rem + var(--bs-border-width))}.col-form-label-lg{font-size:1.25rem;padding-bottom:calc(.5rem + 1px);padding-bottom:calc(.5rem + var(--bs-border-width));padding-top:calc(.5rem + 1px);padding-top:calc(.5rem + var(--bs-border-width))}.col-form-label-sm{font-size:.875rem;padding-bottom:calc(.25rem + 1px);padding-bottom:calc(.25rem + var(--bs-border-width));padding-top:calc(.25rem + 1px);padding-top:calc(.25rem + var(--bs-border-width))}.form-text{color:rgba(33,37,41,.75);color:var(--bs-secondary-color);font-size:.875em;margin-top:.25rem}.form-control{-webkit-appearance:none;appearance:none;background-clip:padding-box;background-color:#fff;background-color:var(--bs-body-bg);border:1px solid #dee2e6;border:var(--bs-border-width) solid var(--bs-border-color);border-radius:.375rem;border-radius:var(--bs-border-radius);color:#212529;color:var(--bs-body-color);display:block;font-size:1rem;font-weight:400;line-height:1.5;padding:.375rem .75rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;width:100%}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{background-color:#fff;background-color:var(--bs-body-bg);border-color:#86b7fe;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);color:#212529;color:var(--bs-body-color);outline:0}.form-control::-webkit-date-and-time-value{height:1.5em;margin:0;min-width:85px}.form-control::-webkit-datetime-edit{display:block;padding:0}.form-control::-webkit-input-placeholder{color:rgba(33,37,41,.75);color:var(--bs-secondary-color);opacity:1}.form-control::placeholder{color:rgba(33,37,41,.75);color:var(--bs-secondary-color);opacity:1}.form-control:disabled{background-color:#e9ecef;background-color:var(--bs-secondary-bg);opacity:1}.form-control::-webkit-file-upload-button{-webkit-margin-end:.75rem;background-color:#f8f9fa;background-color:var(--bs-tertiary-bg);border:0 solid;border-color:inherit;border-inline-end-width:1px;border-inline-end-width:var(--bs-border-width);border-radius:0;color:#212529;color:var(--bs-body-color);margin:-.375rem -.75rem;margin-inline-end:.75rem;padding:.375rem .75rem;pointer-events:none;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}.form-control::file-selector-button{-webkit-margin-end:.75rem;background-color:#f8f9fa;background-color:var(--bs-tertiary-bg);border:0 solid;border-color:inherit;border-inline-end-width:1px;border-inline-end-width:var(--bs-border-width);border-radius:0;color:#212529;color:var(--bs-body-color);margin:-.375rem -.75rem;margin-inline-end:.75rem;padding:.375rem .75rem;pointer-events:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#e9ecef;background-color:var(--bs-secondary-bg)}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#e9ecef;background-color:var(--bs-secondary-bg)}.form-control-plaintext{background-color:initial;border-width:1px 0;border-bottom:var(--bs-border-width) solid transparent;border-left:0 solid transparent;border-right:0 solid transparent;border-top:var(--bs-border-width) solid transparent;color:#212529;color:var(--bs-body-color);display:block;line-height:1.5;margin-bottom:0;padding:.375rem 0;width:100%}.form-control-plaintext:focus{outline:0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-left:0;padding-right:0}.form-control-sm{border-radius:.25rem;border-radius:var(--bs-border-radius-sm);font-size:.875rem;min-height:calc(1.5em + .5rem + 2px);min-height:calc(1.5em + .5rem + var(--bs-border-width)*2);padding:.25rem .5rem}.form-control-sm::-webkit-file-upload-button{-webkit-margin-end:.5rem;margin:-.25rem -.5rem;margin-inline-end:.5rem;padding:.25rem .5rem}.form-control-sm::file-selector-button{-webkit-margin-end:.5rem;margin:-.25rem -.5rem;margin-inline-end:.5rem;padding:.25rem .5rem}.form-control-lg{border-radius:.5rem;border-radius:var(--bs-border-radius-lg);font-size:1.25rem;min-height:calc(1.5em + 1rem + 2px);min-height:calc(1.5em + 1rem + var(--bs-border-width)*2);padding:.5rem 1rem}.form-control-lg::-webkit-file-upload-button{-webkit-margin-end:1rem;margin:-.5rem -1rem;margin-inline-end:1rem;padding:.5rem 1rem}.form-control-lg::file-selector-button{-webkit-margin-end:1rem;margin:-.5rem -1rem;margin-inline-end:1rem;padding:.5rem 1rem}textarea.form-control{min-height:calc(1.5em + .75rem + 2px);min-height:calc(1.5em + .75rem + var(--bs-border-width)*2)}textarea.form-control-sm{min-height:calc(1.5em + .5rem + 2px);min-height:calc(1.5em + .5rem + var(--bs-border-width)*2)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px);min-height:calc(1.5em + 1rem + var(--bs-border-width)*2)}.form-control-color{height:calc(1.5em + .75rem + 2px);height:calc(1.5em + .75rem + var(--bs-border-width)*2);padding:.375rem;width:3rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{border:0!important;border-radius:.375rem;border-radius:var(--bs-border-radius)}.form-control-color::-webkit-color-swatch{border:0!important;border-radius:.375rem;border-radius:var(--bs-border-radius)}.form-control-color.form-control-sm{height:calc(1.5em + .5rem + 2px);height:calc(1.5em + .5rem + var(--bs-border-width)*2)}.form-control-color.form-control-lg{height:calc(1.5em + 1rem + 2px);height:calc(1.5em + 1rem + var(--bs-border-width)*2)}.form-select{--bs-form-select-bg-img:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3E%3C/svg%3E");-webkit-appearance:none;appearance:none;background-color:#fff;background-color:var(--bs-body-bg);background-image:var(--bs-form-select-bg-img),none;background-image:var(--bs-form-select-bg-img),var(--bs-form-select-bg-icon,none);background-position:right .75rem center;background-repeat:no-repeat;background-size:16px 12px;border:1px solid #dee2e6;border:var(--bs-border-width) solid var(--bs-border-color);border-radius:.375rem;border-radius:var(--bs-border-radius);color:#212529;color:var(--bs-body-color);display:block;font-size:1rem;font-weight:400;line-height:1.5;padding:.375rem 2.25rem .375rem .75rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;width:100%}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);outline:0}.form-select[multiple],.form-select[size]:not([size="1"]){background-image:none;padding-right:.75rem}.form-select:disabled{background-color:#e9ecef;background-color:var(--bs-secondary-bg)}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #212529;text-shadow:0 0 0 var(--bs-body-color)}.form-select-sm{border-radius:.25rem;border-radius:var(--bs-border-radius-sm);font-size:.875rem;padding-bottom:.25rem;padding-left:.5rem;padding-top:.25rem}.form-select-lg{border-radius:.5rem;border-radius:var(--bs-border-radius-lg);font-size:1.25rem;padding-bottom:.5rem;padding-left:1rem;padding-top:.5rem}[data-bs-theme=dark] .form-select{--bs-form-select-bg-img:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%23adb5bd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3E%3C/svg%3E")}.form-check{display:block;margin-bottom:.125rem;min-height:1.5rem;padding-left:1.5em}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-reverse{padding-left:0;padding-right:1.5em;text-align:right}.form-check-reverse .form-check-input{float:right;margin-left:0;margin-right:-1.5em}.form-check-input{--bs-form-check-bg:var(--bs-body-bg);-webkit-print-color-adjust:exact;print-color-adjust:exact;-webkit-appearance:none;appearance:none;background-color:var(--bs-form-check-bg);background-image:var(--bs-form-check-bg-image);background-position:50%;background-repeat:no-repeat;background-size:contain;border:1px solid #dee2e6;border:var(--bs-border-width) solid var(--bs-border-color);height:1em;margin-top:.25em;vertical-align:top;width:1em}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{-webkit-filter:brightness(90%);filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);outline:0}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{--bs-form-check-bg-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3E%3C/svg%3E")}.form-check-input:checked[type=radio]{--bs-form-check-bg-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='2' fill='%23fff'/%3E%3C/svg%3E")}.form-check-input[type=checkbox]:indeterminate{--bs-form-check-bg-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3E%3C/svg%3E");background-color:#0d6efd;border-color:#0d6efd}.form-check-input:disabled{-webkit-filter:none;filter:none;opacity:.5;pointer-events:none}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{cursor:default;opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{--bs-form-switch-bg:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='rgba(0, 0, 0, 0.25)'/%3E%3C/svg%3E");background-image:var(--bs-form-switch-bg);background-position:0;border-radius:2em;margin-left:-2.5em;transition:background-position .15s ease-in-out;width:2em}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{--bs-form-switch-bg:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%2386b7fe'/%3E%3C/svg%3E")}.form-switch .form-check-input:checked{--bs-form-switch-bg:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E");background-position:100%}.form-switch.form-check-reverse{padding-left:0;padding-right:2.5em}.form-switch.form-check-reverse .form-check-input{margin-left:0;margin-right:-2.5em}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{clip:rect(0,0,0,0);pointer-events:none;position:absolute}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{-webkit-filter:none;filter:none;opacity:.65;pointer-events:none}[data-bs-theme=dark] .form-switch .form-check-input:not(:checked):not(:focus){--bs-form-switch-bg:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='rgba(255, 255, 255, 0.25)'/%3E%3C/svg%3E")}.form-range{-webkit-appearance:none;appearance:none;background-color:initial;height:1.5rem;padding:0;width:100%}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;background-color:#0d6efd;border:0;border-radius:1rem;height:1rem;margin-top:-.25rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;width:1rem}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{background-color:#f8f9fa;background-color:var(--bs-tertiary-bg);border-color:transparent;border-radius:1rem;color:transparent;cursor:pointer;height:.5rem;width:100%}.form-range::-moz-range-thumb{appearance:none;background-color:#0d6efd;border:0;border-radius:1rem;height:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;width:1rem}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{background-color:#f8f9fa;background-color:var(--bs-tertiary-bg);border-color:transparent;border-radius:1rem;color:transparent;cursor:pointer;height:.5rem;width:100%}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:rgba(33,37,41,.75);background-color:var(--bs-secondary-color)}.form-range:disabled::-moz-range-thumb{background-color:rgba(33,37,41,.75);background-color:var(--bs-secondary-color)}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-control-plaintext,.form-floating>.form-select{height:calc(3.5rem + 2px);height:calc(3.5rem + var(--bs-border-width)*2);line-height:1.25}.form-floating>label{border:1px solid transparent;border:var(--bs-border-width) solid transparent;height:100%;left:0;overflow:hidden;padding:1rem .75rem;pointer-events:none;position:absolute;text-align:start;text-overflow:ellipsis;top:0;-webkit-transform-origin:0 0;transform-origin:0 0;transition:opacity .1s ease-in-out,-webkit-transform .1s ease-in-out;transition:opacity .1s ease-in-out,transform .1s ease-in-out;transition:opacity .1s ease-in-out,transform .1s ease-in-out,-webkit-transform .1s ease-in-out;white-space:nowrap;z-index:2}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control,.form-floating>.form-control-plaintext{padding:1rem .75rem}.form-floating>.form-control-plaintext::-webkit-input-placeholder,.form-floating>.form-control::-webkit-input-placeholder{color:transparent}.form-floating>.form-control-plaintext::placeholder,.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control-plaintext:focus,.form-floating>.form-control-plaintext:not(:placeholder-shown),.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-bottom:.625rem;padding-top:1.625rem}.form-floating>.form-control-plaintext:-webkit-autofill,.form-floating>.form-control:-webkit-autofill{padding-bottom:.625rem;padding-top:1.625rem}.form-floating>.form-select{padding-bottom:.625rem;padding-top:1.625rem}.form-floating>.form-control-plaintext~label,.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{color:rgba(33,37,41,.65);color:rgba(var(--bs-body-color-rgb),.65);-webkit-transform:scale(.85) translateY(-.5rem) translateX(.15rem);transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control-plaintext~label:after,.form-floating>.form-control:focus~label:after,.form-floating>.form-control:not(:placeholder-shown)~label:after,.form-floating>.form-select~label:after{background-color:#fff;background-color:var(--bs-body-bg);border-radius:.375rem;border-radius:var(--bs-border-radius);content:"";height:1.5em;inset:1rem .375rem;position:absolute;z-index:-1}.form-floating>.form-control:-webkit-autofill~label{color:rgba(33,37,41,.65);color:rgba(var(--bs-body-color-rgb),.65);-webkit-transform:scale(.85) translateY(-.5rem) translateX(.15rem);transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control-plaintext~label{border-width:1px 0;border-bottom-width:var(--bs-border-width);border-right-width:0;border-top-width:var(--bs-border-width)}.form-floating>:disabled~label{color:#6c757d}.form-floating>:disabled~label:after{background-color:#e9ecef;background-color:var(--bs-secondary-bg)}.input-group{align-items:stretch;display:flex;flex-wrap:wrap;position:relative;width:100%}.input-group>.form-control,.input-group>.form-floating,.input-group>.form-select{flex:1 1 auto;min-width:0;position:relative;width:1%}.input-group>.form-control:focus,.input-group>.form-floating:focus-within,.input-group>.form-select:focus{z-index:5}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:5}.input-group-text{align-items:center;background-color:#f8f9fa;background-color:var(--bs-tertiary-bg);border:1px solid #dee2e6;border:var(--bs-border-width) solid var(--bs-border-color);border-radius:.375rem;border-radius:var(--bs-border-radius);color:#212529;color:var(--bs-body-color);display:flex;font-size:1rem;font-weight:400;line-height:1.5;padding:.375rem .75rem;text-align:center;white-space:nowrap}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{border-radius:.5rem;border-radius:var(--bs-border-radius-lg);font-size:1.25rem;padding:.5rem 1rem}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{border-radius:.25rem;border-radius:var(--bs-border-radius-sm);font-size:.875rem;padding:.25rem .5rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-control,.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-select,.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-control,.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-select,.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating){border-bottom-right-radius:0;border-top-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-1px;margin-left:calc(var(--bs-border-width)*-1)}.input-group>.form-floating:not(:first-child)>.form-control,.input-group>.form-floating:not(:first-child)>.form-select{border-bottom-left-radius:0;border-top-left-radius:0}.valid-feedback{color:#198754;color:var(--bs-form-valid-color);display:none;font-size:.875em;margin-top:.25rem;width:100%}.valid-tooltip{background-color:#198754;background-color:var(--bs-success);border-radius:.375rem;border-radius:var(--bs-border-radius);color:#fff;display:none;font-size:.875rem;margin-top:.1rem;max-width:100%;padding:.25rem .5rem;position:absolute;top:100%;z-index:5}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3E%3C/svg%3E");background-position:right calc(.375em + .1875rem) center;background-repeat:no-repeat;background-size:calc(.75em + .375rem) calc(.75em + .375rem);border-color:#198754;border-color:var(--bs-form-valid-border-color);padding-right:calc(1.5em + .75rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#198754;border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(25,135,84,.25);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem);padding-right:calc(1.5em + .75rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:#198754;border-color:var(--bs-form-valid-border-color)}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3E%3C/svg%3E");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem);padding-right:4.125rem}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:#198754;border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(25,135,84,.25);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.form-control-color.is-valid,.was-validated .form-control-color:valid{width:calc(3.75rem + 1.5em)}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:#198754;border-color:var(--bs-form-valid-border-color)}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:#198754;background-color:var(--bs-form-valid-color)}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#198754;color:var(--bs-form-valid-color)}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group>.form-control:not(:focus).is-valid,.input-group>.form-floating:not(:focus-within).is-valid,.input-group>.form-select:not(:focus).is-valid,.was-validated .input-group>.form-control:not(:focus):valid,.was-validated .input-group>.form-floating:not(:focus-within):valid,.was-validated .input-group>.form-select:not(:focus):valid{z-index:3}.invalid-feedback{color:#dc3545;color:var(--bs-form-invalid-color);display:none;font-size:.875em;margin-top:.25rem;width:100%}.invalid-tooltip{background-color:#dc3545;background-color:var(--bs-danger);border-radius:.375rem;border-radius:var(--bs-border-radius);color:#fff;display:none;font-size:.875rem;margin-top:.1rem;max-width:100%;padding:.25rem .5rem;position:absolute;top:100%;z-index:5}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545'%3E%3Ccircle cx='6' cy='6' r='4.5'/%3E%3Cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3E%3Ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3E%3C/svg%3E");background-position:right calc(.375em + .1875rem) center;background-repeat:no-repeat;background-size:calc(.75em + .375rem) calc(.75em + .375rem);border-color:#dc3545;border-color:var(--bs-form-invalid-border-color);padding-right:calc(1.5em + .75rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(220,53,69,.25);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem);padding-right:calc(1.5em + .75rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:#dc3545;border-color:var(--bs-form-invalid-border-color)}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545'%3E%3Ccircle cx='6' cy='6' r='4.5'/%3E%3Cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3E%3Ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3E%3C/svg%3E");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem);padding-right:4.125rem}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:#dc3545;border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(220,53,69,.25);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.form-control-color.is-invalid,.was-validated .form-control-color:invalid{width:calc(3.75rem + 1.5em)}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:#dc3545;border-color:var(--bs-form-invalid-border-color)}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:#dc3545;background-color:var(--bs-form-invalid-color)}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545;color:var(--bs-form-invalid-color)}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group>.form-control:not(:focus).is-invalid,.input-group>.form-floating:not(:focus-within).is-invalid,.input-group>.form-select:not(:focus).is-invalid,.was-validated .input-group>.form-control:not(:focus):invalid,.was-validated .input-group>.form-floating:not(:focus-within):invalid,.was-validated .input-group>.form-select:not(:focus):invalid{z-index:4}.btn{--bs-btn-padding-x:0.75rem;--bs-btn-padding-y:0.375rem;--bs-btn-font-family: ;--bs-btn-font-size:1rem;--bs-btn-font-weight:400;--bs-btn-line-height:1.5;--bs-btn-color:var(--bs-body-color);--bs-btn-bg:transparent;--bs-btn-border-width:var(--bs-border-width);--bs-btn-border-color:transparent;--bs-btn-border-radius:var(--bs-border-radius);--bs-btn-hover-border-color:transparent;--bs-btn-box-shadow:inset 0 1px 0 hsla(0,0%,100%,.15),0 1px 1px rgba(0,0,0,.075);--bs-btn-disabled-opacity:0.65;--bs-btn-focus-box-shadow:0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb),.5);background-color:var(--bs-btn-bg);border:var(--bs-btn-border-width) solid var(--bs-btn-border-color);border-radius:var(--bs-btn-border-radius);color:var(--bs-btn-color);cursor:pointer;display:inline-block;font-family:var(--bs-btn-font-family);font-size:var(--bs-btn-font-size);font-weight:var(--bs-btn-font-weight);line-height:var(--bs-btn-line-height);padding:var(--bs-btn-padding-y) var(--bs-btn-padding-x);text-align:center;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-user-select:none;user-select:none;vertical-align:middle}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color);color:var(--bs-btn-hover-color)}.btn-check+.btn:hover{background-color:var(--bs-btn-bg);border-color:var(--bs-btn-border-color);color:var(--bs-btn-color)}.btn:focus-visible{background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color);box-shadow:var(--bs-btn-focus-box-shadow);color:var(--bs-btn-hover-color);outline:0}.btn-check:focus-visible+.btn{border-color:var(--bs-btn-hover-border-color);box-shadow:var(--bs-btn-focus-box-shadow);outline:0}.btn-check:checked+.btn,.btn.active,.btn.show,.btn:first-child:active,:not(.btn-check)+.btn:active{background-color:var(--bs-btn-active-bg);border-color:var(--bs-btn-active-border-color);color:var(--bs-btn-active-color)}.btn-check:checked+.btn:focus-visible,.btn.active:focus-visible,.btn.show:focus-visible,.btn:first-child:active:focus-visible,:not(.btn-check)+.btn:active:focus-visible{box-shadow:var(--bs-btn-focus-box-shadow)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{background-color:var(--bs-btn-disabled-bg);border-color:var(--bs-btn-disabled-border-color);color:var(--bs-btn-disabled-color);opacity:var(--bs-btn-disabled-opacity);pointer-events:none}.btn-primary{--bs-btn-color:#fff;--bs-btn-bg:#0d6efd;--bs-btn-border-color:#0d6efd;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#0b5ed7;--bs-btn-hover-border-color:#0a58ca;--bs-btn-focus-shadow-rgb:49,132,253;--bs-btn-active-color:#fff;--bs-btn-active-bg:#0a58ca;--bs-btn-active-border-color:#0a53be;--bs-btn-active-shadow:inset 0 3px 5px rgba(0,0,0,.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#0d6efd;--bs-btn-disabled-border-color:#0d6efd}.btn-secondary{--bs-btn-color:#fff;--bs-btn-bg:#6c757d;--bs-btn-border-color:#6c757d;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#5c636a;--bs-btn-hover-border-color:#565e64;--bs-btn-focus-shadow-rgb:130,138,145;--bs-btn-active-color:#fff;--bs-btn-active-bg:#565e64;--bs-btn-active-border-color:#51585e;--bs-btn-active-shadow:inset 0 3px 5px rgba(0,0,0,.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#6c757d;--bs-btn-disabled-border-color:#6c757d}.btn-success{--bs-btn-color:#fff;--bs-btn-bg:#198754;--bs-btn-border-color:#198754;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#157347;--bs-btn-hover-border-color:#146c43;--bs-btn-focus-shadow-rgb:60,153,110;--bs-btn-active-color:#fff;--bs-btn-active-bg:#146c43;--bs-btn-active-border-color:#13653f;--bs-btn-active-shadow:inset 0 3px 5px rgba(0,0,0,.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#198754;--bs-btn-disabled-border-color:#198754}.btn-info{--bs-btn-color:#000;--bs-btn-bg:#0dcaf0;--bs-btn-border-color:#0dcaf0;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#31d2f2;--bs-btn-hover-border-color:#25cff2;--bs-btn-focus-shadow-rgb:11,172,204;--bs-btn-active-color:#000;--bs-btn-active-bg:#3dd5f3;--bs-btn-active-border-color:#25cff2;--bs-btn-active-shadow:inset 0 3px 5px rgba(0,0,0,.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#0dcaf0;--bs-btn-disabled-border-color:#0dcaf0}.btn-warning{--bs-btn-color:#000;--bs-btn-bg:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#ffca2c;--bs-btn-hover-border-color:#ffc720;--bs-btn-focus-shadow-rgb:217,164,6;--bs-btn-active-color:#000;--bs-btn-active-bg:#ffcd39;--bs-btn-active-border-color:#ffc720;--bs-btn-active-shadow:inset 0 3px 5px rgba(0,0,0,.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#ffc107;--bs-btn-disabled-border-color:#ffc107}.btn-danger{--bs-btn-color:#fff;--bs-btn-bg:#dc3545;--bs-btn-border-color:#dc3545;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#bb2d3b;--bs-btn-hover-border-color:#b02a37;--bs-btn-focus-shadow-rgb:225,83,97;--bs-btn-active-color:#fff;--bs-btn-active-bg:#b02a37;--bs-btn-active-border-color:#a52834;--bs-btn-active-shadow:inset 0 3px 5px rgba(0,0,0,.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#dc3545;--bs-btn-disabled-border-color:#dc3545}.btn-light{--bs-btn-color:#000;--bs-btn-bg:#f8f9fa;--bs-btn-border-color:#f8f9fa;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#d3d4d5;--bs-btn-hover-border-color:#c6c7c8;--bs-btn-focus-shadow-rgb:211,212,213;--bs-btn-active-color:#000;--bs-btn-active-bg:#c6c7c8;--bs-btn-active-border-color:#babbbc;--bs-btn-active-shadow:inset 0 3px 5px rgba(0,0,0,.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#f8f9fa;--bs-btn-disabled-border-color:#f8f9fa}.btn-dark{--bs-btn-color:#fff;--bs-btn-bg:#212529;--bs-btn-border-color:#212529;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#424649;--bs-btn-hover-border-color:#373b3e;--bs-btn-focus-shadow-rgb:66,70,73;--bs-btn-active-color:#fff;--bs-btn-active-bg:#4d5154;--bs-btn-active-border-color:#373b3e;--bs-btn-active-shadow:inset 0 3px 5px rgba(0,0,0,.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#212529;--bs-btn-disabled-border-color:#212529}.btn-outline-primary{--bs-btn-color:#0d6efd;--bs-btn-border-color:#0d6efd;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#0d6efd;--bs-btn-hover-border-color:#0d6efd;--bs-btn-focus-shadow-rgb:13,110,253;--bs-btn-active-color:#fff;--bs-btn-active-bg:#0d6efd;--bs-btn-active-border-color:#0d6efd;--bs-btn-active-shadow:inset 0 3px 5px rgba(0,0,0,.125);--bs-btn-disabled-color:#0d6efd;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#0d6efd;--bs-gradient:none}.btn-outline-secondary{--bs-btn-color:#6c757d;--bs-btn-border-color:#6c757d;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#6c757d;--bs-btn-hover-border-color:#6c757d;--bs-btn-focus-shadow-rgb:108,117,125;--bs-btn-active-color:#fff;--bs-btn-active-bg:#6c757d;--bs-btn-active-border-color:#6c757d;--bs-btn-active-shadow:inset 0 3px 5px rgba(0,0,0,.125);--bs-btn-disabled-color:#6c757d;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#6c757d;--bs-gradient:none}.btn-outline-success{--bs-btn-color:#198754;--bs-btn-border-color:#198754;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#198754;--bs-btn-hover-border-color:#198754;--bs-btn-focus-shadow-rgb:25,135,84;--bs-btn-active-color:#fff;--bs-btn-active-bg:#198754;--bs-btn-active-border-color:#198754;--bs-btn-active-shadow:inset 0 3px 5px rgba(0,0,0,.125);--bs-btn-disabled-color:#198754;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#198754;--bs-gradient:none}.btn-outline-info{--bs-btn-color:#0dcaf0;--bs-btn-border-color:#0dcaf0;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#0dcaf0;--bs-btn-hover-border-color:#0dcaf0;--bs-btn-focus-shadow-rgb:13,202,240;--bs-btn-active-color:#000;--bs-btn-active-bg:#0dcaf0;--bs-btn-active-border-color:#0dcaf0;--bs-btn-active-shadow:inset 0 3px 5px rgba(0,0,0,.125);--bs-btn-disabled-color:#0dcaf0;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#0dcaf0;--bs-gradient:none}.btn-outline-warning{--bs-btn-color:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#ffc107;--bs-btn-hover-border-color:#ffc107;--bs-btn-focus-shadow-rgb:255,193,7;--bs-btn-active-color:#000;--bs-btn-active-bg:#ffc107;--bs-btn-active-border-color:#ffc107;--bs-btn-active-shadow:inset 0 3px 5px rgba(0,0,0,.125);--bs-btn-disabled-color:#ffc107;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#ffc107;--bs-gradient:none}.btn-outline-danger{--bs-btn-color:#dc3545;--bs-btn-border-color:#dc3545;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#dc3545;--bs-btn-hover-border-color:#dc3545;--bs-btn-focus-shadow-rgb:220,53,69;--bs-btn-active-color:#fff;--bs-btn-active-bg:#dc3545;--bs-btn-active-border-color:#dc3545;--bs-btn-active-shadow:inset 0 3px 5px rgba(0,0,0,.125);--bs-btn-disabled-color:#dc3545;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#dc3545;--bs-gradient:none}.btn-outline-light{--bs-btn-color:#f8f9fa;--bs-btn-border-color:#f8f9fa;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#f8f9fa;--bs-btn-hover-border-color:#f8f9fa;--bs-btn-focus-shadow-rgb:248,249,250;--bs-btn-active-color:#000;--bs-btn-active-bg:#f8f9fa;--bs-btn-active-border-color:#f8f9fa;--bs-btn-active-shadow:inset 0 3px 5px rgba(0,0,0,.125);--bs-btn-disabled-color:#f8f9fa;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#f8f9fa;--bs-gradient:none}.btn-outline-dark{--bs-btn-color:#212529;--bs-btn-border-color:#212529;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#212529;--bs-btn-hover-border-color:#212529;--bs-btn-focus-shadow-rgb:33,37,41;--bs-btn-active-color:#fff;--bs-btn-active-bg:#212529;--bs-btn-active-border-color:#212529;--bs-btn-active-shadow:inset 0 3px 5px rgba(0,0,0,.125);--bs-btn-disabled-color:#212529;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#212529;--bs-gradient:none}.btn-link{--bs-btn-font-weight:400;--bs-btn-color:var(--bs-link-color);--bs-btn-bg:transparent;--bs-btn-border-color:transparent;--bs-btn-hover-color:var(--bs-link-hover-color);--bs-btn-hover-border-color:transparent;--bs-btn-active-color:var(--bs-link-hover-color);--bs-btn-active-border-color:transparent;--bs-btn-disabled-color:#6c757d;--bs-btn-disabled-border-color:transparent;--bs-btn-box-shadow:0 0 0 #000;--bs-btn-focus-shadow-rgb:49,132,253;text-decoration:underline}.btn-link:focus-visible{color:var(--bs-btn-color)}.btn-link:hover{color:var(--bs-btn-hover-color)}.btn-group-lg>.btn,.btn-lg{--bs-btn-padding-y:0.5rem;--bs-btn-padding-x:1rem;--bs-btn-font-size:1.25rem;--bs-btn-border-radius:var(--bs-border-radius-lg)}.btn-group-sm>.btn,.btn-sm{--bs-btn-padding-y:0.25rem;--bs-btn-padding-x:0.5rem;--bs-btn-font-size:0.875rem;--bs-btn-border-radius:var(--bs-border-radius-sm)}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{height:auto;transition:width .35s ease;width:0}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropdown,.dropdown-center,.dropend,.dropstart,.dropup,.dropup-center{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle:after{border-bottom:0;border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid;content:"";display:inline-block;margin-left:.255em;vertical-align:.255em}.dropdown-toggle:empty:after{margin-left:0}.dropdown-menu{--bs-dropdown-zindex:1000;--bs-dropdown-min-width:10rem;--bs-dropdown-padding-x:0;--bs-dropdown-padding-y:0.5rem;--bs-dropdown-spacer:0.125rem;--bs-dropdown-font-size:1rem;--bs-dropdown-color:var(--bs-body-color);--bs-dropdown-bg:var(--bs-body-bg);--bs-dropdown-border-color:var(--bs-border-color-translucent);--bs-dropdown-border-radius:var(--bs-border-radius);--bs-dropdown-border-width:var(--bs-border-width);--bs-dropdown-inner-border-radius:calc(var(--bs-border-radius) - var(--bs-border-width));--bs-dropdown-divider-bg:var(--bs-border-color-translucent);--bs-dropdown-divider-margin-y:0.5rem;--bs-dropdown-box-shadow:0 0.5rem 1rem rgba(0,0,0,.15);--bs-dropdown-link-color:var(--bs-body-color);--bs-dropdown-link-hover-color:var(--bs-body-color);--bs-dropdown-link-hover-bg:var(--bs-tertiary-bg);--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#0d6efd;--bs-dropdown-link-disabled-color:var(--bs-tertiary-color);--bs-dropdown-item-padding-x:1rem;--bs-dropdown-item-padding-y:0.25rem;--bs-dropdown-header-color:#6c757d;--bs-dropdown-header-padding-x:1rem;--bs-dropdown-header-padding-y:0.5rem;background-clip:padding-box;background-color:var(--bs-dropdown-bg);border:var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);border-radius:var(--bs-dropdown-border-radius);color:var(--bs-dropdown-color);display:none;font-size:var(--bs-dropdown-font-size);list-style:none;margin:0;min-width:var(--bs-dropdown-min-width);padding:var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x);position:absolute;text-align:left;z-index:var(--bs-dropdown-zindex)}.dropdown-menu[data-bs-popper]{left:0;margin-top:var(--bs-dropdown-spacer);top:100%}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{left:auto;right:0}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{left:auto;right:0}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{left:auto;right:0}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{left:auto;right:0}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{left:auto;right:0}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{left:auto;right:0}}.dropup .dropdown-menu[data-bs-popper]{bottom:100%;margin-bottom:var(--bs-dropdown-spacer);margin-top:0;top:auto}.dropup .dropdown-toggle:after{border-bottom:.3em solid;border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:0;content:"";display:inline-block;margin-left:.255em;vertical-align:.255em}.dropup .dropdown-toggle:empty:after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{left:100%;margin-left:var(--bs-dropdown-spacer);margin-top:0;right:auto;top:0}.dropend .dropdown-toggle:after{border-bottom:.3em solid transparent;border-left:.3em solid;border-right:0;border-top:.3em solid transparent;content:"";display:inline-block;margin-left:.255em;vertical-align:.255em}.dropend .dropdown-toggle:empty:after{margin-left:0}.dropend .dropdown-toggle:after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{left:auto;margin-right:var(--bs-dropdown-spacer);margin-top:0;right:100%;top:0}.dropstart .dropdown-toggle:after{content:"";display:inline-block;display:none;margin-left:.255em;vertical-align:.255em}.dropstart .dropdown-toggle:before{border-bottom:.3em solid transparent;border-right:.3em solid;border-top:.3em solid transparent;content:"";display:inline-block;margin-right:.255em;vertical-align:.255em}.dropstart .dropdown-toggle:empty:after{margin-left:0}.dropstart .dropdown-toggle:before{vertical-align:0}.dropdown-divider{border-top:1px solid var(--bs-dropdown-divider-bg);height:0;margin:var(--bs-dropdown-divider-margin-y) 0;opacity:1;overflow:hidden}.dropdown-item{background-color:initial;border:0;border-radius:0;border-radius:var(--bs-dropdown-item-border-radius,0);clear:both;color:var(--bs-dropdown-link-color);display:block;font-weight:400;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);text-align:inherit;text-decoration:none;white-space:nowrap;width:100%}.dropdown-item:focus,.dropdown-item:hover{background-color:var(--bs-dropdown-link-hover-bg);color:var(--bs-dropdown-link-hover-color)}.dropdown-item.active,.dropdown-item:active{background-color:var(--bs-dropdown-link-active-bg);color:var(--bs-dropdown-link-active-color);text-decoration:none}.dropdown-item.disabled,.dropdown-item:disabled{background-color:initial;color:var(--bs-dropdown-link-disabled-color);pointer-events:none}.dropdown-menu.show{display:block}.dropdown-header{color:var(--bs-dropdown-header-color);display:block;font-size:.875rem;margin-bottom:0;padding:var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);white-space:nowrap}.dropdown-item-text{color:var(--bs-dropdown-link-color);display:block;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x)}.dropdown-menu-dark{--bs-dropdown-color:#dee2e6;--bs-dropdown-bg:#343a40;--bs-dropdown-border-color:var(--bs-border-color-translucent);--bs-dropdown-box-shadow: ;--bs-dropdown-link-color:#dee2e6;--bs-dropdown-link-hover-color:#fff;--bs-dropdown-divider-bg:var(--bs-border-color-translucent);--bs-dropdown-link-hover-bg:hsla(0,0%,100%,.15);--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#0d6efd;--bs-dropdown-link-disabled-color:#adb5bd;--bs-dropdown-header-color:#adb5bd}.btn-group,.btn-group-vertical{display:inline-flex;position:relative;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{flex:1 1 auto;position:relative}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group{border-radius:.375rem;border-radius:var(--bs-border-radius)}.btn-group>.btn-group:not(:first-child),.btn-group>:not(.btn-check:first-child)+.btn{margin-left:-1px;margin-left:calc(var(--bs-border-width)*-1)}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn.dropdown-toggle-split:first-child,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-bottom-left-radius:0;border-top-left-radius:0}.dropdown-toggle-split{padding-left:.5625rem;padding-right:.5625rem}.dropdown-toggle-split:after,.dropend .dropdown-toggle-split:after,.dropup .dropdown-toggle-split:after{margin-left:0}.dropstart .dropdown-toggle-split:before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-left:.375rem;padding-right:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-left:.75rem;padding-right:.75rem}.btn-group-vertical{align-items:flex-start;flex-direction:column;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px;margin-top:calc(var(--bs-border-width)*-1)}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-left-radius:0;border-bottom-right-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{--bs-nav-link-padding-x:1rem;--bs-nav-link-padding-y:0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-link-color);--bs-nav-link-hover-color:var(--bs-link-hover-color);--bs-nav-link-disabled-color:var(--bs-secondary-color);display:flex;flex-wrap:wrap;list-style:none;margin-bottom:0;padding-left:0}.nav-link{background:0 0;border:0;color:var(--bs-nav-link-color);display:block;font-size:var(--bs-nav-link-font-size);font-weight:var(--bs-nav-link-font-weight);padding:var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:var(--bs-nav-link-hover-color)}.nav-link:focus-visible{box-shadow:0 0 0 .25rem rgba(13,110,253,.25);outline:0}.nav-link.disabled{color:var(--bs-nav-link-disabled-color);cursor:default;pointer-events:none}.nav-tabs{--bs-nav-tabs-border-width:var(--bs-border-width);--bs-nav-tabs-border-color:var(--bs-border-color);--bs-nav-tabs-border-radius:var(--bs-border-radius);--bs-nav-tabs-link-hover-border-color:var(--bs-secondary-bg) var(--bs-secondary-bg) var(--bs-border-color);--bs-nav-tabs-link-active-color:var(--bs-emphasis-color);--bs-nav-tabs-link-active-bg:var(--bs-body-bg);--bs-nav-tabs-link-active-border-color:var(--bs-border-color) var(--bs-border-color) var(--bs-body-bg);border-bottom:var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color)}.nav-tabs .nav-link{border:var(--bs-nav-tabs-border-width) solid transparent;border-top-left-radius:var(--bs-nav-tabs-border-radius);border-top-right-radius:var(--bs-nav-tabs-border-radius);margin-bottom:calc(var(--bs-nav-tabs-border-width)*-1)}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:var(--bs-nav-tabs-link-hover-border-color);isolation:isolate}.nav-tabs .nav-link.disabled,.nav-tabs .nav-link:disabled{background-color:initial;border-color:transparent;color:var(--bs-nav-link-disabled-color)}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{background-color:var(--bs-nav-tabs-link-active-bg);border-color:var(--bs-nav-tabs-link-active-border-color);color:var(--bs-nav-tabs-link-active-color)}.nav-tabs .dropdown-menu{border-top-left-radius:0;border-top-right-radius:0;margin-top:calc(var(--bs-nav-tabs-border-width)*-1)}.nav-pills{--bs-nav-pills-border-radius:var(--bs-border-radius);--bs-nav-pills-link-active-color:#fff;--bs-nav-pills-link-active-bg:#0d6efd}.nav-pills .nav-link{border-radius:var(--bs-nav-pills-border-radius)}.nav-pills .nav-link:disabled{background-color:initial;border-color:transparent;color:var(--bs-nav-link-disabled-color)}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{background-color:var(--bs-nav-pills-link-active-bg);color:var(--bs-nav-pills-link-active-color)}.nav-underline{--bs-nav-underline-gap:1rem;--bs-nav-underline-border-width:0.125rem;--bs-nav-underline-link-active-color:var(--bs-emphasis-color);gap:var(--bs-nav-underline-gap)}.nav-underline .nav-link{border-bottom:var(--bs-nav-underline-border-width) solid transparent;padding-left:0;padding-right:0}.nav-underline .nav-link:focus,.nav-underline .nav-link:hover{border-bottom-color:initial}.nav-underline .nav-link.active,.nav-underline .show>.nav-link{border-bottom-color:initial;color:var(--bs-nav-underline-link-active-color);font-weight:700}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{--bs-navbar-padding-x:0;--bs-navbar-padding-y:0.5rem;--bs-navbar-color:rgba(var(--bs-emphasis-color-rgb),0.65);--bs-navbar-hover-color:rgba(var(--bs-emphasis-color-rgb),0.8);--bs-navbar-disabled-color:rgba(var(--bs-emphasis-color-rgb),0.3);--bs-navbar-active-color:rgba(var(--bs-emphasis-color-rgb),1);--bs-navbar-brand-padding-y:0.3125rem;--bs-navbar-brand-margin-end:1rem;--bs-navbar-brand-font-size:1.25rem;--bs-navbar-brand-color:rgba(var(--bs-emphasis-color-rgb),1);--bs-navbar-brand-hover-color:rgba(var(--bs-emphasis-color-rgb),1);--bs-navbar-nav-link-padding-x:0.5rem;--bs-navbar-toggler-padding-y:0.25rem;--bs-navbar-toggler-padding-x:0.75rem;--bs-navbar-toggler-font-size:1.25rem;--bs-navbar-toggler-icon-bg:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3E%3Cpath stroke='rgba(33, 37, 41, 0.75)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E");--bs-navbar-toggler-border-color:rgba(var(--bs-emphasis-color-rgb),0.15);--bs-navbar-toggler-border-radius:var(--bs-border-radius);--bs-navbar-toggler-focus-width:0.25rem;--bs-navbar-toggler-transition:box-shadow 0.15s ease-in-out;align-items:center;display:flex;flex-wrap:wrap;justify-content:space-between;padding:var(--bs-navbar-padding-y) var(--bs-navbar-padding-x);position:relative}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{align-items:center;display:flex;flex-wrap:inherit;justify-content:space-between}.navbar-brand{color:var(--bs-navbar-brand-color);font-size:var(--bs-navbar-brand-font-size);margin-right:var(--bs-navbar-brand-margin-end);padding-bottom:var(--bs-navbar-brand-padding-y);padding-top:var(--bs-navbar-brand-padding-y);text-decoration:none;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{color:var(--bs-navbar-brand-hover-color)}.navbar-nav{--bs-nav-link-padding-x:0;--bs-nav-link-padding-y:0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-navbar-color);--bs-nav-link-hover-color:var(--bs-navbar-hover-color);--bs-nav-link-disabled-color:var(--bs-navbar-disabled-color);display:flex;flex-direction:column;list-style:none;margin-bottom:0;padding-left:0}.navbar-nav .nav-link.active,.navbar-nav .nav-link.show{color:var(--bs-navbar-active-color)}.navbar-nav .dropdown-menu{position:static}.navbar-text{color:var(--bs-navbar-color);padding-bottom:.5rem;padding-top:.5rem}.navbar-text a,.navbar-text a:focus,.navbar-text a:hover{color:var(--bs-navbar-active-color)}.navbar-collapse{align-items:center;flex-basis:100%;flex-grow:1}.navbar-toggler{background-color:initial;border:var(--bs-border-width) solid var(--bs-navbar-toggler-border-color);border-radius:var(--bs-navbar-toggler-border-radius);color:var(--bs-navbar-color);font-size:var(--bs-navbar-toggler-font-size);line-height:1;padding:var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x);transition:var(--bs-navbar-toggler-transition)}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{box-shadow:0 0 0 var(--bs-navbar-toggler-focus-width);outline:0;text-decoration:none}.navbar-toggler-icon{background-image:var(--bs-navbar-toggler-icon-bg);background-position:50%;background-repeat:no-repeat;background-size:100%;display:inline-block;height:1.5em;vertical-align:middle;width:1.5em}.navbar-nav-scroll{max-height:75vh;max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-left:var(--bs-navbar-nav-link-padding-x);padding-right:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas{background-color:initial!important;border:0!important;flex-grow:1;height:auto!important;position:static;-webkit-transform:none!important;transform:none!important;transition:none;visibility:visible!important;width:auto!important;z-index:auto}.navbar-expand-sm .offcanvas .offcanvas-header{display:none}.navbar-expand-sm .offcanvas .offcanvas-body{display:flex;flex-grow:0;overflow-y:visible;padding:0}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-left:var(--bs-navbar-nav-link-padding-x);padding-right:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas{background-color:initial!important;border:0!important;flex-grow:1;height:auto!important;position:static;-webkit-transform:none!important;transform:none!important;transition:none;visibility:visible!important;width:auto!important;z-index:auto}.navbar-expand-md .offcanvas .offcanvas-header{display:none}.navbar-expand-md .offcanvas .offcanvas-body{display:flex;flex-grow:0;overflow-y:visible;padding:0}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-left:var(--bs-navbar-nav-link-padding-x);padding-right:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas{background-color:initial!important;border:0!important;flex-grow:1;height:auto!important;position:static;-webkit-transform:none!important;transform:none!important;transition:none;visibility:visible!important;width:auto!important;z-index:auto}.navbar-expand-lg .offcanvas .offcanvas-header{display:none}.navbar-expand-lg .offcanvas .offcanvas-body{display:flex;flex-grow:0;overflow-y:visible;padding:0}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-left:var(--bs-navbar-nav-link-padding-x);padding-right:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas{background-color:initial!important;border:0!important;flex-grow:1;height:auto!important;position:static;-webkit-transform:none!important;transform:none!important;transition:none;visibility:visible!important;width:auto!important;z-index:auto}.navbar-expand-xl .offcanvas .offcanvas-header{display:none}.navbar-expand-xl .offcanvas .offcanvas-body{display:flex;flex-grow:0;overflow-y:visible;padding:0}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-left:var(--bs-navbar-nav-link-padding-x);padding-right:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas{background-color:initial!important;border:0!important;flex-grow:1;height:auto!important;position:static;-webkit-transform:none!important;transform:none!important;transition:none;visibility:visible!important;width:auto!important;z-index:auto}.navbar-expand-xxl .offcanvas .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas .offcanvas-body{display:flex;flex-grow:0;overflow-y:visible;padding:0}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-left:var(--bs-navbar-nav-link-padding-x);padding-right:var(--bs-navbar-nav-link-padding-x)}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas{background-color:initial!important;border:0!important;flex-grow:1;height:auto!important;position:static;-webkit-transform:none!important;transform:none!important;transition:none;visibility:visible!important;width:auto!important;z-index:auto}.navbar-expand .offcanvas .offcanvas-header{display:none}.navbar-expand .offcanvas .offcanvas-body{display:flex;flex-grow:0;overflow-y:visible;padding:0}.navbar-dark{--bs-navbar-color:hsla(0,0%,100%,.55);--bs-navbar-hover-color:hsla(0,0%,100%,.75);--bs-navbar-disabled-color:hsla(0,0%,100%,.25);--bs-navbar-active-color:#fff;--bs-navbar-brand-color:#fff;--bs-navbar-brand-hover-color:#fff;--bs-navbar-toggler-border-color:hsla(0,0%,100%,.1)}.navbar-dark,[data-bs-theme=dark] .navbar-toggler-icon{--bs-navbar-toggler-icon-bg:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3E%3Cpath stroke='rgba(255, 255, 255, 0.55)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.card{--bs-card-spacer-y:1rem;--bs-card-spacer-x:1rem;--bs-card-title-spacer-y:0.5rem;--bs-card-title-color: ;--bs-card-subtitle-color: ;--bs-card-border-width:var(--bs-border-width);--bs-card-border-color:var(--bs-border-color-translucent);--bs-card-border-radius:var(--bs-border-radius);--bs-card-box-shadow: ;--bs-card-inner-border-radius:calc(var(--bs-border-radius) - var(--bs-border-width));--bs-card-cap-padding-y:0.5rem;--bs-card-cap-padding-x:1rem;--bs-card-cap-bg:rgba(var(--bs-body-color-rgb),0.03);--bs-card-cap-color: ;--bs-card-height: ;--bs-card-color: ;--bs-card-bg:var(--bs-body-bg);--bs-card-img-overlay-padding:1rem;--bs-card-group-margin:0.75rem;word-wrap:break-word;background-clip:initial;background-color:var(--bs-card-bg);border:var(--bs-card-border-width) solid var(--bs-card-border-color);border-radius:var(--bs-card-border-radius);color:#212529;color:var(--bs-body-color);display:flex;flex-direction:column;height:var(--bs-card-height);min-width:0;position:relative}.card>hr{margin-left:0;margin-right:0}.card>.list-group{border-bottom:inherit;border-top:inherit}.card>.list-group:first-child{border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius);border-top-width:0}.card>.list-group:last-child{border-bottom-left-radius:var(--bs-card-inner-border-radius);border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-width:0}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{color:var(--bs-card-color);flex:1 1 auto;padding:var(--bs-card-spacer-y) var(--bs-card-spacer-x)}.card-title{color:var(--bs-card-title-color);margin-bottom:var(--bs-card-title-spacer-y)}.card-subtitle{color:var(--bs-card-subtitle-color);margin-top:calc(var(--bs-card-title-spacer-y)*-.5)}.card-subtitle,.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:var(--bs-card-spacer-x)}.card-header{background-color:var(--bs-card-cap-bg);border-bottom:var(--bs-card-border-width) solid var(--bs-card-border-color);color:var(--bs-card-cap-color);margin-bottom:0;padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x)}.card-header:first-child{border-radius:var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius) 0 0}.card-footer{background-color:var(--bs-card-cap-bg);border-top:var(--bs-card-border-width) solid var(--bs-card-border-color);color:var(--bs-card-cap-color);padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x)}.card-footer:last-child{border-radius:0 0 var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius)}.card-header-tabs{border-bottom:0;margin-bottom:calc(var(--bs-card-cap-padding-y)*-1);margin-left:calc(var(--bs-card-cap-padding-x)*-.5);margin-right:calc(var(--bs-card-cap-padding-x)*-.5)}.card-header-tabs .nav-link.active{background-color:var(--bs-card-bg);border-bottom-color:var(--bs-card-bg)}.card-header-pills{margin-left:calc(var(--bs-card-cap-padding-x)*-.5);margin-right:calc(var(--bs-card-cap-padding-x)*-.5)}.card-img-overlay{border-radius:var(--bs-card-inner-border-radius);bottom:0;left:0;padding:var(--bs-card-img-overlay-padding);position:absolute;right:0;top:0}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-bottom{border-bottom-left-radius:var(--bs-card-inner-border-radius);border-bottom-right-radius:var(--bs-card-inner-border-radius)}.card-group>.card{margin-bottom:var(--bs-card-group-margin)}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0;margin-bottom:0}.card-group>.card+.card{border-left:0;margin-left:0}.card-group>.card:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion{--bs-accordion-color:var(--bs-body-color);--bs-accordion-bg:var(--bs-body-bg);--bs-accordion-transition:color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out,border-radius 0.15s ease;--bs-accordion-border-color:var(--bs-border-color);--bs-accordion-border-width:var(--bs-border-width);--bs-accordion-border-radius:var(--bs-border-radius);--bs-accordion-inner-border-radius:calc(var(--bs-border-radius) - var(--bs-border-width));--bs-accordion-btn-padding-x:1.25rem;--bs-accordion-btn-padding-y:1rem;--bs-accordion-btn-color:var(--bs-body-color);--bs-accordion-btn-bg:var(--bs-accordion-bg);--bs-accordion-btn-icon:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3E%3Cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E");--bs-accordion-btn-icon-width:1.25rem;--bs-accordion-btn-icon-transform:rotate(-180deg);--bs-accordion-btn-icon-transition:transform 0.2s ease-in-out;--bs-accordion-btn-active-icon:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23052c65'%3E%3Cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E");--bs-accordion-btn-focus-border-color:#86b7fe;--bs-accordion-btn-focus-box-shadow:0 0 0 0.25rem rgba(13,110,253,.25);--bs-accordion-body-padding-x:1.25rem;--bs-accordion-body-padding-y:1rem;--bs-accordion-active-color:var(--bs-primary-text-emphasis);--bs-accordion-active-bg:var(--bs-primary-bg-subtle)}.accordion-button{align-items:center;background-color:var(--bs-accordion-btn-bg);border:0;border-radius:0;color:var(--bs-accordion-btn-color);display:flex;font-size:1rem;overflow-anchor:none;padding:var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x);position:relative;text-align:left;transition:var(--bs-accordion-transition);width:100%}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){background-color:var(--bs-accordion-active-bg);box-shadow:inset 0 calc(var(--bs-accordion-border-width)*-1) 0 var(--bs-accordion-border-color);color:var(--bs-accordion-active-color)}.accordion-button:not(.collapsed):after{background-image:var(--bs-accordion-btn-active-icon);-webkit-transform:var(--bs-accordion-btn-icon-transform);transform:var(--bs-accordion-btn-icon-transform)}.accordion-button:after{background-image:var(--bs-accordion-btn-icon);background-repeat:no-repeat;background-size:var(--bs-accordion-btn-icon-width);content:"";flex-shrink:0;height:var(--bs-accordion-btn-icon-width);margin-left:auto;transition:var(--bs-accordion-btn-icon-transition);width:var(--bs-accordion-btn-icon-width)}@media (prefers-reduced-motion:reduce){.accordion-button:after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{border-color:var(--bs-accordion-btn-focus-border-color);box-shadow:var(--bs-accordion-btn-focus-box-shadow);outline:0;z-index:3}.accordion-header{margin-bottom:0}.accordion-item{background-color:var(--bs-accordion-bg);border:var(--bs-accordion-border-width) solid var(--bs-accordion-border-color);color:var(--bs-accordion-color)}.accordion-item:first-of-type{border-top-left-radius:var(--bs-accordion-border-radius);border-top-right-radius:var(--bs-accordion-border-radius)}.accordion-item:first-of-type .accordion-button{border-top-left-radius:var(--bs-accordion-inner-border-radius);border-top-right-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-left-radius:var(--bs-accordion-border-radius);border-bottom-right-radius:var(--bs-accordion-border-radius)}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-left-radius:var(--bs-accordion-inner-border-radius);border-bottom-right-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:last-of-type .accordion-collapse{border-bottom-left-radius:var(--bs-accordion-border-radius);border-bottom-right-radius:var(--bs-accordion-border-radius)}.accordion-body{padding:var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x)}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-left:0;border-radius:0;border-right:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button,.accordion-flush .accordion-item .accordion-button.collapsed{border-radius:0}[data-bs-theme=dark] .accordion-button:after{--bs-accordion-btn-icon:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236ea8fe'%3E%3Cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E");--bs-accordion-btn-active-icon:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236ea8fe'%3E%3Cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E")}.breadcrumb{--bs-breadcrumb-padding-x:0;--bs-breadcrumb-padding-y:0;--bs-breadcrumb-margin-bottom:1rem;--bs-breadcrumb-bg: ;--bs-breadcrumb-border-radius: ;--bs-breadcrumb-divider-color:var(--bs-secondary-color);--bs-breadcrumb-item-padding-x:0.5rem;--bs-breadcrumb-item-active-color:var(--bs-secondary-color);background-color:var(--bs-breadcrumb-bg);border-radius:var(--bs-breadcrumb-border-radius);display:flex;flex-wrap:wrap;font-size:var(--bs-breadcrumb-font-size);list-style:none;margin-bottom:var(--bs-breadcrumb-margin-bottom);padding:var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x)}.breadcrumb-item+.breadcrumb-item{padding-left:var(--bs-breadcrumb-item-padding-x)}.breadcrumb-item+.breadcrumb-item:before{color:var(--bs-breadcrumb-divider-color);content:"/";content:var(--bs-breadcrumb-divider,"/");float:left;padding-right:var(--bs-breadcrumb-item-padding-x)}.breadcrumb-item.active{color:var(--bs-breadcrumb-item-active-color)}.pagination{--bs-pagination-padding-x:0.75rem;--bs-pagination-padding-y:0.375rem;--bs-pagination-font-size:1rem;--bs-pagination-color:var(--bs-link-color);--bs-pagination-bg:var(--bs-body-bg);--bs-pagination-border-width:var(--bs-border-width);--bs-pagination-border-color:var(--bs-border-color);--bs-pagination-border-radius:var(--bs-border-radius);--bs-pagination-hover-color:var(--bs-link-hover-color);--bs-pagination-hover-bg:var(--bs-tertiary-bg);--bs-pagination-hover-border-color:var(--bs-border-color);--bs-pagination-focus-color:var(--bs-link-hover-color);--bs-pagination-focus-bg:var(--bs-secondary-bg);--bs-pagination-focus-box-shadow:0 0 0 0.25rem rgba(13,110,253,.25);--bs-pagination-active-color:#fff;--bs-pagination-active-bg:#0d6efd;--bs-pagination-active-border-color:#0d6efd;--bs-pagination-disabled-color:var(--bs-secondary-color);--bs-pagination-disabled-bg:var(--bs-secondary-bg);--bs-pagination-disabled-border-color:var(--bs-border-color);display:flex;list-style:none;padding-left:0}.page-link{background-color:var(--bs-pagination-bg);border:var(--bs-pagination-border-width) solid var(--bs-pagination-border-color);color:var(--bs-pagination-color);display:block;font-size:var(--bs-pagination-font-size);padding:var(--bs-pagination-padding-y) var(--bs-pagination-padding-x);position:relative;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{background-color:var(--bs-pagination-hover-bg);border-color:var(--bs-pagination-hover-border-color);color:var(--bs-pagination-hover-color);z-index:2}.page-link:focus{background-color:var(--bs-pagination-focus-bg);box-shadow:var(--bs-pagination-focus-box-shadow);color:var(--bs-pagination-focus-color);outline:0;z-index:3}.active>.page-link,.page-link.active{background-color:var(--bs-pagination-active-bg);border-color:var(--bs-pagination-active-border-color);color:var(--bs-pagination-active-color);z-index:3}.disabled>.page-link,.page-link.disabled{background-color:var(--bs-pagination-disabled-bg);border-color:var(--bs-pagination-disabled-border-color);color:var(--bs-pagination-disabled-color);pointer-events:none}.page-item:not(:first-child) .page-link{margin-left:-1px;margin-left:calc(var(--bs-border-width)*-1)}.page-item:first-child .page-link{border-bottom-left-radius:var(--bs-pagination-border-radius);border-top-left-radius:var(--bs-pagination-border-radius)}.page-item:last-child .page-link{border-bottom-right-radius:var(--bs-pagination-border-radius);border-top-right-radius:var(--bs-pagination-border-radius)}.pagination-lg{--bs-pagination-padding-x:1.5rem;--bs-pagination-padding-y:0.75rem;--bs-pagination-font-size:1.25rem;--bs-pagination-border-radius:var(--bs-border-radius-lg)}.pagination-sm{--bs-pagination-padding-x:0.5rem;--bs-pagination-padding-y:0.25rem;--bs-pagination-font-size:0.875rem;--bs-pagination-border-radius:var(--bs-border-radius-sm)}.badge{--bs-badge-padding-x:0.65em;--bs-badge-padding-y:0.35em;--bs-badge-font-size:0.75em;--bs-badge-font-weight:700;--bs-badge-color:#fff;--bs-badge-border-radius:var(--bs-border-radius);border-radius:var(--bs-badge-border-radius);color:var(--bs-badge-color);display:inline-block;font-size:var(--bs-badge-font-size);font-weight:var(--bs-badge-font-weight);line-height:1;padding:var(--bs-badge-padding-y) var(--bs-badge-padding-x);text-align:center;vertical-align:initial;white-space:nowrap}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{--bs-alert-bg:transparent;--bs-alert-padding-x:1rem;--bs-alert-padding-y:1rem;--bs-alert-margin-bottom:1rem;--bs-alert-color:inherit;--bs-alert-border-color:transparent;--bs-alert-border:var(--bs-border-width) solid var(--bs-alert-border-color);--bs-alert-border-radius:var(--bs-border-radius);--bs-alert-link-color:inherit;background-color:var(--bs-alert-bg);border:var(--bs-alert-border);border-radius:var(--bs-alert-border-radius);color:var(--bs-alert-color);margin-bottom:var(--bs-alert-margin-bottom);padding:var(--bs-alert-padding-y) var(--bs-alert-padding-x);position:relative}.alert-heading{color:inherit}.alert-link{color:var(--bs-alert-link-color);font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{padding:1.25rem 1rem;position:absolute;right:0;top:0;z-index:2}.alert-primary{--bs-alert-color:var(--bs-primary-text-emphasis);--bs-alert-bg:var(--bs-primary-bg-subtle);--bs-alert-border-color:var(--bs-primary-border-subtle);--bs-alert-link-color:var(--bs-primary-text-emphasis)}.alert-secondary{--bs-alert-color:var(--bs-secondary-text-emphasis);--bs-alert-bg:var(--bs-secondary-bg-subtle);--bs-alert-border-color:var(--bs-secondary-border-subtle);--bs-alert-link-color:var(--bs-secondary-text-emphasis)}.alert-success{--bs-alert-color:var(--bs-success-text-emphasis);--bs-alert-bg:var(--bs-success-bg-subtle);--bs-alert-border-color:var(--bs-success-border-subtle);--bs-alert-link-color:var(--bs-success-text-emphasis)}.alert-info{--bs-alert-color:var(--bs-info-text-emphasis);--bs-alert-bg:var(--bs-info-bg-subtle);--bs-alert-border-color:var(--bs-info-border-subtle);--bs-alert-link-color:var(--bs-info-text-emphasis)}.alert-warning{--bs-alert-color:var(--bs-warning-text-emphasis);--bs-alert-bg:var(--bs-warning-bg-subtle);--bs-alert-border-color:var(--bs-warning-border-subtle);--bs-alert-link-color:var(--bs-warning-text-emphasis)}.alert-danger{--bs-alert-color:var(--bs-danger-text-emphasis);--bs-alert-bg:var(--bs-danger-bg-subtle);--bs-alert-border-color:var(--bs-danger-border-subtle);--bs-alert-link-color:var(--bs-danger-text-emphasis)}.alert-light{--bs-alert-color:var(--bs-light-text-emphasis);--bs-alert-bg:var(--bs-light-bg-subtle);--bs-alert-border-color:var(--bs-light-border-subtle);--bs-alert-link-color:var(--bs-light-text-emphasis)}.alert-dark{--bs-alert-color:var(--bs-dark-text-emphasis);--bs-alert-bg:var(--bs-dark-bg-subtle);--bs-alert-border-color:var(--bs-dark-border-subtle);--bs-alert-link-color:var(--bs-dark-text-emphasis)}@-webkit-keyframes progress-bar-stripes{0%{background-position-x:1rem}}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress,.progress-stacked{--bs-progress-height:1rem;--bs-progress-font-size:0.75rem;--bs-progress-bg:var(--bs-secondary-bg);--bs-progress-border-radius:var(--bs-border-radius);--bs-progress-box-shadow:var(--bs-box-shadow-inset);--bs-progress-bar-color:#fff;--bs-progress-bar-bg:#0d6efd;--bs-progress-bar-transition:width 0.6s ease;background-color:var(--bs-progress-bg);border-radius:var(--bs-progress-border-radius);display:flex;font-size:var(--bs-progress-font-size);height:var(--bs-progress-height);overflow:hidden}.progress-bar{background-color:var(--bs-progress-bar-bg);color:var(--bs-progress-bar-color);display:flex;flex-direction:column;justify-content:center;overflow:hidden;text-align:center;transition:var(--bs-progress-bar-transition);white-space:nowrap}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,hsla(0,0%,100%,.15) 25%,transparent 0,transparent 50%,hsla(0,0%,100%,.15) 0,hsla(0,0%,100%,.15) 75%,transparent 0,transparent);background-size:var(--bs-progress-height) var(--bs-progress-height)}.progress-stacked>.progress{overflow:visible}.progress-stacked>.progress>.progress-bar{width:100%}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.list-group{--bs-list-group-color:var(--bs-body-color);--bs-list-group-bg:var(--bs-body-bg);--bs-list-group-border-color:var(--bs-border-color);--bs-list-group-border-width:var(--bs-border-width);--bs-list-group-border-radius:var(--bs-border-radius);--bs-list-group-item-padding-x:1rem;--bs-list-group-item-padding-y:0.5rem;--bs-list-group-action-color:var(--bs-secondary-color);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-tertiary-bg);--bs-list-group-action-active-color:var(--bs-body-color);--bs-list-group-action-active-bg:var(--bs-secondary-bg);--bs-list-group-disabled-color:var(--bs-secondary-color);--bs-list-group-disabled-bg:var(--bs-body-bg);--bs-list-group-active-color:#fff;--bs-list-group-active-bg:#0d6efd;--bs-list-group-active-border-color:#0d6efd;border-radius:var(--bs-list-group-border-radius);display:flex;flex-direction:column;margin-bottom:0;padding-left:0}.list-group-numbered{counter-reset:section;list-style-type:none}.list-group-numbered>.list-group-item:before{content:counters(section,".") ". ";counter-increment:section}.list-group-item-action{color:var(--bs-list-group-action-color);text-align:inherit;width:100%}.list-group-item-action:focus,.list-group-item-action:hover{background-color:var(--bs-list-group-action-hover-bg);color:var(--bs-list-group-action-hover-color);text-decoration:none;z-index:1}.list-group-item-action:active{background-color:var(--bs-list-group-action-active-bg);color:var(--bs-list-group-action-active-color)}.list-group-item{background-color:var(--bs-list-group-bg);border:var(--bs-list-group-border-width) solid var(--bs-list-group-border-color);color:var(--bs-list-group-color);display:block;padding:var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x);position:relative;text-decoration:none}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-left-radius:inherit;border-bottom-right-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{background-color:var(--bs-list-group-disabled-bg);color:var(--bs-list-group-disabled-color);pointer-events:none}.list-group-item.active{background-color:var(--bs-list-group-active-bg);border-color:var(--bs-list-group-active-border-color);color:var(--bs-list-group-active-color);z-index:2}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{border-top-width:var(--bs-list-group-border-width);margin-top:calc(var(--bs-list-group-border-width)*-1)}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child:not(:first-child){border-bottom-left-radius:0;border-top-right-radius:var(--bs-list-group-border-radius)}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-left-width:0;border-top-width:var(--bs-list-group-border-width)}.list-group-horizontal>.list-group-item+.list-group-item.active{border-left-width:var(--bs-list-group-border-width);margin-left:calc(var(--bs-list-group-border-width)*-1)}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child:not(:first-child){border-bottom-left-radius:0;border-top-right-radius:var(--bs-list-group-border-radius)}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-left-width:0;border-top-width:var(--bs-list-group-border-width)}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{border-left-width:var(--bs-list-group-border-width);margin-left:calc(var(--bs-list-group-border-width)*-1)}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child:not(:first-child){border-bottom-left-radius:0;border-top-right-radius:var(--bs-list-group-border-radius)}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-left-width:0;border-top-width:var(--bs-list-group-border-width)}.list-group-horizontal-md>.list-group-item+.list-group-item.active{border-left-width:var(--bs-list-group-border-width);margin-left:calc(var(--bs-list-group-border-width)*-1)}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child:not(:first-child){border-bottom-left-radius:0;border-top-right-radius:var(--bs-list-group-border-radius)}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-left-width:0;border-top-width:var(--bs-list-group-border-width)}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{border-left-width:var(--bs-list-group-border-width);margin-left:calc(var(--bs-list-group-border-width)*-1)}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child:not(:first-child){border-bottom-left-radius:0;border-top-right-radius:var(--bs-list-group-border-radius)}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-left-width:0;border-top-width:var(--bs-list-group-border-width)}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{border-left-width:var(--bs-list-group-border-width);margin-left:calc(var(--bs-list-group-border-width)*-1)}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child:not(:first-child){border-bottom-left-radius:0;border-top-right-radius:var(--bs-list-group-border-radius)}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-left-width:0;border-top-width:var(--bs-list-group-border-width)}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{border-left-width:var(--bs-list-group-border-width);margin-left:calc(var(--bs-list-group-border-width)*-1)}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-bottom-width:var(--bs-list-group-border-width);border-left-width:0;border-right-width:0;border-top-width:0}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{--bs-list-group-color:var(--bs-primary-text-emphasis);--bs-list-group-bg:var(--bs-primary-bg-subtle);--bs-list-group-border-color:var(--bs-primary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-primary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-primary-border-subtle);--bs-list-group-active-color:var(--bs-primary-bg-subtle);--bs-list-group-active-bg:var(--bs-primary-text-emphasis);--bs-list-group-active-border-color:var(--bs-primary-text-emphasis)}.list-group-item-secondary{--bs-list-group-color:var(--bs-secondary-text-emphasis);--bs-list-group-bg:var(--bs-secondary-bg-subtle);--bs-list-group-border-color:var(--bs-secondary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-secondary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-secondary-border-subtle);--bs-list-group-active-color:var(--bs-secondary-bg-subtle);--bs-list-group-active-bg:var(--bs-secondary-text-emphasis);--bs-list-group-active-border-color:var(--bs-secondary-text-emphasis)}.list-group-item-success{--bs-list-group-color:var(--bs-success-text-emphasis);--bs-list-group-bg:var(--bs-success-bg-subtle);--bs-list-group-border-color:var(--bs-success-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-success-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-success-border-subtle);--bs-list-group-active-color:var(--bs-success-bg-subtle);--bs-list-group-active-bg:var(--bs-success-text-emphasis);--bs-list-group-active-border-color:var(--bs-success-text-emphasis)}.list-group-item-info{--bs-list-group-color:var(--bs-info-text-emphasis);--bs-list-group-bg:var(--bs-info-bg-subtle);--bs-list-group-border-color:var(--bs-info-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-info-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-info-border-subtle);--bs-list-group-active-color:var(--bs-info-bg-subtle);--bs-list-group-active-bg:var(--bs-info-text-emphasis);--bs-list-group-active-border-color:var(--bs-info-text-emphasis)}.list-group-item-warning{--bs-list-group-color:var(--bs-warning-text-emphasis);--bs-list-group-bg:var(--bs-warning-bg-subtle);--bs-list-group-border-color:var(--bs-warning-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-warning-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-warning-border-subtle);--bs-list-group-active-color:var(--bs-warning-bg-subtle);--bs-list-group-active-bg:var(--bs-warning-text-emphasis);--bs-list-group-active-border-color:var(--bs-warning-text-emphasis)}.list-group-item-danger{--bs-list-group-color:var(--bs-danger-text-emphasis);--bs-list-group-bg:var(--bs-danger-bg-subtle);--bs-list-group-border-color:var(--bs-danger-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-danger-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-danger-border-subtle);--bs-list-group-active-color:var(--bs-danger-bg-subtle);--bs-list-group-active-bg:var(--bs-danger-text-emphasis);--bs-list-group-active-border-color:var(--bs-danger-text-emphasis)}.list-group-item-light{--bs-list-group-color:var(--bs-light-text-emphasis);--bs-list-group-bg:var(--bs-light-bg-subtle);--bs-list-group-border-color:var(--bs-light-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-light-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-light-border-subtle);--bs-list-group-active-color:var(--bs-light-bg-subtle);--bs-list-group-active-bg:var(--bs-light-text-emphasis);--bs-list-group-active-border-color:var(--bs-light-text-emphasis)}.list-group-item-dark{--bs-list-group-color:var(--bs-dark-text-emphasis);--bs-list-group-bg:var(--bs-dark-bg-subtle);--bs-list-group-border-color:var(--bs-dark-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-dark-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-dark-border-subtle);--bs-list-group-active-color:var(--bs-dark-bg-subtle);--bs-list-group-active-bg:var(--bs-dark-text-emphasis);--bs-list-group-active-border-color:var(--bs-dark-text-emphasis)}.btn-close{--bs-btn-close-color:#000;--bs-btn-close-bg:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3E%3C/svg%3E");--bs-btn-close-opacity:0.5;--bs-btn-close-hover-opacity:0.75;--bs-btn-close-focus-shadow:0 0 0 0.25rem rgba(13,110,253,.25);--bs-btn-close-focus-opacity:1;--bs-btn-close-disabled-opacity:0.25;--bs-btn-close-white-filter:invert(1) grayscale(100%) brightness(200%);background:transparent var(--bs-btn-close-bg) center/1em auto no-repeat;border:0;border-radius:.375rem;box-sizing:initial;height:1em;opacity:var(--bs-btn-close-opacity);padding:.25em;width:1em}.btn-close,.btn-close:hover{color:var(--bs-btn-close-color)}.btn-close:hover{opacity:var(--bs-btn-close-hover-opacity);text-decoration:none}.btn-close:focus{box-shadow:var(--bs-btn-close-focus-shadow);opacity:var(--bs-btn-close-focus-opacity);outline:0}.btn-close.disabled,.btn-close:disabled{opacity:var(--bs-btn-close-disabled-opacity);pointer-events:none;-webkit-user-select:none;user-select:none}.btn-close-white,[data-bs-theme=dark] .btn-close{-webkit-filter:var(--bs-btn-close-white-filter);filter:var(--bs-btn-close-white-filter)}.toast{--bs-toast-zindex:1090;--bs-toast-padding-x:0.75rem;--bs-toast-padding-y:0.5rem;--bs-toast-spacing:1.5rem;--bs-toast-max-width:350px;--bs-toast-font-size:0.875rem;--bs-toast-color: ;--bs-toast-bg:rgba(var(--bs-body-bg-rgb),0.85);--bs-toast-border-width:var(--bs-border-width);--bs-toast-border-color:var(--bs-border-color-translucent);--bs-toast-border-radius:var(--bs-border-radius);--bs-toast-box-shadow:var(--bs-box-shadow);--bs-toast-header-color:var(--bs-secondary-color);--bs-toast-header-bg:rgba(var(--bs-body-bg-rgb),0.85);--bs-toast-header-border-color:var(--bs-border-color-translucent);background-clip:padding-box;background-color:var(--bs-toast-bg);border:var(--bs-toast-border-width) solid var(--bs-toast-border-color);border-radius:var(--bs-toast-border-radius);box-shadow:var(--bs-toast-box-shadow);color:var(--bs-toast-color);font-size:var(--bs-toast-font-size);max-width:100%;pointer-events:auto;width:var(--bs-toast-max-width)}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{--bs-toast-zindex:1090;max-width:100%;pointer-events:none;position:absolute;width:-webkit-max-content;width:max-content;z-index:var(--bs-toast-zindex)}.toast-container>:not(:last-child){margin-bottom:var(--bs-toast-spacing)}.toast-header{align-items:center;background-clip:padding-box;background-color:var(--bs-toast-header-bg);border-bottom:var(--bs-toast-border-width) solid var(--bs-toast-header-border-color);border-top-left-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));border-top-right-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));color:var(--bs-toast-header-color);display:flex;padding:var(--bs-toast-padding-y) var(--bs-toast-padding-x)}.toast-header .btn-close{margin-left:var(--bs-toast-padding-x);margin-right:calc(var(--bs-toast-padding-x)*-.5)}.toast-body{word-wrap:break-word;padding:var(--bs-toast-padding-x)}.modal{--bs-modal-zindex:1055;--bs-modal-width:500px;--bs-modal-padding:1rem;--bs-modal-margin:0.5rem;--bs-modal-color: ;--bs-modal-bg:var(--bs-body-bg);--bs-modal-border-color:var(--bs-border-color-translucent);--bs-modal-border-width:var(--bs-border-width);--bs-modal-border-radius:var(--bs-border-radius-lg);--bs-modal-box-shadow:0 0.125rem 0.25rem rgba(0,0,0,.075);--bs-modal-inner-border-radius:calc(var(--bs-border-radius-lg) - var(--bs-border-width));--bs-modal-header-padding-x:1rem;--bs-modal-header-padding-y:1rem;--bs-modal-header-padding:1rem 1rem;--bs-modal-header-border-color:var(--bs-border-color);--bs-modal-header-border-width:var(--bs-border-width);--bs-modal-title-line-height:1.5;--bs-modal-footer-gap:0.5rem;--bs-modal-footer-bg: ;--bs-modal-footer-border-color:var(--bs-border-color);--bs-modal-footer-border-width:var(--bs-border-width);display:none;height:100%;left:0;outline:0;overflow-x:hidden;overflow-y:auto;position:fixed;top:0;width:100%;z-index:var(--bs-modal-zindex)}.modal-dialog{margin:var(--bs-modal-margin);pointer-events:none;position:relative;width:auto}.modal.fade .modal-dialog{-webkit-transform:translateY(-50px);transform:translateY(-50px);transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal.modal-static .modal-dialog{-webkit-transform:scale(1.02);transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - var(--bs-modal-margin)*2)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{align-items:center;display:flex;min-height:calc(100% - var(--bs-modal-margin)*2)}.modal-content{background-clip:padding-box;background-color:var(--bs-modal-bg);border:var(--bs-modal-border-width) solid var(--bs-modal-border-color);border-radius:var(--bs-modal-border-radius);color:var(--bs-modal-color);display:flex;flex-direction:column;outline:0;pointer-events:auto;position:relative;width:100%}.modal-backdrop{--bs-backdrop-zindex:1050;--bs-backdrop-bg:#000;--bs-backdrop-opacity:0.5;background-color:var(--bs-backdrop-bg);height:100vh;left:0;position:fixed;top:0;width:100vw;z-index:var(--bs-backdrop-zindex)}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:var(--bs-backdrop-opacity)}.modal-header{align-items:center;border-bottom:var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);border-top-left-radius:var(--bs-modal-inner-border-radius);border-top-right-radius:var(--bs-modal-inner-border-radius);display:flex;flex-shrink:0;justify-content:space-between;padding:var(--bs-modal-header-padding)}.modal-header .btn-close{margin:calc(var(--bs-modal-header-padding-y)*-.5) calc(var(--bs-modal-header-padding-x)*-.5) calc(var(--bs-modal-header-padding-y)*-.5) auto;padding:calc(var(--bs-modal-header-padding-y)*.5) calc(var(--bs-modal-header-padding-x)*.5)}.modal-title{line-height:var(--bs-modal-title-line-height);margin-bottom:0}.modal-body{flex:1 1 auto;padding:var(--bs-modal-padding);position:relative}.modal-footer{align-items:center;background-color:var(--bs-modal-footer-bg);border-bottom-left-radius:var(--bs-modal-inner-border-radius);border-bottom-right-radius:var(--bs-modal-inner-border-radius);border-top:var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color);display:flex;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end;padding:calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap)*.5)}.modal-footer>*{margin:calc(var(--bs-modal-footer-gap)*.5)}@media (min-width:576px){.modal{--bs-modal-margin:1.75rem;--bs-modal-box-shadow:0 0.5rem 1rem rgba(0,0,0,.15)}.modal-dialog{margin-left:auto;margin-right:auto;max-width:var(--bs-modal-width)}.modal-sm{--bs-modal-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{--bs-modal-width:800px}}@media (min-width:1200px){.modal-xl{--bs-modal-width:1140px}}.modal-fullscreen{height:100%;margin:0;max-width:none;width:100vw}.modal-fullscreen .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen .modal-footer,.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}@media (max-width:575.98px){.modal-fullscreen-sm-down{height:100%;margin:0;max-width:none;width:100vw}.modal-fullscreen-sm-down .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen-sm-down .modal-footer,.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}}@media (max-width:767.98px){.modal-fullscreen-md-down{height:100%;margin:0;max-width:none;width:100vw}.modal-fullscreen-md-down .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen-md-down .modal-footer,.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}}@media (max-width:991.98px){.modal-fullscreen-lg-down{height:100%;margin:0;max-width:none;width:100vw}.modal-fullscreen-lg-down .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen-lg-down .modal-footer,.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{height:100%;margin:0;max-width:none;width:100vw}.modal-fullscreen-xl-down .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen-xl-down .modal-footer,.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{height:100%;margin:0;max-width:none;width:100vw}.modal-fullscreen-xxl-down .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen-xxl-down .modal-footer,.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}}.tooltip{--bs-tooltip-zindex:1080;--bs-tooltip-max-width:200px;--bs-tooltip-padding-x:0.5rem;--bs-tooltip-padding-y:0.25rem;--bs-tooltip-margin: ;--bs-tooltip-font-size:0.875rem;--bs-tooltip-color:var(--bs-body-bg);--bs-tooltip-bg:var(--bs-emphasis-color);--bs-tooltip-border-radius:var(--bs-border-radius);--bs-tooltip-opacity:0.9;--bs-tooltip-arrow-width:0.8rem;--bs-tooltip-arrow-height:0.4rem;word-wrap:break-word;display:block;font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica Neue,Noto Sans,Liberation Sans,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-family:var(--bs-font-sans-serif);font-size:var(--bs-tooltip-font-size);font-style:normal;font-weight:400;letter-spacing:normal;line-break:auto;line-height:1.5;margin:var(--bs-tooltip-margin);opacity:0;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;z-index:var(--bs-tooltip-zindex)}.tooltip.show{opacity:var(--bs-tooltip-opacity)}.tooltip .tooltip-arrow{display:block;height:var(--bs-tooltip-arrow-height);width:var(--bs-tooltip-arrow-width)}.tooltip .tooltip-arrow:before{border-color:transparent;border-style:solid;content:"";position:absolute}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:calc(var(--bs-tooltip-arrow-height)*-1)}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow:before,.bs-tooltip-top .tooltip-arrow:before{border-bottom-width:0;border-left-width:calc(var(--bs-tooltip-arrow-width)*.5);border-right-width:calc(var(--bs-tooltip-arrow-width)*.5);border-top-color:var(--bs-tooltip-bg);border-top-width:var(--bs-tooltip-arrow-height);top:-1px}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{height:var(--bs-tooltip-arrow-width);left:calc(var(--bs-tooltip-arrow-height)*-1);width:var(--bs-tooltip-arrow-height)}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow:before,.bs-tooltip-end .tooltip-arrow:before{border-bottom-width:calc(var(--bs-tooltip-arrow-width)*.5);border-left-width:0;border-right-color:var(--bs-tooltip-bg);border-right-width:var(--bs-tooltip-arrow-height);border-top-width:calc(var(--bs-tooltip-arrow-width)*.5);right:-1px}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:calc(var(--bs-tooltip-arrow-height)*-1)}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow:before,.bs-tooltip-bottom .tooltip-arrow:before{border-bottom-color:var(--bs-tooltip-bg);border-bottom-width:var(--bs-tooltip-arrow-height);border-left-width:calc(var(--bs-tooltip-arrow-width)*.5);border-right-width:calc(var(--bs-tooltip-arrow-width)*.5);border-top-width:0;bottom:-1px}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{height:var(--bs-tooltip-arrow-width);right:calc(var(--bs-tooltip-arrow-height)*-1);width:var(--bs-tooltip-arrow-height)}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow:before,.bs-tooltip-start .tooltip-arrow:before{border-bottom-width:calc(var(--bs-tooltip-arrow-width)*.5);border-left-color:var(--bs-tooltip-bg);border-left-width:var(--bs-tooltip-arrow-height);border-right-width:0;border-top-width:calc(var(--bs-tooltip-arrow-width)*.5);left:-1px}.tooltip-inner{background-color:var(--bs-tooltip-bg);border-radius:var(--bs-tooltip-border-radius);color:var(--bs-tooltip-color);max-width:var(--bs-tooltip-max-width);padding:var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x);text-align:center}.popover{--bs-popover-zindex:1070;--bs-popover-max-width:276px;--bs-popover-font-size:0.875rem;--bs-popover-bg:var(--bs-body-bg);--bs-popover-border-width:var(--bs-border-width);--bs-popover-border-color:var(--bs-border-color-translucent);--bs-popover-border-radius:var(--bs-border-radius-lg);--bs-popover-inner-border-radius:calc(var(--bs-border-radius-lg) - var(--bs-border-width));--bs-popover-box-shadow:0 0.5rem 1rem rgba(0,0,0,.15);--bs-popover-header-padding-x:1rem;--bs-popover-header-padding-y:0.5rem;--bs-popover-header-font-size:1rem;--bs-popover-header-color: ;--bs-popover-header-bg:var(--bs-secondary-bg);--bs-popover-body-padding-x:1rem;--bs-popover-body-padding-y:1rem;--bs-popover-body-color:var(--bs-body-color);--bs-popover-arrow-width:1rem;--bs-popover-arrow-height:0.5rem;--bs-popover-arrow-border:var(--bs-popover-border-color);word-wrap:break-word;background-clip:padding-box;background-color:var(--bs-popover-bg);border:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-radius:var(--bs-popover-border-radius);display:block;font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica Neue,Noto Sans,Liberation Sans,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-family:var(--bs-font-sans-serif);font-size:var(--bs-popover-font-size);font-style:normal;font-weight:400;letter-spacing:normal;line-break:auto;line-height:1.5;max-width:var(--bs-popover-max-width);text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;z-index:var(--bs-popover-zindex)}.popover .popover-arrow{display:block;height:var(--bs-popover-arrow-height);width:var(--bs-popover-arrow-width)}.popover .popover-arrow:after,.popover .popover-arrow:before{border:0 solid transparent;content:"";display:block;position:absolute}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(var(--bs-popover-arrow-height)*-1 - var(--bs-popover-border-width))}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow:before,.bs-popover-top>.popover-arrow:after,.bs-popover-top>.popover-arrow:before{border-bottom-width:0;border-left-width:calc(var(--bs-popover-arrow-width)*.5);border-right-width:calc(var(--bs-popover-arrow-width)*.5);border-top-width:var(--bs-popover-arrow-height)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow:before,.bs-popover-top>.popover-arrow:before{border-top-color:var(--bs-popover-arrow-border);bottom:0}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow:after,.bs-popover-top>.popover-arrow:after{border-top-color:var(--bs-popover-bg);bottom:var(--bs-popover-border-width)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{height:var(--bs-popover-arrow-width);left:calc(var(--bs-popover-arrow-height)*-1 - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow:before,.bs-popover-end>.popover-arrow:after,.bs-popover-end>.popover-arrow:before{border-bottom-width:calc(var(--bs-popover-arrow-width)*.5);border-left-width:0;border-right-width:var(--bs-popover-arrow-height);border-top-width:calc(var(--bs-popover-arrow-width)*.5)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow:before,.bs-popover-end>.popover-arrow:before{border-right-color:var(--bs-popover-arrow-border);left:0}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow:after,.bs-popover-end>.popover-arrow:after{border-right-color:var(--bs-popover-bg);left:var(--bs-popover-border-width)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(var(--bs-popover-arrow-height)*-1 - var(--bs-popover-border-width))}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow:before,.bs-popover-bottom>.popover-arrow:after,.bs-popover-bottom>.popover-arrow:before{border-bottom-width:var(--bs-popover-arrow-height);border-left-width:calc(var(--bs-popover-arrow-width)*.5);border-right-width:calc(var(--bs-popover-arrow-width)*.5);border-top-width:0}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow:before,.bs-popover-bottom>.popover-arrow:before{border-bottom-color:var(--bs-popover-arrow-border);top:0}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow:after,.bs-popover-bottom>.popover-arrow:after{border-bottom-color:var(--bs-popover-bg);top:var(--bs-popover-border-width)}.bs-popover-auto[data-popper-placement^=bottom] .popover-header:before,.bs-popover-bottom .popover-header:before{border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-header-bg);content:"";display:block;left:50%;margin-left:calc(var(--bs-popover-arrow-width)*-.5);position:absolute;top:0;width:var(--bs-popover-arrow-width)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{height:var(--bs-popover-arrow-width);right:calc(var(--bs-popover-arrow-height)*-1 - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow:before,.bs-popover-start>.popover-arrow:after,.bs-popover-start>.popover-arrow:before{border-bottom-width:calc(var(--bs-popover-arrow-width)*.5);border-left-width:var(--bs-popover-arrow-height);border-right-width:0;border-top-width:calc(var(--bs-popover-arrow-width)*.5)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow:before,.bs-popover-start>.popover-arrow:before{border-left-color:var(--bs-popover-arrow-border);right:0}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow:after,.bs-popover-start>.popover-arrow:after{border-left-color:var(--bs-popover-bg);right:var(--bs-popover-border-width)}.popover-header{background-color:var(--bs-popover-header-bg);border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-top-left-radius:var(--bs-popover-inner-border-radius);border-top-right-radius:var(--bs-popover-inner-border-radius);color:var(--bs-popover-header-color);font-size:var(--bs-popover-header-font-size);margin-bottom:0;padding:var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x)}.popover-header:empty{display:none}.popover-body{color:var(--bs-popover-body-color);padding:var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x)}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{overflow:hidden;position:relative;width:100%}.carousel-inner:after{clear:both;content:"";display:block}.carousel-item{-webkit-backface-visibility:hidden;backface-visibility:hidden;display:none;float:left;margin-right:-100%;position:relative;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out;width:100%}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){-webkit-transform:translateX(100%);transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;-webkit-transform:none;transform:none;transition-property:opacity}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{opacity:1;z-index:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{opacity:0;transition:opacity 0s .6s;z-index:0}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{align-items:center;background:0 0;border:0;bottom:0;color:#fff;display:flex;justify-content:center;opacity:.5;padding:0;position:absolute;text-align:center;top:0;transition:opacity .15s ease;width:15%;z-index:1}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;opacity:.9;outline:0;text-decoration:none}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{background-position:50%;background-repeat:no-repeat;background-size:100% 100%;display:inline-block;height:2rem;width:2rem}.carousel-control-prev-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3E%3Cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3E%3C/svg%3E")}.carousel-control-next-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3E%3Cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E")}.carousel-indicators{bottom:0;display:flex;justify-content:center;left:0;margin-bottom:1rem;margin-left:15%;margin-right:15%;padding:0;position:absolute;right:0;z-index:2}.carousel-indicators [data-bs-target]{background-clip:padding-box;background-color:#fff;border:0;border-bottom:10px solid transparent;border-top:10px solid transparent;box-sizing:initial;cursor:pointer;flex:0 1 auto;height:3px;margin-left:3px;margin-right:3px;opacity:.5;padding:0;text-indent:-999px;transition:opacity .6s ease;width:30px}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{bottom:1.25rem;color:#fff;left:15%;padding-bottom:1.25rem;padding-top:1.25rem;position:absolute;right:15%;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{-webkit-filter:invert(1) grayscale(100);filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}[data-bs-theme=dark].carousel .carousel-control-next-icon,[data-bs-theme=dark] .carousel .carousel-control-next-icon,[data-bs-theme=dark].carousel .carousel-control-prev-icon,[data-bs-theme=dark] .carousel .carousel-control-prev-icon{-webkit-filter:invert(1) grayscale(100);filter:invert(1) grayscale(100)}[data-bs-theme=dark].carousel .carousel-indicators [data-bs-target],[data-bs-theme=dark] .carousel .carousel-indicators [data-bs-target]{background-color:#000}[data-bs-theme=dark].carousel .carousel-caption,[data-bs-theme=dark] .carousel .carousel-caption{color:#000}.spinner-border,.spinner-grow{-webkit-animation:var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name);animation:var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name);border-radius:50%;display:inline-block;height:var(--bs-spinner-height);vertical-align:var(--bs-spinner-vertical-align);width:var(--bs-spinner-width)}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes spinner-border{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.spinner-border{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-0.125em;--bs-spinner-border-width:0.25em;--bs-spinner-animation-speed:0.75s;--bs-spinner-animation-name:spinner-border;border-right-color:currentcolor;border:var(--bs-spinner-border-width) solid;border-right:var(--bs-spinner-border-width) solid transparent}.spinner-border-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem;--bs-spinner-border-width:0.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}.spinner-grow{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-0.125em;--bs-spinner-animation-speed:0.75s;--bs-spinner-animation-name:spinner-grow;background-color:currentcolor;opacity:0}.spinner-grow-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{--bs-spinner-animation-speed:1.5s}}.offcanvas,.offcanvas-lg,.offcanvas-md,.offcanvas-sm,.offcanvas-xl,.offcanvas-xxl{--bs-offcanvas-zindex:1045;--bs-offcanvas-width:400px;--bs-offcanvas-height:30vh;--bs-offcanvas-padding-x:1rem;--bs-offcanvas-padding-y:1rem;--bs-offcanvas-color:var(--bs-body-color);--bs-offcanvas-bg:var(--bs-body-bg);--bs-offcanvas-border-width:var(--bs-border-width);--bs-offcanvas-border-color:var(--bs-border-color-translucent);--bs-offcanvas-box-shadow:0 0.125rem 0.25rem rgba(0,0,0,.075);--bs-offcanvas-transition:transform 0.3s ease-in-out;--bs-offcanvas-title-line-height:1.5}@media (max-width:575.98px){.offcanvas-sm{background-clip:padding-box;background-color:var(--bs-offcanvas-bg);bottom:0;color:var(--bs-offcanvas-color);display:flex;flex-direction:column;max-width:100%;outline:0;position:fixed;transition:var(--bs-offcanvas-transition);visibility:hidden;z-index:var(--bs-offcanvas-zindex)}}@media (max-width:575.98px) and (prefers-reduced-motion:reduce){.offcanvas-sm{transition:none}}@media (max-width:575.98px){.offcanvas-sm.offcanvas-start{border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);left:0;top:0;-webkit-transform:translateX(-100%);transform:translateX(-100%);width:var(--bs-offcanvas-width)}.offcanvas-sm.offcanvas-end{border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);right:0;top:0;-webkit-transform:translateX(100%);transform:translateX(100%);width:var(--bs-offcanvas-width)}.offcanvas-sm.offcanvas-top{border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;-webkit-transform:translateY(-100%);transform:translateY(-100%)}.offcanvas-sm.offcanvas-bottom,.offcanvas-sm.offcanvas-top{height:var(--bs-offcanvas-height);left:0;max-height:100%;right:0}.offcanvas-sm.offcanvas-bottom{border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);-webkit-transform:translateY(100%);transform:translateY(100%)}.offcanvas-sm.show:not(.hiding),.offcanvas-sm.showing{-webkit-transform:none;transform:none}.offcanvas-sm.hiding,.offcanvas-sm.show,.offcanvas-sm.showing{visibility:visible}}@media (min-width:576px){.offcanvas-sm{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:initial!important}.offcanvas-sm .offcanvas-header{display:none}.offcanvas-sm .offcanvas-body{background-color:initial!important;display:flex;flex-grow:0;overflow-y:visible;padding:0}}@media (max-width:767.98px){.offcanvas-md{background-clip:padding-box;background-color:var(--bs-offcanvas-bg);bottom:0;color:var(--bs-offcanvas-color);display:flex;flex-direction:column;max-width:100%;outline:0;position:fixed;transition:var(--bs-offcanvas-transition);visibility:hidden;z-index:var(--bs-offcanvas-zindex)}}@media (max-width:767.98px) and (prefers-reduced-motion:reduce){.offcanvas-md{transition:none}}@media (max-width:767.98px){.offcanvas-md.offcanvas-start{border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);left:0;top:0;-webkit-transform:translateX(-100%);transform:translateX(-100%);width:var(--bs-offcanvas-width)}.offcanvas-md.offcanvas-end{border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);right:0;top:0;-webkit-transform:translateX(100%);transform:translateX(100%);width:var(--bs-offcanvas-width)}.offcanvas-md.offcanvas-top{border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;-webkit-transform:translateY(-100%);transform:translateY(-100%)}.offcanvas-md.offcanvas-bottom,.offcanvas-md.offcanvas-top{height:var(--bs-offcanvas-height);left:0;max-height:100%;right:0}.offcanvas-md.offcanvas-bottom{border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);-webkit-transform:translateY(100%);transform:translateY(100%)}.offcanvas-md.show:not(.hiding),.offcanvas-md.showing{-webkit-transform:none;transform:none}.offcanvas-md.hiding,.offcanvas-md.show,.offcanvas-md.showing{visibility:visible}}@media (min-width:768px){.offcanvas-md{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:initial!important}.offcanvas-md .offcanvas-header{display:none}.offcanvas-md .offcanvas-body{background-color:initial!important;display:flex;flex-grow:0;overflow-y:visible;padding:0}}@media (max-width:991.98px){.offcanvas-lg{background-clip:padding-box;background-color:var(--bs-offcanvas-bg);bottom:0;color:var(--bs-offcanvas-color);display:flex;flex-direction:column;max-width:100%;outline:0;position:fixed;transition:var(--bs-offcanvas-transition);visibility:hidden;z-index:var(--bs-offcanvas-zindex)}}@media (max-width:991.98px) and (prefers-reduced-motion:reduce){.offcanvas-lg{transition:none}}@media (max-width:991.98px){.offcanvas-lg.offcanvas-start{border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);left:0;top:0;-webkit-transform:translateX(-100%);transform:translateX(-100%);width:var(--bs-offcanvas-width)}.offcanvas-lg.offcanvas-end{border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);right:0;top:0;-webkit-transform:translateX(100%);transform:translateX(100%);width:var(--bs-offcanvas-width)}.offcanvas-lg.offcanvas-top{border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;-webkit-transform:translateY(-100%);transform:translateY(-100%)}.offcanvas-lg.offcanvas-bottom,.offcanvas-lg.offcanvas-top{height:var(--bs-offcanvas-height);left:0;max-height:100%;right:0}.offcanvas-lg.offcanvas-bottom{border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);-webkit-transform:translateY(100%);transform:translateY(100%)}.offcanvas-lg.show:not(.hiding),.offcanvas-lg.showing{-webkit-transform:none;transform:none}.offcanvas-lg.hiding,.offcanvas-lg.show,.offcanvas-lg.showing{visibility:visible}}@media (min-width:992px){.offcanvas-lg{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:initial!important}.offcanvas-lg .offcanvas-header{display:none}.offcanvas-lg .offcanvas-body{background-color:initial!important;display:flex;flex-grow:0;overflow-y:visible;padding:0}}@media (max-width:1199.98px){.offcanvas-xl{background-clip:padding-box;background-color:var(--bs-offcanvas-bg);bottom:0;color:var(--bs-offcanvas-color);display:flex;flex-direction:column;max-width:100%;outline:0;position:fixed;transition:var(--bs-offcanvas-transition);visibility:hidden;z-index:var(--bs-offcanvas-zindex)}}@media (max-width:1199.98px) and (prefers-reduced-motion:reduce){.offcanvas-xl{transition:none}}@media (max-width:1199.98px){.offcanvas-xl.offcanvas-start{border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);left:0;top:0;-webkit-transform:translateX(-100%);transform:translateX(-100%);width:var(--bs-offcanvas-width)}.offcanvas-xl.offcanvas-end{border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);right:0;top:0;-webkit-transform:translateX(100%);transform:translateX(100%);width:var(--bs-offcanvas-width)}.offcanvas-xl.offcanvas-top{border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;-webkit-transform:translateY(-100%);transform:translateY(-100%)}.offcanvas-xl.offcanvas-bottom,.offcanvas-xl.offcanvas-top{height:var(--bs-offcanvas-height);left:0;max-height:100%;right:0}.offcanvas-xl.offcanvas-bottom{border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);-webkit-transform:translateY(100%);transform:translateY(100%)}.offcanvas-xl.show:not(.hiding),.offcanvas-xl.showing{-webkit-transform:none;transform:none}.offcanvas-xl.hiding,.offcanvas-xl.show,.offcanvas-xl.showing{visibility:visible}}@media (min-width:1200px){.offcanvas-xl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:initial!important}.offcanvas-xl .offcanvas-header{display:none}.offcanvas-xl .offcanvas-body{background-color:initial!important;display:flex;flex-grow:0;overflow-y:visible;padding:0}}@media (max-width:1399.98px){.offcanvas-xxl{background-clip:padding-box;background-color:var(--bs-offcanvas-bg);bottom:0;color:var(--bs-offcanvas-color);display:flex;flex-direction:column;max-width:100%;outline:0;position:fixed;transition:var(--bs-offcanvas-transition);visibility:hidden;z-index:var(--bs-offcanvas-zindex)}}@media (max-width:1399.98px) and (prefers-reduced-motion:reduce){.offcanvas-xxl{transition:none}}@media (max-width:1399.98px){.offcanvas-xxl.offcanvas-start{border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);left:0;top:0;-webkit-transform:translateX(-100%);transform:translateX(-100%);width:var(--bs-offcanvas-width)}.offcanvas-xxl.offcanvas-end{border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);right:0;top:0;-webkit-transform:translateX(100%);transform:translateX(100%);width:var(--bs-offcanvas-width)}.offcanvas-xxl.offcanvas-top{border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;-webkit-transform:translateY(-100%);transform:translateY(-100%)}.offcanvas-xxl.offcanvas-bottom,.offcanvas-xxl.offcanvas-top{height:var(--bs-offcanvas-height);left:0;max-height:100%;right:0}.offcanvas-xxl.offcanvas-bottom{border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);-webkit-transform:translateY(100%);transform:translateY(100%)}.offcanvas-xxl.show:not(.hiding),.offcanvas-xxl.showing{-webkit-transform:none;transform:none}.offcanvas-xxl.hiding,.offcanvas-xxl.show,.offcanvas-xxl.showing{visibility:visible}}@media (min-width:1400px){.offcanvas-xxl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:initial!important}.offcanvas-xxl .offcanvas-header{display:none}.offcanvas-xxl .offcanvas-body{background-color:initial!important;display:flex;flex-grow:0;overflow-y:visible;padding:0}}.offcanvas{background-clip:padding-box;background-color:var(--bs-offcanvas-bg);bottom:0;color:var(--bs-offcanvas-color);display:flex;flex-direction:column;max-width:100%;outline:0;position:fixed;transition:var(--bs-offcanvas-transition);visibility:hidden;z-index:var(--bs-offcanvas-zindex)}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas.offcanvas-start{border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);left:0;top:0;-webkit-transform:translateX(-100%);transform:translateX(-100%);width:var(--bs-offcanvas-width)}.offcanvas.offcanvas-end{border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);right:0;top:0;-webkit-transform:translateX(100%);transform:translateX(100%);width:var(--bs-offcanvas-width)}.offcanvas.offcanvas-top{border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;-webkit-transform:translateY(-100%);transform:translateY(-100%)}.offcanvas.offcanvas-bottom,.offcanvas.offcanvas-top{height:var(--bs-offcanvas-height);left:0;max-height:100%;right:0}.offcanvas.offcanvas-bottom{border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);-webkit-transform:translateY(100%);transform:translateY(100%)}.offcanvas.show:not(.hiding),.offcanvas.showing{-webkit-transform:none;transform:none}.offcanvas.hiding,.offcanvas.show,.offcanvas.showing{visibility:visible}.offcanvas-backdrop{background-color:#000;height:100vh;left:0;position:fixed;top:0;width:100vw;z-index:1040}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{align-items:center;display:flex;justify-content:space-between;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x)}.offcanvas-header .btn-close{margin-bottom:calc(var(--bs-offcanvas-padding-y)*-.5);margin-right:calc(var(--bs-offcanvas-padding-x)*-.5);margin-top:calc(var(--bs-offcanvas-padding-y)*-.5);padding:calc(var(--bs-offcanvas-padding-y)*.5) calc(var(--bs-offcanvas-padding-x)*.5)}.offcanvas-title{line-height:var(--bs-offcanvas-title-line-height);margin-bottom:0}.offcanvas-body{flex-grow:1;overflow-y:auto;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x)}.placeholder{background-color:currentcolor;cursor:wait;display:inline-block;min-height:1em;opacity:.5;vertical-align:middle}.placeholder.btn:before{content:"";display:inline-block}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{-webkit-animation:placeholder-glow 2s ease-in-out infinite;animation:placeholder-glow 2s ease-in-out infinite}@-webkit-keyframes placeholder-glow{50%{opacity:.2}}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{-webkit-animation:placeholder-wave 2s linear infinite;animation:placeholder-wave 2s linear infinite;-webkit-mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,.8) 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,.8) 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%}@-webkit-keyframes placeholder-wave{to{-webkit-mask-position:-200% 0;mask-position:-200% 0}}@keyframes placeholder-wave{to{-webkit-mask-position:-200% 0;mask-position:-200% 0}}.clearfix:after{clear:both;content:"";display:block}.text-bg-primary{background-color:#0d6efd!important;background-color:RGBA(13,110,253,var(--bs-bg-opacity,1))!important;color:#fff!important}.text-bg-secondary{background-color:#6c757d!important;background-color:RGBA(108,117,125,var(--bs-bg-opacity,1))!important;color:#fff!important}.text-bg-success{background-color:#198754!important;background-color:RGBA(25,135,84,var(--bs-bg-opacity,1))!important;color:#fff!important}.text-bg-info{background-color:#0dcaf0!important;background-color:RGBA(13,202,240,var(--bs-bg-opacity,1))!important;color:#000!important}.text-bg-warning{background-color:#ffc107!important;background-color:RGBA(255,193,7,var(--bs-bg-opacity,1))!important;color:#000!important}.text-bg-danger{background-color:#dc3545!important;background-color:RGBA(220,53,69,var(--bs-bg-opacity,1))!important;color:#fff!important}.text-bg-light{background-color:#f8f9fa!important;background-color:RGBA(248,249,250,var(--bs-bg-opacity,1))!important;color:#000!important}.text-bg-dark{background-color:#212529!important;background-color:RGBA(33,37,41,var(--bs-bg-opacity,1))!important;color:#fff!important}.link-primary{color:#0d6efd;color:RGBA(var(--bs-primary-rgb,var(--bs-link-opacity,1)));text-decoration-color:#0d6efd;text-decoration-color:RGBA(var(--bs-primary-rgb),var(--bs-link-underline-opacity,1))}.link-primary:focus,.link-primary:hover{color:#0a58ca;color:RGBA(10,88,202,var(--bs-link-opacity,1));text-decoration-color:#0a58ca;text-decoration-color:RGBA(10,88,202,var(--bs-link-underline-opacity,1))}.link-secondary{color:#6c757d;color:RGBA(var(--bs-secondary-rgb,var(--bs-link-opacity,1)));text-decoration-color:#6c757d;text-decoration-color:RGBA(var(--bs-secondary-rgb),var(--bs-link-underline-opacity,1))}.link-secondary:focus,.link-secondary:hover{color:#565e64;color:RGBA(86,94,100,var(--bs-link-opacity,1));text-decoration-color:#565e64;text-decoration-color:RGBA(86,94,100,var(--bs-link-underline-opacity,1))}.link-success{color:#198754;color:RGBA(var(--bs-success-rgb,var(--bs-link-opacity,1)));text-decoration-color:#198754;text-decoration-color:RGBA(var(--bs-success-rgb),var(--bs-link-underline-opacity,1))}.link-success:focus,.link-success:hover{color:#146c43;color:RGBA(20,108,67,var(--bs-link-opacity,1));text-decoration-color:#146c43;text-decoration-color:RGBA(20,108,67,var(--bs-link-underline-opacity,1))}.link-info{color:#0dcaf0;color:RGBA(var(--bs-info-rgb,var(--bs-link-opacity,1)));text-decoration-color:#0dcaf0;text-decoration-color:RGBA(var(--bs-info-rgb),var(--bs-link-underline-opacity,1))}.link-info:focus,.link-info:hover{color:#3dd5f3;color:RGBA(61,213,243,var(--bs-link-opacity,1));text-decoration-color:#3dd5f3;text-decoration-color:RGBA(61,213,243,var(--bs-link-underline-opacity,1))}.link-warning{color:#ffc107;color:RGBA(var(--bs-warning-rgb,var(--bs-link-opacity,1)));text-decoration-color:#ffc107;text-decoration-color:RGBA(var(--bs-warning-rgb),var(--bs-link-underline-opacity,1))}.link-warning:focus,.link-warning:hover{color:#ffcd39;color:RGBA(255,205,57,var(--bs-link-opacity,1));text-decoration-color:#ffcd39;text-decoration-color:RGBA(255,205,57,var(--bs-link-underline-opacity,1))}.link-danger{color:#dc3545;color:RGBA(var(--bs-danger-rgb,var(--bs-link-opacity,1)));text-decoration-color:#dc3545;text-decoration-color:RGBA(var(--bs-danger-rgb),var(--bs-link-underline-opacity,1))}.link-danger:focus,.link-danger:hover{color:#b02a37;color:RGBA(176,42,55,var(--bs-link-opacity,1));text-decoration-color:#b02a37;text-decoration-color:RGBA(176,42,55,var(--bs-link-underline-opacity,1))}.link-light{color:#f8f9fa;color:RGBA(var(--bs-light-rgb,var(--bs-link-opacity,1)));text-decoration-color:#f8f9fa;text-decoration-color:RGBA(var(--bs-light-rgb),var(--bs-link-underline-opacity,1))}.link-light:focus,.link-light:hover{color:#f9fafb;color:RGBA(249,250,251,var(--bs-link-opacity,1));text-decoration-color:#f9fafb;text-decoration-color:RGBA(249,250,251,var(--bs-link-underline-opacity,1))}.link-dark{color:#212529;color:RGBA(var(--bs-dark-rgb,var(--bs-link-opacity,1)));text-decoration-color:#212529;text-decoration-color:RGBA(var(--bs-dark-rgb),var(--bs-link-underline-opacity,1))}.link-dark:focus,.link-dark:hover{color:#1a1e21;color:RGBA(26,30,33,var(--bs-link-opacity,1));text-decoration-color:#1a1e21;text-decoration-color:RGBA(26,30,33,var(--bs-link-underline-opacity,1))}.link-body-emphasis{color:#000;color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-opacity,1));text-decoration-color:#000;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,1))}.link-body-emphasis:focus,.link-body-emphasis:hover{color:rgba(0,0,0,.75);color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-opacity,.75));text-decoration-color:rgba(0,0,0,.75);text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,.75))}.focus-ring:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.25);box-shadow:var(--bs-focus-ring-x,0) var(--bs-focus-ring-y,0) var(--bs-focus-ring-blur,0) var(--bs-focus-ring-width) var(--bs-focus-ring-color);outline:0}.icon-link{align-items:center;-webkit-backface-visibility:hidden;backface-visibility:hidden;display:inline-flex;gap:.375rem;text-decoration-color:rgba(13,110,253,.5);text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,.5));text-underline-offset:.25em}.icon-link>.bi{fill:currentcolor;flex-shrink:0;height:1em;transition:transform .2s ease-in-out;width:1em}@media (prefers-reduced-motion:reduce){.icon-link>.bi{transition:none}}.icon-link-hover:focus-visible>.bi,.icon-link-hover:hover>.bi{-webkit-transform:translate3d(.25em,0,0);transform:translate3d(.25em,0,0);-webkit-transform:var(--bs-icon-link-transform,translate3d(.25em,0,0));transform:var(--bs-icon-link-transform,translate3d(.25em,0,0))}.ratio{position:relative;width:100%}.ratio:before{content:"";display:block;padding-top:var(--bs-aspect-ratio)}.ratio>*{height:100%;left:0;position:absolute;top:0;width:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:75%}.ratio-16x9{--bs-aspect-ratio:56.25%}.ratio-21x9{--bs-aspect-ratio:42.8571428571%}.fixed-top{top:0}.fixed-bottom,.fixed-top{left:0;position:fixed;right:0;z-index:1030}.fixed-bottom{bottom:0}.sticky-top{top:0}.sticky-bottom,.sticky-top{position:-webkit-sticky;position:sticky;z-index:1020}.sticky-bottom{bottom:0}@media (min-width:576px){.sticky-sm-top{top:0}.sticky-sm-bottom,.sticky-sm-top{position:-webkit-sticky;position:sticky;z-index:1020}.sticky-sm-bottom{bottom:0}}@media (min-width:768px){.sticky-md-top{top:0}.sticky-md-bottom,.sticky-md-top{position:-webkit-sticky;position:sticky;z-index:1020}.sticky-md-bottom{bottom:0}}@media (min-width:992px){.sticky-lg-top{top:0}.sticky-lg-bottom,.sticky-lg-top{position:-webkit-sticky;position:sticky;z-index:1020}.sticky-lg-bottom{bottom:0}}@media (min-width:1200px){.sticky-xl-top{top:0}.sticky-xl-bottom,.sticky-xl-top{position:-webkit-sticky;position:sticky;z-index:1020}.sticky-xl-bottom{bottom:0}}@media (min-width:1400px){.sticky-xxl-top{top:0}.sticky-xxl-bottom,.sticky-xxl-top{position:-webkit-sticky;position:sticky;z-index:1020}.sticky-xxl-bottom{bottom:0}}.hstack{align-items:center;flex-direction:row}.hstack,.vstack{align-self:stretch;display:flex}.vstack{flex:1 1 auto;flex-direction:column}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){clip:rect(0,0,0,0)!important;border:0!important;height:1px!important;margin:-1px!important;overflow:hidden!important;padding:0!important;white-space:nowrap!important;width:1px!important}.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption),.visually-hidden:not(caption){position:absolute!important}.stretched-link:after{bottom:0;content:"";left:0;position:absolute;right:0;top:0;z-index:1}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{align-self:stretch;background-color:currentcolor;display:inline-block;min-height:1em;opacity:.25;width:1px}.align-baseline{vertical-align:initial!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.object-fit-contain{object-fit:contain!important}.object-fit-cover{object-fit:cover!important}.object-fit-fill{object-fit:fill!important}.object-fit-scale{object-fit:scale-down!important}.object-fit-none{object-fit:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.overflow-x-auto{overflow-x:auto!important}.overflow-x-hidden{overflow-x:hidden!important}.overflow-x-visible{overflow-x:visible!important}.overflow-x-scroll{overflow-x:scroll!important}.overflow-y-auto{overflow-y:auto!important}.overflow-y-hidden{overflow-y:hidden!important}.overflow-y-visible{overflow-y:visible!important}.overflow-y-scroll{overflow-y:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-inline-grid{display:inline-grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.focus-ring-primary{--bs-focus-ring-color:rgba(var(--bs-primary-rgb),var(--bs-focus-ring-opacity))}.focus-ring-secondary{--bs-focus-ring-color:rgba(var(--bs-secondary-rgb),var(--bs-focus-ring-opacity))}.focus-ring-success{--bs-focus-ring-color:rgba(var(--bs-success-rgb),var(--bs-focus-ring-opacity))}.focus-ring-info{--bs-focus-ring-color:rgba(var(--bs-info-rgb),var(--bs-focus-ring-opacity))}.focus-ring-warning{--bs-focus-ring-color:rgba(var(--bs-warning-rgb),var(--bs-focus-ring-opacity))}.focus-ring-danger{--bs-focus-ring-color:rgba(var(--bs-danger-rgb),var(--bs-focus-ring-opacity))}.focus-ring-light{--bs-focus-ring-color:rgba(var(--bs-light-rgb),var(--bs-focus-ring-opacity))}.focus-ring-dark{--bs-focus-ring-color:rgba(var(--bs-dark-rgb),var(--bs-focus-ring-opacity))}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{-webkit-transform:translate(-50%,-50%)!important;transform:translate(-50%,-50%)!important}.translate-middle-x{-webkit-transform:translateX(-50%)!important;transform:translateX(-50%)!important}.translate-middle-y{-webkit-transform:translateY(-50%)!important;transform:translateY(-50%)!important}.border{border:1px solid #dee2e6!important;border:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-0{border:0!important}.border-top{border-top:1px solid #dee2e6!important;border-top:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-top-0{border-top:0!important}.border-end{border-right:1px solid #dee2e6!important;border-right:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:1px solid #dee2e6!important;border-bottom:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:1px solid #dee2e6!important;border-left:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-start-0{border-left:0!important}.border-primary{--bs-border-opacity:1;border-color:rgba(var(--bs-primary-rgb),var(--bs-border-opacity))!important}.border-secondary{--bs-border-opacity:1;border-color:rgba(var(--bs-secondary-rgb),var(--bs-border-opacity))!important}.border-success{--bs-border-opacity:1;border-color:rgba(var(--bs-success-rgb),var(--bs-border-opacity))!important}.border-info{--bs-border-opacity:1;border-color:rgba(var(--bs-info-rgb),var(--bs-border-opacity))!important}.border-warning{--bs-border-opacity:1;border-color:rgba(var(--bs-warning-rgb),var(--bs-border-opacity))!important}.border-danger{--bs-border-opacity:1;border-color:rgba(var(--bs-danger-rgb),var(--bs-border-opacity))!important}.border-light{--bs-border-opacity:1;border-color:rgba(var(--bs-light-rgb),var(--bs-border-opacity))!important}.border-dark{--bs-border-opacity:1;border-color:rgba(var(--bs-dark-rgb),var(--bs-border-opacity))!important}.border-black{--bs-border-opacity:1;border-color:rgba(var(--bs-black-rgb),var(--bs-border-opacity))!important}.border-white{--bs-border-opacity:1;border-color:rgba(var(--bs-white-rgb),var(--bs-border-opacity))!important}.border-primary-subtle{border-color:#9ec5fe!important;border-color:var(--bs-primary-border-subtle)!important}.border-secondary-subtle{border-color:#c4c8cb!important;border-color:var(--bs-secondary-border-subtle)!important}.border-success-subtle{border-color:#a3cfbb!important;border-color:var(--bs-success-border-subtle)!important}.border-info-subtle{border-color:#9eeaf9!important;border-color:var(--bs-info-border-subtle)!important}.border-warning-subtle{border-color:#ffe69c!important;border-color:var(--bs-warning-border-subtle)!important}.border-danger-subtle{border-color:#f1aeb5!important;border-color:var(--bs-danger-border-subtle)!important}.border-light-subtle{border-color:#e9ecef!important;border-color:var(--bs-light-border-subtle)!important}.border-dark-subtle{border-color:#adb5bd!important;border-color:var(--bs-dark-border-subtle)!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.border-opacity-10{--bs-border-opacity:0.1}.border-opacity-25{--bs-border-opacity:0.25}.border-opacity-50{--bs-border-opacity:0.5}.border-opacity-75{--bs-border-opacity:0.75}.border-opacity-100{--bs-border-opacity:1}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-left:0!important;margin-right:0!important}.mx-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-3{margin-left:1rem!important;margin-right:1rem!important}.mx-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-5{margin-left:3rem!important;margin-right:3rem!important}.mx-auto{margin-left:auto!important;margin-right:auto!important}.my-0{margin-bottom:0!important;margin-top:0!important}.my-1{margin-bottom:.25rem!important;margin-top:.25rem!important}.my-2{margin-bottom:.5rem!important;margin-top:.5rem!important}.my-3{margin-bottom:1rem!important;margin-top:1rem!important}.my-4{margin-bottom:1.5rem!important;margin-top:1.5rem!important}.my-5{margin-bottom:3rem!important;margin-top:3rem!important}.my-auto{margin-bottom:auto!important;margin-top:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-left:0!important;padding-right:0!important}.px-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-3{padding-left:1rem!important;padding-right:1rem!important}.px-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-5{padding-left:3rem!important;padding-right:3rem!important}.py-0{padding-bottom:0!important;padding-top:0!important}.py-1{padding-bottom:.25rem!important;padding-top:.25rem!important}.py-2{padding-bottom:.5rem!important;padding-top:.5rem!important}.py-3{padding-bottom:1rem!important;padding-top:1rem!important}.py-4{padding-bottom:1.5rem!important;padding-top:1.5rem!important}.py-5{padding-bottom:3rem!important;padding-top:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.row-gap-0{row-gap:0!important}.row-gap-1{row-gap:.25rem!important}.row-gap-2{row-gap:.5rem!important}.row-gap-3{row-gap:1rem!important}.row-gap-4{row-gap:1.5rem!important}.row-gap-5{row-gap:3rem!important}.column-gap-0{-webkit-column-gap:0!important;column-gap:0!important}.column-gap-1{-webkit-column-gap:.25rem!important;column-gap:.25rem!important}.column-gap-2{-webkit-column-gap:.5rem!important;column-gap:.5rem!important}.column-gap-3{-webkit-column-gap:1rem!important;column-gap:1rem!important}.column-gap-4{-webkit-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-5{-webkit-column-gap:3rem!important;column-gap:3rem!important}.font-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace!important;font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-lighter{font-weight:lighter!important}.fw-light{font-weight:300!important}.fw-normal{font-weight:400!important}.fw-medium{font-weight:500!important}.fw-semibold{font-weight:600!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(13,110,253,var(--bs-text-opacity))!important;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(108,117,125,var(--bs-text-opacity))!important;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(25,135,84,var(--bs-text-opacity))!important;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(13,202,240,var(--bs-text-opacity))!important;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(255,193,7,var(--bs-text-opacity))!important;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(220,53,69,var(--bs-text-opacity))!important;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(248,249,250,var(--bs-text-opacity))!important;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(33,37,41,var(--bs-text-opacity))!important;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(0,0,0,var(--bs-text-opacity))!important;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(255,255,255,var(--bs-text-opacity))!important;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(33,37,41,var(--bs-text-opacity))!important;color:rgba(var(--bs-body-color-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:rgba(33,37,41,.75)!important;color:var(--bs-secondary-color)!important}.text-black-50{--bs-text-opacity:1;color:rgba(0,0,0,.5)!important}.text-white-50{--bs-text-opacity:1;color:hsla(0,0%,100%,.5)!important}.text-body-secondary{--bs-text-opacity:1;color:rgba(33,37,41,.75)!important;color:var(--bs-secondary-color)!important}.text-body-tertiary{--bs-text-opacity:1;color:rgba(33,37,41,.5)!important;color:var(--bs-tertiary-color)!important}.text-body-emphasis{--bs-text-opacity:1;color:#000!important;color:var(--bs-emphasis-color)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:0.25}.text-opacity-50{--bs-text-opacity:0.5}.text-opacity-75{--bs-text-opacity:0.75}.text-opacity-100{--bs-text-opacity:1}.text-primary-emphasis{color:#052c65!important;color:var(--bs-primary-text-emphasis)!important}.text-secondary-emphasis{color:#2b2f32!important;color:var(--bs-secondary-text-emphasis)!important}.text-success-emphasis{color:#0a3622!important;color:var(--bs-success-text-emphasis)!important}.text-info-emphasis{color:#055160!important;color:var(--bs-info-text-emphasis)!important}.text-warning-emphasis{color:#664d03!important;color:var(--bs-warning-text-emphasis)!important}.text-danger-emphasis{color:#58151c!important;color:var(--bs-danger-text-emphasis)!important}.text-light-emphasis{color:#495057!important;color:var(--bs-light-text-emphasis)!important}.text-dark-emphasis{color:#495057!important;color:var(--bs-dark-text-emphasis)!important}.link-opacity-10,.link-opacity-10-hover:hover{--bs-link-opacity:0.1}.link-opacity-25,.link-opacity-25-hover:hover{--bs-link-opacity:0.25}.link-opacity-50,.link-opacity-50-hover:hover{--bs-link-opacity:0.5}.link-opacity-75,.link-opacity-75-hover:hover{--bs-link-opacity:0.75}.link-opacity-100,.link-opacity-100-hover:hover{--bs-link-opacity:1}.link-offset-1,.link-offset-1-hover:hover{text-underline-offset:.125em!important}.link-offset-2,.link-offset-2-hover:hover{text-underline-offset:.25em!important}.link-offset-3,.link-offset-3-hover:hover{text-underline-offset:.375em!important}.link-underline-primary{--bs-link-underline-opacity:1;text-decoration-color:rgba(13,110,253,var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-primary-rgb),var(--bs-link-underline-opacity))!important}.link-underline-secondary{--bs-link-underline-opacity:1;text-decoration-color:rgba(108,117,125,var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-secondary-rgb),var(--bs-link-underline-opacity))!important}.link-underline-success{--bs-link-underline-opacity:1;text-decoration-color:rgba(25,135,84,var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-success-rgb),var(--bs-link-underline-opacity))!important}.link-underline-info{--bs-link-underline-opacity:1;text-decoration-color:rgba(13,202,240,var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-info-rgb),var(--bs-link-underline-opacity))!important}.link-underline-warning{--bs-link-underline-opacity:1;text-decoration-color:rgba(255,193,7,var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-warning-rgb),var(--bs-link-underline-opacity))!important}.link-underline-danger{--bs-link-underline-opacity:1;text-decoration-color:rgba(220,53,69,var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-danger-rgb),var(--bs-link-underline-opacity))!important}.link-underline-light{--bs-link-underline-opacity:1;text-decoration-color:rgba(248,249,250,var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-light-rgb),var(--bs-link-underline-opacity))!important}.link-underline-dark{--bs-link-underline-opacity:1;text-decoration-color:rgba(33,37,41,var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-dark-rgb),var(--bs-link-underline-opacity))!important}.link-underline{--bs-link-underline-opacity:1;text-decoration-color:#0d6efd!important;text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-underline-opacity,1))!important}.link-underline-opacity-0,.link-underline-opacity-0-hover:hover{--bs-link-underline-opacity:0}.link-underline-opacity-10,.link-underline-opacity-10-hover:hover{--bs-link-underline-opacity:0.1}.link-underline-opacity-25,.link-underline-opacity-25-hover:hover{--bs-link-underline-opacity:0.25}.link-underline-opacity-50,.link-underline-opacity-50-hover:hover{--bs-link-underline-opacity:0.5}.link-underline-opacity-75,.link-underline-opacity-75-hover:hover{--bs-link-underline-opacity:0.75}.link-underline-opacity-100,.link-underline-opacity-100-hover:hover{--bs-link-underline-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(13,110,253,var(--bs-bg-opacity))!important;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(108,117,125,var(--bs-bg-opacity))!important;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(25,135,84,var(--bs-bg-opacity))!important;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(13,202,240,var(--bs-bg-opacity))!important;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(255,193,7,var(--bs-bg-opacity))!important;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(220,53,69,var(--bs-bg-opacity))!important;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(248,249,250,var(--bs-bg-opacity))!important;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(33,37,41,var(--bs-bg-opacity))!important;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(0,0,0,var(--bs-bg-opacity))!important;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(255,255,255,var(--bs-bg-opacity))!important;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(255,255,255,var(--bs-bg-opacity))!important;background-color:rgba(var(--bs-body-bg-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:initial!important}.bg-body-secondary{--bs-bg-opacity:1;background-color:rgba(233,236,239,var(--bs-bg-opacity))!important;background-color:rgba(var(--bs-secondary-bg-rgb),var(--bs-bg-opacity))!important}.bg-body-tertiary{--bs-bg-opacity:1;background-color:rgba(248,249,250,var(--bs-bg-opacity))!important;background-color:rgba(var(--bs-tertiary-bg-rgb),var(--bs-bg-opacity))!important}.bg-opacity-10{--bs-bg-opacity:0.1}.bg-opacity-25{--bs-bg-opacity:0.25}.bg-opacity-50{--bs-bg-opacity:0.5}.bg-opacity-75{--bs-bg-opacity:0.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-primary-subtle{background-color:#cfe2ff!important;background-color:var(--bs-primary-bg-subtle)!important}.bg-secondary-subtle{background-color:#e2e3e5!important;background-color:var(--bs-secondary-bg-subtle)!important}.bg-success-subtle{background-color:#d1e7dd!important;background-color:var(--bs-success-bg-subtle)!important}.bg-info-subtle{background-color:#cff4fc!important;background-color:var(--bs-info-bg-subtle)!important}.bg-warning-subtle{background-color:#fff3cd!important;background-color:var(--bs-warning-bg-subtle)!important}.bg-danger-subtle{background-color:#f8d7da!important;background-color:var(--bs-danger-bg-subtle)!important}.bg-light-subtle{background-color:#fcfcfd!important;background-color:var(--bs-light-bg-subtle)!important}.bg-dark-subtle{background-color:#ced4da!important;background-color:var(--bs-dark-bg-subtle)!important}.bg-gradient{background-image:linear-gradient(180deg,hsla(0,0%,100%,.15),hsla(0,0%,100%,0))!important;background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:.375rem!important;border-radius:var(--bs-border-radius)!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:.25rem!important;border-radius:var(--bs-border-radius-sm)!important}.rounded-2{border-radius:.375rem!important;border-radius:var(--bs-border-radius)!important}.rounded-3{border-radius:.5rem!important;border-radius:var(--bs-border-radius-lg)!important}.rounded-4{border-radius:1rem!important;border-radius:var(--bs-border-radius-xl)!important}.rounded-5{border-radius:2rem!important;border-radius:var(--bs-border-radius-xxl)!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important;border-radius:var(--bs-border-radius-pill)!important}.rounded-top{border-top-left-radius:.375rem!important;border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:.375rem!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-0{border-top-left-radius:0!important;border-top-right-radius:0!important}.rounded-top-1{border-top-left-radius:.25rem!important;border-top-left-radius:var(--bs-border-radius-sm)!important;border-top-right-radius:.25rem!important;border-top-right-radius:var(--bs-border-radius-sm)!important}.rounded-top-2{border-top-left-radius:.375rem!important;border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:.375rem!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-3{border-top-left-radius:.5rem!important;border-top-left-radius:var(--bs-border-radius-lg)!important;border-top-right-radius:.5rem!important;border-top-right-radius:var(--bs-border-radius-lg)!important}.rounded-top-4{border-top-left-radius:1rem!important;border-top-left-radius:var(--bs-border-radius-xl)!important;border-top-right-radius:1rem!important;border-top-right-radius:var(--bs-border-radius-xl)!important}.rounded-top-5{border-top-left-radius:2rem!important;border-top-left-radius:var(--bs-border-radius-xxl)!important;border-top-right-radius:2rem!important;border-top-right-radius:var(--bs-border-radius-xxl)!important}.rounded-top-circle{border-top-left-radius:50%!important;border-top-right-radius:50%!important}.rounded-top-pill{border-top-left-radius:50rem!important;border-top-left-radius:var(--bs-border-radius-pill)!important;border-top-right-radius:50rem!important;border-top-right-radius:var(--bs-border-radius-pill)!important}.rounded-end{border-bottom-right-radius:.375rem!important;border-bottom-right-radius:var(--bs-border-radius)!important;border-top-right-radius:.375rem!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-end-0{border-bottom-right-radius:0!important;border-top-right-radius:0!important}.rounded-end-1{border-bottom-right-radius:.25rem!important;border-bottom-right-radius:var(--bs-border-radius-sm)!important;border-top-right-radius:.25rem!important;border-top-right-radius:var(--bs-border-radius-sm)!important}.rounded-end-2{border-bottom-right-radius:.375rem!important;border-bottom-right-radius:var(--bs-border-radius)!important;border-top-right-radius:.375rem!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-end-3{border-bottom-right-radius:.5rem!important;border-bottom-right-radius:var(--bs-border-radius-lg)!important;border-top-right-radius:.5rem!important;border-top-right-radius:var(--bs-border-radius-lg)!important}.rounded-end-4{border-bottom-right-radius:1rem!important;border-bottom-right-radius:var(--bs-border-radius-xl)!important;border-top-right-radius:1rem!important;border-top-right-radius:var(--bs-border-radius-xl)!important}.rounded-end-5{border-bottom-right-radius:2rem!important;border-bottom-right-radius:var(--bs-border-radius-xxl)!important;border-top-right-radius:2rem!important;border-top-right-radius:var(--bs-border-radius-xxl)!important}.rounded-end-circle{border-bottom-right-radius:50%!important;border-top-right-radius:50%!important}.rounded-end-pill{border-bottom-right-radius:50rem!important;border-bottom-right-radius:var(--bs-border-radius-pill)!important;border-top-right-radius:50rem!important;border-top-right-radius:var(--bs-border-radius-pill)!important}.rounded-bottom{border-bottom-left-radius:.375rem!important;border-bottom-left-radius:var(--bs-border-radius)!important;border-bottom-right-radius:.375rem!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-bottom-0{border-bottom-left-radius:0!important;border-bottom-right-radius:0!important}.rounded-bottom-1{border-bottom-left-radius:.25rem!important;border-bottom-left-radius:var(--bs-border-radius-sm)!important;border-bottom-right-radius:.25rem!important;border-bottom-right-radius:var(--bs-border-radius-sm)!important}.rounded-bottom-2{border-bottom-left-radius:.375rem!important;border-bottom-left-radius:var(--bs-border-radius)!important;border-bottom-right-radius:.375rem!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-bottom-3{border-bottom-left-radius:.5rem!important;border-bottom-left-radius:var(--bs-border-radius-lg)!important;border-bottom-right-radius:.5rem!important;border-bottom-right-radius:var(--bs-border-radius-lg)!important}.rounded-bottom-4{border-bottom-left-radius:1rem!important;border-bottom-left-radius:var(--bs-border-radius-xl)!important;border-bottom-right-radius:1rem!important;border-bottom-right-radius:var(--bs-border-radius-xl)!important}.rounded-bottom-5{border-bottom-left-radius:2rem!important;border-bottom-left-radius:var(--bs-border-radius-xxl)!important;border-bottom-right-radius:2rem!important;border-bottom-right-radius:var(--bs-border-radius-xxl)!important}.rounded-bottom-circle{border-bottom-left-radius:50%!important;border-bottom-right-radius:50%!important}.rounded-bottom-pill{border-bottom-left-radius:50rem!important;border-bottom-left-radius:var(--bs-border-radius-pill)!important;border-bottom-right-radius:50rem!important;border-bottom-right-radius:var(--bs-border-radius-pill)!important}.rounded-start{border-bottom-left-radius:.375rem!important;border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:.375rem!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-0{border-bottom-left-radius:0!important;border-top-left-radius:0!important}.rounded-start-1{border-bottom-left-radius:.25rem!important;border-bottom-left-radius:var(--bs-border-radius-sm)!important;border-top-left-radius:.25rem!important;border-top-left-radius:var(--bs-border-radius-sm)!important}.rounded-start-2{border-bottom-left-radius:.375rem!important;border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:.375rem!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-3{border-bottom-left-radius:.5rem!important;border-bottom-left-radius:var(--bs-border-radius-lg)!important;border-top-left-radius:.5rem!important;border-top-left-radius:var(--bs-border-radius-lg)!important}.rounded-start-4{border-bottom-left-radius:1rem!important;border-bottom-left-radius:var(--bs-border-radius-xl)!important;border-top-left-radius:1rem!important;border-top-left-radius:var(--bs-border-radius-xl)!important}.rounded-start-5{border-bottom-left-radius:2rem!important;border-bottom-left-radius:var(--bs-border-radius-xxl)!important;border-top-left-radius:2rem!important;border-top-left-radius:var(--bs-border-radius-xxl)!important}.rounded-start-circle{border-bottom-left-radius:50%!important;border-top-left-radius:50%!important}.rounded-start-pill{border-bottom-left-radius:50rem!important;border-bottom-left-radius:var(--bs-border-radius-pill)!important;border-top-left-radius:50rem!important;border-top-left-radius:var(--bs-border-radius-pill)!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}.z-n1{z-index:-1!important}.z-0{z-index:0!important}.z-1{z-index:1!important}.z-2{z-index:2!important}.z-3{z-index:3!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.object-fit-sm-contain{object-fit:contain!important}.object-fit-sm-cover{object-fit:cover!important}.object-fit-sm-fill{object-fit:fill!important}.object-fit-sm-scale{object-fit:scale-down!important}.object-fit-sm-none{object-fit:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-inline-grid{display:inline-grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-left:0!important;margin-right:0!important}.mx-sm-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-sm-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-sm-3{margin-left:1rem!important;margin-right:1rem!important}.mx-sm-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-sm-5{margin-left:3rem!important;margin-right:3rem!important}.mx-sm-auto{margin-left:auto!important;margin-right:auto!important}.my-sm-0{margin-bottom:0!important;margin-top:0!important}.my-sm-1{margin-bottom:.25rem!important;margin-top:.25rem!important}.my-sm-2{margin-bottom:.5rem!important;margin-top:.5rem!important}.my-sm-3{margin-bottom:1rem!important;margin-top:1rem!important}.my-sm-4{margin-bottom:1.5rem!important;margin-top:1.5rem!important}.my-sm-5{margin-bottom:3rem!important;margin-top:3rem!important}.my-sm-auto{margin-bottom:auto!important;margin-top:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-left:0!important;padding-right:0!important}.px-sm-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-sm-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-sm-3{padding-left:1rem!important;padding-right:1rem!important}.px-sm-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-sm-5{padding-left:3rem!important;padding-right:3rem!important}.py-sm-0{padding-bottom:0!important;padding-top:0!important}.py-sm-1{padding-bottom:.25rem!important;padding-top:.25rem!important}.py-sm-2{padding-bottom:.5rem!important;padding-top:.5rem!important}.py-sm-3{padding-bottom:1rem!important;padding-top:1rem!important}.py-sm-4{padding-bottom:1.5rem!important;padding-top:1.5rem!important}.py-sm-5{padding-bottom:3rem!important;padding-top:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.row-gap-sm-0{row-gap:0!important}.row-gap-sm-1{row-gap:.25rem!important}.row-gap-sm-2{row-gap:.5rem!important}.row-gap-sm-3{row-gap:1rem!important}.row-gap-sm-4{row-gap:1.5rem!important}.row-gap-sm-5{row-gap:3rem!important}.column-gap-sm-0{-webkit-column-gap:0!important;column-gap:0!important}.column-gap-sm-1{-webkit-column-gap:.25rem!important;column-gap:.25rem!important}.column-gap-sm-2{-webkit-column-gap:.5rem!important;column-gap:.5rem!important}.column-gap-sm-3{-webkit-column-gap:1rem!important;column-gap:1rem!important}.column-gap-sm-4{-webkit-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-sm-5{-webkit-column-gap:3rem!important;column-gap:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.object-fit-md-contain{object-fit:contain!important}.object-fit-md-cover{object-fit:cover!important}.object-fit-md-fill{object-fit:fill!important}.object-fit-md-scale{object-fit:scale-down!important}.object-fit-md-none{object-fit:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-inline-grid{display:inline-grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-left:0!important;margin-right:0!important}.mx-md-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-md-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-md-3{margin-left:1rem!important;margin-right:1rem!important}.mx-md-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-md-5{margin-left:3rem!important;margin-right:3rem!important}.mx-md-auto{margin-left:auto!important;margin-right:auto!important}.my-md-0{margin-bottom:0!important;margin-top:0!important}.my-md-1{margin-bottom:.25rem!important;margin-top:.25rem!important}.my-md-2{margin-bottom:.5rem!important;margin-top:.5rem!important}.my-md-3{margin-bottom:1rem!important;margin-top:1rem!important}.my-md-4{margin-bottom:1.5rem!important;margin-top:1.5rem!important}.my-md-5{margin-bottom:3rem!important;margin-top:3rem!important}.my-md-auto{margin-bottom:auto!important;margin-top:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-left:0!important;padding-right:0!important}.px-md-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-md-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-md-3{padding-left:1rem!important;padding-right:1rem!important}.px-md-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-md-5{padding-left:3rem!important;padding-right:3rem!important}.py-md-0{padding-bottom:0!important;padding-top:0!important}.py-md-1{padding-bottom:.25rem!important;padding-top:.25rem!important}.py-md-2{padding-bottom:.5rem!important;padding-top:.5rem!important}.py-md-3{padding-bottom:1rem!important;padding-top:1rem!important}.py-md-4{padding-bottom:1.5rem!important;padding-top:1.5rem!important}.py-md-5{padding-bottom:3rem!important;padding-top:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.row-gap-md-0{row-gap:0!important}.row-gap-md-1{row-gap:.25rem!important}.row-gap-md-2{row-gap:.5rem!important}.row-gap-md-3{row-gap:1rem!important}.row-gap-md-4{row-gap:1.5rem!important}.row-gap-md-5{row-gap:3rem!important}.column-gap-md-0{-webkit-column-gap:0!important;column-gap:0!important}.column-gap-md-1{-webkit-column-gap:.25rem!important;column-gap:.25rem!important}.column-gap-md-2{-webkit-column-gap:.5rem!important;column-gap:.5rem!important}.column-gap-md-3{-webkit-column-gap:1rem!important;column-gap:1rem!important}.column-gap-md-4{-webkit-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-md-5{-webkit-column-gap:3rem!important;column-gap:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.object-fit-lg-contain{object-fit:contain!important}.object-fit-lg-cover{object-fit:cover!important}.object-fit-lg-fill{object-fit:fill!important}.object-fit-lg-scale{object-fit:scale-down!important}.object-fit-lg-none{object-fit:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-inline-grid{display:inline-grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-left:0!important;margin-right:0!important}.mx-lg-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-lg-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-lg-3{margin-left:1rem!important;margin-right:1rem!important}.mx-lg-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-lg-5{margin-left:3rem!important;margin-right:3rem!important}.mx-lg-auto{margin-left:auto!important;margin-right:auto!important}.my-lg-0{margin-bottom:0!important;margin-top:0!important}.my-lg-1{margin-bottom:.25rem!important;margin-top:.25rem!important}.my-lg-2{margin-bottom:.5rem!important;margin-top:.5rem!important}.my-lg-3{margin-bottom:1rem!important;margin-top:1rem!important}.my-lg-4{margin-bottom:1.5rem!important;margin-top:1.5rem!important}.my-lg-5{margin-bottom:3rem!important;margin-top:3rem!important}.my-lg-auto{margin-bottom:auto!important;margin-top:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-left:0!important;padding-right:0!important}.px-lg-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-lg-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-lg-3{padding-left:1rem!important;padding-right:1rem!important}.px-lg-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-lg-5{padding-left:3rem!important;padding-right:3rem!important}.py-lg-0{padding-bottom:0!important;padding-top:0!important}.py-lg-1{padding-bottom:.25rem!important;padding-top:.25rem!important}.py-lg-2{padding-bottom:.5rem!important;padding-top:.5rem!important}.py-lg-3{padding-bottom:1rem!important;padding-top:1rem!important}.py-lg-4{padding-bottom:1.5rem!important;padding-top:1.5rem!important}.py-lg-5{padding-bottom:3rem!important;padding-top:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.row-gap-lg-0{row-gap:0!important}.row-gap-lg-1{row-gap:.25rem!important}.row-gap-lg-2{row-gap:.5rem!important}.row-gap-lg-3{row-gap:1rem!important}.row-gap-lg-4{row-gap:1.5rem!important}.row-gap-lg-5{row-gap:3rem!important}.column-gap-lg-0{-webkit-column-gap:0!important;column-gap:0!important}.column-gap-lg-1{-webkit-column-gap:.25rem!important;column-gap:.25rem!important}.column-gap-lg-2{-webkit-column-gap:.5rem!important;column-gap:.5rem!important}.column-gap-lg-3{-webkit-column-gap:1rem!important;column-gap:1rem!important}.column-gap-lg-4{-webkit-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-lg-5{-webkit-column-gap:3rem!important;column-gap:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.object-fit-xl-contain{object-fit:contain!important}.object-fit-xl-cover{object-fit:cover!important}.object-fit-xl-fill{object-fit:fill!important}.object-fit-xl-scale{object-fit:scale-down!important}.object-fit-xl-none{object-fit:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-inline-grid{display:inline-grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-left:0!important;margin-right:0!important}.mx-xl-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-xl-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-xl-3{margin-left:1rem!important;margin-right:1rem!important}.mx-xl-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-xl-5{margin-left:3rem!important;margin-right:3rem!important}.mx-xl-auto{margin-left:auto!important;margin-right:auto!important}.my-xl-0{margin-bottom:0!important;margin-top:0!important}.my-xl-1{margin-bottom:.25rem!important;margin-top:.25rem!important}.my-xl-2{margin-bottom:.5rem!important;margin-top:.5rem!important}.my-xl-3{margin-bottom:1rem!important;margin-top:1rem!important}.my-xl-4{margin-bottom:1.5rem!important;margin-top:1.5rem!important}.my-xl-5{margin-bottom:3rem!important;margin-top:3rem!important}.my-xl-auto{margin-bottom:auto!important;margin-top:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-left:0!important;padding-right:0!important}.px-xl-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-xl-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-xl-3{padding-left:1rem!important;padding-right:1rem!important}.px-xl-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-xl-5{padding-left:3rem!important;padding-right:3rem!important}.py-xl-0{padding-bottom:0!important;padding-top:0!important}.py-xl-1{padding-bottom:.25rem!important;padding-top:.25rem!important}.py-xl-2{padding-bottom:.5rem!important;padding-top:.5rem!important}.py-xl-3{padding-bottom:1rem!important;padding-top:1rem!important}.py-xl-4{padding-bottom:1.5rem!important;padding-top:1.5rem!important}.py-xl-5{padding-bottom:3rem!important;padding-top:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.row-gap-xl-0{row-gap:0!important}.row-gap-xl-1{row-gap:.25rem!important}.row-gap-xl-2{row-gap:.5rem!important}.row-gap-xl-3{row-gap:1rem!important}.row-gap-xl-4{row-gap:1.5rem!important}.row-gap-xl-5{row-gap:3rem!important}.column-gap-xl-0{-webkit-column-gap:0!important;column-gap:0!important}.column-gap-xl-1{-webkit-column-gap:.25rem!important;column-gap:.25rem!important}.column-gap-xl-2{-webkit-column-gap:.5rem!important;column-gap:.5rem!important}.column-gap-xl-3{-webkit-column-gap:1rem!important;column-gap:1rem!important}.column-gap-xl-4{-webkit-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-xl-5{-webkit-column-gap:3rem!important;column-gap:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.object-fit-xxl-contain{object-fit:contain!important}.object-fit-xxl-cover{object-fit:cover!important}.object-fit-xxl-fill{object-fit:fill!important}.object-fit-xxl-scale{object-fit:scale-down!important}.object-fit-xxl-none{object-fit:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-inline-grid{display:inline-grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-left:0!important;margin-right:0!important}.mx-xxl-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-xxl-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-xxl-3{margin-left:1rem!important;margin-right:1rem!important}.mx-xxl-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-xxl-5{margin-left:3rem!important;margin-right:3rem!important}.mx-xxl-auto{margin-left:auto!important;margin-right:auto!important}.my-xxl-0{margin-bottom:0!important;margin-top:0!important}.my-xxl-1{margin-bottom:.25rem!important;margin-top:.25rem!important}.my-xxl-2{margin-bottom:.5rem!important;margin-top:.5rem!important}.my-xxl-3{margin-bottom:1rem!important;margin-top:1rem!important}.my-xxl-4{margin-bottom:1.5rem!important;margin-top:1.5rem!important}.my-xxl-5{margin-bottom:3rem!important;margin-top:3rem!important}.my-xxl-auto{margin-bottom:auto!important;margin-top:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-left:0!important;padding-right:0!important}.px-xxl-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-xxl-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-xxl-3{padding-left:1rem!important;padding-right:1rem!important}.px-xxl-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-xxl-5{padding-left:3rem!important;padding-right:3rem!important}.py-xxl-0{padding-bottom:0!important;padding-top:0!important}.py-xxl-1{padding-bottom:.25rem!important;padding-top:.25rem!important}.py-xxl-2{padding-bottom:.5rem!important;padding-top:.5rem!important}.py-xxl-3{padding-bottom:1rem!important;padding-top:1rem!important}.py-xxl-4{padding-bottom:1.5rem!important;padding-top:1.5rem!important}.py-xxl-5{padding-bottom:3rem!important;padding-top:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.row-gap-xxl-0{row-gap:0!important}.row-gap-xxl-1{row-gap:.25rem!important}.row-gap-xxl-2{row-gap:.5rem!important}.row-gap-xxl-3{row-gap:1rem!important}.row-gap-xxl-4{row-gap:1.5rem!important}.row-gap-xxl-5{row-gap:3rem!important}.column-gap-xxl-0{-webkit-column-gap:0!important;column-gap:0!important}.column-gap-xxl-1{-webkit-column-gap:.25rem!important;column-gap:.25rem!important}.column-gap-xxl-2{-webkit-column-gap:.5rem!important;column-gap:.5rem!important}.column-gap-xxl-3{-webkit-column-gap:1rem!important;column-gap:1rem!important}.column-gap-xxl-4{-webkit-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-xxl-5{-webkit-column-gap:3rem!important;column-gap:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-inline-grid{display:inline-grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}}@keyframes react-loading-skeleton{to{transform:translateX(100%)}}.react-loading-skeleton{--base-color:#ebebeb;--highlight-color:#f5f5f5;--animation-duration:1.5s;--animation-direction:normal;--pseudo-element-display:block;background-color:var(--base-color);border-radius:.25rem;display:inline-flex;line-height:1;overflow:hidden;position:relative;-webkit-user-select:none;user-select:none;width:100%;z-index:1}.react-loading-skeleton:after{animation-direction:var(--animation-direction);animation-duration:var(--animation-duration);animation-iteration-count:infinite;animation-name:react-loading-skeleton;animation-timing-function:ease-in-out;background-image:linear-gradient(90deg,var(--base-color),var(--highlight-color),var(--base-color));background-repeat:no-repeat;content:" ";display:var(--pseudo-element-display);height:100%;left:0;position:absolute;right:0;top:0;transform:translateX(-100%)}@media (prefers-reduced-motion){.react-loading-skeleton{--pseudo-element-display:none}} +/*# sourceMappingURL=main.f771c7c4.css.map*/ \ No newline at end of file diff --git a/resources/[qb]/[qb_core]/qb-policehub/web/build/static/css/main.f771c7c4.css.map b/resources/[qb]/[qb_core]/qb-policehub/web/build/static/css/main.f771c7c4.css.map new file mode 100644 index 0000000..7925151 --- /dev/null +++ b/resources/[qb]/[qb_core]/qb-policehub/web/build/static/css/main.f771c7c4.css.map @@ -0,0 +1 @@ +{"version":3,"file":"static/css/main.f771c7c4.css","mappings":"AAsGE,gBC/FF,C,0ECPA,KAKE,kCAAmC,CACnC,iCAAkC,CAJlC,mIAEY,CAGZ,YACF,CAEA,MACE,WACF,CAEA,KACE,uEAEF,CCfA,EACE,6BACF,CAUA,iBACE,kCACF,CAEA,MAQE,gCAAqC,CACrC,iBAAkB,CARlB,YAAa,CASb,cAAe,CAPf,UAAW,CAGX,QAAS,CAJT,iBAAkB,CAGlB,OAAQ,CAER,sCAAgC,CAAhC,8BAAgC,CAHhC,SAOF,CAEA,aAGE,SAAU,CAFV,iBAAkB,CAClB,UAEF,CAEA,YACE,mBAAoB,CAEpB,+BAAuC,CADvC,iBAEF,CAEA,kBACE,wBAAyB,CACzB,UAAY,CACZ,eACF,CAQA,6BALE,wBAAyB,CACzB,UAAY,CACZ,qBAQF,CALA,iBAIE,cACF,CAEA,cAIE,uBAAwB,CADzB,iBAAkB,CAFjB,iBAAkB,CAIlB,kBAAmB,CAHnB,UAIF,CAEA,cACE,mBAAoB,CAEpB,gBAAiB,CACjB,mBAAoB,CACpB,iBAAkB,CAHlB,iBAIF,CAEA,oBACE,wBAAyB,CACzB,UAAY,CAEZ,iBAAkB,CADlB,eAEF,CAQA,iCALE,wBAAyB,CACzB,UAAY,CACZ,qBAQF,CALA,mBAIE,cACF,CAEA,oBACE,UAAW,CACX,OACF,CAGA,0BACE,kBACF,CAQA,0DACE,kBACF,CAKA,0BACE,cACF,CAEA,iBACE,cACF,CAEA,eACE,sBACF,CAEA,cAEE,wBAAyB,CAEzB,uCAAyC,CADzC,wCAGF,CAEA,8BAHE,sCAAgC,CAAhC,8BAAgC,CAJhC,oBAeF,CARA,gBAIE,yCAAkD,CAElD,oCAAsC,CADtC,qCAAuC,CAJvC,4BAOF,CAEA,+BAPE,kCAUF,CAHA,eACE,oBAEF,CAEA,WACE,gGAAkG,CAClG,mFACF,CAEA,cACE,qCACF,CAEA,cACE,kCACF,CCtKE;;;;ECDF,6BASI,oQAIA,sMAIA,iKAIA,sNAIA,iRAIA,iPAIA,iRAGF,2BACA,qBAMA,yMACA,mGACA,4EAOA,gDC2OI,yBDzOJ,0BACA,0BAKA,wBACA,6BACA,kBACA,6BAEA,yBACA,8BAEA,wCACA,kCACA,0BACA,kCAEA,sCACA,iCACA,yBACA,iCAOA,wBACA,+BACA,+BAEA,8BACA,oCAMA,wBACA,0BAGA,sBACA,wBACA,0BACA,+CAEA,4BACA,8BACA,6BACA,2BACA,4BACA,mDACA,8BAGA,8CACA,uDACA,gDACA,uDAIA,8BACA,6BACA,2CAIA,8BACA,qCACA,gCACA,uCEjHE,sBF0HA,uBACA,gCACA,qBACA,0BAEA,yBACA,oCAEA,2CACA,qCACA,0BACA,+BAEA,yCACA,oCACA,yBACA,8BAGE,iRAIA,iPAIA,iRAOF,wBACA,8BACA,gCACA,sCAEA,wBAEA,0BACA,kDAEA,8BACA,qCACA,gCACA,wCAlDA,iBG8CJ,kBClKE,qBAeE,+CANJ,MAOM,sBAcN,OASE,6BACA,yCAFA,qBACA,CADA,kCACA,CAHA,aACA,CADA,0BACA,CALA,+KH6OI,CG7OJ,sCH6OI,eG3OJ,CH2OI,kCG3OJ,gBACA,CADA,sCACA,gBACA,CADA,sCACA,CALA,QACA,CAKA,oCAYF,CACE,GAEA,QACA,qBACA,CADA,uCACA,CAHA,aACA,CAFA,aACA,CAGA,WAUF,2CAOE,aAGF,CAHE,sCAFA,eACA,gBACA,CALA,mBAGA,CAJA,YASF,QHuMQ,gCA5JJ,2BG3CJ,OH8MQ,gBGzMR,SHkMQ,+BA5JJ,2BGtCJ,OHyMQ,cGpMR,SH6LQ,6BA5JJ,2BGjCJ,OHoMQ,iBG/LR,SHwLQ,+BA5JJ,2BG5BJ,OH+LQ,gBG1LR,SH+KM,iBG1KN,QH0KM,cG9JJ,GACA,mBADA,YAWF,aAEE,WACA,CAFA,yEACA,CACA,mEAMF,SAEE,iBACA,qBAFA,kBAQF,CD6HA,MC3HE,iBDiIF,CC9HA,SAIE,mBADA,YAIF,yBAIE,eAGF,CACE,kBAKF,CACE,sBACA,cAMF,YACE,eD2HF,UCjHE,kBAQF,cH6EM,gBGtEN,YAEE,wBASF,CATE,wCADA,eAUF,SH2DM,eGvDJ,cACA,CAHA,iBHyDI,CGtDJ,sBAGF,KAAM,aACN,KAAM,SAMJ,gBACA,CADA,6DACA,0BAEA,SACE,kDAWF,6DAEE,aACA,qBDiGJ,mBCtFE,sFHeI,CGfJ,oCHeI,cGPN,KACE,aACA,CHKI,iBGJJ,kBACA,CAFA,YACA,CACA,aAKA,UAEE,aACA,CHLE,iBGIF,CACA,iBAIJ,MAGE,qBADA,aACA,CADA,0BACA,CHZI,gBGeJ,QACE,aAIJ,KAIE,wBCpSE,CDoSF,qCCpSE,sBDmSF,UACA,CADA,uBACA,CHxBI,gBGuBJ,CAFA,wBAMA,SH3BI,cG4BF,SAWJ,QACE,eAMF,SAEE,qBAQF,OAEE,yBADA,mBAIF,SAGE,wBACA,CADA,+BACA,CAFA,oBACA,CAFA,iBACA,CAEA,eAOF,CAEE,qBACA,gCDgEF,4BCrDE,eAFA,oBAUF,OACE,oBAMF,QAEE,eAQF,kCACE,SDkDF,uCCvCE,mBH3HI,kBG6HJ,qBAHA,QAOF,eAEE,mBAKF,eACE,cAGF,QAGE,gBAGA,iBACE,SAOJ,2IACE,sBDsCF,iDC1BE,yBDgCF,6GC5BM,cAON,oBAEE,kBADA,SAMF,UACE,eAUF,UAIE,SADA,QACA,CAHA,WACA,UAUF,QACE,UACA,CH9MM,+BGmNN,qBAHA,mBHhNM,CG+MN,SACA,CAFA,UH1WE,2BGwWJ,OHrMQ,gBG8MN,WACE,UDwBJ,gPCVE,SAGF,6BACE,WASF,eAEE,6BADA,mBAoBF,6BACE,uBAKF,gCACE,SAOF,8BAEE,0BADA,YADF,wBAEE,0BADA,YAMF,QACE,oBAKF,QACE,QAOF,SAEE,eADA,iBASF,UACE,sBAQF,UACE,sBTpkBF,OMmQM,iBNjQJ,gBAKA,YMgQM,gCN5PJ,gBACA,gBM+FA,2BNpGF,WMuQM,cNvQN,aMgQM,gCN5PJ,gBACA,gBM+FA,2BNpGF,WMuQM,gBNvQN,aMgQM,gCN5PJ,gBACA,gBM+FA,2BNpGF,WMuQM,cNvQN,aMgQM,gCN5PJ,gBACA,gBM+FA,2BNpGF,WMuQM,gBNvQN,aMgQM,gCN5PJ,gBACA,gBM+FA,2BNpGF,WMuQM,cNvQN,aMgQM,gCN5PJ,gBACA,gBM+FA,2BNpGF,WMuQM,gBN/OR,EAKA,4BW3DE,gBADA,cX+DF,mBACE,oBAEA,oCACE,kBAUJ,aM8MM,gBN5MJ,yBAIF,aMwMM,kBNvMJ,kBAGA,yBACE,eAIJ,oBAIE,cM2LI,gBN3LJ,CAFA,kBM6LI,CN9LJ,gBAKA,2BACE,YChGJ,CAMA,0BWCE,YAHA,cXiBF,CAfA,eAEE,qBACA,CADA,kCACA,yBSGE,CTHF,0DSGE,sBERF,CFQE,qCERF,CXGA,cAcF,SAEE,oBAGF,aAEE,cADA,mBAIF,iBAEE,wBYlCA,CZkCA,gCKuPI,gBOzRJ,oGCHA,oBACA,gBACA,CAIA,iBADA,iBACA,CAFA,wCACA,CAFA,yCACA,CAFA,UC0DE,0BF5CE,yBACE,eE2CJ,2BF5CE,uCACE,eE2CJ,2BF5CE,qDACE,eE2CJ,4BF5CE,mEACE,gBE2CJ,4BF5CE,kFACE,gBGhBR,QAEI,oJAKF,MCNA,oBACA,gBACA,aACA,eAEA,CAEA,yCADA,yCACA,CAFA,sCDIE,QCOF,aACA,CAIA,8BAHA,cACA,CACA,wCACA,CAFA,yCACA,CAHA,UAmDI,MACE,QAGF,kBApCJ,aACA,WAcA,eACE,aACA,WAFF,eACE,aACA,UAFF,eACE,aACA,qBAFF,eACE,aACA,UAFF,eACE,aACA,UAFF,eACE,aACA,qBA+BE,WAhDJ,aACA,WAqDQ,QAhEN,aACA,kBA+DM,QAhEN,aACA,mBA+DM,QAhEN,aACA,UA+DM,QAhEN,aACA,mBA+DM,QAhEN,aACA,mBA+DM,QAhEN,aACA,UA+DM,QAhEN,aACA,mBA+DM,QAhEN,aACA,mBA+DM,QAhEN,aACA,UA+DM,SAhEN,aACA,mBA+DM,SAhEN,aACA,mBA+DM,SAhEN,aACA,WAuEQ,WAxDV,uBAwDU,WAxDV,wBAwDU,WAxDV,eAwDU,WAxDV,wBAwDU,WAxDV,wBAwDU,WAxDV,eAwDU,WAxDV,wBAwDU,WAxDV,wBAwDU,WAxDV,eAwDU,YAxDV,wBAwDU,YAxDV,wBAmEM,YAEE,eAGF,YAEE,eAPF,YAEE,qBAGF,YAEE,qBAPF,YAEE,oBAGF,YAEE,oBAPF,YAEE,kBAGF,YAEE,kBAPF,YAEE,oBAGF,YAEE,oBAPF,YAEE,kBAGF,YAEE,kBF1DN,0BEUE,QACE,QAGF,qBApCJ,aACA,WAcA,kBACE,aACA,WAFF,kBACE,aACA,UAFF,kBACE,aACA,qBAFF,kBACE,aACA,UAFF,kBACE,aACA,UAFF,kBACE,aACA,qBA+BE,cAhDJ,aACA,WAqDQ,WAhEN,aACA,kBA+DM,WAhEN,aACA,mBA+DM,WAhEN,aACA,UA+DM,WAhEN,aACA,mBA+DM,WAhEN,aACA,mBA+DM,WAhEN,aACA,UA+DM,WAhEN,aACA,mBA+DM,WAhEN,aACA,mBA+DM,WAhEN,aACA,UA+DM,YAhEN,aACA,mBA+DM,YAhEN,aACA,mBA+DM,YAhEN,aACA,WAuEQ,cAxDV,aAwDU,cAxDV,uBAwDU,cAxDV,wBAwDU,cAxDV,eAwDU,cAxDV,wBAwDU,cAxDV,wBAwDU,cAxDV,eAwDU,cAxDV,wBAwDU,cAxDV,wBAwDU,cAxDV,eAwDU,eAxDV,wBAwDU,eAxDV,wBAmEM,kBAEE,eAGF,kBAEE,eAPF,kBAEE,qBAGF,kBAEE,qBAPF,kBAEE,oBAGF,kBAEE,oBAPF,kBAEE,kBAGF,kBAEE,kBAPF,kBAEE,oBAGF,kBAEE,oBAPF,kBAEE,kBAGF,kBAEE,kBF1DN,2BEUE,QACE,QAGF,qBApCJ,aACA,WAcA,kBACE,aACA,WAFF,kBACE,aACA,UAFF,kBACE,aACA,qBAFF,kBACE,aACA,UAFF,kBACE,aACA,UAFF,kBACE,aACA,qBA+BE,cAhDJ,aACA,WAqDQ,WAhEN,aACA,kBA+DM,WAhEN,aACA,mBA+DM,WAhEN,aACA,UA+DM,WAhEN,aACA,mBA+DM,WAhEN,aACA,mBA+DM,WAhEN,aACA,UA+DM,WAhEN,aACA,mBA+DM,WAhEN,aACA,mBA+DM,WAhEN,aACA,UA+DM,YAhEN,aACA,mBA+DM,YAhEN,aACA,mBA+DM,YAhEN,aACA,WAuEQ,cAxDV,aAwDU,cAxDV,uBAwDU,cAxDV,wBAwDU,cAxDV,eAwDU,cAxDV,wBAwDU,cAxDV,wBAwDU,cAxDV,eAwDU,cAxDV,wBAwDU,cAxDV,wBAwDU,cAxDV,eAwDU,eAxDV,wBAwDU,eAxDV,wBAmEM,kBAEE,eAGF,kBAEE,eAPF,kBAEE,qBAGF,kBAEE,qBAPF,kBAEE,oBAGF,kBAEE,oBAPF,kBAEE,kBAGF,kBAEE,kBAPF,kBAEE,oBAGF,kBAEE,oBAPF,kBAEE,kBAGF,kBAEE,kBF1DN,2BEUE,QACE,QAGF,qBApCJ,aACA,WAcA,kBACE,aACA,WAFF,kBACE,aACA,UAFF,kBACE,aACA,qBAFF,kBACE,aACA,UAFF,kBACE,aACA,UAFF,kBACE,aACA,qBA+BE,cAhDJ,aACA,WAqDQ,WAhEN,aACA,kBA+DM,WAhEN,aACA,mBA+DM,WAhEN,aACA,UA+DM,WAhEN,aACA,mBA+DM,WAhEN,aACA,mBA+DM,WAhEN,aACA,UA+DM,WAhEN,aACA,mBA+DM,WAhEN,aACA,mBA+DM,WAhEN,aACA,UA+DM,YAhEN,aACA,mBA+DM,YAhEN,aACA,mBA+DM,YAhEN,aACA,WAuEQ,cAxDV,aAwDU,cAxDV,uBAwDU,cAxDV,wBAwDU,cAxDV,eAwDU,cAxDV,wBAwDU,cAxDV,wBAwDU,cAxDV,eAwDU,cAxDV,wBAwDU,cAxDV,wBAwDU,cAxDV,eAwDU,eAxDV,wBAwDU,eAxDV,wBAmEM,kBAEE,eAGF,kBAEE,eAPF,kBAEE,qBAGF,kBAEE,qBAPF,kBAEE,oBAGF,kBAEE,oBAPF,kBAEE,kBAGF,kBAEE,kBAPF,kBAEE,oBAGF,kBAEE,oBAPF,kBAEE,kBAGF,kBAEE,kBF1DN,4BEUE,QACE,QAGF,qBApCJ,aACA,WAcA,kBACE,aACA,WAFF,kBACE,aACA,UAFF,kBACE,aACA,qBAFF,kBACE,aACA,UAFF,kBACE,aACA,UAFF,kBACE,aACA,qBA+BE,cAhDJ,aACA,WAqDQ,WAhEN,aACA,kBA+DM,WAhEN,aACA,mBA+DM,WAhEN,aACA,UA+DM,WAhEN,aACA,mBA+DM,WAhEN,aACA,mBA+DM,WAhEN,aACA,UA+DM,WAhEN,aACA,mBA+DM,WAhEN,aACA,mBA+DM,WAhEN,aACA,UA+DM,YAhEN,aACA,mBA+DM,YAhEN,aACA,mBA+DM,YAhEN,aACA,WAuEQ,cAxDV,aAwDU,cAxDV,uBAwDU,cAxDV,wBAwDU,cAxDV,eAwDU,cAxDV,wBAwDU,cAxDV,wBAwDU,cAxDV,eAwDU,cAxDV,wBAwDU,cAxDV,wBAwDU,cAxDV,eAwDU,eAxDV,wBAwDU,eAxDV,wBAmEM,kBAEE,eAGF,kBAEE,eAPF,kBAEE,qBAGF,kBAEE,qBAPF,kBAEE,oBAGF,kBAEE,oBAPF,kBAEE,kBAGF,kBAEE,kBAPF,kBAEE,oBAGF,kBAEE,oBAPF,kBAEE,kBAGF,kBAEE,kBF1DN,4BEUE,SACE,QAGF,sBApCJ,aACA,WAcA,mBACE,aACA,WAFF,mBACE,aACA,UAFF,mBACE,aACA,qBAFF,mBACE,aACA,UAFF,mBACE,aACA,UAFF,mBACE,aACA,qBA+BE,eAhDJ,aACA,WAqDQ,YAhEN,aACA,kBA+DM,YAhEN,aACA,mBA+DM,YAhEN,aACA,UA+DM,YAhEN,aACA,mBA+DM,YAhEN,aACA,mBA+DM,YAhEN,aACA,UA+DM,YAhEN,aACA,mBA+DM,YAhEN,aACA,mBA+DM,YAhEN,aACA,UA+DM,aAhEN,aACA,mBA+DM,aAhEN,aACA,mBA+DM,aAhEN,aACA,WAuEQ,eAxDV,aAwDU,eAxDV,uBAwDU,eAxDV,wBAwDU,eAxDV,eAwDU,eAxDV,wBAwDU,eAxDV,wBAwDU,eAxDV,eAwDU,eAxDV,wBAwDU,eAxDV,wBAwDU,eAxDV,eAwDU,gBAxDV,wBAwDU,gBAxDV,wBAmEM,oBAEE,eAGF,oBAEE,eAPF,oBAEE,qBAGF,oBAEE,qBAPF,oBAEE,oBAGF,oBAEE,oBAPF,oBAEE,kBAGF,oBAEE,kBAPF,oBAEE,oBAGF,oBAEE,oBAPF,oBAEE,kBAGF,oBAEE,kBCrHV,SACE,qCACA,0BACA,+CACA,iCACA,8CACA,sCACA,6CACA,oCACA,4CACA,qCAEA,CAIA,0CAFA,2BACA,CAFA,kBACA,CACA,kBACA,CAJA,UAWA,0BAEE,mCACA,wBACA,CADA,0CACA,yDAHA,aAMF,cACE,sBAGF,cACE,qBAIJ,sBACE,oBAOF,CAPE,+CAOF,cACE,gBAUA,6BACE,cAeF,iCACE,0HAGA,mCACE,kBAOJ,CAPI,2HAOJ,qCACE,qBAGF,sCACE,kBAUF,CAQA,kGACE,+CACA,oCAQJ,eACE,8CACA,mCAQA,+BACE,6CACA,kCCrIF,gBAOE,qBACA,sBACA,gCACA,8BACA,8BACA,6BACA,6BACA,4BACA,4BAfF,iCAkBE,0CADA,2BAjBF,kBAOE,qBACA,sBACA,gCACA,8BACA,8BACA,6BACA,6BACA,4BACA,4BAfF,gBAOE,qBACA,sBACA,gCACA,8BACA,8BACA,6BACA,6BACA,4BACA,4BAfF,4BAkBE,0CADA,2BAjBF,aAOE,qBACA,sBACA,gCACA,8BACA,8BACA,6BACA,6BACA,4BACA,4BAfF,gBAOE,qBACA,sBACA,gCACA,8BACA,8BACA,6BACA,6BACA,4BACA,4BAfF,8BAkBE,0CADA,2BAjBF,eAOE,qBACA,sBACA,gCACA,8BACA,8BACA,6BACA,6BACA,4BACA,4BAfF,cAOE,qBACA,sBACA,gCACA,8BACA,8BACA,6BACA,6BACA,4BACA,4BAfF,0BAkBE,0CADA,2BD2IA,CC5JF,YAOE,qBACA,sBACA,gCACA,8BACA,8BACA,6BACA,6BACA,4BACA,4BD6IA,mBAEE,iCADA,eHnFF,6BGkFA,qBAEE,iCADA,eHnFF,8BGkFA,qBAEE,iCADA,eHnFF,8BGkFA,qBAEE,iCADA,eHnFF,+BGkFA,qBAEE,iCADA,eHnFF,+BGkFA,sBAEE,iCADA,eE3JN,cACE,mBASF,iBdiRM,iBc1QJ,iBAJA,ed8QI,Cc/QJ,kCACA,CADA,qDACA,CAFA,+BACA,CADA,kDAUF,oBdsQM,kBcpQJ,gCdoQI,CcpQJ,mDdoQI,CcrQJ,6BACA,CADA,gDAKF,oBdgQM,kBc9PJ,iCd8PI,Cc9PJ,oDd8PI,Cc/PJ,8BACA,CADA,iDC5BF,YAKE,wBCLF,CDKE,gCfsRI,gBetRJ,CAJA,iBCDF,eAYE,uCZGE,CYLF,2BACA,CAFA,qBACA,CADA,kCACA,CACA,wBACA,CADA,0DACA,CZGE,qBaHE,CbGF,qCaHE,CDJJ,aACA,CADA,0BACA,CARA,aACA,ChByRI,cgBrRJ,gBACA,gBACA,CALA,sBhBwRI,CiB/QA,qEDVJ,UCcI,wCDhBN,cCiBQ,eDGN,2BACE,eAEA,yDACE,cAKJ,qBAEE,qBACA,CADA,kCACA,qBACA,CAKE,6CARF,aACA,CADA,0BACA,CAEA,SASF,4CAYE,YAKA,UAXA,cAgBF,sCACE,aACA,UAIF,0CACE,wBAEA,CAFA,+BAEA,UAQF,CAXA,2BACE,wBAEA,CAFA,+BAEA,UAQF,wBAEE,wBAGA,CAHA,uCAGA,UAIF,2CAGE,0BE7FF,wBFgGE,CEhGF,sCFgGE,CAGA,cACA,CAHA,oBACA,CAEA,2BACA,CADA,8CACA,gBCzFE,CDkFF,aE9FF,CF8FE,0BE9FF,CF4FE,uBACA,yBACA,CAHA,sBACA,CAIA,mBACA,CCrFE,qID8EJ,CC9EI,6HD8EJ,qCAGE,0BE7FF,wBFgGE,CEhGF,sCFgGE,CAGA,cACA,CAHA,oBACA,CAEA,2BACA,CADA,8CACA,gBCzFE,CDkFF,aE9FF,CF8FE,0BE9FF,CF4FE,uBACA,yBACA,CAHA,sBACA,CAIA,mBACA,CCrFE,6HAIA,wCD0EJ,0CCzEM,uBDyEN,CCzEM,eDyEN,qCCzEM,eDwFN,iFACE,wBADF,CACE,uCADF,0EACE,wBASJ,CATI,uCASJ,yBAOE,wBACA,CACA,kBAEA,CAFA,4KAHA,aACA,CADA,0BACA,CANA,aACA,CAGA,eACA,CAFA,eACA,CAFA,iBACA,CAFA,UASA,+BACE,SAGF,iFAGE,eADA,eAYJ,kBZjII,oBYuIF,CZvIE,yCJ4QE,iBI5QF,CYkIF,oCACA,CADA,yDACA,qBAIA,8CAGE,yBADA,qBACA,yBAFA,oBADF,wCAGE,yBADA,qBACA,yBAFA,oBAMJ,kBZ9II,mBYoJF,CZpJE,yCJ4QE,iBI5QF,CY+IF,mCACA,CADA,wDACA,mBAIA,8CAGE,wBADA,mBACA,wBAFA,kBADF,wCAGE,wBADA,mBACA,wBAFA,kBAUF,uBACE,qCAGF,CAHE,0DAGF,0BACE,oCAGF,CAHE,yDAGF,0BACE,mCAKJ,CALI,wDAKJ,qBAEE,iCACA,CADA,sDACA,iBAFA,UAIA,oDACE,cAGF,wCACE,kBZvLA,sBY2LF,CZ3LE,qCY2LF,2CACE,kBZ5LA,sBYgMF,CZhME,qCYgMF,qCAAoB,gCACpB,CADoB,qDACpB,qCAAoB,+BG/MtB,CH+MsB,oDG/MtB,cACE,qQAWA,CAqBA,wCAnBA,wDACA,mDACA,iFfFE,CaHE,wCbGF,2BaHE,0BESJ,oFApBF,sBAsBE,sCACE,CAXF,aACA,4BADA,oGAWE,oEAUF,CArBA,UAqBA,wCAGE,aAGF,oCAEE,iEAKF,CALE,SAKF,CACE,0DACA,qBAKF,CALE,oBAKF,uBACA,wBACA,wCfzCE,wEe+CF,uCAEA,iBAOE,6DACE,CfzDF,iBewDA,CfxDA,yCJ4QE,kBmBnNA,iBCrEJ,mBACA,yCAGE,CALF,iBACA,CAHF,oBACE,kBACA,CDsEI,iBCjEF,mCAKJ,qQAmBE,0BACA,sBACA,CADA,oCACA,+BACA,UACA,wCACA,0FACA,YACA,cADA,mBACA,uDAGA,CAuBE,gCAII,0BANJ,uBAEA,iBAjBA,wCAGF,+CACE,CAGF,uBAEE,CALA,2BAGF,CAEE,uBACA,oFAKA,ChB/CA,2BgB8BF,oBAJA,SA2BM,kCAIJ,kDAII,yEAKN,uBACE,yBACA,oBAKE,kFAIJ,wBACE,qBACA,0CAQA,oQAwBA,uCACA,4KAIA,gDAIA,8PAiBE,CApBA,6CAoBA,4BAOJ,mBAGF,YACE,WACA,CANA,mBAMA,4FAMI,yBACA,gCACA,gCAOF,oLC5KF,0CAOE,wCAA0B,CAP5B,mBAO4B,+CAC1B,CARF,SAQE,wCAA0B,qFAG5B,0KJNI,+MAIA,CAJA,wBAIA,iCACE,eIKN,mBJLM,mDCjBN,cGiCE,mBHjCF,oBGsCA,sCACE,YAEA,kBACA,CACA,oBAHA,iBAGA,oDjB5BA,mBiBkCF,yBALE,mBAKF,+EjBlCE,0LaHE,aIqCJ,uCJhCM,CADF,wBIiCJ,CAQE,aJzCE,WAJA,UAKE,4BI0CJ,yCH3DF,2DGiEE,qCAGA,2DACA,+BACA,2CCvEF,wCD+EE,iCACE,oBAHF,8BAEA,CACE,8GAIA,uGCpFJ,CD4EA,UC5EA,0EAGE,kFACA,wBAGF,4CAQE,wBACA,uCAEA,yBACA,oBANA,iBACA,eACA,CAHA,YACA,CAFA,UAQA,+BLFE,gBKGF,oDLPE,CKOF,YLPE,kNAIA,CKEF,ULFE,wCKVJ,8BAiBA,2EAEE,wBpBgiFJ,uDoB7hFM,uCpB6hFN,2HoB7hFM,mBpBqiFN,qMoBhiFM,2CpBsiFN,kIoBhiFM,yBAKF,+CpByiFJ,uCoBhiFM,4BACA,iDpB+hFN,uDoBhiFM,oBpBgiFN,yDoBhiFM,CpBgiFN,MoB/hFM,sHpB+iFN,6JoBhjFM,mBpBgiFN,SAgBA,wCoB3iFQ,qBAEA,eACA,sElB9CJ,8IkBuDE,gHACA,sNAcA,uBADF,oBACE,uGC9EJ,uBAHA,oBAGA,mDAGE,CAHF,oBAGE,oLAiBA,wBAGA,yCAYJ,mEvBiPM,2DuB1OJ,4MrB0nFF,oHqBxmFE,CnBpDE,UF4pFJ,CE5pFI,gCmBsCF,iBnBtCE,WmBoDF,qDnBpDE,iEFqqFJ,8HErqFI,8CmBkEJ,0JAEE,arB6mFF,ibAOA,+HExqFI,wBACA,uCmBsFF,gJACE,CrBglFJ,gKqBhlFI,mHAIF,oNC5FA,oBAHE,yCAIA,CxB2PE,iBwB/PF,CAHA,oBAOA,2DAKA,kBACA,CAiBE,iqBAWE,6BADF,yBACE,2IAWA,4BADA,wBACA,CA3EJ,4DA2EI,wHAOF,2BAGE,mKAEE,wBACA,mCACA,4DACA,CAHA,WAFF,aAEE,mCAFF,mCAEE,CAFF,oCAKE,+HAKF,wEA/FJ,wQAiHE,yDACE,CADF,4BACE,2DAGF,CArBE,sGAqBF,uEACE,+GAGF,mIAOA,6EAhIF,CA+HA,kCA/HA,yVxByRE,gRI5QF,6DF2zFJ,uEE3zFI,sBF2zFJ,qEsBx0FI,mEAqDE,oGAIE,uEACA,2BACA,mEACA,oBAGF,kIACE,wBACA,2HAjEJ,2CA0EI,wDACA,uGA3EJ,aAkFE,uFAGE,mWAIE,SACA,mBAIJ,iDAJI,6BAIJ,CAJI,4BAIJ,kBAEE,0DAhGJ,4DAwGI,CARA,WADA,aACA,mCADA,cACA,sBADA,oCASA,+IAOF,4EAMA,qUAWA,CAhIF,yDAgIE,2BAhIF,6DAiHE,qEACE,mCAlHJ,mPCAF,+EAIA,6EAGA,CzBkRI,kCyBlRJ,6DAEA,qEACA,qOAIA,6UAgBA,6DrBjBE,sEaHE,CQoBJ,sBRpBI,8IQZN,4CAuCI,uDAEA,2EAMA,2BACA,uEAIF,oBACE,iDPrDF,uFO6DI,wBAIJ,8CACE,mFAUF,kGAKE,2GAIA,aAGA,0cA8BF,SCtGA,gCAEA,6BACA,8CACA,yBACA,yBACA,oCACA,wBACA,6CACA,kCACA,+CACA,wCACA,iFAXA,+BACA,gFAGA,CACA,kCAJA,kEAEA,0CAEA,CAKA,yBACA,CD0FA,cCtGA,CAKA,oBACA,CACA,qCAEA,mCACA,2EACA,CALA,uDACA,CAKA,iBACA,sBAPA,6HAIA,CAVA,wBACA,iBACA,CAWA,qBAHA,wCACA,oBACA,aACA,uCAbA,8CAEA,CAUA,+BAVA,uBAEA,iCACA,wCACA,CAHA,yBAGA,oBAEA,uCACA,8CACA,0CACA,CAJA,+BACA,CAEA,SACA,+BACA,6CACA,CDyFA,yCCrGA,CDqGA,SCrGA,oGAKA,wCACA,+CAEA,CAJA,gCAIA,0KAIA,yCAbA,oDAKA,0CACA,iDACA,CALA,kCAEA,CAGA,sCAEA,CALA,mBAKA,iCACA,4EACA,0BACA,oCACA,qCAbA,2BACA,2BACA,qCAEA,wDAEA,0DACA,uCAEA,mCACA,4EACA,0BACA,oCACA,sCAbA,2BACA,CACA,+DAEA,wDAEA,8BACA,4BACA,uCACA,iCACA,mDACA,mDACA,oCACA,qCAYA,2BACA,2BACA,qCACA,wDAEA,6BACA,6BACA,uCACA,8BACA,kDACA,2BACA,6DACA,qCD2FA,2BCvGA,2BACA,qCACA,wDAEA,6BACA,6BACA,uCACA,CACA,oDACA,wDACA,0BACA,oCACA,oCACA,2BD0FA,2BCvGA,qCAEA,wDAEA,6BACA,oEAEA,gCACA,kDACA,oDACA,oCACA,oCACA,2BACA,2BAbA,qCACA,wDAEA,6BACA,6BACA,uCACA,YACA,uCACA,8BACA,oDACA,oCACA,sCACA,4BACA,0BD0FA,qCCtGA,wDAEA,6BACA,6BACA,uCACA,8BACA,qBACA,uDACA,8DACA,mCACA,2BACA,2BACA,qCAbA,wDAEA,6BACA,6BACA,uCACA,sBACA,sBACA,8BACA,oDACA,yEACA,CACA,qDACA,qCACA,CD0FA,uDCtGA,gCACA,iCAEA,uCACA,2CACA,sBACA,8BACA,oDACA,0EAEA,sDACA,qCD2FA,wDCtGA,gCACA,iCAEA,uCACA,yCACA,sBACA,8BACA,0BACA,8DACA,oCACA,2BACA,gEDuGF,wDAEE,gCAEA,iCACA,0DACA,yCACA,wDACA,0BACA,oCACA,qCACA,2BACA,gEAYA,wDAIA,gCACE,iCC7HF,uCACA,mB1B8NI,sB0B5NJ,oDDyIF,0BC5IE,0BACA,oC1B8NI,oC0B5NJ,4BCnEF,0BVgBM,qCAIA,wDUhBF,gCAOA,iCAMF,uCVGI,wCULN,sBVMQ,8BUDN,0BVJI,2BAIA,uEUAJ,CVCM,0BfoyGR,gE0BlzGE,wDC4BE,gCACE,iCAEA,uCApCJ,mBACA,0CAEA,+BA0DE,mDD7CJ,oCAGE,sCACA,2BACA,2BACA,qC5BuQI,wD4BpQJ,gCACA,wEACA,4DACA,+BACA,0HACA,2BACA,gEACA,wDACA,gCACA,wEACA,mBACA,mCACA,qCACA,yGAEA,wCACA,iDACA,yCACA,gCAIA,2CAEA,oEACA,mD5B2OI,0C4BxOJ,+BACA,4BAEA,iDACA,2BACA,8ExBzCE,2BwB6CF,qDAGE,kDAwBA,OACE,8BAEA,wCAMF,qBACE,mBAEA,2CAEE,anB1CJ,wBmB4BA,4BACE,wCAEA,YACE,eACA,EAIJ,gCAGE,uCAFA,OAEA,CACE,uCnBzCJ,gCmB6BE,gBAEA,uEAMF,iBACE,kBAEA,0CAPE,eAIJ,mCAGE,CATA,mCAEE,CAJF,qBAEA,CAFA,WAaE,oBnB1CJ,mBmB4BA,sBAYE,8BACE,aACA,gBnB1CJ,yBmB4BA,8BACE,0BAEA,+BAEE,8BAKF,6BAEA,yCAEE,CnB1CJ,kCmB4BA,8DAGE,oDAMF,kDAGE,yFAcF,4DClFA,sCACE,uDAGA,8CA7BJ,oDAEA,kDAmDE,qCDiEF,qCAGE,2DAEA,kCClGA,qCAEE,mCAEA,oCAtBJ,sCACA,CAmBI,2BACA,CAHF,sCAEE,CACA,4EAgBA,+CACE,CD8FJ,8BACA,CCvFA,aDkFF,sCAIE,CClHA,gBD8GF,SClFE,sCD2EA,kEAOF,CC/HA,iBACA,CDmIE,eCnHA,CAhBF,iCAoCM,gCAEA,MACA,qCAnCN,CAkCM,QAlCN,sBACA,mBACA,sCAsCE,qCACE,iBD2FF,oCAQJ,UAPM,OAON,0BAEE,2CACA,yCACA,iBACA,uBAMF,iBAEE,uCACA,oEAEA,mBACA,yCAEA,OADA,UACA,uBACA,iBACA,uCxBrKE,oEwByKF,4DVxLA,OU0LE,UV1LF,+EUiME,UAFF,OAEE,oDACA,mBVlMF,yCUsMA,yDAEE,uCACA,SACA,CADA,OACA,4BAMJ,yBACE,mBAIF,0CAEE,0DACA,wCAEA,0DAMA,YACA,wCADA,YACA,CAFF,QAEE,gCASA,2DACA,CAFA,mCACA,CAFA,YACA,CADA,WANA,uCAIF,sBAKE,sCACA,aACA,yCACA,+CACA,CADA,6BACA,iCAIA,oCEtPF,uBAEE,CFoPA,eADA,iCACA,CADA,WAFA,uCACA,sBElPA,uCAEA,a5BsnHF,iD4BnnHI,2C5B6nHJ,0O4BzmHE,oCAEA,CAJA,uBAEA,C5BymHF,iC4B3mHE,C5B2mHF,yE4BvmHE,yC1BXE,cFwoHJ,mCExoHI,gBFwoHJ,mB4BlnHI,mD5BknHJ,qD4BlnHI,U5BunHJ,C4BvnHI,e5BunHJ,gBAMA,uGEpnHI,CF8mHJ,8CE5nHI,CF4nHJ,0GE5nHI,mBFkoHJ,wCANA,UE9mHI,2C0B4BF,kDAHA,yCAGA,6CAMA,kDAKF,CAXE,0CAMA,qBAKF,iDAKA,yBAJE,4CAIF,yCACE,+BA0BA,qC5BilHF,C4B1mHE,aAoBF,CAKE,kCALF,+EAKE,C5BilHF,uC4B1kHI,mC5B8kHJ,CAJA,yFAIA,uIEpqHI,2BACA,iCFwqHJ,gGEtrHI,gD2BrBF,qCAEA,qCACA,0CACA,mCACA,gCAGA,mBACA,CAJA,iBAGA,CACA,qBAEA,0CAMA,cADA,iBACA,mXASA,mCAEE,0CAIF,2BAEE,2CAIF,sCACE,sFAYF,4DACA,sJAGA,6BADA,yBACA,8GAEA,4EAGA,qJAIE,uD3B5CA,wF2B+CA,qB3B9CA,qB2B8CA,0EAIE,mBAGF,CAHE,oBAGF,2CAEE,CAFF,sBAEE,sBACA,yD7BwsHN,uG6BhsHI,0DACA,sH3BjEA,2B2BiFJ,C3BlFI,4B2BkFJ,qFAGE,wBACA,0BAGA,kC3BlGE,+B2BqGA,qEACE,CACA,oDACA,uDAIJ,aAEE,gBb7HF,gBa6HE,eb7HF,Ca6HE,cb7HF,WagJA,cACE,SACA,CAJF,8BAEA,CbhJA,aauIF,CAGE,sCACA,2CAGA,CAPF,iEAGE,CAMA,qBAEE,iGAGA,kDAEE,iDAIJ,6DAGE,4CACA,CADA,SACA,2D7B8qHJ,C6BjqHI,c7BuqHJ,CANA,mBAMA,4D6BhqHI,kDAQF,oDACE,2GCzLF,yDAEA,+CACA,uGACA,oFAEA,qBAEA,wDACA,wDACA,yDACA,CALA,sDAKA,qDAEA,uDACA,CAFA,iBAEA,2DAEA,kDADA,uCACA,CACA,8DAEA,2GAMA,CAPA,0CAOA,0BAEA,wBAMA,2BAPA,mDAOA,4IACE,CACA,qBACA,+CAqBJ,+BAEE,iDACA,CAFA,uCAEA,wDAEA,mDAIA,ChCsNI,2CgCtNJ,2CAEE,yCAUJ,8DAKE,gCACA,0BACA,oEACA,CADA,eADA,eAEA,+DAIA,2BAEA,CACA,+DASA,2BAUF,CAdM,+CAIJ,CANE,eAgBJ,yCAEE,aACA,mBAEA,8DAGE,+BAaJ,iEASA,kCACE,+EhCyII,6BgCvIJ,0DAEA,+D5BvIE,C4BwIF,+Hf3II,wEAIA,oCACE,6De8IN,mEAGE,sCAMJ,sCAEE,sCAEA,sCACA,qRAyBM,yEAII,0DAIA,oGAKJ,CAIA,mBAJA,YACE,gBAGF,6BACE,8DAKA,CAVF,iBAUE,4JAWA,kBACA,CADA,+BACA,6BACA,eAaE,kCvB5LR,CuBwLM,0CAIE,CAPA,8CAGF,CAJA,+CACE,CANF,4CAKA,CvBpLN,oBuBsIA,CAEI,kBACA,yCAEA,wCAGE,uCACE,8BAGF,4BACE,kGAKJ,6DAIA,mCACE,CAIF,gBAHE,gBADA,cAIF,yDAIA,mCAIE,4BACA,eACA,cAEA,4BACA,CAFA,oBACA,CADA,iBAEA,0DACA,mCAKA,kBACE,kBAGF,CAJA,2BAIA,iBA7CF,wBACE,CAEA,yEAKE,qDACA,CAXJ,4BAEA,CvB3IJ,4CuBwII,cACA,CA+CE,6EvBxLN,CuBoJQ,8CAIJ,wCACE,gBAGF,uCACE,oBACA,uBAOF,qDAGE,CANA,SAGF,CAJA,oBAOE,sBAKA,iDACA,CACA,wBADA,2BACA,qBf9NJ,CeyNI,oBACA,aACA,sBACA,CAFA,Wf1NJ,oBemOI,uDAIA,0CACE,kBAEA,gBACA,2BvB5LR,+BuBwII,kBACA,8CAEA,iBACE,yCAMA,gDACE,CALF,iDAKE,sCACA,oDAIJ,sBACE,gBAGF,mCACE,0CAaA,kCACA,mBACA,CAXF,WACE,CAGF,qBAEE,CANF,gBAWE,gCACA,yBACA,gBACA,CARA,4BAEA,CAPA,oBAGF,CAJA,YAcE,4Df9NJ,8CemOI,wBACE,CAGF,mBAHE,SAGF,2BACE,kBAEA,gBACA,2BvB5LR,+BuBsIA,kBAGI,8CAEA,iBACE,yCAMA,gDACE,CALF,iDAKE,sCACA,oDAIJ,sCACE,mCAGF,YACE,8BAYA,kCAEA,mBACA,CAXF,WACE,CAGF,qBAEE,CATA,eAGF,CAWE,gCACA,yCACA,CAPA,4BAEA,CAPA,oBAGF,CAJA,YAaE,gDACA,0DAKA,yBACE,kBAGF,CAHE,SAGF,6CAEE,gBACA,2BArDR,+BAGI,gEAGE,iBAEA,yCAKE,gDACA,CALA,iDAKA,sDAIJ,oCACE,sBAGF,mDAEE,YAGF,8BAUE,kCAEA,oBARF,WAEE,CACA,qBAEA,CARA,eAGF,CAQE,gCACA,yBACA,iBALA,4BACA,CAJA,oBACA,CAHF,YAUE,gDAKA,0DAIA,yBACE,kBACA,CAFF,SAGE,4BACA,kBAmBV,2CACA,iDACA,+DACA,yCAGA,iDAFA,iDAEA,CACA,yFAME,uBACE,4FCjRJ,qDACA,CALA,WACA,CACA,sBAHA,eACA,CAKA,yDACA,iBAJA,4BACA,CAFA,qBADA,YAMA,gDAEA,mFACA,wDACA,mBACA,2CACA,gCACA,kBACA,+CAEA,2DAOA,gDAEA,CALA,iDAKA,uCACA,gBACA,qCACA,sCACA,+EAWE,kCAGE,mB7BtBF,C6BaA,YAIF,qBACE,C7B3BA,gBASA,yDACA,iB6BiBA,4BACA,CANA,oBAIF,C7B1BE,YAUA,iD6ByBA,2D7BZA,2CACA,CADA,SACA,4D6BmBF,yFASA,iBACA,sCAKA,gDACA,CANA,iDAMA,mCAGF,gBACE,uDAEA,gDAGF,YACE,2BAkBA,kCAEA,mBACA,CAZE,YASF,sBAVA,eACE,CAYF,yDACA,iBAJA,6BATE,oBASF,CATE,YAaF,6CAEA,uD7B7FE,sD6BkGJ,kDACE,4CACA,+CAEA,8F7BtGE,oD6B0HA,wD7B1HA,uR6B8IJ,8BAMA,wB7B3II,wDACA,yE6B+IJ,0D7BlII,gDACA,4GKoBA,+BwBgIA,6BAGA,qDAMI,sBACA,mBAKA,iD7B1KJ,mCFq5IF,gCAIA,qB+BpuIU,uBAIJ,C/BguIN,kC+BpuIU,CAIJ,oE7B3KJ,2CFm5IF,CARA,yCAJA,mC+BruIU,4B/ByuIV,CAJA,W+BruIU,C/BquIV,iBAYA,yD+BluIU,qB/BsuIV,CAJA,kBAIA,wF+BjuIU,2DClOV,ChCm8IA,kBgCn8IA,8BAEA,2HADA,qBACA,+DACA,wBAEA,2BAFA,aACA,wDACA,aACA,iCADA,2CACA,gBAEA,mCACA,CAHA,kDAGA,sCADA,eAEA,0DACA,cAGA,kHACA,CAFA,8BACA,CADA,gBAFA,iEAIA,0BACA,uFACA,cAGA,+GAKF,CANE,8BACA,CAHA,iEAQF,yBAEE,uFAGA,mBAOA,ejB3BI,CiBsBJ,mDAEA,mDAGA,CAPA,mDjBpBI,oCAIA,wEiB0BJ,oBAEE,kDACA,CAHF,mDAGE,mBAEA,gDACE,CAHF,0DAEA,CAFA,+BAGE,0CACA,4FAKJ,2DAEE,4BAGA,4DAEA,CAJA,6DAIA,mBACA,mEjBlDE,2CAIA,4BiBsCJ,wCAYA,aACE,CADF,aACE,oCAKA,6BADA,yBACA,kGAOF,yBAIA,qGAEA,iE9B7DE,4B8B+DF,wB9B/DE,oG8BkEA,wB9BlEA,uGACA,2B8BsEF,sDAKA,oC9B9DE,gRACA,qD8BsEA,0F9BtEA,uE8B6EF,2FASA,sTAaI,sC9B3HF,kD8BsIE,+DACA,4TChJJ,qHAKA,sCACC,CAAD,8FAEA,qD/BCE,mB+BMF,mBAKI,2CACA,SACA,iBAJF,mCAEE,C/BXF,Y+BMF,CAGE,eAIE,qBAPJ,2EAGE,C/BTA,kB+BWE,gBAEA,yCAKF,CAZF,UAYE,0DCnCF,mDpC6RI,8CoC1RJ,gGAGA,CALA,sCAKA,6FACA,yDACA,iDACA,yBAGA,6CACA,+EAEA,CAHA,WAFA,cACA,yCACA,kBAGA,kDACA,CANA,wCAMA,wCACA,uCACA,4DACA,uD/BjBA,oD+B0BA,C/B1BA,U+BiBA,SASA,mBACA,gCpCgQI,uCoC9PJ,+EAEA,CAJA,+BAIA,+BACA,kHnBpBI,+GAIA,gEmBqBF,qCAEA,0CAIF,2DAEE,CANA,4DAMA,0DAMF,iEAGE,CAPA,kEAOA,kDAKF,2DAEE,ClB7DF,4DkB6DE,8FAGA,qDAKF,iCACE,8BADF,cACE,8CAKE,yDhC9BF,gIAdA,6DACA,qTJ2PE,6TqC/QJ,wCrC+QI,4BqC7QJ,wDAEA,gCACA,wDjCFE,kGiCiBF,CCxBA,yFACA,CDuBA,YChCF,eAEE,CAKA,wCACA,iBAHA,gDAEA,CALA,qEAOA,mCACA,gDAIA,0CAEA,wCACA,YACA,yClCHE,CkCAF,4DlCAE,yBkCQJ,4CAOE,aACA,iCAQF,mCACE,CAGA,+BACE,0CAIA,qCASA,oDACA,oDACA,sDACA,uDAJF,+CACE,0DAEA,uDACA,oHAFA,oEACA,4CACA,yDAHA,mDACA,6DACA,cACA,gBADA,cACA,oDAJF,iFAEE,CACA,gCACA,CAJF,aACE,CACA,wCACA,CAFA,qEACA,CAEA,iBAJF,CAIE,qBAFA,6HAFF,wCACE,0BACA,mBACA,8CACA,qDAHA,CAEA,uCADA,SADA,kBAEA,+CACA,gDC5DF,CD0DE,sCACA,UACA,CAHA,SCzDF,qFAMF,sDvCqRM,CuC1RG,uCADP,CACO,SvC0RH,0CuC/QJ,iDACA,wDAEA,CAJA,yCACA,oBAGA,yCACA,gBAGA,4CAEA,mCnCNE,4DmCaJ,CvC+PM,yDuC/PN,kCAKE,6DAGA,CANA,0DAMA,gDtBxBI,kCAIA,2FsBwBN,CrBAE,qLqBEA,mFAGF,2BAIA,uEAKE,CCpDA,2CACA,CDmDA,2BAKM,CALN,oBACE,oCAGE,wCAJJ,eACE,4DAII,iBC3DR,CAEE,uBAFF,kBAGE,CACA,uDACA,oEACA,wDACA,yBACA,oCACA,4EACA,iDACA,+BAGA,mCACA,0EAEA,CAHA,4BADA,2CACA,CAFA,2DACA,CAFA,iBAMA,8BACA,4CAIA,CAJA,eAIA,oBAGA,kBACA,+BAIF,oBACE,CpChBE,yBoCeJ,CpCfI,MoCeJ,SACE,gBACA,gDAEA,0CAEE,wDAUJ,sDAGE,kBAGA,8FAGE,0DAEA,wDAGF,gBACE,gDACA,kGAUF,sDACA,0DACA,uCAEA,qDACA,mEAEA,gDpCtDE,0CoC0DF,wDpC5CE,sDoCgDF,8DAEE,yCAEA,uDAIF,qDAEE,cACA,8CACA,8FAKA,oDAGE,0DACA,4FAiBE,4FpCvDJ,+BAZA,gCoCwEI,oFpCxEJ,wEoC6EI,oDACE,oDAGF,6BACE,6BACA,6CAEA,CAEE,qFAxBR,CAsBM,aACE,sCACA,CAFF,gCACE,gBAvBR,epCnDA,0CoC4DI,CALA,kCpCvDJ,CoCoDE,kCAGE,wCpCvDJ,kBoC4DI,6CpC5DJ,kBoC4DI,wCpCxEJ,qDAYA,qKoCsEM,oEAGA,6BACE,2DACA,2FAxBR,kDAII,+DpCvDJ,mDAZA,0CoCwEI,yFpCxEJ,oDoC6EI,sDACE,oCAGF,sCACE,uDACA,4DAEA,sDACE,yDACA,wDAxBR,yDAII,8CpCvDJ,oEAZA,4CoCwEI,CpCxEJ,gDAYA,CoC4DI,mCpCxEJ,gBoCwEI,cpC5DJ,sBoCiEI,wFAIA,kCACE,mDACA,uCAEA,oBAFA,UAEA,6D/BpFN,qD+B+DE,CAuBM,kE/BtFR,C+BqFQ,SAnBJ,gCpCvDJ,uDoCuDI,8CpCnEJ,kBAYA,wCoCiEI,iFAIA,CpCjFJ,gCAYA,CoC4DI,6FpCxEJ,kBoCwEI,CpC5DJ,oBoCqEI,8BACE,8BACA,gCAEA,6BACE,kCADF,kCACE,qDAvBR,iDAII,CAoBI,yCAxBR,oBAII,yBpCvDJ,+CAZA,sDoCwEI,CALA,uCpCvDJ,CoCuDI,SAKA,mCpCxEJ,4DoC6EI,kDAIA,CpCrEJ,qDoCqEI,0CACE,sEAGA,sFACE,sEAkBV,4BAjBU,0DAiBV,gDACE,YAEA,0DAeA,oBAdE,kDAcF,iEAGA,oDADA,sDACA,0BACA,6CACA,wEACA,4DACA,mGAEA,2BAVF,CASE,0DATF,mDACE,yEAEA,mBACA,CADA,kDACA,oEAEA,oDADA,sDACA,2BACA,4CACA,yEACA,4DACA,mGARA,2BACA,CAFA,0DAEA,+DACA,6DACA,mBACA,CADA,kDACA,oEAEA,mDACA,CAFA,sDAEA,qDACA,2FATA,4DACA,mGAGA,4BADA,0DACA,mDACA,yEAEA,oBADA,kDACA,oEAEA,mDATA,CASA,sDATA,sDACA,2FAEA,4DACA,mGAEA,4BADA,0DACA,mDACA,YACA,6DACA,mBAVF,CAUE,kDAVF,oEAGE,mDACA,CAFA,sDAEA,yEACA,0EACA,4DACA,oGAGA,4BADA,0DACA,oDAVF,YACE,8DAEA,oBADA,kDACA,qEAEA,mDACA,CAFA,sDAEA,mCACA,yFACA,CADA,mBACA,CADA,oBACA,CADA,kBACA,+CACA,+CACA,qDAVF,+CACE,6DACA,4DACA,CACA,+DACA,6DACA,iEACA,yDACA,0DACA,oEACA,mFCzLF,gHAEA,4DACA,kEAEA,6DACA,mEAKA,2DAGA,kIAGA,0BAIE,qDAEA,+CAGF,6DAEE,4DAIF,gEAGE,6DACA,iEAKF,yDASE,0DATF,oEC/CA,uBACA,kDAEA,4CACA,0DAGA,4DACA,6DACA,6DACA,8DAEA,sDACA,uDACA,iEAGA,+EAGA,+CACA,CACA,4DACA,4DACA,gEtCPE,6DsCeF,iEAMA,yDAIA,0DAEA,oEAGE,yBAIJ,oDAGE,0GAEA,4DAEA,4HtChCE,gEACA,iHsCmCA,mEACA,wBAKF,mDC7DF,6CAGE,2DAGA,4DAEA,8DACA,6DACA,+DACA,uDACA,wDACA,kEAEA,yEACA,4CACA,0DAEA,4DAEA,6DACA,6DAKA,8DAIA,sDAYF,uDAGE,iE1BvCI,yX0BsDF,6DAIJ,+DAGE,+BACE,qCAIF,uEAMA,CAMF,uEAME,+BACA,CAbA,kBACA,CACA,WAWA,mCACA,CAZA,cADA,SAaA,6BAZA,+BvCvEE,CuCmFF,iBAEA,yCvCrFE,CuCqFF,oBvCrFE,6DuC+FF,0CACA,CvChGE,SuCgGF,yCC5GA,4CACA,CANA,mBAGA,0CAGA,CDoHF,iDAGE,+CAEA,wCACA,iHvCtGE,yDACA,kEuCyGA,0GACA,8IAKJ,sDAEE,kEAUA,CAUA,2BACA,CADA,oCACA,sEACA,4CvC1HE,CuC0HF,sCAJA,2BACA,CAJA,mCAGA,CAJF,cACE,CAIA,mBACA,CAVA,+BvC9GE,4CACA,qDuCsIF,cACE,oBACA,CvCxIA,iBuC+HF,CACE,yBlC5GA,mBkC2GF,8BASE,yEAKA,eACA,kBACA,ClC3HA,2BkC4IA,CANA,0ClCtIA,CkC4IA,oFAgBE,0FzCuzKN,2FyC3yKQ,ClCxKJ,kCkCoIF,CAXE,YACA,CACA,2DA6CI,0BAhBF,qCAIA,ClC/IF,gDkC+IE,aACE,oBvCzMJ,CuCwME,iCvCxMF,8BFmhLF,wEEnhLE,mBuCmNE,gCACE,2DAlBJ,+CAIE,oDAGE,0DzC81KN,yFyCp1KI,iClC1JF,iCkCyIA,oCAEE,sDAIA,sDzCm3KJ,qFE3jLE,sDuCoNI,sDAlBJ,CAME,yBAJA,OvCpMF,SF+kLF,CyCv4KI,iBAEE,gBvC1MJ,CuCkMA,cACE,OAKA,WAJA,8BzC24KJ,gEE/kLE,CF+kLF,4ByC53KI,2BAXA,mCACE,4BvCzMJ,CuCmNE,yClC1JF,kCkCyIA,iEvClMA,wCFmmLF,qEyCh5KI,qCACE,mCEpON,6BACA,sBACA,0BACA,4C7CyRI,yC6CtRJ,+BACA,sCACA,uCACA,mBADA,YACA,iDAEA,gBCbA,mLDkBA,2BClBA,CDgBA,kCAEA,CClBA,8BDaA,iBAGA,CAEA,UClBA,iBAEA,yBACA,sBAEA,0BACA,CAKA,sCAEA,CAHA,YACA,CAJA,MACA,CAFA,cACA,OAEA,WACA,CAFA,iCAKA,sBACA,+B9CgRI,kC6CrQJ,eAGS,mBAIP,2FAIE,2DAQN,6DAhBE,0BAAS,8BAGP,uCAaJ,0BAGE,4IAEE,CALJ,2FAKI,cACA,6CAKJ,CANI,eAMJ,0DACE,CADF,iBACE,eACA,mBAGA,2CAGE,6DAMJ,CAPI,8DACA,CAHF,wFAEE,CANF,2BACA,uCACA,sEAWF,qFACE,gCAEA,mEACE,gBACA,CAFF,iBACE,CADF,+BAEE,gFACA,kDAKJ,qDACE,qBADF,0BACE,kCACA,wBACA,CADA,WACA,iEAEA,0FAEE,mHACA,yBADA,WACA,iFAwBF,sDACA,6CAEA,0BzCjGE,oCyCiGF,WzCjGE,0C2ChBF,wB/C4RI,C+C5RJ,W/C4RI,iF+CzRJ,sDACA,6CACA,0BACA,qBADA,cACA,CADA,WACA,kDACA,iBADA,WACA,iFAEA,sD/CmRI,e+CjRJ,+BACA,0BACA,qBADA,cACA,CADA,WACA,CACA,yCACA,qCACA,gFAEA,sDAGA,8CAEA,2BDzBA,wQAIA,eACA,mCAEA,6BACA,8BAEA,+BACA,sBACA,gCAEA,qC9CgRI,yC+C9PJ,mDACA,yBACA,iE3ChBE,C2CyCA,qBArBF,cAEE,+KAQE,sCAEA,CASF,sCAHF,kCACE,sBAEA,gBAHF,gBAlBA,+BAEE,CAmBA,UAHF,qDACE,sCAEA,mBAFA,iBAEA,qB3CzCA,gC2CyCA,sFAEE,0GAGF,oKAKA,wJASF,yIACE,sCADF,gDAPI,QAQF,8FAIA,qCAFA,4CAEA,iJAEE,0DAGF,CAHE,mBAGF,yCAHE,iDAGF,CAHE,uDAGF,CALA,UAKA,kGAEE,2CAGF,gHAUF,yCARI,kDAQJ,CARI,wDAQJ,CARI,yDAQJ,CARI,kBAQJ,CATI,WASJ,+FAGE,oOAEE,0DAGF,wCAHE,gDAGF,CAHE,oBAGF,CAHE,uDAGF,CAHE,SAGF,gBAKA,qCACE,8CACA,CAFF,8BAJE,qCACA,gEAGF,kBAEE,UAKJ,uHACE,iDAIA,6DACA,sDAEA,2FAMF,yFACE,wEACA,4BACA,8CAEA,uOAEE,C/C8IA,oB+CzGJ,CACA,4BADA,qCACA,2EACA,8C3C5JE,C2CqHE,6LAQF,uCA2BF,qC/C2GI,C+CtIF,kCAyBJ,qBACE,gBACA,CA3BE,gBARE,sCAQF,eACE,sCACA,qCAuBN,CACE,yDAlCI,gC3CrHF,sCACA,sC2C+JA,C3C/JA,mC2C+JA,8DC9KF,0BAGF,CDgLE,UACA,CADA,+BChLF,4FAOE,+EAIF,CACE,gN/BbI,qBAIA,CAJA,wDAIA,CAJA,yDAIA,CAJA,+CAIA,0Gf+4LN,+CAMA,CANA,QAMA,wGAKA,qC8C33LE,C9C23LF,qC8C33LE,8F9Cq4LF,qC8C33LI,6E9C23LJ,0Pep6LM,0DACE,CADF,mBACE,CADF,iDACE,CADF,uDACE,4G+ByEN,iDAGA,CAJA,MAIA,0G9C63LA,wCe18LI,mCf08LJ,kGAMF,4E8Cr3LI,6NAoBF,kDAYA,CAZA,wDAYA,CAZA,yDAYA,CAZA,kBAYA,gHAWF,kDAIE,CAJF,KAIE,8GAUA,wCAGE,CAHF,kCAGE,kH/B3JE,8E+BwJJ,C/BxJI,W+BoKF,aAEA,CAHA,QACA,CAGA,mD/BvKE,C+BkKF,iBACA,OAGA,mCAdF,+F9Cy5LF,qC8Cz3LE,8EAKA,qC9Co3LF,uN8Cn2LI,0D9C82LJ,C8C92LI,gD9C82LJ,C8C92LI,oB9C82LJ,C8C92LI,uD9C82LJ,6G8Ct3LI,iD9Cs3LJ,O8Ct3LI,2GAGF,sCAIA,CAJA,oCAIA,iBCrNA,4CACA,kFAGA,4HALA,oCACA,C/CglMF,4C+CjlME,CDuNE,e9C03LJ,C8C33LE,6ECjNA,mCAIF,eACO,mCADP,yEACO,uEAIP,iBAGE,eACA,CAFA,iBACA,WACA,uBACA,sBADA,aACA,gBAKA,6DACA,CALA,YACA,8BAGA,CAJA,kBAKA,4CAKA,qCACA,uEAYE,CAtBF,UAsBG,CAAD,sDAGA,eACA,gEAJA,sFAIA,kCAOF,2BACA,yEAEA,mCACA,4BAGA,+BAIF,UAEE,sBAIA,gBANF,2BAME,kJC5EF,wGAKE,mCACA,CAFA,SAEA,wCACA,oFAEA,+DAEA,mBAEA,wBAFA,SACA,WADA,mCACA,CACA,UzC6DE,CyC9DF,UAFA,kBAEA,iBACA,CAHA,KACA,CzC+DE,4ByC5CF,CAlBA,UADA,SAmBA,wCAII,8CAEA,qIAKA,WjC3BA,qBiC2BA,oBjC3BA,wBAIA,8FiC+BE,wBAHF,2BAGE,0BACA,CzCwBJ,qByC5BE,YzC4BF,UyCxBI,mTAQA,6BACA,uRAUA,sBzCKJ,SyCFE,mCAEE,CzCAJ,OyCCI,kBACA,gBACA,CAFA,2BANA,iBzCKJ,kByCGI,uCAIF,2BAEE,CAFF,sBAEE,SzCTJ,oCyCYE,CAHE,iCzCTJ,CyCGI,mBzCHJ,cyCOE,CAJE,aACA,2BzCJJ,CyCII,iBAQF,WARE,SzCJJ,oByCYE,4BARE,UAQF,wCzCzBF,sCyCkCI,eACA,+BACA,4BAEA,cACE,CAKA,WALA,QAGF,uBAEE,CAFF,oBANA,iBAEA,WAOE,iBACA,uFA7ER,uCAII,gCACA,sDAGA,uDAEA,qPAMA,uCAGE,gCACA,0IzCwBJ,uHyChBI,yCAKA,oGAKA,6FACA,CAPF,iBACE,CALA,qBACA,+EAGF,CAJE,6BAWA,mCzCKJ,kCyCFE,wBAEE,4BACA,kCAEA,0FACA,sEzCJJ,mCyCOE,2CAEE,gCAGF,qJzCzBF,yByC/BF,gCAiEM,iCACA,6BACA,mBAEA,qCACE,eAGF,0BACE,6BAGA,mBAEA,sCzCnCN,8ByC5CF,uBAEI,yBAEA,qCAEA,mCACA,yCAEA,8BACA,4BACA,uBACA,yBjC5BA,wCAIA,+DiCYJ,EjCXM,kFiC6BA,0BACA,2BACA,uFACA,8EAGF,mDAGE,+DACA,8DACA,qDzCeJ,qCyCZE,6BAIE,czCQJ,2ByCFE,CAHE,uCzCKJ,CyCRI,QACA,CACA,+BACA,CADA,kDAIF,UANE,eAMF,yCAGE,CANA,kBAFA,kCAQA,kEAEA,2DACA,8BzCJJ,oFyCSI,CALA,aAKA,mCzCTJ,4ByCYE,CARE,+BAQF,6BASE,mFAEA,CzCpCJ,cyCoCI,kCAEA,2BAIA,CzC1CJ,+ByC0CI,CACE,4BzC9BN,qFyCxCE,CAyEI,MAxEJ,mCAEA,4BACA,CACA,2DAsEI,iCzCnCN,CyCiCM,MAEA,CzCnCN,gByCiCM,OzCjCN,CyCnCE,+BjCzBA,kFAIA,mCACE,2BRuDJ,CyC5BE,sDAGE,sBACA,8EACA,mBAmCF,CAnCE,yBzCuBJ,cyCpBE,0BACI,CAAF,6BAEA,mCACA,4EzCgBJ,kCyCZE,CAHE,2CzCeJ,CyCfI,SAGF,8BAGE,czCSJ,2ByCFE,CzCEF,wCyCRI,QACA,CACA,+BACA,CADA,kDAIF,UANE,eAMF,yCAGE,CANA,kBAFA,kCAQA,kEAEA,2DACA,8BzCJJ,oFyCSI,CALA,aAKA,mCzCTJ,4ByCYE,CARE,+BAQF,6BAxDJ,mFAmEM,CARA,OzC5BJ,CyC4BI,MAQA,kCAEA,2BAIA,CzC1CJ,+ByC0CI,CACE,4BzC9BN,qFyCxCE,CAwEI,KACA,CAzEJ,mCAEA,4BAEA,CACA,2DAoEI,iCAEA,CAFA,OAEA,ezCnCN,CyCiCM,OzCjCN,CyCnCE,+BjCzBA,kFAIA,mCiCYJ,2BzC4CE,CyC5BE,sDAGE,sBACA,8EACA,mBAmCF,CAnCE,yBzCuBJ,wCyCpBE,8BAGE,mCACA,4EACA,kCAGF,CAHE,qDAGF,8BAEE,cAKA,2BzCKJ,CyCLI,wCAHA,SAEA,+BACA,CADA,kDzCMJ,SyCFE,CAPE,cACA,CAMF,yCAEE,CALA,kBAHA,kCASA,kEAEA,yFzCHJ,oFyCSI,CALA,aAKA,mCzCTJ,6DyCYE,4BzCzBF,mFyCoCI,CAXF,OAGE,CAHF,MAWE,mCAEA,2BAVA,+BAUA,CAIA,4BAME,qFA3EJ,CAsEI,MArEJ,mCAEA,4BACA,CACA,2DAmEI,iCAGA,CAJA,MACA,CAGA,gBAJA,OAxDF,CAVF,+BjCzBA,kFAKE,mCiC2BF,2BAGE,CACA,2FACA,+DAKA,kBACA,CzCiBJ,CyCjBI,yBACA,yGACC,CAAD,gCAGF,2CAME,mCAFA,yBACA,kBACA,CAFA,SAEA,6CASA,oEARA,SAMA,+BACA,CAJF,kCACE,CACA,cACA,CAEA,SACA,CATA,eASA,0CAFA,iBACA,CARA,kCASA,mEAKA,4DAGF,8BNtFJ,oFASA,CMgFM,OAHF,KAGE,CNhFN,mCACA,4BAAS,CM+EH,+BN/EG,CM+GT,4BAGA,mFAGE,CAJF,cAIE,8DAJF,+BAIE,CACA,4BAEA,qFAKF,CAPE,MAOF,mCACA,4BAGF,CAEE,2DAZE,kCADA,MACA,iBADA,OCzHA,CDsIF,+BC9IA,kFAIA,mCAGA,2BACE,CAOF,sDAOF,sBACE,eAKA,CACE,iFPlBO,COkBP,0BAIJ,wCAMA,8BALE,mCAMA,8GACA,CADA,qDACA,+BACA,eAGF,2BAEI,wCAFJ,CAHE,SAGF,+BAEI,CALF,kCAGF,gBAEI,UALF,eAKE,yCC9CF,CD8CE,kBALF,kCCzCA,mECIE,eACA,6EAFF,oFAEE,6EAFF,CAEE,+BAFF,CACE,6BACA,qIAFF,0BAEE,mJADA,0CACA,4HADA,MACA,iBADA,OACA,mHAFF,mCAEE,oFCHF,sBACE,iFACA,mBDDF,CCCE,mEAGE,iEAGE,8CACA,kEARJ,CADF,YASM,yCARJ,aAIE,4BAHF,uCAGE,CAJF,SACA,gCADA,YACA,sCAGE,UAJF,eAIE,yCAGE,CANJ,kBADA,kCAOI,wCACA,2IARJ,CAQI,aARJ,mCACA,6BAOI,+BAPJ,6GAMI,CANJ,cAMI,mCACA,2BAPJ,+BAOI,0BATN,qFAEE,CAOI,MAPJ,mCAGE,CAHF,iFAOI,wDATN,CASM,QAPJ,4BAMI,kFACA,+GATN,sBACE,qEACA,4DAGE,CAHF,iEAGE,mCAGE,yEACA,CADA,aACA,kGATN,6BAKI,qDAGE,CANJ,oDAGE,CAHF,mDADA,qFAOI,mEACA,CADA,eACA,CATN,gBASM,YARJ,gBAQI,mEARJ,cACA,0CADA,oCACA,WADA,qBACA,mCAGE,CAHF,oBAGE,iDAGE,gFACA,6GATN,CACE,gFACA,kCAOI,mGAOR,CAdI,uJAMI,gDAQR,qCACE,oFACA,6FAGE,CAHF,aAGE,oDAEE,mEACA,CAHF,oBAGE,0HCzBJ,CDyBI,oBCzBJ,0JCHF,kCAGE,mEACA,CDDA,oBCCA,oDACA,kEACA,CAFA,oBAEA,iBAGE,kCvCOE,kEAIA,CuCZJ,oBvCYI,gBACE,kCuCDJ,oEACE,CAbJ,oBAaI,mKClBJ,2DAKE,8BAIF,CACE,oFAUA,yCADF,aACE,6EClBF,yEASA,iBACA,aASI,6DAKF,8BACE,uFANA,6CAKF,aACE,+CAEA,8BATF,yEAMA,eACE,wEjDiCF,CiDxCA,6BACE,qFAMA,yCjDiCF,aiDxCA,+CAEE,8BAKA,yEjDiCF,yBiDvCE,wDAKF,8BACE,kFC1BJ,mCAIF,aAEE,gDAEA,8BCRF,0ECKE,eACA,aACA,2DAEA,8BAEA,qF3D8iOF,yC2D1iOI,aCdF,gDAGE,CACA,6BCLJ,0ECJA,CACE,0BACA,0DAGA,8BCoEU,2HAPJ,aAOI,sJAPJ,aAOI,4NAPJ,aAOI,iDAPJ,CAOI,oHAPJ,aAOI,wDAPJ,8BAOI,kIAPJ,8CAOI,sGAPJ,CAOI,8BAPJ,kEAOI,yYAPJ,+DAOI,+IAPJ,CAOI,SAPJ,YAVA,kBACE,mCADF,2BACE,CAgBE,oBAjBJ,YACE,qLADF,CACE,yBADF,oCACE,iEADF,eACE,uRADF,iBACE,0BASF,WATE,gDASF,UAOI,YAPJ,OAOI,kBAPJ,gBAOI,YAPJ,sBAOI,iCAPJ,aAOI,wBAPJ,6CAOI,iEAPJ,OAOI,kFAPJ,uBAOI,gBAPJ,CAOI,kFAPJ,yDAOI,CAPJ,eAOI,cAPJ,kBAOI,mCAPJ,eAOI,uCAPJ,uBAOI,6LAPJ,CAOI,0BAPJ,2CAOI,2FAPJ,CAOI,0BAPJ,CAOI,2BAPJ,gBAOI,4WAPJ,CAOI,kBAPJ,CAOI,oBAPJ,CAOI,oEAPJ,4BAOI,CAPJ,yHAOI,kDAHI,SAGJ,WAHI,OAGJ,iBAPJ,QAIQ,CAJR,MAIQ,SAGJ,iGAPJ,6BAOI,sBAPJ,eAOI,4JAHI,8CAGJ,wHAPJ,oBAIQ,YAGJ,oJAPJ,kBAIQ,yBAGJ,4HAPJ,kCAIQ,aAGJ,uDAPJ,aAOI,4EAHI,yBAGJ,mIAPJ,oBAIQ,2BAGJ,iDAPJ,CAOI,yFAHI,oBAGJ,8JAPJ,iBAOI,8FAPJ,gBAOI,iMAPJ,6BAOI,yFAPJ,YAOI,uoBAhBF,oBADF,4KAUA,kBAOI,qKAPJ,oBAOI,uHAPJ,0BAOI,QAPJ,eAOI,0BAPJ,CAOI,2BAPJ,WAOI,8BAPJ,oBAOI,aAPJ,qBAOI,0BAPJ,6BAOI,+BAPJ,yBAOI,4BAPJ,8BAOI,iIAPJ,4CAOI,qCAPJ,qBAOI,iFAPJ,2CAOI,sFAPJ,WAOI,+JAPJ,qCAOI,wLAPJ,gBAOI,sIAPJ,kBAOI,yBAPJ,sDAOI,mIAPJ,iBAOI,qBAPJ,CAOI,2EAPJ,mBAOI,sBAPJ,8FAOI,+GAPJ,qBAOI,+GAPJ,4EAOI,gHAPJ,eAOI,uaAPJ,wBAOI,+GAPJ,uFAOI,sDAPJ,uDAOI,mDAPJ,oDAOI,sDAPJ,uDAOI,qYAPJ,WAOI,gXAPJ,OAOI,sHAPJ,qBAOI,6CAPJ,oBAOI,iHAPJ,CAOI,+LAPJ,+BAOI,CAPJ,kBAOI,qVAPJ,wDAOI,qHAPJ,gCAOI,CAPJ,yBAOI,sGAPJ,yBAOI,0DAPJ,kDAOI,+EAPJ,uBAOI,iFAPJ,sBAOI,mKAPJ,qCAOI,6PAPJ,oBAOI,oMAPJ,iBAOI,2BAPJ,UAOI,oFAPJ,iBAOI,qDAPJ,4BAOI,MAPJ,qBAOI,6BAPJ,MAOI,8BAPJ,qBAOI,8BAPJ,CAOI,wBAPJ,mCAOI,iEAPJ,4BAOI,+JAPJ,CAOI,2BAPJ,oCAOI,mCAPJ,yBAOI,8BAPJ,8BAOI,mCAPJ,6BAOI,CAPJ,0BAOI,kGAPJ,mCAOI,4FAPJ,CAOI,yBAPJ,6BAOI,mEAHI,gCAGJ,4GAPJ,wBAIQ,OAGJ,sIAPJ,OAIQ,2BAGJ,8IAHI,OAGJ,0IAHI,8BAGJ,6GAPJ,4BAOI,4HAPJ,6BAOI,qHAHI,CAGJ,yBAHI,OAGJ,8BAHI,8BAGJ,mCAPJ,CAOI,6BAPJ,OAOI,4BAPJ,4BAOI,0EAHI,2BAGJ,4HAHI,CAGJ,4BAHI,OAGJ,+BAHI,2BAGJ,CAPJ,MAOI,+DAHI,+BAGJ,iEAPJ,CAOI,0BAPJ,OAIQ,uBAGJ,mCAPJ,kCAOI,iCAPJ,mCAIQ,OAGJ,+FAPJ,OAIQ,6BAGJ,wEAPJ,mCAOI,uEAPJ,OAIQ,8BAGJ,oCAhBF,yGADF,OACE,6BASF,oCAOI,qEAPJ,OAOI,8EAPJ,2BAOI,8DAPJ,mBAOI,8EAPJ,uBAOI,sEAPJ,kCAOI,oEAPJ,eAOI,+DAPJ,iDAOI,0CAjBJ,iCAKE,yCACE,mCALF,4BAIA,gDALF,0BACE,iBAIA,+IALF,OACE,0CAIA,OACE,yCAWA,8CAKF,gDAOI,OAnBN,2BAOI,+BAKF,wCAOI,wCAZF,0CAKF,oCAOI,qCAnBN,YAIQ,0BAGJ,iLAPJ,mCAIQ,+BAGJ,yKAPJ,4BAIQ,mCAGJ,qEAPJ,CAOI,qGAPJ,kBAIQ,mCAGJ,4KAPJ,mBAIQ,wDAGJ,gKAPJ,qEAOI,4JAPJ,YAIQ,2EAGJ,yJAHI,mEAGJ,iCAPJ,CAOI,wHAHI,gCAGJ,sIAjBJ,yEAKE,gEALF,aACE,sEAIA,CACE,gEALF,gCAIA,yDACE,iEALF,YAIA,yEALF,sEAKE,gCACE,mCANJ,0CAKE,kEACE,CAIJ,eAIQ,mBAGJ,yJAHI,qBAGJ,sIAPJ,qBAIQ,yCAGJ,kHAHI,qBAGJ,+TAHI,gDAGJ,iHAPJ,uBAOI,6IAPJ,sBAOI,8JAHI,CAGJ,kHAHI,sBAGJ,qEAPJ,8CAOI,2FAHI,0CAGJ,sCAHI,CAGJ,gFAPJ,0CAOI,8KAjBJ,6FACE,2BADF,6BAUA,mFAOI,qJAPJ,CAOI,mMAPJ,6BAOI,kFAPJ,0FAOI,yBAPJ,6BAOI,8KAPJ,wBAOI,kcAPJ,6BAOI,2LAPJ,6BAOI,gfAPJ,+BAOI,0sBAPJ,UAOI,kaAPJ,2EAOI,WAPJ,iBAOI,q4CAPJ,oBAOI,0MAPJ,oBAOI,yFAPJ,CAOI,mNAPJ,mBAOI,kNAPJ,kCAOI,kEAPJ,wFAOI,gEAPJ,iCAOI,6CAPJ,kCAOI,yLAPJ,yCAOI,8LAPJ,+BAOI,yvBAPJ,gBAOI,yMAPJ,gBAOI,uTAPJ,CAOI,qNAPJ,uCAOI,+KAPJ,uCAOI,gsBAPJ,2CAOI,sLAPJ,yGAOI,CxDVR,yCwDUQ,oLAPJ,CAOI,oGAPJ,yHAOI,8NAPJ,uCAOI,CAPJ,6DAOI,kOAPJ,CAOI,uCAPJ,uLAOI,2GAPJ,kBAOI,sCAPJ,sCAOI,6DAPJ,gEAOI,2GAPJ,mBAOI,2CAPJ,6DAOI,4CAPJ,6DAOI,iSAPJ,+DAOI,6HAPJ,wCAOI,gEAPJ,CAOI,0GAPJ,gEAOI,wCAPJ,CAOI,8DAPJ,4GAOI,kEAPJ,gBAOI,2CAPJ,qGAOI,+lBAPJ,yCAOI,4NAPJ,iKAOI,kBAPJ,wCAOI,gEAPJ,mGAOI,iKAPJ,iEAOI,2IAPJ,YAOI,2BAPJ,OAOI,oBAPJ,yBAOI,kDAPJ,yBAOI,8JAPJ,kDAOI,wEAPJ,sBAOI,6EAPJ,CAOI,uIAPJ,sBAOI,8KAPJ,YAOI,uHAPJ,uBAOI,2CAPJ,gBAOI,qDAPJ,oCAOI,sGAPJ,iBAOI,iKAPJ,0BAOI,6MAPJ,gCAOI,kKAPJ,6DAOI,qDAPJ,8BAOI,oDAPJ,CAOI,6GAPJ,4IAOI,8BAPJ,2BAOI,8HAPJ,+BAOI,8CAPJ,sBAOI,mDAPJ,6BAOI,kDAPJ,yBAOI,qDAPJ,4BAOI,gDAPJ,iBAOI,4DAPJ,8BAOI,4DAPJ,gBAOI,iBAPJ,SAOI,2BAPJ,uBAOI,+BxDVR,8BwDGI,SAOI,uBAPJ,SAOI,qBAPJ,YAOI,qBAPJ,UAOI,uBAPJ,mCAOI,6BAPJ,6BAOI,UAPJ,2BAOI,iEAPJ,kEAOI,mEAPJ,2BAOI,uCAPJ,CAOI,2BAPJ,mCAOI,+DAPJ,CAOI,2BAPJ,uCAOI,mIAPJ,CAOI,2BAPJ,UAOI,mEAPJ,4BAOI,0DAPJ,qCAOI,CAPJ,mCAOI,UAPJ,yBAOI,UAPJ,2BAOI,2GAPJ,uCAOI,UAPJ,4BAOI,qCAPJ,uCAOI,qCAPJ,aAOI,2BAPJ,mCAOI,wCAPJ,uCAOI,sCAPJ,UAOI,wCAPJ,4BAOI,yCAPJ,UAOI,uBAPJ,sCAOI,qCAPJ,UAOI,0BAPJ,sCAOI,oCAPJ,CAOI,sCAPJ,4BAOI,iCAPJ,gCAOI,+BAPJ,iCAOI,+BAPJ,UAOI,wBAPJ,CAOI,yBAPJ,UAOI,8BAPJ,8BAOI,UAPJ,4BAOI,mEAPJ,oEAOI,yCAPJ,2BAOI,kEAPJ,uBAOI,gFAPJ,kGAOI,4KAPJ,uBAOI,UAPJ,sCAOI,sLAPJ,8BAOI,uCAPJ,UAOI,oHAPJ,0BAOI,yCAPJ,UAOI,wHAPJ,6BAOI,kCAPJ,UAOI,+LAPJ,eAOI,yHAPJ,WAOI,mBAPJ,cAOI,0DAPJ,eAOI,uBAPJ,eAOI,sBAPJ,eAOI,wBAPJ,eAOI,sBAPJ,kBAOI,qDAPJ,kBAOI,+DAPJ,kBAOI,0IAPJ,kBAOI,4IAPJ,CAOI,2RAPJ,kDAOI,wEAPJ,sBAOI,6EAPJ,CAOI,uIAPJ,sBAOI,8KAPJ,YAOI,uHAPJ,uBAOI,2CAPJ,gBAOI,qDAPJ,oCAOI,sGAPJ,iBAOI,iKAPJ,0BAOI,6MAPJ,gCAOI,kKAPJ,6DAOI,qDAPJ,8BAOI,oDAPJ,CAOI,6GAPJ,4IAOI,8BAPJ,2BAOI,8HAPJ,+BAOI,8CAPJ,sBAOI,mDAPJ,6BAOI,kDAPJ,yBAOI,qDAPJ,4BAOI,gDAPJ,iBAOI,4DAPJ,8BAOI,4DAPJ,gBAOI,iBAPJ,SAOI,2BAPJ,uBAOI,+BxDVR,8BwDGI,SAOI,uBAPJ,SAOI,qBAPJ,YAOI,qBAPJ,UAOI,uBAPJ,mCAOI,6BAPJ,6BAOI,UAPJ,2BAOI,iEAPJ,kEAOI,mEAPJ,2BAOI,uCAPJ,CAOI,2BAPJ,mCAOI,+DAPJ,CAOI,2BAPJ,uCAOI,mIAPJ,CAOI,2BAPJ,UAOI,mEAPJ,4BAOI,0DAPJ,qCAOI,CAPJ,mCAOI,UAPJ,yBAOI,UAPJ,2BAOI,2GAPJ,uCAOI,UAPJ,4BAOI,qCAPJ,uCAOI,qCAPJ,aAOI,2BAPJ,mCAOI,wCAPJ,uCAOI,sCAPJ,UAOI,wCAPJ,4BAOI,yCAPJ,UAOI,uBAPJ,sCAOI,qCAPJ,UAOI,0BAPJ,sCAOI,oCAPJ,CAOI,sCAPJ,4BAOI,iCAPJ,gCAOI,+BAPJ,iCAOI,+BAPJ,UAOI,wBAPJ,CAOI,yBAPJ,UAOI,8BAPJ,8BAOI,UAPJ,4BAOI,mEAPJ,oEAOI,yCAPJ,2BAOI,kEAPJ,uBAOI,gFAPJ,kGAOI,4KAPJ,uBAOI,UAPJ,sCAOI,sLAPJ,8BAOI,uCAPJ,UAOI,oHAPJ,0BAOI,yCAPJ,UAOI,wHAPJ,6BAOI,kCAPJ,UAOI,+LAPJ,eAOI,yHAPJ,WAOI,mBAPJ,cAOI,0DAPJ,eAOI,uBAPJ,eAOI,sBAPJ,eAOI,wBAPJ,eAOI,sBAPJ,kBAOI,qDAPJ,kBAOI,+DAPJ,kBAOI,0IAPJ,kBAOI,4IAPJ,CAOI,2RAPJ,kDAOI,wEAPJ,sBAOI,6EAPJ,CAOI,uIAPJ,sBAOI,8KAPJ,YAOI,uHAPJ,uBAOI,2CAPJ,gBAOI,qDAPJ,oCAOI,sGAPJ,iBAOI,iKAPJ,0BAOI,6MAPJ,gCAOI,kKAPJ,6DAOI,qDAPJ,8BAOI,oDAPJ,CAOI,6GAPJ,4IAOI,8BAPJ,2BAOI,8HAPJ,+BAOI,8CAPJ,sBAOI,mDAPJ,6BAOI,kDAPJ,yBAOI,qDAPJ,4BAOI,gDAPJ,iBAOI,4DAPJ,8BAOI,4DAPJ,gBAOI,iBAPJ,SAOI,2BAPJ,uBAOI,+BxDVR,8BwDGI,SAOI,uBAPJ,SAOI,qBAPJ,2CAOI,uBAPJ,mCAOI,6BAPJ,6BAOI,UAPJ,2BAOI,iEAPJ,kEAOI,mEAPJ,2BAOI,uCAPJ,CAOI,2BAPJ,mCAOI,+DAPJ,CAOI,2BAPJ,uCAOI,mIAPJ,CAOI,2BAPJ,UAOI,4BAPJ,0BAOI,aAPJ,4BAOI,2DAPJ,oCAOI,UAPJ,0BAOI,UAPJ,yBAOI,gJAPJ,uCAOI,UAPJ,4BAOI,qCAPJ,uCAOI,qCAPJ,aAOI,2BAPJ,mCAOI,wCAPJ,uCAOI,sCAPJ,UAOI,wCAPJ,4BAOI,yCAPJ,UAOI,uBAPJ,sCAOI,qCAPJ,UAOI,0BAPJ,sCAOI,oCAPJ,aAOI,0BAPJ,4BAOI,iCAPJ,gCAOI,+BAPJ,iCAOI,+BAPJ,UAOI,wBAPJ,CAOI,yBAPJ,UAOI,8BAPJ,8BAOI,UAPJ,4BAOI,mEAPJ,oEAOI,CAPJ,8BAOI,UAPJ,2BAOI,kEAPJ,uBAOI,gFAPJ,kGAOI,0BAPJ,CAOI,iJAPJ,uBAOI,UAPJ,sCAOI,sLAPJ,8BAOI,uCAPJ,UAOI,wLAPJ,uCAOI,0FAPJ,6BAOI,kCAPJ,UAOI,+LAPJ,eAOI,yHAPJ,WAOI,4FAPJ,cAOI,uBAPJ,eAOI,sBAPJ,eAOI,wBAPJ,eAOI,sBAPJ,kBAOI,qDAPJ,kBAOI,+DAPJ,kBAOI,6DAPJ,CAOI,4EAPJ,kBAOI,4IAPJ,gBAOI,6QAPJ,kDAOI,wEAPJ,sBAOI,6EAPJ,CAOI,uIAPJ,sBAOI,8KAPJ,YAOI,uHAPJ,uBAOI,2CAPJ,gBAOI,qDAPJ,oCAOI,sGAPJ,iBAOI,iKAPJ,0BAOI,6MAPJ,gCAOI,kKAPJ,6DAOI,qDAPJ,8BAOI,oDAPJ,CAOI,6GAPJ,4IAOI,8BAPJ,2BAOI,8HAPJ,+BAOI,8CAPJ,sBAOI,mDAPJ,6BAOI,kDAPJ,yBAOI,qDAPJ,4BAOI,gDAPJ,iBAOI,4DAPJ,8BAOI,4DAPJ,gBAOI,iBAPJ,SAOI,2BAPJ,uBAOI,+BxDVR,8BwDGI,SAOI,uBAPJ,SAOI,qBAPJ,iCAOI,iCAPJ,mCAOI,4BAPJ,+BAOI,SAPJ,2BAOI,iEAPJ,CAOI,2BAPJ,sCAOI,CAPJ,6BAOI,oCAPJ,CAOI,2BAPJ,uCAOI,+DAPJ,+DAOI,mEAPJ,CAOI,0BAPJ,CAOI,SAPJ,4BAOI,2BAPJ,SAOI,+BAPJ,2BAOI,gEAPJ,aAOI,4BAPJ,0DAOI,qCAPJ,UAOI,0BAPJ,UAOI,yBAPJ,qCAOI,CAPJ,kCAOI,aAPJ,yBAOI,yEAPJ,UAOI,4BAPJ,qCAOI,uCAPJ,qCAOI,aAPJ,2BAOI,mCAPJ,wCAOI,uCAPJ,sCAOI,wCAPJ,UAOI,yCAPJ,4BAOI,iCAPJ,UAOI,6BAPJ,oCAOI,oCAPJ,UAOI,4BAPJ,oCAOI,uCAPJ,SAOI,oDAPJ,SAOI,gCAPJ,sBAOI,iCAPJ,+BAOI,UAPJ,wBAOI,oCAPJ,6BAOI,yCAPJ,4BAOI,+BAPJ,SAOI,2BAPJ,oEAOI,yCAPJ,2BAOI,kEAPJ,uBAOI,UAPJ,wKAOI,0BAPJ,UAOI,yKAPJ,4BAOI,UAPJ,yEAOI,4LAPJ,4BAOI,wCAPJ,CAOI,4HAPJ,8BAOI,uCAPJ,UAOI,+LAPJ,2BAOI,uCAPJ,UAOI,qDAPJ,WAOI,mDAPJ,4BAOI,0CAPJ,kBAOI,eAPJ,mBAOI,sDAPJ,uBAOI,eAPJ,sBAOI,eAPJ,wBAOI,eAPJ,sBAOI,kBAPJ,qDAOI,kBAPJ,mCAOI,8CAPJ,kCAOI,CAPJ,0BAOI,kBAPJ,iCAOI,4CAPJ,+DAOI,kBAPJ,2DAOI,gBAPJ,yBAOI,cAPJ,0BAOI,iBAPJ,2BAOI,6CAPJ,oBAOI,sDAPJ,oBAOI,yBAPJ,4BAOI,iDAPJ,sBAOI,qGAPJ,yBAOI,eAPJ,wBAOI,CAPJ,oBAOI,4CAPJ,uBAOI,uDAPJ,6BAOI,qCAPJ,kBAOI,8CAPJ,4BAOI,6KAPJ,4BAOI,4GAPJ,0BAOI,uCAPJ,kBAOI,4DAPJ,oBAOI,2CAPJ,uBAOI,gBAPJ,wBAOI,6CAPJ,uBAOI,4DAPJ,8DAOI,+FAPJ,CAOI,6BAPJ,uCAOI,CAPJ,4BAOI,qNAPJ,yBAOI,uDAPJ,8BAOI,2KAPJ,CAOI,0BAPJ,+FAOI,2BAPJ,+FAOI,+CAPJ,uBAOI,oDAPJ,6BAOI,wBAPJ,2BAOI,6GAPJ,mCAOI,6CAPJ,+BAOI,gDAPJ,+BAOI,iEAPJ,UAOI,6DAPJ,sBAOI,gEAPJ,UAOI,kCCtDZ,qBD+CQ,WAOI,iDAPJ,sCC5BR,CDmCY,6BCnCZ,WDmCY,2BAPJ,CC5BR,4BD4BQ,WAOI,6FAPJ,CAOI,6BAPJ,qCAOI,oEAPJ,uCAOI,yFAPJ,CAOI,2BAPJ,WAOI,s4JEzEZ,kCACE,GACE,0BACF,CACF,CAEA,wBACE,oBAAqB,CACrB,yBAA0B,CAC1B,yBAA0B,CAC1B,4BAA6B,CAC7B,8BAA+B,CAE/B,kCAAmC,CAGnC,oBAAsB,CACtB,mBAAoB,CACpB,aAAc,CAId,eAAgB,CAFhB,iBAAkB,CAClB,wBAAiB,CAAjB,gBAAiB,CANjB,UAAW,CAQX,SACF,CAEA,8BAkBE,8CAA+C,CAC/C,4CAA6C,CAE7C,kCAAmC,CAJnC,qCAAsC,CAGtC,qCAAsC,CAXtC,kGAKC,CAND,2BAA4B,CAP5B,WAAY,CACZ,qCAAsC,CAKtC,WAAY,CAFZ,MAAO,CAFP,iBAAkB,CAGlB,OAAQ,CAFR,KAAM,CAWN,2BAOF,CAEA,gCACE,wBACE,6BACF,CACF","sources":["../node_modules/bootstrap/scss/_type.scss","../node_modules/bootstrap/scss/_images.scss","index.css","components/App.css","../node_modules/bootstrap/scss/mixins/_banner.scss","../node_modules/bootstrap/scss/_root.scss","../node_modules/bootstrap/scss/vendor/_rfs.scss","../node_modules/bootstrap/scss/mixins/_color-mode.scss","../node_modules/bootstrap/dist/css/dist/css/bootstrap.css","../node_modules/bootstrap/scss/_reboot.scss","../node_modules/bootstrap/scss/mixins/_border-radius.scss","../node_modules/bootstrap/scss/mixins/_lists.scss","../node_modules/bootstrap/scss/mixins/_image.scss","../node_modules/bootstrap/scss/_containers.scss","../node_modules/bootstrap/scss/mixins/_container.scss","../node_modules/bootstrap/scss/mixins/_breakpoints.scss","../node_modules/bootstrap/scss/_grid.scss","../node_modules/bootstrap/scss/mixins/_grid.scss","../node_modules/bootstrap/scss/_tables.scss","../node_modules/bootstrap/scss/mixins/_table-variants.scss","../node_modules/bootstrap/scss/forms/_labels.scss","../node_modules/bootstrap/scss/forms/_form-text.scss","../node_modules/bootstrap/scss/forms/_form-control.scss","../node_modules/bootstrap/scss/mixins/_transition.scss","../node_modules/bootstrap/scss/mixins/_gradients.scss","../node_modules/bootstrap/scss/forms/_form-select.scss","../node_modules/bootstrap/scss/forms/_form-check.scss","../node_modules/bootstrap/scss/forms/_form-range.scss","../node_modules/bootstrap/scss/forms/_floating-labels.scss","../node_modules/bootstrap/scss/forms/_input-group.scss","../node_modules/bootstrap/scss/mixins/_forms.scss","../node_modules/bootstrap/scss/_buttons.scss","../node_modules/bootstrap/scss/mixins/_buttons.scss","../node_modules/bootstrap/scss/_transitions.scss","../node_modules/bootstrap/scss/_dropdown.scss","../node_modules/bootstrap/scss/mixins/_caret.scss","../node_modules/bootstrap/scss/_button-group.scss","../node_modules/bootstrap/scss/_nav.scss","../node_modules/bootstrap/scss/_navbar.scss","../node_modules/bootstrap/scss/_card.scss","../node_modules/bootstrap/scss/_accordion.scss","../node_modules/bootstrap/scss/_breadcrumb.scss","../node_modules/bootstrap/scss/_pagination.scss","../node_modules/bootstrap/scss/_badge.scss","../node_modules/bootstrap/scss/_alert.scss","../node_modules/bootstrap/scss/_progress.scss","../node_modules/bootstrap/scss/_list-group.scss","../node_modules/bootstrap/scss/_close.scss","../node_modules/bootstrap/scss/_toasts.scss","../node_modules/bootstrap/scss/_modal.scss","../node_modules/bootstrap/scss/mixins/_backdrop.scss","../node_modules/bootstrap/scss/_tooltip.scss","../node_modules/bootstrap/scss/mixins/_reset-text.scss","../node_modules/bootstrap/scss/_popover.scss","../node_modules/bootstrap/scss/_carousel.scss","../node_modules/bootstrap/scss/_spinners.scss","../node_modules/bootstrap/scss/_offcanvas.scss","../node_modules/bootstrap/scss/_placeholders.scss","../node_modules/bootstrap/scss/mixins/_clearfix.scss","../node_modules/bootstrap/scss/helpers/_color-bg.scss","../node_modules/bootstrap/scss/helpers/_colored-links.scss","../node_modules/bootstrap/scss/helpers/_focus-ring.scss","../node_modules/bootstrap/scss/helpers/_icon-link.scss","../node_modules/bootstrap/scss/helpers/_ratio.scss","../node_modules/bootstrap/scss/helpers/_position.scss","../node_modules/bootstrap/scss/helpers/_stacks.scss","../node_modules/bootstrap/scss/helpers/_visually-hidden.scss","../node_modules/bootstrap/scss/mixins/_visually-hidden.scss","../node_modules/bootstrap/scss/helpers/_stretched-link.scss","../node_modules/bootstrap/scss/helpers/_text-truncation.scss","../node_modules/bootstrap/scss/helpers/_vr.scss","../node_modules/bootstrap/scss/mixins/_utilities.scss","../node_modules/bootstrap/scss/utilities/_api.scss","../node_modules/react-loading-skeleton/dist/skeleton.css"],"sourcesContent":["//\n// Headings\n//\n.h1 {\n @extend h1;\n}\n\n.h2 {\n @extend h2;\n}\n\n.h3 {\n @extend h3;\n}\n\n.h4 {\n @extend h4;\n}\n\n.h5 {\n @extend h5;\n}\n\n.h6 {\n @extend h6;\n}\n\n\n.lead {\n @include font-size($lead-font-size);\n font-weight: $lead-font-weight;\n}\n\n// Type display classes\n@each $display, $font-size in $display-font-sizes {\n .display-#{$display} {\n @include font-size($font-size);\n font-family: $display-font-family;\n font-style: $display-font-style;\n font-weight: $display-font-weight;\n line-height: $display-line-height;\n }\n}\n\n//\n// Emphasis\n//\n.small {\n @extend small;\n}\n\n.mark {\n @extend mark;\n}\n\n//\n// Lists\n//\n\n.list-unstyled {\n @include list-unstyled();\n}\n\n// Inline turns list items into inline-block\n.list-inline {\n @include list-unstyled();\n}\n.list-inline-item {\n display: inline-block;\n\n &:not(:last-child) {\n margin-right: $list-inline-padding;\n }\n}\n\n\n//\n// Misc\n//\n\n// Builds on `abbr`\n.initialism {\n @include font-size($initialism-font-size);\n text-transform: uppercase;\n}\n\n// Blockquotes\n.blockquote {\n margin-bottom: $blockquote-margin-y;\n @include font-size($blockquote-font-size);\n\n > :last-child {\n margin-bottom: 0;\n }\n}\n\n.blockquote-footer {\n margin-top: -$blockquote-margin-y;\n margin-bottom: $blockquote-margin-y;\n @include font-size($blockquote-footer-font-size);\n color: $blockquote-footer-color;\n\n &::before {\n content: \"\\2014\\00A0\"; // em dash, nbsp\n }\n}\n","// Responsive images (ensure images don't scale beyond their parents)\n//\n// This is purposefully opt-in via an explicit class rather than being the default for all ``s.\n// We previously tried the \"images are responsive by default\" approach in Bootstrap v2,\n// and abandoned it in Bootstrap v3 because it breaks lots of third-party widgets (including Google Maps)\n// which weren't expecting the images within themselves to be involuntarily resized.\n// See also https://github.com/twbs/bootstrap/issues/18178\n.img-fluid {\n @include img-fluid();\n}\n\n\n// Image thumbnails\n.img-thumbnail {\n padding: $thumbnail-padding;\n background-color: $thumbnail-bg;\n border: $thumbnail-border-width solid $thumbnail-border-color;\n @include border-radius($thumbnail-border-radius);\n @include box-shadow($thumbnail-box-shadow);\n\n // Keep them at most 100% wide\n @include img-fluid();\n}\n\n//\n// Figures\n//\n\n.figure {\n // Ensures the caption's text aligns with the image.\n display: inline-block;\n}\n\n.figure-img {\n margin-bottom: $spacer * .5;\n line-height: 1;\n}\n\n.figure-caption {\n @include font-size($figure-caption-font-size);\n color: $figure-caption-color;\n}\n","body {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\n 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\n sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n height: 100vh;\n}\n\n#root {\n height: 100%\n}\n\ncode {\n font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',\n monospace;\n}\n","@import url('https://fonts.googleapis.com/css2?family=Changa&display=swap');\n\n*{\n font-family: 'Changa', sans-serif;\n}\n\nbody {\n background-color: transparent !important;\n}\n\n#root {\n background-color: transparent !important;\n}\n\n:root {\n background-color: transparent !important;\n}\n\n.list {\n display: flex;\n position: absolute;\n height: 85%;\n width: 20%;\n top: 50%;\n left: 85%;\n transform: translate(-50%, -50%);\n background-color: rgba(0, 0, 0, 0.17);\n border-radius: 5px;\n flex-wrap: wrap;\n}\n\n.header-area {\n position: relative;\n width: 100%;\n height: 3%;\n}\n\n.colums-row {\n grid-column-gap: 2px;\n text-align: center;\n border-bottom: 3px solid rgb(185, 0, 0);\n}\n\n.colums-col-title {\n background-color: #242424;\n color: white;\n text-align: left;\n}\n\n.colums-col {\n background-color: #242424;\n color: white;\n vertical-align: middle;\n}\n\n.colums-col-icon {\n background-color: #242424;\n color: white;\n vertical-align: middle;\n font-size: 15px;\n}\n\n.players-area {\n position: relative;\n width: 100%;\n\toverflow-y: scroll;\n height: calc(96% - 20px);\n white-space: nowrap;\n}\n\n.colums-row-2 {\n grid-column-gap: 2px;\n text-align: center;\n font-size: 14.5px;\n font-weight: lighter;\n margin-bottom: 2px;\n}\n\n.colums-col-title-2 {\n background-color: #242424;\n color: white;\n text-align: left;\n overflow-x: scroll;\n}\n\n.colums-col-2 {\n background-color: #242424;\n color: white;\n vertical-align: middle;\n}\n\n.colums-col-icon-2 {\n background-color: #242424;\n color: white;\n vertical-align: middle;\n font-size: 15px;\n}\n\n::-webkit-scrollbar {\n height: 3px;\n width: 0px;\n}\n\n/* Track */\n::-webkit-scrollbar-track {\n background: #242424; \n}\n \n/* Handle */\n::-webkit-scrollbar-thumb {\n background: #3e3e3e; \n}\n\n/* Handle on hover */\n::-webkit-scrollbar-thumb:hover {\n background: #3e3e3e;\n}\n\n#player-loc {\n cursor: pointer;\n}\n#player-radio {\n cursor: pointer;\n}\n\n.popover-content {\n font-size: 15px;\n}\n\n.popover-arrow {\n display: none !important;\n}\n\n.popover-body {\n color: white !important;\n background-color: #242424;\n border-bottom-right-radius: 2px !important;\n border-bottom-left-radius: 2px !important;\n animation: fadeIn 30s !important;\n}\n\n.popover-header {\n padding: 0.5rem 6rem !important;\n color: white !important;\n background-color: #242424 !important;\n border-bottom: 3px solid rgb(185, 0, 0) !important;\n border-top-right-radius: 2px !important;\n border-top-left-radius: 2px !important;\n animation: fadeIn 30s !important;\n}\n\n.modal-content {\n color: white !important;\n background-color: #242424 !important;\n}\n\n.btn-close {\n --bs-btn-close-bg: url(https://api.iconify.design/ic:outline-close.svg?color=%23ffffff) !important;\n background: transparent var(--bs-btn-close-bg) center/1.5em auto no-repeat !important;\n}\n\n.modal-header {\n border-bottom: 1px solid #ff0000 !important;\n}\n\n.modal-footer {\n border-top: 1px solid #ff0000 !important;\n}","@mixin bsBanner($file) {\n /*!\n * Bootstrap #{$file} v5.3.0-alpha3 (https://getbootstrap.com/)\n * Copyright 2011-2023 The Bootstrap Authors\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\n}\n",":root,\n[data-bs-theme=\"light\"] {\n // Note: Custom variable values only support SassScript inside `#{}`.\n\n // Colors\n //\n // Generate palettes for full colors, grays, and theme colors.\n\n @each $color, $value in $colors {\n --#{$prefix}#{$color}: #{$value};\n }\n\n @each $color, $value in $grays {\n --#{$prefix}gray-#{$color}: #{$value};\n }\n\n @each $color, $value in $theme-colors {\n --#{$prefix}#{$color}: #{$value};\n }\n\n @each $color, $value in $theme-colors-rgb {\n --#{$prefix}#{$color}-rgb: #{$value};\n }\n\n @each $color, $value in $theme-colors-text {\n --#{$prefix}#{$color}-text-emphasis: #{$value};\n }\n\n @each $color, $value in $theme-colors-bg-subtle {\n --#{$prefix}#{$color}-bg-subtle: #{$value};\n }\n\n @each $color, $value in $theme-colors-border-subtle {\n --#{$prefix}#{$color}-border-subtle: #{$value};\n }\n\n --#{$prefix}white-rgb: #{to-rgb($white)};\n --#{$prefix}black-rgb: #{to-rgb($black)};\n\n // Fonts\n\n // Note: Use `inspect` for lists so that quoted items keep the quotes.\n // See https://github.com/sass/sass/issues/2383#issuecomment-336349172\n --#{$prefix}font-sans-serif: #{inspect($font-family-sans-serif)};\n --#{$prefix}font-monospace: #{inspect($font-family-monospace)};\n --#{$prefix}gradient: #{$gradient};\n\n // Root and body\n // scss-docs-start root-body-variables\n @if $font-size-root != null {\n --#{$prefix}root-font-size: #{$font-size-root};\n }\n --#{$prefix}body-font-family: #{inspect($font-family-base)};\n @include rfs($font-size-base, --#{$prefix}body-font-size);\n --#{$prefix}body-font-weight: #{$font-weight-base};\n --#{$prefix}body-line-height: #{$line-height-base};\n @if $body-text-align != null {\n --#{$prefix}body-text-align: #{$body-text-align};\n }\n\n --#{$prefix}body-color: #{$body-color};\n --#{$prefix}body-color-rgb: #{to-rgb($body-color)};\n --#{$prefix}body-bg: #{$body-bg};\n --#{$prefix}body-bg-rgb: #{to-rgb($body-bg)};\n\n --#{$prefix}emphasis-color: #{$body-emphasis-color};\n --#{$prefix}emphasis-color-rgb: #{to-rgb($body-emphasis-color)};\n\n --#{$prefix}secondary-color: #{$body-secondary-color};\n --#{$prefix}secondary-color-rgb: #{to-rgb($body-secondary-color)};\n --#{$prefix}secondary-bg: #{$body-secondary-bg};\n --#{$prefix}secondary-bg-rgb: #{to-rgb($body-secondary-bg)};\n\n --#{$prefix}tertiary-color: #{$body-tertiary-color};\n --#{$prefix}tertiary-color-rgb: #{to-rgb($body-tertiary-color)};\n --#{$prefix}tertiary-bg: #{$body-tertiary-bg};\n --#{$prefix}tertiary-bg-rgb: #{to-rgb($body-tertiary-bg)};\n // scss-docs-end root-body-variables\n\n @if $headings-color != null {\n --#{$prefix}heading-color: #{$headings-color};\n }\n\n --#{$prefix}link-color: #{$link-color};\n --#{$prefix}link-color-rgb: #{to-rgb($link-color)};\n --#{$prefix}link-decoration: #{$link-decoration};\n\n --#{$prefix}link-hover-color: #{$link-hover-color};\n --#{$prefix}link-hover-color-rgb: #{to-rgb($link-hover-color)};\n\n @if $link-hover-decoration != null {\n --#{$prefix}link-hover-decoration: #{$link-hover-decoration};\n }\n\n --#{$prefix}code-color: #{$code-color};\n --#{$prefix}highlight-bg: #{$mark-bg};\n\n // scss-docs-start root-border-var\n --#{$prefix}border-width: #{$border-width};\n --#{$prefix}border-style: #{$border-style};\n --#{$prefix}border-color: #{$border-color};\n --#{$prefix}border-color-translucent: #{$border-color-translucent};\n\n --#{$prefix}border-radius: #{$border-radius};\n --#{$prefix}border-radius-sm: #{$border-radius-sm};\n --#{$prefix}border-radius-lg: #{$border-radius-lg};\n --#{$prefix}border-radius-xl: #{$border-radius-xl};\n --#{$prefix}border-radius-xxl: #{$border-radius-xxl};\n --#{$prefix}border-radius-2xl: var(--#{$prefix}border-radius-xxl); // Deprecated in v5.3.0 for consistency\n --#{$prefix}border-radius-pill: #{$border-radius-pill};\n // scss-docs-end root-border-var\n\n --#{$prefix}box-shadow: #{$box-shadow};\n --#{$prefix}box-shadow-sm: #{$box-shadow-sm};\n --#{$prefix}box-shadow-lg: #{$box-shadow-lg};\n --#{$prefix}box-shadow-inset: #{$box-shadow-inset};\n\n // Focus styles\n // scss-docs-start root-focus-variables\n --#{$prefix}focus-ring-width: #{$focus-ring-width};\n --#{$prefix}focus-ring-opacity: #{$focus-ring-opacity};\n --#{$prefix}focus-ring-color: #{$focus-ring-color};\n // scss-docs-end root-focus-variables\n\n // scss-docs-start root-form-validation-variables\n --#{$prefix}form-valid-color: #{$form-valid-color};\n --#{$prefix}form-valid-border-color: #{$form-valid-border-color};\n --#{$prefix}form-invalid-color: #{$form-invalid-color};\n --#{$prefix}form-invalid-border-color: #{$form-invalid-border-color};\n // scss-docs-end root-form-validation-variables\n}\n\n@if $enable-dark-mode {\n @include color-mode(dark, true) {\n color-scheme: dark;\n\n // scss-docs-start root-dark-mode-vars\n --#{$prefix}body-color: #{$body-color-dark};\n --#{$prefix}body-color-rgb: #{to-rgb($body-color-dark)};\n --#{$prefix}body-bg: #{$body-bg-dark};\n --#{$prefix}body-bg-rgb: #{to-rgb($body-bg-dark)};\n\n --#{$prefix}emphasis-color: #{$body-emphasis-color-dark};\n --#{$prefix}emphasis-color-rgb: #{to-rgb($body-emphasis-color-dark)};\n\n --#{$prefix}secondary-color: #{$body-secondary-color-dark};\n --#{$prefix}secondary-color-rgb: #{to-rgb($body-secondary-color-dark)};\n --#{$prefix}secondary-bg: #{$body-secondary-bg-dark};\n --#{$prefix}secondary-bg-rgb: #{to-rgb($body-secondary-bg-dark)};\n\n --#{$prefix}tertiary-color: #{$body-tertiary-color-dark};\n --#{$prefix}tertiary-color-rgb: #{to-rgb($body-tertiary-color-dark)};\n --#{$prefix}tertiary-bg: #{$body-tertiary-bg-dark};\n --#{$prefix}tertiary-bg-rgb: #{to-rgb($body-tertiary-bg-dark)};\n\n @each $color, $value in $theme-colors-text-dark {\n --#{$prefix}#{$color}-text-emphasis: #{$value};\n }\n\n @each $color, $value in $theme-colors-bg-subtle-dark {\n --#{$prefix}#{$color}-bg-subtle: #{$value};\n }\n\n @each $color, $value in $theme-colors-border-subtle-dark {\n --#{$prefix}#{$color}-border-subtle: #{$value};\n }\n\n @if $headings-color-dark != null {\n --#{$prefix}heading-color: #{$headings-color-dark};\n }\n\n --#{$prefix}link-color: #{$link-color-dark};\n --#{$prefix}link-hover-color: #{$link-hover-color-dark};\n --#{$prefix}link-color-rgb: #{to-rgb($link-color-dark)};\n --#{$prefix}link-hover-color-rgb: #{to-rgb($link-hover-color-dark)};\n\n --#{$prefix}code-color: #{$code-color-dark};\n\n --#{$prefix}border-color: #{$border-color-dark};\n --#{$prefix}border-color-translucent: #{$border-color-translucent-dark};\n\n --#{$prefix}form-valid-color: #{$form-valid-color-dark};\n --#{$prefix}form-valid-border-color: #{$form-valid-border-color-dark};\n --#{$prefix}form-invalid-color: #{$form-invalid-color-dark};\n --#{$prefix}form-invalid-border-color: #{$form-invalid-border-color-dark};\n // scss-docs-end root-dark-mode-vars\n }\n}\n","// stylelint-disable scss/dimension-no-non-numeric-values\n\n// SCSS RFS mixin\n//\n// Automated responsive values for font sizes, paddings, margins and much more\n//\n// Licensed under MIT (https://github.com/twbs/rfs/blob/main/LICENSE)\n\n// Configuration\n\n// Base value\n$rfs-base-value: 1.25rem !default;\n$rfs-unit: rem !default;\n\n@if $rfs-unit != rem and $rfs-unit != px {\n @error \"`#{$rfs-unit}` is not a valid unit for $rfs-unit. Use `px` or `rem`.\";\n}\n\n// Breakpoint at where values start decreasing if screen width is smaller\n$rfs-breakpoint: 1200px !default;\n$rfs-breakpoint-unit: px !default;\n\n@if $rfs-breakpoint-unit != px and $rfs-breakpoint-unit != em and $rfs-breakpoint-unit != rem {\n @error \"`#{$rfs-breakpoint-unit}` is not a valid unit for $rfs-breakpoint-unit. Use `px`, `em` or `rem`.\";\n}\n\n// Resize values based on screen height and width\n$rfs-two-dimensional: false !default;\n\n// Factor of decrease\n$rfs-factor: 10 !default;\n\n@if type-of($rfs-factor) != number or $rfs-factor <= 1 {\n @error \"`#{$rfs-factor}` is not a valid $rfs-factor, it must be greater than 1.\";\n}\n\n// Mode. Possibilities: \"min-media-query\", \"max-media-query\"\n$rfs-mode: min-media-query !default;\n\n// Generate enable or disable classes. Possibilities: false, \"enable\" or \"disable\"\n$rfs-class: false !default;\n\n// 1 rem = $rfs-rem-value px\n$rfs-rem-value: 16 !default;\n\n// Safari iframe resize bug: https://github.com/twbs/rfs/issues/14\n$rfs-safari-iframe-resize-bug-fix: false !default;\n\n// Disable RFS by setting $enable-rfs to false\n$enable-rfs: true !default;\n\n// Cache $rfs-base-value unit\n$rfs-base-value-unit: unit($rfs-base-value);\n\n@function divide($dividend, $divisor, $precision: 10) {\n $sign: if($dividend > 0 and $divisor > 0 or $dividend < 0 and $divisor < 0, 1, -1);\n $dividend: abs($dividend);\n $divisor: abs($divisor);\n @if $dividend == 0 {\n @return 0;\n }\n @if $divisor == 0 {\n @error \"Cannot divide by 0\";\n }\n $remainder: $dividend;\n $result: 0;\n $factor: 10;\n @while ($remainder > 0 and $precision >= 0) {\n $quotient: 0;\n @while ($remainder >= $divisor) {\n $remainder: $remainder - $divisor;\n $quotient: $quotient + 1;\n }\n $result: $result * 10 + $quotient;\n $factor: $factor * .1;\n $remainder: $remainder * 10;\n $precision: $precision - 1;\n @if ($precision < 0 and $remainder >= $divisor * 5) {\n $result: $result + 1;\n }\n }\n $result: $result * $factor * $sign;\n $dividend-unit: unit($dividend);\n $divisor-unit: unit($divisor);\n $unit-map: (\n \"px\": 1px,\n \"rem\": 1rem,\n \"em\": 1em,\n \"%\": 1%\n );\n @if ($dividend-unit != $divisor-unit and map-has-key($unit-map, $dividend-unit)) {\n $result: $result * map-get($unit-map, $dividend-unit);\n }\n @return $result;\n}\n\n// Remove px-unit from $rfs-base-value for calculations\n@if $rfs-base-value-unit == px {\n $rfs-base-value: divide($rfs-base-value, $rfs-base-value * 0 + 1);\n}\n@else if $rfs-base-value-unit == rem {\n $rfs-base-value: divide($rfs-base-value, divide($rfs-base-value * 0 + 1, $rfs-rem-value));\n}\n\n// Cache $rfs-breakpoint unit to prevent multiple calls\n$rfs-breakpoint-unit-cache: unit($rfs-breakpoint);\n\n// Remove unit from $rfs-breakpoint for calculations\n@if $rfs-breakpoint-unit-cache == px {\n $rfs-breakpoint: divide($rfs-breakpoint, $rfs-breakpoint * 0 + 1);\n}\n@else if $rfs-breakpoint-unit-cache == rem or $rfs-breakpoint-unit-cache == \"em\" {\n $rfs-breakpoint: divide($rfs-breakpoint, divide($rfs-breakpoint * 0 + 1, $rfs-rem-value));\n}\n\n// Calculate the media query value\n$rfs-mq-value: if($rfs-breakpoint-unit == px, #{$rfs-breakpoint}px, #{divide($rfs-breakpoint, $rfs-rem-value)}#{$rfs-breakpoint-unit});\n$rfs-mq-property-width: if($rfs-mode == max-media-query, max-width, min-width);\n$rfs-mq-property-height: if($rfs-mode == max-media-query, max-height, min-height);\n\n// Internal mixin used to determine which media query needs to be used\n@mixin _rfs-media-query {\n @if $rfs-two-dimensional {\n @if $rfs-mode == max-media-query {\n @media (#{$rfs-mq-property-width}: #{$rfs-mq-value}), (#{$rfs-mq-property-height}: #{$rfs-mq-value}) {\n @content;\n }\n }\n @else {\n @media (#{$rfs-mq-property-width}: #{$rfs-mq-value}) and (#{$rfs-mq-property-height}: #{$rfs-mq-value}) {\n @content;\n }\n }\n }\n @else {\n @media (#{$rfs-mq-property-width}: #{$rfs-mq-value}) {\n @content;\n }\n }\n}\n\n// Internal mixin that adds disable classes to the selector if needed.\n@mixin _rfs-rule {\n @if $rfs-class == disable and $rfs-mode == max-media-query {\n // Adding an extra class increases specificity, which prevents the media query to override the property\n &,\n .disable-rfs &,\n &.disable-rfs {\n @content;\n }\n }\n @else if $rfs-class == enable and $rfs-mode == min-media-query {\n .enable-rfs &,\n &.enable-rfs {\n @content;\n }\n } @else {\n @content;\n }\n}\n\n// Internal mixin that adds enable classes to the selector if needed.\n@mixin _rfs-media-query-rule {\n\n @if $rfs-class == enable {\n @if $rfs-mode == min-media-query {\n @content;\n }\n\n @include _rfs-media-query () {\n .enable-rfs &,\n &.enable-rfs {\n @content;\n }\n }\n }\n @else {\n @if $rfs-class == disable and $rfs-mode == min-media-query {\n .disable-rfs &,\n &.disable-rfs {\n @content;\n }\n }\n @include _rfs-media-query () {\n @content;\n }\n }\n}\n\n// Helper function to get the formatted non-responsive value\n@function rfs-value($values) {\n // Convert to list\n $values: if(type-of($values) != list, ($values,), $values);\n\n $val: \"\";\n\n // Loop over each value and calculate value\n @each $value in $values {\n @if $value == 0 {\n $val: $val + \" 0\";\n }\n @else {\n // Cache $value unit\n $unit: if(type-of($value) == \"number\", unit($value), false);\n\n @if $unit == px {\n // Convert to rem if needed\n $val: $val + \" \" + if($rfs-unit == rem, #{divide($value, $value * 0 + $rfs-rem-value)}rem, $value);\n }\n @else if $unit == rem {\n // Convert to px if needed\n $val: $val + \" \" + if($rfs-unit == px, #{divide($value, $value * 0 + 1) * $rfs-rem-value}px, $value);\n } @else {\n // If $value isn't a number (like inherit) or $value has a unit (not px or rem, like 1.5em) or $ is 0, just print the value\n $val: $val + \" \" + $value;\n }\n }\n }\n\n // Remove first space\n @return unquote(str-slice($val, 2));\n}\n\n// Helper function to get the responsive value calculated by RFS\n@function rfs-fluid-value($values) {\n // Convert to list\n $values: if(type-of($values) != list, ($values,), $values);\n\n $val: \"\";\n\n // Loop over each value and calculate value\n @each $value in $values {\n @if $value == 0 {\n $val: $val + \" 0\";\n } @else {\n // Cache $value unit\n $unit: if(type-of($value) == \"number\", unit($value), false);\n\n // If $value isn't a number (like inherit) or $value has a unit (not px or rem, like 1.5em) or $ is 0, just print the value\n @if not $unit or $unit != px and $unit != rem {\n $val: $val + \" \" + $value;\n } @else {\n // Remove unit from $value for calculations\n $value: divide($value, $value * 0 + if($unit == px, 1, divide(1, $rfs-rem-value)));\n\n // Only add the media query if the value is greater than the minimum value\n @if abs($value) <= $rfs-base-value or not $enable-rfs {\n $val: $val + \" \" + if($rfs-unit == rem, #{divide($value, $rfs-rem-value)}rem, #{$value}px);\n }\n @else {\n // Calculate the minimum value\n $value-min: $rfs-base-value + divide(abs($value) - $rfs-base-value, $rfs-factor);\n\n // Calculate difference between $value and the minimum value\n $value-diff: abs($value) - $value-min;\n\n // Base value formatting\n $min-width: if($rfs-unit == rem, #{divide($value-min, $rfs-rem-value)}rem, #{$value-min}px);\n\n // Use negative value if needed\n $min-width: if($value < 0, -$min-width, $min-width);\n\n // Use `vmin` if two-dimensional is enabled\n $variable-unit: if($rfs-two-dimensional, vmin, vw);\n\n // Calculate the variable width between 0 and $rfs-breakpoint\n $variable-width: #{divide($value-diff * 100, $rfs-breakpoint)}#{$variable-unit};\n\n // Return the calculated value\n $val: $val + \" calc(\" + $min-width + if($value < 0, \" - \", \" + \") + $variable-width + \")\";\n }\n }\n }\n }\n\n // Remove first space\n @return unquote(str-slice($val, 2));\n}\n\n// RFS mixin\n@mixin rfs($values, $property: font-size) {\n @if $values != null {\n $val: rfs-value($values);\n $fluid-val: rfs-fluid-value($values);\n\n // Do not print the media query if responsive & non-responsive values are the same\n @if $val == $fluid-val {\n #{$property}: $val;\n }\n @else {\n @include _rfs-rule () {\n #{$property}: if($rfs-mode == max-media-query, $val, $fluid-val);\n\n // Include safari iframe resize fix if needed\n min-width: if($rfs-safari-iframe-resize-bug-fix, (0 * 1vw), null);\n }\n\n @include _rfs-media-query-rule () {\n #{$property}: if($rfs-mode == max-media-query, $fluid-val, $val);\n }\n }\n }\n}\n\n// Shorthand helper mixins\n@mixin font-size($value) {\n @include rfs($value);\n}\n\n@mixin padding($value) {\n @include rfs($value, padding);\n}\n\n@mixin padding-top($value) {\n @include rfs($value, padding-top);\n}\n\n@mixin padding-right($value) {\n @include rfs($value, padding-right);\n}\n\n@mixin padding-bottom($value) {\n @include rfs($value, padding-bottom);\n}\n\n@mixin padding-left($value) {\n @include rfs($value, padding-left);\n}\n\n@mixin margin($value) {\n @include rfs($value, margin);\n}\n\n@mixin margin-top($value) {\n @include rfs($value, margin-top);\n}\n\n@mixin margin-right($value) {\n @include rfs($value, margin-right);\n}\n\n@mixin margin-bottom($value) {\n @include rfs($value, margin-bottom);\n}\n\n@mixin margin-left($value) {\n @include rfs($value, margin-left);\n}\n","// scss-docs-start color-mode-mixin\n@mixin color-mode($mode: light, $root: false) {\n @if $color-mode-type == \"media-query\" {\n @if $root == true {\n @media (prefers-color-scheme: $mode) {\n :root {\n @content;\n }\n }\n } @else {\n @media (prefers-color-scheme: $mode) {\n @content;\n }\n }\n } @else {\n [data-bs-theme=\"#{$mode}\"] {\n @content;\n }\n }\n}\n// scss-docs-end color-mode-mixin\n","@charset \"UTF-8\";\n/*!\n * Bootstrap v5.3.0-alpha3 (https://getbootstrap.com/)\n * Copyright 2011-2023 The Bootstrap Authors\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\n:root,\n[data-bs-theme=light] {\n --bs-blue: #0d6efd;\n --bs-indigo: #6610f2;\n --bs-purple: #6f42c1;\n --bs-pink: #d63384;\n --bs-red: #dc3545;\n --bs-orange: #fd7e14;\n --bs-yellow: #ffc107;\n --bs-green: #198754;\n --bs-teal: #20c997;\n --bs-cyan: #0dcaf0;\n --bs-black: #000;\n --bs-white: #fff;\n --bs-gray: #6c757d;\n --bs-gray-dark: #343a40;\n --bs-gray-100: #f8f9fa;\n --bs-gray-200: #e9ecef;\n --bs-gray-300: #dee2e6;\n --bs-gray-400: #ced4da;\n --bs-gray-500: #adb5bd;\n --bs-gray-600: #6c757d;\n --bs-gray-700: #495057;\n --bs-gray-800: #343a40;\n --bs-gray-900: #212529;\n --bs-primary: #0d6efd;\n --bs-secondary: #6c757d;\n --bs-success: #198754;\n --bs-info: #0dcaf0;\n --bs-warning: #ffc107;\n --bs-danger: #dc3545;\n --bs-light: #f8f9fa;\n --bs-dark: #212529;\n --bs-primary-rgb: 13, 110, 253;\n --bs-secondary-rgb: 108, 117, 125;\n --bs-success-rgb: 25, 135, 84;\n --bs-info-rgb: 13, 202, 240;\n --bs-warning-rgb: 255, 193, 7;\n --bs-danger-rgb: 220, 53, 69;\n --bs-light-rgb: 248, 249, 250;\n --bs-dark-rgb: 33, 37, 41;\n --bs-primary-text-emphasis: #052c65;\n --bs-secondary-text-emphasis: #2b2f32;\n --bs-success-text-emphasis: #0a3622;\n --bs-info-text-emphasis: #055160;\n --bs-warning-text-emphasis: #664d03;\n --bs-danger-text-emphasis: #58151c;\n --bs-light-text-emphasis: #495057;\n --bs-dark-text-emphasis: #495057;\n --bs-primary-bg-subtle: #cfe2ff;\n --bs-secondary-bg-subtle: #e2e3e5;\n --bs-success-bg-subtle: #d1e7dd;\n --bs-info-bg-subtle: #cff4fc;\n --bs-warning-bg-subtle: #fff3cd;\n --bs-danger-bg-subtle: #f8d7da;\n --bs-light-bg-subtle: #fcfcfd;\n --bs-dark-bg-subtle: #ced4da;\n --bs-primary-border-subtle: #9ec5fe;\n --bs-secondary-border-subtle: #c4c8cb;\n --bs-success-border-subtle: #a3cfbb;\n --bs-info-border-subtle: #9eeaf9;\n --bs-warning-border-subtle: #ffe69c;\n --bs-danger-border-subtle: #f1aeb5;\n --bs-light-border-subtle: #e9ecef;\n --bs-dark-border-subtle: #adb5bd;\n --bs-white-rgb: 255, 255, 255;\n --bs-black-rgb: 0, 0, 0;\n --bs-font-sans-serif: system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", \"Noto Sans\", \"Liberation Sans\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));\n --bs-body-font-family: var(--bs-font-sans-serif);\n --bs-body-font-size: 1rem;\n --bs-body-font-weight: 400;\n --bs-body-line-height: 1.5;\n --bs-body-color: #212529;\n --bs-body-color-rgb: 33, 37, 41;\n --bs-body-bg: #fff;\n --bs-body-bg-rgb: 255, 255, 255;\n --bs-emphasis-color: #000;\n --bs-emphasis-color-rgb: 0, 0, 0;\n --bs-secondary-color: rgba(33, 37, 41, 0.75);\n --bs-secondary-color-rgb: 33, 37, 41;\n --bs-secondary-bg: #e9ecef;\n --bs-secondary-bg-rgb: 233, 236, 239;\n --bs-tertiary-color: rgba(33, 37, 41, 0.5);\n --bs-tertiary-color-rgb: 33, 37, 41;\n --bs-tertiary-bg: #f8f9fa;\n --bs-tertiary-bg-rgb: 248, 249, 250;\n --bs-link-color: #0d6efd;\n --bs-link-color-rgb: 13, 110, 253;\n --bs-link-decoration: underline;\n --bs-link-hover-color: #0a58ca;\n --bs-link-hover-color-rgb: 10, 88, 202;\n --bs-code-color: #d63384;\n --bs-highlight-bg: #fff3cd;\n --bs-border-width: 1px;\n --bs-border-style: solid;\n --bs-border-color: #dee2e6;\n --bs-border-color-translucent: rgba(0, 0, 0, 0.175);\n --bs-border-radius: 0.375rem;\n --bs-border-radius-sm: 0.25rem;\n --bs-border-radius-lg: 0.5rem;\n --bs-border-radius-xl: 1rem;\n --bs-border-radius-xxl: 2rem;\n --bs-border-radius-2xl: var(--bs-border-radius-xxl);\n --bs-border-radius-pill: 50rem;\n --bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);\n --bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);\n --bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);\n --bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);\n --bs-focus-ring-width: 0.25rem;\n --bs-focus-ring-opacity: 0.25;\n --bs-focus-ring-color: rgba(13, 110, 253, 0.25);\n --bs-form-valid-color: #198754;\n --bs-form-valid-border-color: #198754;\n --bs-form-invalid-color: #dc3545;\n --bs-form-invalid-border-color: #dc3545;\n}\n\n[data-bs-theme=dark] {\n color-scheme: dark;\n --bs-body-color: #adb5bd;\n --bs-body-color-rgb: 173, 181, 189;\n --bs-body-bg: #212529;\n --bs-body-bg-rgb: 33, 37, 41;\n --bs-emphasis-color: #fff;\n --bs-emphasis-color-rgb: 255, 255, 255;\n --bs-secondary-color: rgba(173, 181, 189, 0.75);\n --bs-secondary-color-rgb: 173, 181, 189;\n --bs-secondary-bg: #343a40;\n --bs-secondary-bg-rgb: 52, 58, 64;\n --bs-tertiary-color: rgba(173, 181, 189, 0.5);\n --bs-tertiary-color-rgb: 173, 181, 189;\n --bs-tertiary-bg: #2b3035;\n --bs-tertiary-bg-rgb: 43, 48, 53;\n --bs-primary-text-emphasis: #6ea8fe;\n --bs-secondary-text-emphasis: #a7acb1;\n --bs-success-text-emphasis: #75b798;\n --bs-info-text-emphasis: #6edff6;\n --bs-warning-text-emphasis: #ffda6a;\n --bs-danger-text-emphasis: #ea868f;\n --bs-light-text-emphasis: #f8f9fa;\n --bs-dark-text-emphasis: #dee2e6;\n --bs-primary-bg-subtle: #031633;\n --bs-secondary-bg-subtle: #161719;\n --bs-success-bg-subtle: #051b11;\n --bs-info-bg-subtle: #032830;\n --bs-warning-bg-subtle: #332701;\n --bs-danger-bg-subtle: #2c0b0e;\n --bs-light-bg-subtle: #343a40;\n --bs-dark-bg-subtle: #1a1d20;\n --bs-primary-border-subtle: #084298;\n --bs-secondary-border-subtle: #41464b;\n --bs-success-border-subtle: #0f5132;\n --bs-info-border-subtle: #087990;\n --bs-warning-border-subtle: #997404;\n --bs-danger-border-subtle: #842029;\n --bs-light-border-subtle: #495057;\n --bs-dark-border-subtle: #343a40;\n --bs-link-color: #6ea8fe;\n --bs-link-hover-color: #8bb9fe;\n --bs-link-color-rgb: 110, 168, 254;\n --bs-link-hover-color-rgb: 139, 185, 254;\n --bs-code-color: #e685b5;\n --bs-border-color: #495057;\n --bs-border-color-translucent: rgba(255, 255, 255, 0.15);\n --bs-form-valid-color: #75b798;\n --bs-form-valid-border-color: #75b798;\n --bs-form-invalid-color: #ea868f;\n --bs-form-invalid-border-color: #ea868f;\n}\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n@media (prefers-reduced-motion: no-preference) {\n :root {\n scroll-behavior: smooth;\n }\n}\n\nbody {\n margin: 0;\n font-family: var(--bs-body-font-family);\n font-size: var(--bs-body-font-size);\n font-weight: var(--bs-body-font-weight);\n line-height: var(--bs-body-line-height);\n color: var(--bs-body-color);\n text-align: var(--bs-body-text-align);\n background-color: var(--bs-body-bg);\n -webkit-text-size-adjust: 100%;\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\n\nhr {\n margin: 1rem 0;\n color: inherit;\n border: 0;\n border-top: var(--bs-border-width) solid;\n opacity: 0.25;\n}\n\nh6, .h6, h5, .h5, h4, .h4, h3, .h3, h2, .h2, h1, .h1 {\n margin-top: 0;\n margin-bottom: 0.5rem;\n font-weight: 500;\n line-height: 1.2;\n color: var(--bs-heading-color, inherit);\n}\n\nh1, .h1 {\n font-size: calc(1.375rem + 1.5vw);\n}\n@media (min-width: 1200px) {\n h1, .h1 {\n font-size: 2.5rem;\n }\n}\n\nh2, .h2 {\n font-size: calc(1.325rem + 0.9vw);\n}\n@media (min-width: 1200px) {\n h2, .h2 {\n font-size: 2rem;\n }\n}\n\nh3, .h3 {\n font-size: calc(1.3rem + 0.6vw);\n}\n@media (min-width: 1200px) {\n h3, .h3 {\n font-size: 1.75rem;\n }\n}\n\nh4, .h4 {\n font-size: calc(1.275rem + 0.3vw);\n}\n@media (min-width: 1200px) {\n h4, .h4 {\n font-size: 1.5rem;\n }\n}\n\nh5, .h5 {\n font-size: 1.25rem;\n}\n\nh6, .h6 {\n font-size: 1rem;\n}\n\np {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nabbr[title] {\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted;\n cursor: help;\n -webkit-text-decoration-skip-ink: none;\n text-decoration-skip-ink: none;\n}\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\nol,\nul {\n padding-left: 2rem;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: 700;\n}\n\ndd {\n margin-bottom: 0.5rem;\n margin-left: 0;\n}\n\nblockquote {\n margin: 0 0 1rem;\n}\n\nb,\nstrong {\n font-weight: bolder;\n}\n\nsmall, .small {\n font-size: 0.875em;\n}\n\nmark, .mark {\n padding: 0.1875em;\n background-color: var(--bs-highlight-bg);\n}\n\nsub,\nsup {\n position: relative;\n font-size: 0.75em;\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub {\n bottom: -0.25em;\n}\n\nsup {\n top: -0.5em;\n}\n\na {\n color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));\n text-decoration: underline;\n}\na:hover {\n --bs-link-color-rgb: var(--bs-link-hover-color-rgb);\n}\n\na:not([href]):not([class]), a:not([href]):not([class]):hover {\n color: inherit;\n text-decoration: none;\n}\n\npre,\ncode,\nkbd,\nsamp {\n font-family: var(--bs-font-monospace);\n font-size: 1em;\n}\n\npre {\n display: block;\n margin-top: 0;\n margin-bottom: 1rem;\n overflow: auto;\n font-size: 0.875em;\n}\npre code {\n font-size: inherit;\n color: inherit;\n word-break: normal;\n}\n\ncode {\n font-size: 0.875em;\n color: var(--bs-code-color);\n word-wrap: break-word;\n}\na > code {\n color: inherit;\n}\n\nkbd {\n padding: 0.1875rem 0.375rem;\n font-size: 0.875em;\n color: var(--bs-body-bg);\n background-color: var(--bs-body-color);\n border-radius: 0.25rem;\n}\nkbd kbd {\n padding: 0;\n font-size: 1em;\n}\n\nfigure {\n margin: 0 0 1rem;\n}\n\nimg,\nsvg {\n vertical-align: middle;\n}\n\ntable {\n caption-side: bottom;\n border-collapse: collapse;\n}\n\ncaption {\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n color: var(--bs-secondary-color);\n text-align: left;\n}\n\nth {\n text-align: inherit;\n text-align: -webkit-match-parent;\n}\n\nthead,\ntbody,\ntfoot,\ntr,\ntd,\nth {\n border-color: inherit;\n border-style: solid;\n border-width: 0;\n}\n\nlabel {\n display: inline-block;\n}\n\nbutton {\n border-radius: 0;\n}\n\nbutton:focus:not(:focus-visible) {\n outline: 0;\n}\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0;\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\nbutton,\nselect {\n text-transform: none;\n}\n\n[role=button] {\n cursor: pointer;\n}\n\nselect {\n word-wrap: normal;\n}\nselect:disabled {\n opacity: 1;\n}\n\n[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {\n display: none !important;\n}\n\nbutton,\n[type=button],\n[type=reset],\n[type=submit] {\n -webkit-appearance: button;\n}\nbutton:not(:disabled),\n[type=button]:not(:disabled),\n[type=reset]:not(:disabled),\n[type=submit]:not(:disabled) {\n cursor: pointer;\n}\n\n::-moz-focus-inner {\n padding: 0;\n border-style: none;\n}\n\ntextarea {\n resize: vertical;\n}\n\nfieldset {\n min-width: 0;\n padding: 0;\n margin: 0;\n border: 0;\n}\n\nlegend {\n float: left;\n width: 100%;\n padding: 0;\n margin-bottom: 0.5rem;\n font-size: calc(1.275rem + 0.3vw);\n line-height: inherit;\n}\n@media (min-width: 1200px) {\n legend {\n font-size: 1.5rem;\n }\n}\nlegend + * {\n clear: left;\n}\n\n::-webkit-datetime-edit-fields-wrapper,\n::-webkit-datetime-edit-text,\n::-webkit-datetime-edit-minute,\n::-webkit-datetime-edit-hour-field,\n::-webkit-datetime-edit-day-field,\n::-webkit-datetime-edit-month-field,\n::-webkit-datetime-edit-year-field {\n padding: 0;\n}\n\n::-webkit-inner-spin-button {\n height: auto;\n}\n\n[type=search] {\n outline-offset: -2px;\n -webkit-appearance: textfield;\n}\n\n/* rtl:raw:\n[type=\"tel\"],\n[type=\"url\"],\n[type=\"email\"],\n[type=\"number\"] {\n direction: ltr;\n}\n*/\n::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n::-webkit-color-swatch-wrapper {\n padding: 0;\n}\n\n::-webkit-file-upload-button {\n font: inherit;\n -webkit-appearance: button;\n}\n\n::file-selector-button {\n font: inherit;\n -webkit-appearance: button;\n}\n\noutput {\n display: inline-block;\n}\n\niframe {\n border: 0;\n}\n\nsummary {\n display: list-item;\n cursor: pointer;\n}\n\nprogress {\n vertical-align: baseline;\n}\n\n[hidden] {\n display: none !important;\n}\n\n.lead {\n font-size: 1.25rem;\n font-weight: 300;\n}\n\n.display-1 {\n font-size: calc(1.625rem + 4.5vw);\n font-weight: 300;\n line-height: 1.2;\n}\n@media (min-width: 1200px) {\n .display-1 {\n font-size: 5rem;\n }\n}\n\n.display-2 {\n font-size: calc(1.575rem + 3.9vw);\n font-weight: 300;\n line-height: 1.2;\n}\n@media (min-width: 1200px) {\n .display-2 {\n font-size: 4.5rem;\n }\n}\n\n.display-3 {\n font-size: calc(1.525rem + 3.3vw);\n font-weight: 300;\n line-height: 1.2;\n}\n@media (min-width: 1200px) {\n .display-3 {\n font-size: 4rem;\n }\n}\n\n.display-4 {\n font-size: calc(1.475rem + 2.7vw);\n font-weight: 300;\n line-height: 1.2;\n}\n@media (min-width: 1200px) {\n .display-4 {\n font-size: 3.5rem;\n }\n}\n\n.display-5 {\n font-size: calc(1.425rem + 2.1vw);\n font-weight: 300;\n line-height: 1.2;\n}\n@media (min-width: 1200px) {\n .display-5 {\n font-size: 3rem;\n }\n}\n\n.display-6 {\n font-size: calc(1.375rem + 1.5vw);\n font-weight: 300;\n line-height: 1.2;\n}\n@media (min-width: 1200px) {\n .display-6 {\n font-size: 2.5rem;\n }\n}\n\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n\n.list-inline {\n padding-left: 0;\n list-style: none;\n}\n\n.list-inline-item {\n display: inline-block;\n}\n.list-inline-item:not(:last-child) {\n margin-right: 0.5rem;\n}\n\n.initialism {\n font-size: 0.875em;\n text-transform: uppercase;\n}\n\n.blockquote {\n margin-bottom: 1rem;\n font-size: 1.25rem;\n}\n.blockquote > :last-child {\n margin-bottom: 0;\n}\n\n.blockquote-footer {\n margin-top: -1rem;\n margin-bottom: 1rem;\n font-size: 0.875em;\n color: #6c757d;\n}\n.blockquote-footer::before {\n content: \"— \";\n}\n\n.img-fluid {\n max-width: 100%;\n height: auto;\n}\n\n.img-thumbnail {\n padding: 0.25rem;\n background-color: var(--bs-body-bg);\n border: var(--bs-border-width) solid var(--bs-border-color);\n border-radius: var(--bs-border-radius);\n max-width: 100%;\n height: auto;\n}\n\n.figure {\n display: inline-block;\n}\n\n.figure-img {\n margin-bottom: 0.5rem;\n line-height: 1;\n}\n\n.figure-caption {\n font-size: 0.875em;\n color: var(--bs-secondary-color);\n}\n\n.container,\n.container-fluid,\n.container-xxl,\n.container-xl,\n.container-lg,\n.container-md,\n.container-sm {\n --bs-gutter-x: 1.5rem;\n --bs-gutter-y: 0;\n width: 100%;\n padding-right: calc(var(--bs-gutter-x) * 0.5);\n padding-left: calc(var(--bs-gutter-x) * 0.5);\n margin-right: auto;\n margin-left: auto;\n}\n\n@media (min-width: 576px) {\n .container-sm, .container {\n max-width: 540px;\n }\n}\n@media (min-width: 768px) {\n .container-md, .container-sm, .container {\n max-width: 720px;\n }\n}\n@media (min-width: 992px) {\n .container-lg, .container-md, .container-sm, .container {\n max-width: 960px;\n }\n}\n@media (min-width: 1200px) {\n .container-xl, .container-lg, .container-md, .container-sm, .container {\n max-width: 1140px;\n }\n}\n@media (min-width: 1400px) {\n .container-xxl, .container-xl, .container-lg, .container-md, .container-sm, .container {\n max-width: 1320px;\n }\n}\n:root {\n --bs-breakpoint-xs: 0;\n --bs-breakpoint-sm: 576px;\n --bs-breakpoint-md: 768px;\n --bs-breakpoint-lg: 992px;\n --bs-breakpoint-xl: 1200px;\n --bs-breakpoint-xxl: 1400px;\n}\n\n.row {\n --bs-gutter-x: 1.5rem;\n --bs-gutter-y: 0;\n display: flex;\n flex-wrap: wrap;\n margin-top: calc(-1 * var(--bs-gutter-y));\n margin-right: calc(-0.5 * var(--bs-gutter-x));\n margin-left: calc(-0.5 * var(--bs-gutter-x));\n}\n.row > * {\n flex-shrink: 0;\n width: 100%;\n max-width: 100%;\n padding-right: calc(var(--bs-gutter-x) * 0.5);\n padding-left: calc(var(--bs-gutter-x) * 0.5);\n margin-top: var(--bs-gutter-y);\n}\n\n.col {\n flex: 1 0 0%;\n}\n\n.row-cols-auto > * {\n flex: 0 0 auto;\n width: auto;\n}\n\n.row-cols-1 > * {\n flex: 0 0 auto;\n width: 100%;\n}\n\n.row-cols-2 > * {\n flex: 0 0 auto;\n width: 50%;\n}\n\n.row-cols-3 > * {\n flex: 0 0 auto;\n width: 33.3333333333%;\n}\n\n.row-cols-4 > * {\n flex: 0 0 auto;\n width: 25%;\n}\n\n.row-cols-5 > * {\n flex: 0 0 auto;\n width: 20%;\n}\n\n.row-cols-6 > * {\n flex: 0 0 auto;\n width: 16.6666666667%;\n}\n\n.col-auto {\n flex: 0 0 auto;\n width: auto;\n}\n\n.col-1 {\n flex: 0 0 auto;\n width: 8.33333333%;\n}\n\n.col-2 {\n flex: 0 0 auto;\n width: 16.66666667%;\n}\n\n.col-3 {\n flex: 0 0 auto;\n width: 25%;\n}\n\n.col-4 {\n flex: 0 0 auto;\n width: 33.33333333%;\n}\n\n.col-5 {\n flex: 0 0 auto;\n width: 41.66666667%;\n}\n\n.col-6 {\n flex: 0 0 auto;\n width: 50%;\n}\n\n.col-7 {\n flex: 0 0 auto;\n width: 58.33333333%;\n}\n\n.col-8 {\n flex: 0 0 auto;\n width: 66.66666667%;\n}\n\n.col-9 {\n flex: 0 0 auto;\n width: 75%;\n}\n\n.col-10 {\n flex: 0 0 auto;\n width: 83.33333333%;\n}\n\n.col-11 {\n flex: 0 0 auto;\n width: 91.66666667%;\n}\n\n.col-12 {\n flex: 0 0 auto;\n width: 100%;\n}\n\n.offset-1 {\n margin-left: 8.33333333%;\n}\n\n.offset-2 {\n margin-left: 16.66666667%;\n}\n\n.offset-3 {\n margin-left: 25%;\n}\n\n.offset-4 {\n margin-left: 33.33333333%;\n}\n\n.offset-5 {\n margin-left: 41.66666667%;\n}\n\n.offset-6 {\n margin-left: 50%;\n}\n\n.offset-7 {\n margin-left: 58.33333333%;\n}\n\n.offset-8 {\n margin-left: 66.66666667%;\n}\n\n.offset-9 {\n margin-left: 75%;\n}\n\n.offset-10 {\n margin-left: 83.33333333%;\n}\n\n.offset-11 {\n margin-left: 91.66666667%;\n}\n\n.g-0,\n.gx-0 {\n --bs-gutter-x: 0;\n}\n\n.g-0,\n.gy-0 {\n --bs-gutter-y: 0;\n}\n\n.g-1,\n.gx-1 {\n --bs-gutter-x: 0.25rem;\n}\n\n.g-1,\n.gy-1 {\n --bs-gutter-y: 0.25rem;\n}\n\n.g-2,\n.gx-2 {\n --bs-gutter-x: 0.5rem;\n}\n\n.g-2,\n.gy-2 {\n --bs-gutter-y: 0.5rem;\n}\n\n.g-3,\n.gx-3 {\n --bs-gutter-x: 1rem;\n}\n\n.g-3,\n.gy-3 {\n --bs-gutter-y: 1rem;\n}\n\n.g-4,\n.gx-4 {\n --bs-gutter-x: 1.5rem;\n}\n\n.g-4,\n.gy-4 {\n --bs-gutter-y: 1.5rem;\n}\n\n.g-5,\n.gx-5 {\n --bs-gutter-x: 3rem;\n}\n\n.g-5,\n.gy-5 {\n --bs-gutter-y: 3rem;\n}\n\n@media (min-width: 576px) {\n .col-sm {\n flex: 1 0 0%;\n }\n .row-cols-sm-auto > * {\n flex: 0 0 auto;\n width: auto;\n }\n .row-cols-sm-1 > * {\n flex: 0 0 auto;\n width: 100%;\n }\n .row-cols-sm-2 > * {\n flex: 0 0 auto;\n width: 50%;\n }\n .row-cols-sm-3 > * {\n flex: 0 0 auto;\n width: 33.3333333333%;\n }\n .row-cols-sm-4 > * {\n flex: 0 0 auto;\n width: 25%;\n }\n .row-cols-sm-5 > * {\n flex: 0 0 auto;\n width: 20%;\n }\n .row-cols-sm-6 > * {\n flex: 0 0 auto;\n width: 16.6666666667%;\n }\n .col-sm-auto {\n flex: 0 0 auto;\n width: auto;\n }\n .col-sm-1 {\n flex: 0 0 auto;\n width: 8.33333333%;\n }\n .col-sm-2 {\n flex: 0 0 auto;\n width: 16.66666667%;\n }\n .col-sm-3 {\n flex: 0 0 auto;\n width: 25%;\n }\n .col-sm-4 {\n flex: 0 0 auto;\n width: 33.33333333%;\n }\n .col-sm-5 {\n flex: 0 0 auto;\n width: 41.66666667%;\n }\n .col-sm-6 {\n flex: 0 0 auto;\n width: 50%;\n }\n .col-sm-7 {\n flex: 0 0 auto;\n width: 58.33333333%;\n }\n .col-sm-8 {\n flex: 0 0 auto;\n width: 66.66666667%;\n }\n .col-sm-9 {\n flex: 0 0 auto;\n width: 75%;\n }\n .col-sm-10 {\n flex: 0 0 auto;\n width: 83.33333333%;\n }\n .col-sm-11 {\n flex: 0 0 auto;\n width: 91.66666667%;\n }\n .col-sm-12 {\n flex: 0 0 auto;\n width: 100%;\n }\n .offset-sm-0 {\n margin-left: 0;\n }\n .offset-sm-1 {\n margin-left: 8.33333333%;\n }\n .offset-sm-2 {\n margin-left: 16.66666667%;\n }\n .offset-sm-3 {\n margin-left: 25%;\n }\n .offset-sm-4 {\n margin-left: 33.33333333%;\n }\n .offset-sm-5 {\n margin-left: 41.66666667%;\n }\n .offset-sm-6 {\n margin-left: 50%;\n }\n .offset-sm-7 {\n margin-left: 58.33333333%;\n }\n .offset-sm-8 {\n margin-left: 66.66666667%;\n }\n .offset-sm-9 {\n margin-left: 75%;\n }\n .offset-sm-10 {\n margin-left: 83.33333333%;\n }\n .offset-sm-11 {\n margin-left: 91.66666667%;\n }\n .g-sm-0,\n .gx-sm-0 {\n --bs-gutter-x: 0;\n }\n .g-sm-0,\n .gy-sm-0 {\n --bs-gutter-y: 0;\n }\n .g-sm-1,\n .gx-sm-1 {\n --bs-gutter-x: 0.25rem;\n }\n .g-sm-1,\n .gy-sm-1 {\n --bs-gutter-y: 0.25rem;\n }\n .g-sm-2,\n .gx-sm-2 {\n --bs-gutter-x: 0.5rem;\n }\n .g-sm-2,\n .gy-sm-2 {\n --bs-gutter-y: 0.5rem;\n }\n .g-sm-3,\n .gx-sm-3 {\n --bs-gutter-x: 1rem;\n }\n .g-sm-3,\n .gy-sm-3 {\n --bs-gutter-y: 1rem;\n }\n .g-sm-4,\n .gx-sm-4 {\n --bs-gutter-x: 1.5rem;\n }\n .g-sm-4,\n .gy-sm-4 {\n --bs-gutter-y: 1.5rem;\n }\n .g-sm-5,\n .gx-sm-5 {\n --bs-gutter-x: 3rem;\n }\n .g-sm-5,\n .gy-sm-5 {\n --bs-gutter-y: 3rem;\n }\n}\n@media (min-width: 768px) {\n .col-md {\n flex: 1 0 0%;\n }\n .row-cols-md-auto > * {\n flex: 0 0 auto;\n width: auto;\n }\n .row-cols-md-1 > * {\n flex: 0 0 auto;\n width: 100%;\n }\n .row-cols-md-2 > * {\n flex: 0 0 auto;\n width: 50%;\n }\n .row-cols-md-3 > * {\n flex: 0 0 auto;\n width: 33.3333333333%;\n }\n .row-cols-md-4 > * {\n flex: 0 0 auto;\n width: 25%;\n }\n .row-cols-md-5 > * {\n flex: 0 0 auto;\n width: 20%;\n }\n .row-cols-md-6 > * {\n flex: 0 0 auto;\n width: 16.6666666667%;\n }\n .col-md-auto {\n flex: 0 0 auto;\n width: auto;\n }\n .col-md-1 {\n flex: 0 0 auto;\n width: 8.33333333%;\n }\n .col-md-2 {\n flex: 0 0 auto;\n width: 16.66666667%;\n }\n .col-md-3 {\n flex: 0 0 auto;\n width: 25%;\n }\n .col-md-4 {\n flex: 0 0 auto;\n width: 33.33333333%;\n }\n .col-md-5 {\n flex: 0 0 auto;\n width: 41.66666667%;\n }\n .col-md-6 {\n flex: 0 0 auto;\n width: 50%;\n }\n .col-md-7 {\n flex: 0 0 auto;\n width: 58.33333333%;\n }\n .col-md-8 {\n flex: 0 0 auto;\n width: 66.66666667%;\n }\n .col-md-9 {\n flex: 0 0 auto;\n width: 75%;\n }\n .col-md-10 {\n flex: 0 0 auto;\n width: 83.33333333%;\n }\n .col-md-11 {\n flex: 0 0 auto;\n width: 91.66666667%;\n }\n .col-md-12 {\n flex: 0 0 auto;\n width: 100%;\n }\n .offset-md-0 {\n margin-left: 0;\n }\n .offset-md-1 {\n margin-left: 8.33333333%;\n }\n .offset-md-2 {\n margin-left: 16.66666667%;\n }\n .offset-md-3 {\n margin-left: 25%;\n }\n .offset-md-4 {\n margin-left: 33.33333333%;\n }\n .offset-md-5 {\n margin-left: 41.66666667%;\n }\n .offset-md-6 {\n margin-left: 50%;\n }\n .offset-md-7 {\n margin-left: 58.33333333%;\n }\n .offset-md-8 {\n margin-left: 66.66666667%;\n }\n .offset-md-9 {\n margin-left: 75%;\n }\n .offset-md-10 {\n margin-left: 83.33333333%;\n }\n .offset-md-11 {\n margin-left: 91.66666667%;\n }\n .g-md-0,\n .gx-md-0 {\n --bs-gutter-x: 0;\n }\n .g-md-0,\n .gy-md-0 {\n --bs-gutter-y: 0;\n }\n .g-md-1,\n .gx-md-1 {\n --bs-gutter-x: 0.25rem;\n }\n .g-md-1,\n .gy-md-1 {\n --bs-gutter-y: 0.25rem;\n }\n .g-md-2,\n .gx-md-2 {\n --bs-gutter-x: 0.5rem;\n }\n .g-md-2,\n .gy-md-2 {\n --bs-gutter-y: 0.5rem;\n }\n .g-md-3,\n .gx-md-3 {\n --bs-gutter-x: 1rem;\n }\n .g-md-3,\n .gy-md-3 {\n --bs-gutter-y: 1rem;\n }\n .g-md-4,\n .gx-md-4 {\n --bs-gutter-x: 1.5rem;\n }\n .g-md-4,\n .gy-md-4 {\n --bs-gutter-y: 1.5rem;\n }\n .g-md-5,\n .gx-md-5 {\n --bs-gutter-x: 3rem;\n }\n .g-md-5,\n .gy-md-5 {\n --bs-gutter-y: 3rem;\n }\n}\n@media (min-width: 992px) {\n .col-lg {\n flex: 1 0 0%;\n }\n .row-cols-lg-auto > * {\n flex: 0 0 auto;\n width: auto;\n }\n .row-cols-lg-1 > * {\n flex: 0 0 auto;\n width: 100%;\n }\n .row-cols-lg-2 > * {\n flex: 0 0 auto;\n width: 50%;\n }\n .row-cols-lg-3 > * {\n flex: 0 0 auto;\n width: 33.3333333333%;\n }\n .row-cols-lg-4 > * {\n flex: 0 0 auto;\n width: 25%;\n }\n .row-cols-lg-5 > * {\n flex: 0 0 auto;\n width: 20%;\n }\n .row-cols-lg-6 > * {\n flex: 0 0 auto;\n width: 16.6666666667%;\n }\n .col-lg-auto {\n flex: 0 0 auto;\n width: auto;\n }\n .col-lg-1 {\n flex: 0 0 auto;\n width: 8.33333333%;\n }\n .col-lg-2 {\n flex: 0 0 auto;\n width: 16.66666667%;\n }\n .col-lg-3 {\n flex: 0 0 auto;\n width: 25%;\n }\n .col-lg-4 {\n flex: 0 0 auto;\n width: 33.33333333%;\n }\n .col-lg-5 {\n flex: 0 0 auto;\n width: 41.66666667%;\n }\n .col-lg-6 {\n flex: 0 0 auto;\n width: 50%;\n }\n .col-lg-7 {\n flex: 0 0 auto;\n width: 58.33333333%;\n }\n .col-lg-8 {\n flex: 0 0 auto;\n width: 66.66666667%;\n }\n .col-lg-9 {\n flex: 0 0 auto;\n width: 75%;\n }\n .col-lg-10 {\n flex: 0 0 auto;\n width: 83.33333333%;\n }\n .col-lg-11 {\n flex: 0 0 auto;\n width: 91.66666667%;\n }\n .col-lg-12 {\n flex: 0 0 auto;\n width: 100%;\n }\n .offset-lg-0 {\n margin-left: 0;\n }\n .offset-lg-1 {\n margin-left: 8.33333333%;\n }\n .offset-lg-2 {\n margin-left: 16.66666667%;\n }\n .offset-lg-3 {\n margin-left: 25%;\n }\n .offset-lg-4 {\n margin-left: 33.33333333%;\n }\n .offset-lg-5 {\n margin-left: 41.66666667%;\n }\n .offset-lg-6 {\n margin-left: 50%;\n }\n .offset-lg-7 {\n margin-left: 58.33333333%;\n }\n .offset-lg-8 {\n margin-left: 66.66666667%;\n }\n .offset-lg-9 {\n margin-left: 75%;\n }\n .offset-lg-10 {\n margin-left: 83.33333333%;\n }\n .offset-lg-11 {\n margin-left: 91.66666667%;\n }\n .g-lg-0,\n .gx-lg-0 {\n --bs-gutter-x: 0;\n }\n .g-lg-0,\n .gy-lg-0 {\n --bs-gutter-y: 0;\n }\n .g-lg-1,\n .gx-lg-1 {\n --bs-gutter-x: 0.25rem;\n }\n .g-lg-1,\n .gy-lg-1 {\n --bs-gutter-y: 0.25rem;\n }\n .g-lg-2,\n .gx-lg-2 {\n --bs-gutter-x: 0.5rem;\n }\n .g-lg-2,\n .gy-lg-2 {\n --bs-gutter-y: 0.5rem;\n }\n .g-lg-3,\n .gx-lg-3 {\n --bs-gutter-x: 1rem;\n }\n .g-lg-3,\n .gy-lg-3 {\n --bs-gutter-y: 1rem;\n }\n .g-lg-4,\n .gx-lg-4 {\n --bs-gutter-x: 1.5rem;\n }\n .g-lg-4,\n .gy-lg-4 {\n --bs-gutter-y: 1.5rem;\n }\n .g-lg-5,\n .gx-lg-5 {\n --bs-gutter-x: 3rem;\n }\n .g-lg-5,\n .gy-lg-5 {\n --bs-gutter-y: 3rem;\n }\n}\n@media (min-width: 1200px) {\n .col-xl {\n flex: 1 0 0%;\n }\n .row-cols-xl-auto > * {\n flex: 0 0 auto;\n width: auto;\n }\n .row-cols-xl-1 > * {\n flex: 0 0 auto;\n width: 100%;\n }\n .row-cols-xl-2 > * {\n flex: 0 0 auto;\n width: 50%;\n }\n .row-cols-xl-3 > * {\n flex: 0 0 auto;\n width: 33.3333333333%;\n }\n .row-cols-xl-4 > * {\n flex: 0 0 auto;\n width: 25%;\n }\n .row-cols-xl-5 > * {\n flex: 0 0 auto;\n width: 20%;\n }\n .row-cols-xl-6 > * {\n flex: 0 0 auto;\n width: 16.6666666667%;\n }\n .col-xl-auto {\n flex: 0 0 auto;\n width: auto;\n }\n .col-xl-1 {\n flex: 0 0 auto;\n width: 8.33333333%;\n }\n .col-xl-2 {\n flex: 0 0 auto;\n width: 16.66666667%;\n }\n .col-xl-3 {\n flex: 0 0 auto;\n width: 25%;\n }\n .col-xl-4 {\n flex: 0 0 auto;\n width: 33.33333333%;\n }\n .col-xl-5 {\n flex: 0 0 auto;\n width: 41.66666667%;\n }\n .col-xl-6 {\n flex: 0 0 auto;\n width: 50%;\n }\n .col-xl-7 {\n flex: 0 0 auto;\n width: 58.33333333%;\n }\n .col-xl-8 {\n flex: 0 0 auto;\n width: 66.66666667%;\n }\n .col-xl-9 {\n flex: 0 0 auto;\n width: 75%;\n }\n .col-xl-10 {\n flex: 0 0 auto;\n width: 83.33333333%;\n }\n .col-xl-11 {\n flex: 0 0 auto;\n width: 91.66666667%;\n }\n .col-xl-12 {\n flex: 0 0 auto;\n width: 100%;\n }\n .offset-xl-0 {\n margin-left: 0;\n }\n .offset-xl-1 {\n margin-left: 8.33333333%;\n }\n .offset-xl-2 {\n margin-left: 16.66666667%;\n }\n .offset-xl-3 {\n margin-left: 25%;\n }\n .offset-xl-4 {\n margin-left: 33.33333333%;\n }\n .offset-xl-5 {\n margin-left: 41.66666667%;\n }\n .offset-xl-6 {\n margin-left: 50%;\n }\n .offset-xl-7 {\n margin-left: 58.33333333%;\n }\n .offset-xl-8 {\n margin-left: 66.66666667%;\n }\n .offset-xl-9 {\n margin-left: 75%;\n }\n .offset-xl-10 {\n margin-left: 83.33333333%;\n }\n .offset-xl-11 {\n margin-left: 91.66666667%;\n }\n .g-xl-0,\n .gx-xl-0 {\n --bs-gutter-x: 0;\n }\n .g-xl-0,\n .gy-xl-0 {\n --bs-gutter-y: 0;\n }\n .g-xl-1,\n .gx-xl-1 {\n --bs-gutter-x: 0.25rem;\n }\n .g-xl-1,\n .gy-xl-1 {\n --bs-gutter-y: 0.25rem;\n }\n .g-xl-2,\n .gx-xl-2 {\n --bs-gutter-x: 0.5rem;\n }\n .g-xl-2,\n .gy-xl-2 {\n --bs-gutter-y: 0.5rem;\n }\n .g-xl-3,\n .gx-xl-3 {\n --bs-gutter-x: 1rem;\n }\n .g-xl-3,\n .gy-xl-3 {\n --bs-gutter-y: 1rem;\n }\n .g-xl-4,\n .gx-xl-4 {\n --bs-gutter-x: 1.5rem;\n }\n .g-xl-4,\n .gy-xl-4 {\n --bs-gutter-y: 1.5rem;\n }\n .g-xl-5,\n .gx-xl-5 {\n --bs-gutter-x: 3rem;\n }\n .g-xl-5,\n .gy-xl-5 {\n --bs-gutter-y: 3rem;\n }\n}\n@media (min-width: 1400px) {\n .col-xxl {\n flex: 1 0 0%;\n }\n .row-cols-xxl-auto > * {\n flex: 0 0 auto;\n width: auto;\n }\n .row-cols-xxl-1 > * {\n flex: 0 0 auto;\n width: 100%;\n }\n .row-cols-xxl-2 > * {\n flex: 0 0 auto;\n width: 50%;\n }\n .row-cols-xxl-3 > * {\n flex: 0 0 auto;\n width: 33.3333333333%;\n }\n .row-cols-xxl-4 > * {\n flex: 0 0 auto;\n width: 25%;\n }\n .row-cols-xxl-5 > * {\n flex: 0 0 auto;\n width: 20%;\n }\n .row-cols-xxl-6 > * {\n flex: 0 0 auto;\n width: 16.6666666667%;\n }\n .col-xxl-auto {\n flex: 0 0 auto;\n width: auto;\n }\n .col-xxl-1 {\n flex: 0 0 auto;\n width: 8.33333333%;\n }\n .col-xxl-2 {\n flex: 0 0 auto;\n width: 16.66666667%;\n }\n .col-xxl-3 {\n flex: 0 0 auto;\n width: 25%;\n }\n .col-xxl-4 {\n flex: 0 0 auto;\n width: 33.33333333%;\n }\n .col-xxl-5 {\n flex: 0 0 auto;\n width: 41.66666667%;\n }\n .col-xxl-6 {\n flex: 0 0 auto;\n width: 50%;\n }\n .col-xxl-7 {\n flex: 0 0 auto;\n width: 58.33333333%;\n }\n .col-xxl-8 {\n flex: 0 0 auto;\n width: 66.66666667%;\n }\n .col-xxl-9 {\n flex: 0 0 auto;\n width: 75%;\n }\n .col-xxl-10 {\n flex: 0 0 auto;\n width: 83.33333333%;\n }\n .col-xxl-11 {\n flex: 0 0 auto;\n width: 91.66666667%;\n }\n .col-xxl-12 {\n flex: 0 0 auto;\n width: 100%;\n }\n .offset-xxl-0 {\n margin-left: 0;\n }\n .offset-xxl-1 {\n margin-left: 8.33333333%;\n }\n .offset-xxl-2 {\n margin-left: 16.66666667%;\n }\n .offset-xxl-3 {\n margin-left: 25%;\n }\n .offset-xxl-4 {\n margin-left: 33.33333333%;\n }\n .offset-xxl-5 {\n margin-left: 41.66666667%;\n }\n .offset-xxl-6 {\n margin-left: 50%;\n }\n .offset-xxl-7 {\n margin-left: 58.33333333%;\n }\n .offset-xxl-8 {\n margin-left: 66.66666667%;\n }\n .offset-xxl-9 {\n margin-left: 75%;\n }\n .offset-xxl-10 {\n margin-left: 83.33333333%;\n }\n .offset-xxl-11 {\n margin-left: 91.66666667%;\n }\n .g-xxl-0,\n .gx-xxl-0 {\n --bs-gutter-x: 0;\n }\n .g-xxl-0,\n .gy-xxl-0 {\n --bs-gutter-y: 0;\n }\n .g-xxl-1,\n .gx-xxl-1 {\n --bs-gutter-x: 0.25rem;\n }\n .g-xxl-1,\n .gy-xxl-1 {\n --bs-gutter-y: 0.25rem;\n }\n .g-xxl-2,\n .gx-xxl-2 {\n --bs-gutter-x: 0.5rem;\n }\n .g-xxl-2,\n .gy-xxl-2 {\n --bs-gutter-y: 0.5rem;\n }\n .g-xxl-3,\n .gx-xxl-3 {\n --bs-gutter-x: 1rem;\n }\n .g-xxl-3,\n .gy-xxl-3 {\n --bs-gutter-y: 1rem;\n }\n .g-xxl-4,\n .gx-xxl-4 {\n --bs-gutter-x: 1.5rem;\n }\n .g-xxl-4,\n .gy-xxl-4 {\n --bs-gutter-y: 1.5rem;\n }\n .g-xxl-5,\n .gx-xxl-5 {\n --bs-gutter-x: 3rem;\n }\n .g-xxl-5,\n .gy-xxl-5 {\n --bs-gutter-y: 3rem;\n }\n}\n.table {\n --bs-table-color: var(--bs-body-color);\n --bs-table-bg: transparent;\n --bs-table-border-color: var(--bs-border-color);\n --bs-table-accent-bg: transparent;\n --bs-table-striped-color: var(--bs-body-color);\n --bs-table-striped-bg: rgba(0, 0, 0, 0.05);\n --bs-table-active-color: var(--bs-body-color);\n --bs-table-active-bg: rgba(0, 0, 0, 0.1);\n --bs-table-hover-color: var(--bs-body-color);\n --bs-table-hover-bg: rgba(0, 0, 0, 0.075);\n width: 100%;\n margin-bottom: 1rem;\n color: var(--bs-table-color);\n vertical-align: top;\n border-color: var(--bs-table-border-color);\n}\n.table > :not(caption) > * > * {\n padding: 0.5rem 0.5rem;\n background-color: var(--bs-table-bg);\n border-bottom-width: var(--bs-border-width);\n box-shadow: inset 0 0 0 9999px var(--bs-table-accent-bg);\n}\n.table > tbody {\n vertical-align: inherit;\n}\n.table > thead {\n vertical-align: bottom;\n}\n\n.table-group-divider {\n border-top: calc(var(--bs-border-width) * 2) solid currentcolor;\n}\n\n.caption-top {\n caption-side: top;\n}\n\n.table-sm > :not(caption) > * > * {\n padding: 0.25rem 0.25rem;\n}\n\n.table-bordered > :not(caption) > * {\n border-width: var(--bs-border-width) 0;\n}\n.table-bordered > :not(caption) > * > * {\n border-width: 0 var(--bs-border-width);\n}\n\n.table-borderless > :not(caption) > * > * {\n border-bottom-width: 0;\n}\n.table-borderless > :not(:first-child) {\n border-top-width: 0;\n}\n\n.table-striped > tbody > tr:nth-of-type(odd) > * {\n --bs-table-accent-bg: var(--bs-table-striped-bg);\n color: var(--bs-table-striped-color);\n}\n\n.table-striped-columns > :not(caption) > tr > :nth-child(even) {\n --bs-table-accent-bg: var(--bs-table-striped-bg);\n color: var(--bs-table-striped-color);\n}\n\n.table-active {\n --bs-table-accent-bg: var(--bs-table-active-bg);\n color: var(--bs-table-active-color);\n}\n\n.table-hover > tbody > tr:hover > * {\n --bs-table-accent-bg: var(--bs-table-hover-bg);\n color: var(--bs-table-hover-color);\n}\n\n.table-primary {\n --bs-table-color: #000;\n --bs-table-bg: #cfe2ff;\n --bs-table-border-color: #bacbe6;\n --bs-table-striped-bg: #c5d7f2;\n --bs-table-striped-color: #000;\n --bs-table-active-bg: #bacbe6;\n --bs-table-active-color: #000;\n --bs-table-hover-bg: #bfd1ec;\n --bs-table-hover-color: #000;\n color: var(--bs-table-color);\n border-color: var(--bs-table-border-color);\n}\n\n.table-secondary {\n --bs-table-color: #000;\n --bs-table-bg: #e2e3e5;\n --bs-table-border-color: #cbccce;\n --bs-table-striped-bg: #d7d8da;\n --bs-table-striped-color: #000;\n --bs-table-active-bg: #cbccce;\n --bs-table-active-color: #000;\n --bs-table-hover-bg: #d1d2d4;\n --bs-table-hover-color: #000;\n color: var(--bs-table-color);\n border-color: var(--bs-table-border-color);\n}\n\n.table-success {\n --bs-table-color: #000;\n --bs-table-bg: #d1e7dd;\n --bs-table-border-color: #bcd0c7;\n --bs-table-striped-bg: #c7dbd2;\n --bs-table-striped-color: #000;\n --bs-table-active-bg: #bcd0c7;\n --bs-table-active-color: #000;\n --bs-table-hover-bg: #c1d6cc;\n --bs-table-hover-color: #000;\n color: var(--bs-table-color);\n border-color: var(--bs-table-border-color);\n}\n\n.table-info {\n --bs-table-color: #000;\n --bs-table-bg: #cff4fc;\n --bs-table-border-color: #badce3;\n --bs-table-striped-bg: #c5e8ef;\n --bs-table-striped-color: #000;\n --bs-table-active-bg: #badce3;\n --bs-table-active-color: #000;\n --bs-table-hover-bg: #bfe2e9;\n --bs-table-hover-color: #000;\n color: var(--bs-table-color);\n border-color: var(--bs-table-border-color);\n}\n\n.table-warning {\n --bs-table-color: #000;\n --bs-table-bg: #fff3cd;\n --bs-table-border-color: #e6dbb9;\n --bs-table-striped-bg: #f2e7c3;\n --bs-table-striped-color: #000;\n --bs-table-active-bg: #e6dbb9;\n --bs-table-active-color: #000;\n --bs-table-hover-bg: #ece1be;\n --bs-table-hover-color: #000;\n color: var(--bs-table-color);\n border-color: var(--bs-table-border-color);\n}\n\n.table-danger {\n --bs-table-color: #000;\n --bs-table-bg: #f8d7da;\n --bs-table-border-color: #dfc2c4;\n --bs-table-striped-bg: #eccccf;\n --bs-table-striped-color: #000;\n --bs-table-active-bg: #dfc2c4;\n --bs-table-active-color: #000;\n --bs-table-hover-bg: #e5c7ca;\n --bs-table-hover-color: #000;\n color: var(--bs-table-color);\n border-color: var(--bs-table-border-color);\n}\n\n.table-light {\n --bs-table-color: #000;\n --bs-table-bg: #f8f9fa;\n --bs-table-border-color: #dfe0e1;\n --bs-table-striped-bg: #ecedee;\n --bs-table-striped-color: #000;\n --bs-table-active-bg: #dfe0e1;\n --bs-table-active-color: #000;\n --bs-table-hover-bg: #e5e6e7;\n --bs-table-hover-color: #000;\n color: var(--bs-table-color);\n border-color: var(--bs-table-border-color);\n}\n\n.table-dark {\n --bs-table-color: #fff;\n --bs-table-bg: #212529;\n --bs-table-border-color: #373b3e;\n --bs-table-striped-bg: #2c3034;\n --bs-table-striped-color: #fff;\n --bs-table-active-bg: #373b3e;\n --bs-table-active-color: #fff;\n --bs-table-hover-bg: #323539;\n --bs-table-hover-color: #fff;\n color: var(--bs-table-color);\n border-color: var(--bs-table-border-color);\n}\n\n.table-responsive {\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n}\n\n@media (max-width: 575.98px) {\n .table-responsive-sm {\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n}\n@media (max-width: 767.98px) {\n .table-responsive-md {\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n}\n@media (max-width: 991.98px) {\n .table-responsive-lg {\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n}\n@media (max-width: 1199.98px) {\n .table-responsive-xl {\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n}\n@media (max-width: 1399.98px) {\n .table-responsive-xxl {\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n}\n.form-label {\n margin-bottom: 0.5rem;\n}\n\n.col-form-label {\n padding-top: calc(0.375rem + var(--bs-border-width));\n padding-bottom: calc(0.375rem + var(--bs-border-width));\n margin-bottom: 0;\n font-size: inherit;\n line-height: 1.5;\n}\n\n.col-form-label-lg {\n padding-top: calc(0.5rem + var(--bs-border-width));\n padding-bottom: calc(0.5rem + var(--bs-border-width));\n font-size: 1.25rem;\n}\n\n.col-form-label-sm {\n padding-top: calc(0.25rem + var(--bs-border-width));\n padding-bottom: calc(0.25rem + var(--bs-border-width));\n font-size: 0.875rem;\n}\n\n.form-text {\n margin-top: 0.25rem;\n font-size: 0.875em;\n color: var(--bs-secondary-color);\n}\n\n.form-control {\n display: block;\n width: 100%;\n padding: 0.375rem 0.75rem;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: var(--bs-body-color);\n background-color: var(--bs-body-bg);\n background-clip: padding-box;\n border: var(--bs-border-width) solid var(--bs-border-color);\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n border-radius: var(--bs-border-radius);\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n@media (prefers-reduced-motion: reduce) {\n .form-control {\n transition: none;\n }\n}\n.form-control[type=file] {\n overflow: hidden;\n}\n.form-control[type=file]:not(:disabled):not([readonly]) {\n cursor: pointer;\n}\n.form-control:focus {\n color: var(--bs-body-color);\n background-color: var(--bs-body-bg);\n border-color: #86b7fe;\n outline: 0;\n box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n}\n.form-control::-webkit-date-and-time-value {\n min-width: 85px;\n height: 1.5em;\n margin: 0;\n}\n.form-control::-webkit-datetime-edit {\n display: block;\n padding: 0;\n}\n.form-control::-moz-placeholder {\n color: var(--bs-secondary-color);\n opacity: 1;\n}\n.form-control::placeholder {\n color: var(--bs-secondary-color);\n opacity: 1;\n}\n.form-control:disabled {\n background-color: var(--bs-secondary-bg);\n opacity: 1;\n}\n.form-control::-webkit-file-upload-button {\n padding: 0.375rem 0.75rem;\n margin: -0.375rem -0.75rem;\n -webkit-margin-end: 0.75rem;\n margin-inline-end: 0.75rem;\n color: var(--bs-body-color);\n background-color: var(--bs-tertiary-bg);\n pointer-events: none;\n border-color: inherit;\n border-style: solid;\n border-width: 0;\n border-inline-end-width: var(--bs-border-width);\n border-radius: 0;\n -webkit-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n.form-control::file-selector-button {\n padding: 0.375rem 0.75rem;\n margin: -0.375rem -0.75rem;\n -webkit-margin-end: 0.75rem;\n margin-inline-end: 0.75rem;\n color: var(--bs-body-color);\n background-color: var(--bs-tertiary-bg);\n pointer-events: none;\n border-color: inherit;\n border-style: solid;\n border-width: 0;\n border-inline-end-width: var(--bs-border-width);\n border-radius: 0;\n transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n@media (prefers-reduced-motion: reduce) {\n .form-control::-webkit-file-upload-button {\n -webkit-transition: none;\n transition: none;\n }\n .form-control::file-selector-button {\n transition: none;\n }\n}\n.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button {\n background-color: var(--bs-secondary-bg);\n}\n.form-control:hover:not(:disabled):not([readonly])::file-selector-button {\n background-color: var(--bs-secondary-bg);\n}\n\n.form-control-plaintext {\n display: block;\n width: 100%;\n padding: 0.375rem 0;\n margin-bottom: 0;\n line-height: 1.5;\n color: var(--bs-body-color);\n background-color: transparent;\n border: solid transparent;\n border-width: var(--bs-border-width) 0;\n}\n.form-control-plaintext:focus {\n outline: 0;\n}\n.form-control-plaintext.form-control-sm, .form-control-plaintext.form-control-lg {\n padding-right: 0;\n padding-left: 0;\n}\n\n.form-control-sm {\n min-height: calc(1.5em + 0.5rem + calc(var(--bs-border-width) * 2));\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n border-radius: var(--bs-border-radius-sm);\n}\n.form-control-sm::-webkit-file-upload-button {\n padding: 0.25rem 0.5rem;\n margin: -0.25rem -0.5rem;\n -webkit-margin-end: 0.5rem;\n margin-inline-end: 0.5rem;\n}\n.form-control-sm::file-selector-button {\n padding: 0.25rem 0.5rem;\n margin: -0.25rem -0.5rem;\n -webkit-margin-end: 0.5rem;\n margin-inline-end: 0.5rem;\n}\n\n.form-control-lg {\n min-height: calc(1.5em + 1rem + calc(var(--bs-border-width) * 2));\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n border-radius: var(--bs-border-radius-lg);\n}\n.form-control-lg::-webkit-file-upload-button {\n padding: 0.5rem 1rem;\n margin: -0.5rem -1rem;\n -webkit-margin-end: 1rem;\n margin-inline-end: 1rem;\n}\n.form-control-lg::file-selector-button {\n padding: 0.5rem 1rem;\n margin: -0.5rem -1rem;\n -webkit-margin-end: 1rem;\n margin-inline-end: 1rem;\n}\n\ntextarea.form-control {\n min-height: calc(1.5em + 0.75rem + calc(var(--bs-border-width) * 2));\n}\ntextarea.form-control-sm {\n min-height: calc(1.5em + 0.5rem + calc(var(--bs-border-width) * 2));\n}\ntextarea.form-control-lg {\n min-height: calc(1.5em + 1rem + calc(var(--bs-border-width) * 2));\n}\n\n.form-control-color {\n width: 3rem;\n height: calc(1.5em + 0.75rem + calc(var(--bs-border-width) * 2));\n padding: 0.375rem;\n}\n.form-control-color:not(:disabled):not([readonly]) {\n cursor: pointer;\n}\n.form-control-color::-moz-color-swatch {\n border: 0 !important;\n border-radius: var(--bs-border-radius);\n}\n.form-control-color::-webkit-color-swatch {\n border: 0 !important;\n border-radius: var(--bs-border-radius);\n}\n.form-control-color.form-control-sm {\n height: calc(1.5em + 0.5rem + calc(var(--bs-border-width) * 2));\n}\n.form-control-color.form-control-lg {\n height: calc(1.5em + 1rem + calc(var(--bs-border-width) * 2));\n}\n\n.form-select {\n --bs-form-select-bg-img: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e\");\n display: block;\n width: 100%;\n padding: 0.375rem 2.25rem 0.375rem 0.75rem;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: var(--bs-body-color);\n background-color: var(--bs-body-bg);\n background-image: var(--bs-form-select-bg-img), var(--bs-form-select-bg-icon, none);\n background-repeat: no-repeat;\n background-position: right 0.75rem center;\n background-size: 16px 12px;\n border: var(--bs-border-width) solid var(--bs-border-color);\n border-radius: var(--bs-border-radius);\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n@media (prefers-reduced-motion: reduce) {\n .form-select {\n transition: none;\n }\n}\n.form-select:focus {\n border-color: #86b7fe;\n outline: 0;\n box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n}\n.form-select[multiple], .form-select[size]:not([size=\"1\"]) {\n padding-right: 0.75rem;\n background-image: none;\n}\n.form-select:disabled {\n background-color: var(--bs-secondary-bg);\n}\n.form-select:-moz-focusring {\n color: transparent;\n text-shadow: 0 0 0 var(--bs-body-color);\n}\n\n.form-select-sm {\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n padding-left: 0.5rem;\n font-size: 0.875rem;\n border-radius: var(--bs-border-radius-sm);\n}\n\n.form-select-lg {\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n padding-left: 1rem;\n font-size: 1.25rem;\n border-radius: var(--bs-border-radius-lg);\n}\n\n[data-bs-theme=dark] .form-select {\n --bs-form-select-bg-img: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23adb5bd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e\");\n}\n\n.form-check {\n display: block;\n min-height: 1.5rem;\n padding-left: 1.5em;\n margin-bottom: 0.125rem;\n}\n.form-check .form-check-input {\n float: left;\n margin-left: -1.5em;\n}\n\n.form-check-reverse {\n padding-right: 1.5em;\n padding-left: 0;\n text-align: right;\n}\n.form-check-reverse .form-check-input {\n float: right;\n margin-right: -1.5em;\n margin-left: 0;\n}\n\n.form-check-input {\n --bs-form-check-bg: var(--bs-body-bg);\n width: 1em;\n height: 1em;\n margin-top: 0.25em;\n vertical-align: top;\n background-color: var(--bs-form-check-bg);\n background-image: var(--bs-form-check-bg-image);\n background-repeat: no-repeat;\n background-position: center;\n background-size: contain;\n border: var(--bs-border-width) solid var(--bs-border-color);\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n -webkit-print-color-adjust: exact;\n color-adjust: exact;\n print-color-adjust: exact;\n}\n.form-check-input[type=checkbox] {\n border-radius: 0.25em;\n}\n.form-check-input[type=radio] {\n border-radius: 50%;\n}\n.form-check-input:active {\n filter: brightness(90%);\n}\n.form-check-input:focus {\n border-color: #86b7fe;\n outline: 0;\n box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n}\n.form-check-input:checked {\n background-color: #0d6efd;\n border-color: #0d6efd;\n}\n.form-check-input:checked[type=checkbox] {\n --bs-form-check-bg-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e\");\n}\n.form-check-input:checked[type=radio] {\n --bs-form-check-bg-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e\");\n}\n.form-check-input[type=checkbox]:indeterminate {\n background-color: #0d6efd;\n border-color: #0d6efd;\n --bs-form-check-bg-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e\");\n}\n.form-check-input:disabled {\n pointer-events: none;\n filter: none;\n opacity: 0.5;\n}\n.form-check-input[disabled] ~ .form-check-label, .form-check-input:disabled ~ .form-check-label {\n cursor: default;\n opacity: 0.5;\n}\n\n.form-switch {\n padding-left: 2.5em;\n}\n.form-switch .form-check-input {\n --bs-form-switch-bg: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e\");\n width: 2em;\n margin-left: -2.5em;\n background-image: var(--bs-form-switch-bg);\n background-position: left center;\n border-radius: 2em;\n transition: background-position 0.15s ease-in-out;\n}\n@media (prefers-reduced-motion: reduce) {\n .form-switch .form-check-input {\n transition: none;\n }\n}\n.form-switch .form-check-input:focus {\n --bs-form-switch-bg: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e\");\n}\n.form-switch .form-check-input:checked {\n background-position: right center;\n --bs-form-switch-bg: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e\");\n}\n.form-switch.form-check-reverse {\n padding-right: 2.5em;\n padding-left: 0;\n}\n.form-switch.form-check-reverse .form-check-input {\n margin-right: -2.5em;\n margin-left: 0;\n}\n\n.form-check-inline {\n display: inline-block;\n margin-right: 1rem;\n}\n\n.btn-check {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n pointer-events: none;\n}\n.btn-check[disabled] + .btn, .btn-check:disabled + .btn {\n pointer-events: none;\n filter: none;\n opacity: 0.65;\n}\n\n[data-bs-theme=dark] .form-switch .form-check-input:not(:checked):not(:focus) {\n --bs-form-switch-bg: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e\");\n}\n\n.form-range {\n width: 100%;\n height: 1.5rem;\n padding: 0;\n background-color: transparent;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n.form-range:focus {\n outline: 0;\n}\n.form-range:focus::-webkit-slider-thumb {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n}\n.form-range:focus::-moz-range-thumb {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n}\n.form-range::-moz-focus-outer {\n border: 0;\n}\n.form-range::-webkit-slider-thumb {\n width: 1rem;\n height: 1rem;\n margin-top: -0.25rem;\n background-color: #0d6efd;\n border: 0;\n border-radius: 1rem;\n -webkit-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n -webkit-appearance: none;\n appearance: none;\n}\n@media (prefers-reduced-motion: reduce) {\n .form-range::-webkit-slider-thumb {\n -webkit-transition: none;\n transition: none;\n }\n}\n.form-range::-webkit-slider-thumb:active {\n background-color: #b6d4fe;\n}\n.form-range::-webkit-slider-runnable-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: var(--bs-tertiary-bg);\n border-color: transparent;\n border-radius: 1rem;\n}\n.form-range::-moz-range-thumb {\n width: 1rem;\n height: 1rem;\n background-color: #0d6efd;\n border: 0;\n border-radius: 1rem;\n -moz-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n -moz-appearance: none;\n appearance: none;\n}\n@media (prefers-reduced-motion: reduce) {\n .form-range::-moz-range-thumb {\n -moz-transition: none;\n transition: none;\n }\n}\n.form-range::-moz-range-thumb:active {\n background-color: #b6d4fe;\n}\n.form-range::-moz-range-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: var(--bs-tertiary-bg);\n border-color: transparent;\n border-radius: 1rem;\n}\n.form-range:disabled {\n pointer-events: none;\n}\n.form-range:disabled::-webkit-slider-thumb {\n background-color: var(--bs-secondary-color);\n}\n.form-range:disabled::-moz-range-thumb {\n background-color: var(--bs-secondary-color);\n}\n\n.form-floating {\n position: relative;\n}\n.form-floating > .form-control,\n.form-floating > .form-control-plaintext,\n.form-floating > .form-select {\n height: calc(3.5rem + calc(var(--bs-border-width) * 2));\n line-height: 1.25;\n}\n.form-floating > label {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 2;\n height: 100%;\n padding: 1rem 0.75rem;\n overflow: hidden;\n text-align: start;\n text-overflow: ellipsis;\n white-space: nowrap;\n pointer-events: none;\n border: var(--bs-border-width) solid transparent;\n transform-origin: 0 0;\n transition: opacity 0.1s ease-in-out, transform 0.1s ease-in-out;\n}\n@media (prefers-reduced-motion: reduce) {\n .form-floating > label {\n transition: none;\n }\n}\n.form-floating > .form-control,\n.form-floating > .form-control-plaintext {\n padding: 1rem 0.75rem;\n}\n.form-floating > .form-control::-moz-placeholder, .form-floating > .form-control-plaintext::-moz-placeholder {\n color: transparent;\n}\n.form-floating > .form-control::placeholder,\n.form-floating > .form-control-plaintext::placeholder {\n color: transparent;\n}\n.form-floating > .form-control:not(:-moz-placeholder-shown), .form-floating > .form-control-plaintext:not(:-moz-placeholder-shown) {\n padding-top: 1.625rem;\n padding-bottom: 0.625rem;\n}\n.form-floating > .form-control:focus, .form-floating > .form-control:not(:placeholder-shown),\n.form-floating > .form-control-plaintext:focus,\n.form-floating > .form-control-plaintext:not(:placeholder-shown) {\n padding-top: 1.625rem;\n padding-bottom: 0.625rem;\n}\n.form-floating > .form-control:-webkit-autofill,\n.form-floating > .form-control-plaintext:-webkit-autofill {\n padding-top: 1.625rem;\n padding-bottom: 0.625rem;\n}\n.form-floating > .form-select {\n padding-top: 1.625rem;\n padding-bottom: 0.625rem;\n}\n.form-floating > .form-control:not(:-moz-placeholder-shown) ~ label {\n color: rgba(var(--bs-body-color-rgb), 0.65);\n transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem);\n}\n.form-floating > .form-control:focus ~ label,\n.form-floating > .form-control:not(:placeholder-shown) ~ label,\n.form-floating > .form-control-plaintext ~ label,\n.form-floating > .form-select ~ label {\n color: rgba(var(--bs-body-color-rgb), 0.65);\n transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem);\n}\n.form-floating > .form-control:not(:-moz-placeholder-shown) ~ label::after {\n position: absolute;\n inset: 1rem 0.375rem;\n z-index: -1;\n height: 1.5em;\n content: \"\";\n background-color: var(--bs-body-bg);\n border-radius: var(--bs-border-radius);\n}\n.form-floating > .form-control:focus ~ label::after,\n.form-floating > .form-control:not(:placeholder-shown) ~ label::after,\n.form-floating > .form-control-plaintext ~ label::after,\n.form-floating > .form-select ~ label::after {\n position: absolute;\n inset: 1rem 0.375rem;\n z-index: -1;\n height: 1.5em;\n content: \"\";\n background-color: var(--bs-body-bg);\n border-radius: var(--bs-border-radius);\n}\n.form-floating > .form-control:-webkit-autofill ~ label {\n color: rgba(var(--bs-body-color-rgb), 0.65);\n transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem);\n}\n.form-floating > .form-control-plaintext ~ label {\n border-width: var(--bs-border-width) 0;\n}\n.form-floating > :disabled ~ label {\n color: #6c757d;\n}\n.form-floating > :disabled ~ label::after {\n background-color: var(--bs-secondary-bg);\n}\n\n.input-group {\n position: relative;\n display: flex;\n flex-wrap: wrap;\n align-items: stretch;\n width: 100%;\n}\n.input-group > .form-control,\n.input-group > .form-select,\n.input-group > .form-floating {\n position: relative;\n flex: 1 1 auto;\n width: 1%;\n min-width: 0;\n}\n.input-group > .form-control:focus,\n.input-group > .form-select:focus,\n.input-group > .form-floating:focus-within {\n z-index: 5;\n}\n.input-group .btn {\n position: relative;\n z-index: 2;\n}\n.input-group .btn:focus {\n z-index: 5;\n}\n\n.input-group-text {\n display: flex;\n align-items: center;\n padding: 0.375rem 0.75rem;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: var(--bs-body-color);\n text-align: center;\n white-space: nowrap;\n background-color: var(--bs-tertiary-bg);\n border: var(--bs-border-width) solid var(--bs-border-color);\n border-radius: var(--bs-border-radius);\n}\n\n.input-group-lg > .form-control,\n.input-group-lg > .form-select,\n.input-group-lg > .input-group-text,\n.input-group-lg > .btn {\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n border-radius: var(--bs-border-radius-lg);\n}\n\n.input-group-sm > .form-control,\n.input-group-sm > .form-select,\n.input-group-sm > .input-group-text,\n.input-group-sm > .btn {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n border-radius: var(--bs-border-radius-sm);\n}\n\n.input-group-lg > .form-select,\n.input-group-sm > .form-select {\n padding-right: 3rem;\n}\n\n.input-group:not(.has-validation) > :not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),\n.input-group:not(.has-validation) > .dropdown-toggle:nth-last-child(n+3),\n.input-group:not(.has-validation) > .form-floating:not(:last-child) > .form-control,\n.input-group:not(.has-validation) > .form-floating:not(:last-child) > .form-select {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n.input-group.has-validation > :nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),\n.input-group.has-validation > .dropdown-toggle:nth-last-child(n+4),\n.input-group.has-validation > .form-floating:nth-last-child(n+3) > .form-control,\n.input-group.has-validation > .form-floating:nth-last-child(n+3) > .form-select {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n.input-group > :not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback) {\n margin-left: calc(var(--bs-border-width) * -1);\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n.input-group > .form-floating:not(:first-child) > .form-control,\n.input-group > .form-floating:not(:first-child) > .form-select {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.valid-feedback {\n display: none;\n width: 100%;\n margin-top: 0.25rem;\n font-size: 0.875em;\n color: var(--bs-form-valid-color);\n}\n\n.valid-tooltip {\n position: absolute;\n top: 100%;\n z-index: 5;\n display: none;\n max-width: 100%;\n padding: 0.25rem 0.5rem;\n margin-top: 0.1rem;\n font-size: 0.875rem;\n color: #fff;\n background-color: var(--bs-success);\n border-radius: var(--bs-border-radius);\n}\n\n.was-validated :valid ~ .valid-feedback,\n.was-validated :valid ~ .valid-tooltip,\n.is-valid ~ .valid-feedback,\n.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .form-control:valid, .form-control.is-valid {\n border-color: var(--bs-form-valid-border-color);\n padding-right: calc(1.5em + 0.75rem);\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e\");\n background-repeat: no-repeat;\n background-position: right calc(0.375em + 0.1875rem) center;\n background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n.was-validated .form-control:valid:focus, .form-control.is-valid:focus {\n border-color: var(--bs-form-valid-border-color);\n box-shadow: 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25);\n}\n\n.was-validated textarea.form-control:valid, textarea.form-control.is-valid {\n padding-right: calc(1.5em + 0.75rem);\n background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem);\n}\n\n.was-validated .form-select:valid, .form-select.is-valid {\n border-color: var(--bs-form-valid-border-color);\n}\n.was-validated .form-select:valid:not([multiple]):not([size]), .was-validated .form-select:valid:not([multiple])[size=\"1\"], .form-select.is-valid:not([multiple]):not([size]), .form-select.is-valid:not([multiple])[size=\"1\"] {\n --bs-form-select-bg-icon: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e\");\n padding-right: 4.125rem;\n background-position: right 0.75rem center, center right 2.25rem;\n background-size: 16px 12px, calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n.was-validated .form-select:valid:focus, .form-select.is-valid:focus {\n border-color: var(--bs-form-valid-border-color);\n box-shadow: 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25);\n}\n\n.was-validated .form-control-color:valid, .form-control-color.is-valid {\n width: calc(3rem + calc(1.5em + 0.75rem));\n}\n\n.was-validated .form-check-input:valid, .form-check-input.is-valid {\n border-color: var(--bs-form-valid-border-color);\n}\n.was-validated .form-check-input:valid:checked, .form-check-input.is-valid:checked {\n background-color: var(--bs-form-valid-color);\n}\n.was-validated .form-check-input:valid:focus, .form-check-input.is-valid:focus {\n box-shadow: 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25);\n}\n.was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label {\n color: var(--bs-form-valid-color);\n}\n\n.form-check-inline .form-check-input ~ .valid-feedback {\n margin-left: 0.5em;\n}\n\n.was-validated .input-group > .form-control:not(:focus):valid, .input-group > .form-control:not(:focus).is-valid,\n.was-validated .input-group > .form-select:not(:focus):valid,\n.input-group > .form-select:not(:focus).is-valid,\n.was-validated .input-group > .form-floating:not(:focus-within):valid,\n.input-group > .form-floating:not(:focus-within).is-valid {\n z-index: 3;\n}\n\n.invalid-feedback {\n display: none;\n width: 100%;\n margin-top: 0.25rem;\n font-size: 0.875em;\n color: var(--bs-form-invalid-color);\n}\n\n.invalid-tooltip {\n position: absolute;\n top: 100%;\n z-index: 5;\n display: none;\n max-width: 100%;\n padding: 0.25rem 0.5rem;\n margin-top: 0.1rem;\n font-size: 0.875rem;\n color: #fff;\n background-color: var(--bs-danger);\n border-radius: var(--bs-border-radius);\n}\n\n.was-validated :invalid ~ .invalid-feedback,\n.was-validated :invalid ~ .invalid-tooltip,\n.is-invalid ~ .invalid-feedback,\n.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .form-control:invalid, .form-control.is-invalid {\n border-color: var(--bs-form-invalid-border-color);\n padding-right: calc(1.5em + 0.75rem);\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e\");\n background-repeat: no-repeat;\n background-position: right calc(0.375em + 0.1875rem) center;\n background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus {\n border-color: var(--bs-form-invalid-border-color);\n box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25);\n}\n\n.was-validated textarea.form-control:invalid, textarea.form-control.is-invalid {\n padding-right: calc(1.5em + 0.75rem);\n background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem);\n}\n\n.was-validated .form-select:invalid, .form-select.is-invalid {\n border-color: var(--bs-form-invalid-border-color);\n}\n.was-validated .form-select:invalid:not([multiple]):not([size]), .was-validated .form-select:invalid:not([multiple])[size=\"1\"], .form-select.is-invalid:not([multiple]):not([size]), .form-select.is-invalid:not([multiple])[size=\"1\"] {\n --bs-form-select-bg-icon: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e\");\n padding-right: 4.125rem;\n background-position: right 0.75rem center, center right 2.25rem;\n background-size: 16px 12px, calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n.was-validated .form-select:invalid:focus, .form-select.is-invalid:focus {\n border-color: var(--bs-form-invalid-border-color);\n box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25);\n}\n\n.was-validated .form-control-color:invalid, .form-control-color.is-invalid {\n width: calc(3rem + calc(1.5em + 0.75rem));\n}\n\n.was-validated .form-check-input:invalid, .form-check-input.is-invalid {\n border-color: var(--bs-form-invalid-border-color);\n}\n.was-validated .form-check-input:invalid:checked, .form-check-input.is-invalid:checked {\n background-color: var(--bs-form-invalid-color);\n}\n.was-validated .form-check-input:invalid:focus, .form-check-input.is-invalid:focus {\n box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25);\n}\n.was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label {\n color: var(--bs-form-invalid-color);\n}\n\n.form-check-inline .form-check-input ~ .invalid-feedback {\n margin-left: 0.5em;\n}\n\n.was-validated .input-group > .form-control:not(:focus):invalid, .input-group > .form-control:not(:focus).is-invalid,\n.was-validated .input-group > .form-select:not(:focus):invalid,\n.input-group > .form-select:not(:focus).is-invalid,\n.was-validated .input-group > .form-floating:not(:focus-within):invalid,\n.input-group > .form-floating:not(:focus-within).is-invalid {\n z-index: 4;\n}\n\n.btn {\n --bs-btn-padding-x: 0.75rem;\n --bs-btn-padding-y: 0.375rem;\n --bs-btn-font-family: ;\n --bs-btn-font-size: 1rem;\n --bs-btn-font-weight: 400;\n --bs-btn-line-height: 1.5;\n --bs-btn-color: var(--bs-body-color);\n --bs-btn-bg: transparent;\n --bs-btn-border-width: var(--bs-border-width);\n --bs-btn-border-color: transparent;\n --bs-btn-border-radius: var(--bs-border-radius);\n --bs-btn-hover-border-color: transparent;\n --bs-btn-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n --bs-btn-disabled-opacity: 0.65;\n --bs-btn-focus-box-shadow: 0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5);\n display: inline-block;\n padding: var(--bs-btn-padding-y) var(--bs-btn-padding-x);\n font-family: var(--bs-btn-font-family);\n font-size: var(--bs-btn-font-size);\n font-weight: var(--bs-btn-font-weight);\n line-height: var(--bs-btn-line-height);\n color: var(--bs-btn-color);\n text-align: center;\n text-decoration: none;\n vertical-align: middle;\n cursor: pointer;\n -webkit-user-select: none;\n -moz-user-select: none;\n user-select: none;\n border: var(--bs-btn-border-width) solid var(--bs-btn-border-color);\n border-radius: var(--bs-btn-border-radius);\n background-color: var(--bs-btn-bg);\n transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n@media (prefers-reduced-motion: reduce) {\n .btn {\n transition: none;\n }\n}\n.btn:hover {\n color: var(--bs-btn-hover-color);\n background-color: var(--bs-btn-hover-bg);\n border-color: var(--bs-btn-hover-border-color);\n}\n.btn-check + .btn:hover {\n color: var(--bs-btn-color);\n background-color: var(--bs-btn-bg);\n border-color: var(--bs-btn-border-color);\n}\n.btn:focus-visible {\n color: var(--bs-btn-hover-color);\n background-color: var(--bs-btn-hover-bg);\n border-color: var(--bs-btn-hover-border-color);\n outline: 0;\n box-shadow: var(--bs-btn-focus-box-shadow);\n}\n.btn-check:focus-visible + .btn {\n border-color: var(--bs-btn-hover-border-color);\n outline: 0;\n box-shadow: var(--bs-btn-focus-box-shadow);\n}\n.btn-check:checked + .btn, :not(.btn-check) + .btn:active, .btn:first-child:active, .btn.active, .btn.show {\n color: var(--bs-btn-active-color);\n background-color: var(--bs-btn-active-bg);\n border-color: var(--bs-btn-active-border-color);\n}\n.btn-check:checked + .btn:focus-visible, :not(.btn-check) + .btn:active:focus-visible, .btn:first-child:active:focus-visible, .btn.active:focus-visible, .btn.show:focus-visible {\n box-shadow: var(--bs-btn-focus-box-shadow);\n}\n.btn:disabled, .btn.disabled, fieldset:disabled .btn {\n color: var(--bs-btn-disabled-color);\n pointer-events: none;\n background-color: var(--bs-btn-disabled-bg);\n border-color: var(--bs-btn-disabled-border-color);\n opacity: var(--bs-btn-disabled-opacity);\n}\n\n.btn-primary {\n --bs-btn-color: #fff;\n --bs-btn-bg: #0d6efd;\n --bs-btn-border-color: #0d6efd;\n --bs-btn-hover-color: #fff;\n --bs-btn-hover-bg: #0b5ed7;\n --bs-btn-hover-border-color: #0a58ca;\n --bs-btn-focus-shadow-rgb: 49, 132, 253;\n --bs-btn-active-color: #fff;\n --bs-btn-active-bg: #0a58ca;\n --bs-btn-active-border-color: #0a53be;\n --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n --bs-btn-disabled-color: #fff;\n --bs-btn-disabled-bg: #0d6efd;\n --bs-btn-disabled-border-color: #0d6efd;\n}\n\n.btn-secondary {\n --bs-btn-color: #fff;\n --bs-btn-bg: #6c757d;\n --bs-btn-border-color: #6c757d;\n --bs-btn-hover-color: #fff;\n --bs-btn-hover-bg: #5c636a;\n --bs-btn-hover-border-color: #565e64;\n --bs-btn-focus-shadow-rgb: 130, 138, 145;\n --bs-btn-active-color: #fff;\n --bs-btn-active-bg: #565e64;\n --bs-btn-active-border-color: #51585e;\n --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n --bs-btn-disabled-color: #fff;\n --bs-btn-disabled-bg: #6c757d;\n --bs-btn-disabled-border-color: #6c757d;\n}\n\n.btn-success {\n --bs-btn-color: #fff;\n --bs-btn-bg: #198754;\n --bs-btn-border-color: #198754;\n --bs-btn-hover-color: #fff;\n --bs-btn-hover-bg: #157347;\n --bs-btn-hover-border-color: #146c43;\n --bs-btn-focus-shadow-rgb: 60, 153, 110;\n --bs-btn-active-color: #fff;\n --bs-btn-active-bg: #146c43;\n --bs-btn-active-border-color: #13653f;\n --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n --bs-btn-disabled-color: #fff;\n --bs-btn-disabled-bg: #198754;\n --bs-btn-disabled-border-color: #198754;\n}\n\n.btn-info {\n --bs-btn-color: #000;\n --bs-btn-bg: #0dcaf0;\n --bs-btn-border-color: #0dcaf0;\n --bs-btn-hover-color: #000;\n --bs-btn-hover-bg: #31d2f2;\n --bs-btn-hover-border-color: #25cff2;\n --bs-btn-focus-shadow-rgb: 11, 172, 204;\n --bs-btn-active-color: #000;\n --bs-btn-active-bg: #3dd5f3;\n --bs-btn-active-border-color: #25cff2;\n --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n --bs-btn-disabled-color: #000;\n --bs-btn-disabled-bg: #0dcaf0;\n --bs-btn-disabled-border-color: #0dcaf0;\n}\n\n.btn-warning {\n --bs-btn-color: #000;\n --bs-btn-bg: #ffc107;\n --bs-btn-border-color: #ffc107;\n --bs-btn-hover-color: #000;\n --bs-btn-hover-bg: #ffca2c;\n --bs-btn-hover-border-color: #ffc720;\n --bs-btn-focus-shadow-rgb: 217, 164, 6;\n --bs-btn-active-color: #000;\n --bs-btn-active-bg: #ffcd39;\n --bs-btn-active-border-color: #ffc720;\n --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n --bs-btn-disabled-color: #000;\n --bs-btn-disabled-bg: #ffc107;\n --bs-btn-disabled-border-color: #ffc107;\n}\n\n.btn-danger {\n --bs-btn-color: #fff;\n --bs-btn-bg: #dc3545;\n --bs-btn-border-color: #dc3545;\n --bs-btn-hover-color: #fff;\n --bs-btn-hover-bg: #bb2d3b;\n --bs-btn-hover-border-color: #b02a37;\n --bs-btn-focus-shadow-rgb: 225, 83, 97;\n --bs-btn-active-color: #fff;\n --bs-btn-active-bg: #b02a37;\n --bs-btn-active-border-color: #a52834;\n --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n --bs-btn-disabled-color: #fff;\n --bs-btn-disabled-bg: #dc3545;\n --bs-btn-disabled-border-color: #dc3545;\n}\n\n.btn-light {\n --bs-btn-color: #000;\n --bs-btn-bg: #f8f9fa;\n --bs-btn-border-color: #f8f9fa;\n --bs-btn-hover-color: #000;\n --bs-btn-hover-bg: #d3d4d5;\n --bs-btn-hover-border-color: #c6c7c8;\n --bs-btn-focus-shadow-rgb: 211, 212, 213;\n --bs-btn-active-color: #000;\n --bs-btn-active-bg: #c6c7c8;\n --bs-btn-active-border-color: #babbbc;\n --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n --bs-btn-disabled-color: #000;\n --bs-btn-disabled-bg: #f8f9fa;\n --bs-btn-disabled-border-color: #f8f9fa;\n}\n\n.btn-dark {\n --bs-btn-color: #fff;\n --bs-btn-bg: #212529;\n --bs-btn-border-color: #212529;\n --bs-btn-hover-color: #fff;\n --bs-btn-hover-bg: #424649;\n --bs-btn-hover-border-color: #373b3e;\n --bs-btn-focus-shadow-rgb: 66, 70, 73;\n --bs-btn-active-color: #fff;\n --bs-btn-active-bg: #4d5154;\n --bs-btn-active-border-color: #373b3e;\n --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n --bs-btn-disabled-color: #fff;\n --bs-btn-disabled-bg: #212529;\n --bs-btn-disabled-border-color: #212529;\n}\n\n.btn-outline-primary {\n --bs-btn-color: #0d6efd;\n --bs-btn-border-color: #0d6efd;\n --bs-btn-hover-color: #fff;\n --bs-btn-hover-bg: #0d6efd;\n --bs-btn-hover-border-color: #0d6efd;\n --bs-btn-focus-shadow-rgb: 13, 110, 253;\n --bs-btn-active-color: #fff;\n --bs-btn-active-bg: #0d6efd;\n --bs-btn-active-border-color: #0d6efd;\n --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n --bs-btn-disabled-color: #0d6efd;\n --bs-btn-disabled-bg: transparent;\n --bs-btn-disabled-border-color: #0d6efd;\n --bs-gradient: none;\n}\n\n.btn-outline-secondary {\n --bs-btn-color: #6c757d;\n --bs-btn-border-color: #6c757d;\n --bs-btn-hover-color: #fff;\n --bs-btn-hover-bg: #6c757d;\n --bs-btn-hover-border-color: #6c757d;\n --bs-btn-focus-shadow-rgb: 108, 117, 125;\n --bs-btn-active-color: #fff;\n --bs-btn-active-bg: #6c757d;\n --bs-btn-active-border-color: #6c757d;\n --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n --bs-btn-disabled-color: #6c757d;\n --bs-btn-disabled-bg: transparent;\n --bs-btn-disabled-border-color: #6c757d;\n --bs-gradient: none;\n}\n\n.btn-outline-success {\n --bs-btn-color: #198754;\n --bs-btn-border-color: #198754;\n --bs-btn-hover-color: #fff;\n --bs-btn-hover-bg: #198754;\n --bs-btn-hover-border-color: #198754;\n --bs-btn-focus-shadow-rgb: 25, 135, 84;\n --bs-btn-active-color: #fff;\n --bs-btn-active-bg: #198754;\n --bs-btn-active-border-color: #198754;\n --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n --bs-btn-disabled-color: #198754;\n --bs-btn-disabled-bg: transparent;\n --bs-btn-disabled-border-color: #198754;\n --bs-gradient: none;\n}\n\n.btn-outline-info {\n --bs-btn-color: #0dcaf0;\n --bs-btn-border-color: #0dcaf0;\n --bs-btn-hover-color: #000;\n --bs-btn-hover-bg: #0dcaf0;\n --bs-btn-hover-border-color: #0dcaf0;\n --bs-btn-focus-shadow-rgb: 13, 202, 240;\n --bs-btn-active-color: #000;\n --bs-btn-active-bg: #0dcaf0;\n --bs-btn-active-border-color: #0dcaf0;\n --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n --bs-btn-disabled-color: #0dcaf0;\n --bs-btn-disabled-bg: transparent;\n --bs-btn-disabled-border-color: #0dcaf0;\n --bs-gradient: none;\n}\n\n.btn-outline-warning {\n --bs-btn-color: #ffc107;\n --bs-btn-border-color: #ffc107;\n --bs-btn-hover-color: #000;\n --bs-btn-hover-bg: #ffc107;\n --bs-btn-hover-border-color: #ffc107;\n --bs-btn-focus-shadow-rgb: 255, 193, 7;\n --bs-btn-active-color: #000;\n --bs-btn-active-bg: #ffc107;\n --bs-btn-active-border-color: #ffc107;\n --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n --bs-btn-disabled-color: #ffc107;\n --bs-btn-disabled-bg: transparent;\n --bs-btn-disabled-border-color: #ffc107;\n --bs-gradient: none;\n}\n\n.btn-outline-danger {\n --bs-btn-color: #dc3545;\n --bs-btn-border-color: #dc3545;\n --bs-btn-hover-color: #fff;\n --bs-btn-hover-bg: #dc3545;\n --bs-btn-hover-border-color: #dc3545;\n --bs-btn-focus-shadow-rgb: 220, 53, 69;\n --bs-btn-active-color: #fff;\n --bs-btn-active-bg: #dc3545;\n --bs-btn-active-border-color: #dc3545;\n --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n --bs-btn-disabled-color: #dc3545;\n --bs-btn-disabled-bg: transparent;\n --bs-btn-disabled-border-color: #dc3545;\n --bs-gradient: none;\n}\n\n.btn-outline-light {\n --bs-btn-color: #f8f9fa;\n --bs-btn-border-color: #f8f9fa;\n --bs-btn-hover-color: #000;\n --bs-btn-hover-bg: #f8f9fa;\n --bs-btn-hover-border-color: #f8f9fa;\n --bs-btn-focus-shadow-rgb: 248, 249, 250;\n --bs-btn-active-color: #000;\n --bs-btn-active-bg: #f8f9fa;\n --bs-btn-active-border-color: #f8f9fa;\n --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n --bs-btn-disabled-color: #f8f9fa;\n --bs-btn-disabled-bg: transparent;\n --bs-btn-disabled-border-color: #f8f9fa;\n --bs-gradient: none;\n}\n\n.btn-outline-dark {\n --bs-btn-color: #212529;\n --bs-btn-border-color: #212529;\n --bs-btn-hover-color: #fff;\n --bs-btn-hover-bg: #212529;\n --bs-btn-hover-border-color: #212529;\n --bs-btn-focus-shadow-rgb: 33, 37, 41;\n --bs-btn-active-color: #fff;\n --bs-btn-active-bg: #212529;\n --bs-btn-active-border-color: #212529;\n --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n --bs-btn-disabled-color: #212529;\n --bs-btn-disabled-bg: transparent;\n --bs-btn-disabled-border-color: #212529;\n --bs-gradient: none;\n}\n\n.btn-link {\n --bs-btn-font-weight: 400;\n --bs-btn-color: var(--bs-link-color);\n --bs-btn-bg: transparent;\n --bs-btn-border-color: transparent;\n --bs-btn-hover-color: var(--bs-link-hover-color);\n --bs-btn-hover-border-color: transparent;\n --bs-btn-active-color: var(--bs-link-hover-color);\n --bs-btn-active-border-color: transparent;\n --bs-btn-disabled-color: #6c757d;\n --bs-btn-disabled-border-color: transparent;\n --bs-btn-box-shadow: 0 0 0 #000;\n --bs-btn-focus-shadow-rgb: 49, 132, 253;\n text-decoration: underline;\n}\n.btn-link:focus-visible {\n color: var(--bs-btn-color);\n}\n.btn-link:hover {\n color: var(--bs-btn-hover-color);\n}\n\n.btn-lg, .btn-group-lg > .btn {\n --bs-btn-padding-y: 0.5rem;\n --bs-btn-padding-x: 1rem;\n --bs-btn-font-size: 1.25rem;\n --bs-btn-border-radius: var(--bs-border-radius-lg);\n}\n\n.btn-sm, .btn-group-sm > .btn {\n --bs-btn-padding-y: 0.25rem;\n --bs-btn-padding-x: 0.5rem;\n --bs-btn-font-size: 0.875rem;\n --bs-btn-border-radius: var(--bs-border-radius-sm);\n}\n\n.fade {\n transition: opacity 0.15s linear;\n}\n@media (prefers-reduced-motion: reduce) {\n .fade {\n transition: none;\n }\n}\n.fade:not(.show) {\n opacity: 0;\n}\n\n.collapse:not(.show) {\n display: none;\n}\n\n.collapsing {\n height: 0;\n overflow: hidden;\n transition: height 0.35s ease;\n}\n@media (prefers-reduced-motion: reduce) {\n .collapsing {\n transition: none;\n }\n}\n.collapsing.collapse-horizontal {\n width: 0;\n height: auto;\n transition: width 0.35s ease;\n}\n@media (prefers-reduced-motion: reduce) {\n .collapsing.collapse-horizontal {\n transition: none;\n }\n}\n\n.dropup,\n.dropend,\n.dropdown,\n.dropstart,\n.dropup-center,\n.dropdown-center {\n position: relative;\n}\n\n.dropdown-toggle {\n white-space: nowrap;\n}\n.dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid;\n border-right: 0.3em solid transparent;\n border-bottom: 0;\n border-left: 0.3em solid transparent;\n}\n.dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropdown-menu {\n --bs-dropdown-zindex: 1000;\n --bs-dropdown-min-width: 10rem;\n --bs-dropdown-padding-x: 0;\n --bs-dropdown-padding-y: 0.5rem;\n --bs-dropdown-spacer: 0.125rem;\n --bs-dropdown-font-size: 1rem;\n --bs-dropdown-color: var(--bs-body-color);\n --bs-dropdown-bg: var(--bs-body-bg);\n --bs-dropdown-border-color: var(--bs-border-color-translucent);\n --bs-dropdown-border-radius: var(--bs-border-radius);\n --bs-dropdown-border-width: var(--bs-border-width);\n --bs-dropdown-inner-border-radius: calc(var(--bs-border-radius) - var(--bs-border-width));\n --bs-dropdown-divider-bg: var(--bs-border-color-translucent);\n --bs-dropdown-divider-margin-y: 0.5rem;\n --bs-dropdown-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);\n --bs-dropdown-link-color: var(--bs-body-color);\n --bs-dropdown-link-hover-color: var(--bs-body-color);\n --bs-dropdown-link-hover-bg: var(--bs-tertiary-bg);\n --bs-dropdown-link-active-color: #fff;\n --bs-dropdown-link-active-bg: #0d6efd;\n --bs-dropdown-link-disabled-color: var(--bs-tertiary-color);\n --bs-dropdown-item-padding-x: 1rem;\n --bs-dropdown-item-padding-y: 0.25rem;\n --bs-dropdown-header-color: #6c757d;\n --bs-dropdown-header-padding-x: 1rem;\n --bs-dropdown-header-padding-y: 0.5rem;\n position: absolute;\n z-index: var(--bs-dropdown-zindex);\n display: none;\n min-width: var(--bs-dropdown-min-width);\n padding: var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x);\n margin: 0;\n font-size: var(--bs-dropdown-font-size);\n color: var(--bs-dropdown-color);\n text-align: left;\n list-style: none;\n background-color: var(--bs-dropdown-bg);\n background-clip: padding-box;\n border: var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);\n border-radius: var(--bs-dropdown-border-radius);\n}\n.dropdown-menu[data-bs-popper] {\n top: 100%;\n left: 0;\n margin-top: var(--bs-dropdown-spacer);\n}\n\n.dropdown-menu-start {\n --bs-position: start;\n}\n.dropdown-menu-start[data-bs-popper] {\n right: auto;\n left: 0;\n}\n\n.dropdown-menu-end {\n --bs-position: end;\n}\n.dropdown-menu-end[data-bs-popper] {\n right: 0;\n left: auto;\n}\n\n@media (min-width: 576px) {\n .dropdown-menu-sm-start {\n --bs-position: start;\n }\n .dropdown-menu-sm-start[data-bs-popper] {\n right: auto;\n left: 0;\n }\n .dropdown-menu-sm-end {\n --bs-position: end;\n }\n .dropdown-menu-sm-end[data-bs-popper] {\n right: 0;\n left: auto;\n }\n}\n@media (min-width: 768px) {\n .dropdown-menu-md-start {\n --bs-position: start;\n }\n .dropdown-menu-md-start[data-bs-popper] {\n right: auto;\n left: 0;\n }\n .dropdown-menu-md-end {\n --bs-position: end;\n }\n .dropdown-menu-md-end[data-bs-popper] {\n right: 0;\n left: auto;\n }\n}\n@media (min-width: 992px) {\n .dropdown-menu-lg-start {\n --bs-position: start;\n }\n .dropdown-menu-lg-start[data-bs-popper] {\n right: auto;\n left: 0;\n }\n .dropdown-menu-lg-end {\n --bs-position: end;\n }\n .dropdown-menu-lg-end[data-bs-popper] {\n right: 0;\n left: auto;\n }\n}\n@media (min-width: 1200px) {\n .dropdown-menu-xl-start {\n --bs-position: start;\n }\n .dropdown-menu-xl-start[data-bs-popper] {\n right: auto;\n left: 0;\n }\n .dropdown-menu-xl-end {\n --bs-position: end;\n }\n .dropdown-menu-xl-end[data-bs-popper] {\n right: 0;\n left: auto;\n }\n}\n@media (min-width: 1400px) {\n .dropdown-menu-xxl-start {\n --bs-position: start;\n }\n .dropdown-menu-xxl-start[data-bs-popper] {\n right: auto;\n left: 0;\n }\n .dropdown-menu-xxl-end {\n --bs-position: end;\n }\n .dropdown-menu-xxl-end[data-bs-popper] {\n right: 0;\n left: auto;\n }\n}\n.dropup .dropdown-menu[data-bs-popper] {\n top: auto;\n bottom: 100%;\n margin-top: 0;\n margin-bottom: var(--bs-dropdown-spacer);\n}\n.dropup .dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0;\n border-right: 0.3em solid transparent;\n border-bottom: 0.3em solid;\n border-left: 0.3em solid transparent;\n}\n.dropup .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropend .dropdown-menu[data-bs-popper] {\n top: 0;\n right: auto;\n left: 100%;\n margin-top: 0;\n margin-left: var(--bs-dropdown-spacer);\n}\n.dropend .dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid transparent;\n border-right: 0;\n border-bottom: 0.3em solid transparent;\n border-left: 0.3em solid;\n}\n.dropend .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n.dropend .dropdown-toggle::after {\n vertical-align: 0;\n}\n\n.dropstart .dropdown-menu[data-bs-popper] {\n top: 0;\n right: 100%;\n left: auto;\n margin-top: 0;\n margin-right: var(--bs-dropdown-spacer);\n}\n.dropstart .dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n}\n.dropstart .dropdown-toggle::after {\n display: none;\n}\n.dropstart .dropdown-toggle::before {\n display: inline-block;\n margin-right: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid transparent;\n border-right: 0.3em solid;\n border-bottom: 0.3em solid transparent;\n}\n.dropstart .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n.dropstart .dropdown-toggle::before {\n vertical-align: 0;\n}\n\n.dropdown-divider {\n height: 0;\n margin: var(--bs-dropdown-divider-margin-y) 0;\n overflow: hidden;\n border-top: 1px solid var(--bs-dropdown-divider-bg);\n opacity: 1;\n}\n\n.dropdown-item {\n display: block;\n width: 100%;\n padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);\n clear: both;\n font-weight: 400;\n color: var(--bs-dropdown-link-color);\n text-align: inherit;\n text-decoration: none;\n white-space: nowrap;\n background-color: transparent;\n border: 0;\n border-radius: var(--bs-dropdown-item-border-radius, 0);\n}\n.dropdown-item:hover, .dropdown-item:focus {\n color: var(--bs-dropdown-link-hover-color);\n background-color: var(--bs-dropdown-link-hover-bg);\n}\n.dropdown-item.active, .dropdown-item:active {\n color: var(--bs-dropdown-link-active-color);\n text-decoration: none;\n background-color: var(--bs-dropdown-link-active-bg);\n}\n.dropdown-item.disabled, .dropdown-item:disabled {\n color: var(--bs-dropdown-link-disabled-color);\n pointer-events: none;\n background-color: transparent;\n}\n\n.dropdown-menu.show {\n display: block;\n}\n\n.dropdown-header {\n display: block;\n padding: var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);\n margin-bottom: 0;\n font-size: 0.875rem;\n color: var(--bs-dropdown-header-color);\n white-space: nowrap;\n}\n\n.dropdown-item-text {\n display: block;\n padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);\n color: var(--bs-dropdown-link-color);\n}\n\n.dropdown-menu-dark {\n --bs-dropdown-color: #dee2e6;\n --bs-dropdown-bg: #343a40;\n --bs-dropdown-border-color: var(--bs-border-color-translucent);\n --bs-dropdown-box-shadow: ;\n --bs-dropdown-link-color: #dee2e6;\n --bs-dropdown-link-hover-color: #fff;\n --bs-dropdown-divider-bg: var(--bs-border-color-translucent);\n --bs-dropdown-link-hover-bg: rgba(255, 255, 255, 0.15);\n --bs-dropdown-link-active-color: #fff;\n --bs-dropdown-link-active-bg: #0d6efd;\n --bs-dropdown-link-disabled-color: #adb5bd;\n --bs-dropdown-header-color: #adb5bd;\n}\n\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-flex;\n vertical-align: middle;\n}\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n position: relative;\n flex: 1 1 auto;\n}\n.btn-group > .btn-check:checked + .btn,\n.btn-group > .btn-check:focus + .btn,\n.btn-group > .btn:hover,\n.btn-group > .btn:focus,\n.btn-group > .btn:active,\n.btn-group > .btn.active,\n.btn-group-vertical > .btn-check:checked + .btn,\n.btn-group-vertical > .btn-check:focus + .btn,\n.btn-group-vertical > .btn:hover,\n.btn-group-vertical > .btn:focus,\n.btn-group-vertical > .btn:active,\n.btn-group-vertical > .btn.active {\n z-index: 1;\n}\n\n.btn-toolbar {\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-start;\n}\n.btn-toolbar .input-group {\n width: auto;\n}\n\n.btn-group {\n border-radius: var(--bs-border-radius);\n}\n.btn-group > :not(.btn-check:first-child) + .btn,\n.btn-group > .btn-group:not(:first-child) {\n margin-left: calc(var(--bs-border-width) * -1);\n}\n.btn-group > .btn:not(:last-child):not(.dropdown-toggle),\n.btn-group > .btn.dropdown-toggle-split:first-child,\n.btn-group > .btn-group:not(:last-child) > .btn {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n.btn-group > .btn:nth-child(n+3),\n.btn-group > :not(.btn-check) + .btn,\n.btn-group > .btn-group:not(:first-child) > .btn {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.dropdown-toggle-split {\n padding-right: 0.5625rem;\n padding-left: 0.5625rem;\n}\n.dropdown-toggle-split::after, .dropup .dropdown-toggle-split::after, .dropend .dropdown-toggle-split::after {\n margin-left: 0;\n}\n.dropstart .dropdown-toggle-split::before {\n margin-right: 0;\n}\n\n.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split {\n padding-right: 0.375rem;\n padding-left: 0.375rem;\n}\n\n.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split {\n padding-right: 0.75rem;\n padding-left: 0.75rem;\n}\n\n.btn-group-vertical {\n flex-direction: column;\n align-items: flex-start;\n justify-content: center;\n}\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group {\n width: 100%;\n}\n.btn-group-vertical > .btn:not(:first-child),\n.btn-group-vertical > .btn-group:not(:first-child) {\n margin-top: calc(var(--bs-border-width) * -1);\n}\n.btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle),\n.btn-group-vertical > .btn-group:not(:last-child) > .btn {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn ~ .btn,\n.btn-group-vertical > .btn-group:not(:first-child) > .btn {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.nav {\n --bs-nav-link-padding-x: 1rem;\n --bs-nav-link-padding-y: 0.5rem;\n --bs-nav-link-font-weight: ;\n --bs-nav-link-color: var(--bs-link-color);\n --bs-nav-link-hover-color: var(--bs-link-hover-color);\n --bs-nav-link-disabled-color: var(--bs-secondary-color);\n display: flex;\n flex-wrap: wrap;\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n}\n\n.nav-link {\n display: block;\n padding: var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);\n font-size: var(--bs-nav-link-font-size);\n font-weight: var(--bs-nav-link-font-weight);\n color: var(--bs-nav-link-color);\n text-decoration: none;\n background: none;\n border: 0;\n transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out;\n}\n@media (prefers-reduced-motion: reduce) {\n .nav-link {\n transition: none;\n }\n}\n.nav-link:hover, .nav-link:focus {\n color: var(--bs-nav-link-hover-color);\n}\n.nav-link:focus-visible {\n outline: 0;\n box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n}\n.nav-link.disabled {\n color: var(--bs-nav-link-disabled-color);\n pointer-events: none;\n cursor: default;\n}\n\n.nav-tabs {\n --bs-nav-tabs-border-width: var(--bs-border-width);\n --bs-nav-tabs-border-color: var(--bs-border-color);\n --bs-nav-tabs-border-radius: var(--bs-border-radius);\n --bs-nav-tabs-link-hover-border-color: var(--bs-secondary-bg) var(--bs-secondary-bg) var(--bs-border-color);\n --bs-nav-tabs-link-active-color: var(--bs-emphasis-color);\n --bs-nav-tabs-link-active-bg: var(--bs-body-bg);\n --bs-nav-tabs-link-active-border-color: var(--bs-border-color) var(--bs-border-color) var(--bs-body-bg);\n border-bottom: var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color);\n}\n.nav-tabs .nav-link {\n margin-bottom: calc(-1 * var(--bs-nav-tabs-border-width));\n border: var(--bs-nav-tabs-border-width) solid transparent;\n border-top-left-radius: var(--bs-nav-tabs-border-radius);\n border-top-right-radius: var(--bs-nav-tabs-border-radius);\n}\n.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus {\n isolation: isolate;\n border-color: var(--bs-nav-tabs-link-hover-border-color);\n}\n.nav-tabs .nav-link.disabled, .nav-tabs .nav-link:disabled {\n color: var(--bs-nav-link-disabled-color);\n background-color: transparent;\n border-color: transparent;\n}\n.nav-tabs .nav-link.active,\n.nav-tabs .nav-item.show .nav-link {\n color: var(--bs-nav-tabs-link-active-color);\n background-color: var(--bs-nav-tabs-link-active-bg);\n border-color: var(--bs-nav-tabs-link-active-border-color);\n}\n.nav-tabs .dropdown-menu {\n margin-top: calc(-1 * var(--bs-nav-tabs-border-width));\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.nav-pills {\n --bs-nav-pills-border-radius: var(--bs-border-radius);\n --bs-nav-pills-link-active-color: #fff;\n --bs-nav-pills-link-active-bg: #0d6efd;\n}\n.nav-pills .nav-link {\n border-radius: var(--bs-nav-pills-border-radius);\n}\n.nav-pills .nav-link:disabled {\n color: var(--bs-nav-link-disabled-color);\n background-color: transparent;\n border-color: transparent;\n}\n.nav-pills .nav-link.active,\n.nav-pills .show > .nav-link {\n color: var(--bs-nav-pills-link-active-color);\n background-color: var(--bs-nav-pills-link-active-bg);\n}\n\n.nav-underline {\n --bs-nav-underline-gap: 1rem;\n --bs-nav-underline-border-width: 0.125rem;\n --bs-nav-underline-link-active-color: var(--bs-emphasis-color);\n gap: var(--bs-nav-underline-gap);\n}\n.nav-underline .nav-link {\n padding-right: 0;\n padding-left: 0;\n border-bottom: var(--bs-nav-underline-border-width) solid transparent;\n}\n.nav-underline .nav-link:hover, .nav-underline .nav-link:focus {\n border-bottom-color: currentcolor;\n}\n.nav-underline .nav-link.active,\n.nav-underline .show > .nav-link {\n font-weight: 700;\n color: var(--bs-nav-underline-link-active-color);\n border-bottom-color: currentcolor;\n}\n\n.nav-fill > .nav-link,\n.nav-fill .nav-item {\n flex: 1 1 auto;\n text-align: center;\n}\n\n.nav-justified > .nav-link,\n.nav-justified .nav-item {\n flex-basis: 0;\n flex-grow: 1;\n text-align: center;\n}\n\n.nav-fill .nav-item .nav-link,\n.nav-justified .nav-item .nav-link {\n width: 100%;\n}\n\n.tab-content > .tab-pane {\n display: none;\n}\n.tab-content > .active {\n display: block;\n}\n\n.navbar {\n --bs-navbar-padding-x: 0;\n --bs-navbar-padding-y: 0.5rem;\n --bs-navbar-color: rgba(var(--bs-emphasis-color-rgb), 0.65);\n --bs-navbar-hover-color: rgba(var(--bs-emphasis-color-rgb), 0.8);\n --bs-navbar-disabled-color: rgba(var(--bs-emphasis-color-rgb), 0.3);\n --bs-navbar-active-color: rgba(var(--bs-emphasis-color-rgb), 1);\n --bs-navbar-brand-padding-y: 0.3125rem;\n --bs-navbar-brand-margin-end: 1rem;\n --bs-navbar-brand-font-size: 1.25rem;\n --bs-navbar-brand-color: rgba(var(--bs-emphasis-color-rgb), 1);\n --bs-navbar-brand-hover-color: rgba(var(--bs-emphasis-color-rgb), 1);\n --bs-navbar-nav-link-padding-x: 0.5rem;\n --bs-navbar-toggler-padding-y: 0.25rem;\n --bs-navbar-toggler-padding-x: 0.75rem;\n --bs-navbar-toggler-font-size: 1.25rem;\n --bs-navbar-toggler-icon-bg: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833, 37, 41, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e\");\n --bs-navbar-toggler-border-color: rgba(var(--bs-emphasis-color-rgb), 0.15);\n --bs-navbar-toggler-border-radius: var(--bs-border-radius);\n --bs-navbar-toggler-focus-width: 0.25rem;\n --bs-navbar-toggler-transition: box-shadow 0.15s ease-in-out;\n position: relative;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n padding: var(--bs-navbar-padding-y) var(--bs-navbar-padding-x);\n}\n.navbar > .container,\n.navbar > .container-fluid,\n.navbar > .container-sm,\n.navbar > .container-md,\n.navbar > .container-lg,\n.navbar > .container-xl,\n.navbar > .container-xxl {\n display: flex;\n flex-wrap: inherit;\n align-items: center;\n justify-content: space-between;\n}\n.navbar-brand {\n padding-top: var(--bs-navbar-brand-padding-y);\n padding-bottom: var(--bs-navbar-brand-padding-y);\n margin-right: var(--bs-navbar-brand-margin-end);\n font-size: var(--bs-navbar-brand-font-size);\n color: var(--bs-navbar-brand-color);\n text-decoration: none;\n white-space: nowrap;\n}\n.navbar-brand:hover, .navbar-brand:focus {\n color: var(--bs-navbar-brand-hover-color);\n}\n\n.navbar-nav {\n --bs-nav-link-padding-x: 0;\n --bs-nav-link-padding-y: 0.5rem;\n --bs-nav-link-font-weight: ;\n --bs-nav-link-color: var(--bs-navbar-color);\n --bs-nav-link-hover-color: var(--bs-navbar-hover-color);\n --bs-nav-link-disabled-color: var(--bs-navbar-disabled-color);\n display: flex;\n flex-direction: column;\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n}\n.navbar-nav .nav-link.active, .navbar-nav .nav-link.show {\n color: var(--bs-navbar-active-color);\n}\n.navbar-nav .dropdown-menu {\n position: static;\n}\n\n.navbar-text {\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n color: var(--bs-navbar-color);\n}\n.navbar-text a,\n.navbar-text a:hover,\n.navbar-text a:focus {\n color: var(--bs-navbar-active-color);\n}\n\n.navbar-collapse {\n flex-basis: 100%;\n flex-grow: 1;\n align-items: center;\n}\n\n.navbar-toggler {\n padding: var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x);\n font-size: var(--bs-navbar-toggler-font-size);\n line-height: 1;\n color: var(--bs-navbar-color);\n background-color: transparent;\n border: var(--bs-border-width) solid var(--bs-navbar-toggler-border-color);\n border-radius: var(--bs-navbar-toggler-border-radius);\n transition: var(--bs-navbar-toggler-transition);\n}\n@media (prefers-reduced-motion: reduce) {\n .navbar-toggler {\n transition: none;\n }\n}\n.navbar-toggler:hover {\n text-decoration: none;\n}\n.navbar-toggler:focus {\n text-decoration: none;\n outline: 0;\n box-shadow: 0 0 0 var(--bs-navbar-toggler-focus-width);\n}\n\n.navbar-toggler-icon {\n display: inline-block;\n width: 1.5em;\n height: 1.5em;\n vertical-align: middle;\n background-image: var(--bs-navbar-toggler-icon-bg);\n background-repeat: no-repeat;\n background-position: center;\n background-size: 100%;\n}\n\n.navbar-nav-scroll {\n max-height: var(--bs-scroll-height, 75vh);\n overflow-y: auto;\n}\n\n@media (min-width: 576px) {\n .navbar-expand-sm {\n flex-wrap: nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-sm .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-sm .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-sm .navbar-nav .nav-link {\n padding-right: var(--bs-navbar-nav-link-padding-x);\n padding-left: var(--bs-navbar-nav-link-padding-x);\n }\n .navbar-expand-sm .navbar-nav-scroll {\n overflow: visible;\n }\n .navbar-expand-sm .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n }\n .navbar-expand-sm .navbar-toggler {\n display: none;\n }\n .navbar-expand-sm .offcanvas {\n position: static;\n z-index: auto;\n flex-grow: 1;\n width: auto !important;\n height: auto !important;\n visibility: visible !important;\n background-color: transparent !important;\n border: 0 !important;\n transform: none !important;\n transition: none;\n }\n .navbar-expand-sm .offcanvas .offcanvas-header {\n display: none;\n }\n .navbar-expand-sm .offcanvas .offcanvas-body {\n display: flex;\n flex-grow: 0;\n padding: 0;\n overflow-y: visible;\n }\n}\n@media (min-width: 768px) {\n .navbar-expand-md {\n flex-wrap: nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-md .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-md .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-md .navbar-nav .nav-link {\n padding-right: var(--bs-navbar-nav-link-padding-x);\n padding-left: var(--bs-navbar-nav-link-padding-x);\n }\n .navbar-expand-md .navbar-nav-scroll {\n overflow: visible;\n }\n .navbar-expand-md .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n }\n .navbar-expand-md .navbar-toggler {\n display: none;\n }\n .navbar-expand-md .offcanvas {\n position: static;\n z-index: auto;\n flex-grow: 1;\n width: auto !important;\n height: auto !important;\n visibility: visible !important;\n background-color: transparent !important;\n border: 0 !important;\n transform: none !important;\n transition: none;\n }\n .navbar-expand-md .offcanvas .offcanvas-header {\n display: none;\n }\n .navbar-expand-md .offcanvas .offcanvas-body {\n display: flex;\n flex-grow: 0;\n padding: 0;\n overflow-y: visible;\n }\n}\n@media (min-width: 992px) {\n .navbar-expand-lg {\n flex-wrap: nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-lg .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-lg .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-lg .navbar-nav .nav-link {\n padding-right: var(--bs-navbar-nav-link-padding-x);\n padding-left: var(--bs-navbar-nav-link-padding-x);\n }\n .navbar-expand-lg .navbar-nav-scroll {\n overflow: visible;\n }\n .navbar-expand-lg .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n }\n .navbar-expand-lg .navbar-toggler {\n display: none;\n }\n .navbar-expand-lg .offcanvas {\n position: static;\n z-index: auto;\n flex-grow: 1;\n width: auto !important;\n height: auto !important;\n visibility: visible !important;\n background-color: transparent !important;\n border: 0 !important;\n transform: none !important;\n transition: none;\n }\n .navbar-expand-lg .offcanvas .offcanvas-header {\n display: none;\n }\n .navbar-expand-lg .offcanvas .offcanvas-body {\n display: flex;\n flex-grow: 0;\n padding: 0;\n overflow-y: visible;\n }\n}\n@media (min-width: 1200px) {\n .navbar-expand-xl {\n flex-wrap: nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-xl .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-xl .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-xl .navbar-nav .nav-link {\n padding-right: var(--bs-navbar-nav-link-padding-x);\n padding-left: var(--bs-navbar-nav-link-padding-x);\n }\n .navbar-expand-xl .navbar-nav-scroll {\n overflow: visible;\n }\n .navbar-expand-xl .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n }\n .navbar-expand-xl .navbar-toggler {\n display: none;\n }\n .navbar-expand-xl .offcanvas {\n position: static;\n z-index: auto;\n flex-grow: 1;\n width: auto !important;\n height: auto !important;\n visibility: visible !important;\n background-color: transparent !important;\n border: 0 !important;\n transform: none !important;\n transition: none;\n }\n .navbar-expand-xl .offcanvas .offcanvas-header {\n display: none;\n }\n .navbar-expand-xl .offcanvas .offcanvas-body {\n display: flex;\n flex-grow: 0;\n padding: 0;\n overflow-y: visible;\n }\n}\n@media (min-width: 1400px) {\n .navbar-expand-xxl {\n flex-wrap: nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-xxl .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-xxl .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-xxl .navbar-nav .nav-link {\n padding-right: var(--bs-navbar-nav-link-padding-x);\n padding-left: var(--bs-navbar-nav-link-padding-x);\n }\n .navbar-expand-xxl .navbar-nav-scroll {\n overflow: visible;\n }\n .navbar-expand-xxl .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n }\n .navbar-expand-xxl .navbar-toggler {\n display: none;\n }\n .navbar-expand-xxl .offcanvas {\n position: static;\n z-index: auto;\n flex-grow: 1;\n width: auto !important;\n height: auto !important;\n visibility: visible !important;\n background-color: transparent !important;\n border: 0 !important;\n transform: none !important;\n transition: none;\n }\n .navbar-expand-xxl .offcanvas .offcanvas-header {\n display: none;\n }\n .navbar-expand-xxl .offcanvas .offcanvas-body {\n display: flex;\n flex-grow: 0;\n padding: 0;\n overflow-y: visible;\n }\n}\n.navbar-expand {\n flex-wrap: nowrap;\n justify-content: flex-start;\n}\n.navbar-expand .navbar-nav {\n flex-direction: row;\n}\n.navbar-expand .navbar-nav .dropdown-menu {\n position: absolute;\n}\n.navbar-expand .navbar-nav .nav-link {\n padding-right: var(--bs-navbar-nav-link-padding-x);\n padding-left: var(--bs-navbar-nav-link-padding-x);\n}\n.navbar-expand .navbar-nav-scroll {\n overflow: visible;\n}\n.navbar-expand .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n}\n.navbar-expand .navbar-toggler {\n display: none;\n}\n.navbar-expand .offcanvas {\n position: static;\n z-index: auto;\n flex-grow: 1;\n width: auto !important;\n height: auto !important;\n visibility: visible !important;\n background-color: transparent !important;\n border: 0 !important;\n transform: none !important;\n transition: none;\n}\n.navbar-expand .offcanvas .offcanvas-header {\n display: none;\n}\n.navbar-expand .offcanvas .offcanvas-body {\n display: flex;\n flex-grow: 0;\n padding: 0;\n overflow-y: visible;\n}\n\n.navbar-dark {\n --bs-navbar-color: rgba(255, 255, 255, 0.55);\n --bs-navbar-hover-color: rgba(255, 255, 255, 0.75);\n --bs-navbar-disabled-color: rgba(255, 255, 255, 0.25);\n --bs-navbar-active-color: #fff;\n --bs-navbar-brand-color: #fff;\n --bs-navbar-brand-hover-color: #fff;\n --bs-navbar-toggler-border-color: rgba(255, 255, 255, 0.1);\n --bs-navbar-toggler-icon-bg: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e\");\n}\n\n[data-bs-theme=dark] .navbar-toggler-icon {\n --bs-navbar-toggler-icon-bg: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e\");\n}\n\n.card {\n --bs-card-spacer-y: 1rem;\n --bs-card-spacer-x: 1rem;\n --bs-card-title-spacer-y: 0.5rem;\n --bs-card-title-color: ;\n --bs-card-subtitle-color: ;\n --bs-card-border-width: var(--bs-border-width);\n --bs-card-border-color: var(--bs-border-color-translucent);\n --bs-card-border-radius: var(--bs-border-radius);\n --bs-card-box-shadow: ;\n --bs-card-inner-border-radius: calc(var(--bs-border-radius) - (var(--bs-border-width)));\n --bs-card-cap-padding-y: 0.5rem;\n --bs-card-cap-padding-x: 1rem;\n --bs-card-cap-bg: rgba(var(--bs-body-color-rgb), 0.03);\n --bs-card-cap-color: ;\n --bs-card-height: ;\n --bs-card-color: ;\n --bs-card-bg: var(--bs-body-bg);\n --bs-card-img-overlay-padding: 1rem;\n --bs-card-group-margin: 0.75rem;\n position: relative;\n display: flex;\n flex-direction: column;\n min-width: 0;\n height: var(--bs-card-height);\n color: var(--bs-body-color);\n word-wrap: break-word;\n background-color: var(--bs-card-bg);\n background-clip: border-box;\n border: var(--bs-card-border-width) solid var(--bs-card-border-color);\n border-radius: var(--bs-card-border-radius);\n}\n.card > hr {\n margin-right: 0;\n margin-left: 0;\n}\n.card > .list-group {\n border-top: inherit;\n border-bottom: inherit;\n}\n.card > .list-group:first-child {\n border-top-width: 0;\n border-top-left-radius: var(--bs-card-inner-border-radius);\n border-top-right-radius: var(--bs-card-inner-border-radius);\n}\n.card > .list-group:last-child {\n border-bottom-width: 0;\n border-bottom-right-radius: var(--bs-card-inner-border-radius);\n border-bottom-left-radius: var(--bs-card-inner-border-radius);\n}\n.card > .card-header + .list-group,\n.card > .list-group + .card-footer {\n border-top: 0;\n}\n\n.card-body {\n flex: 1 1 auto;\n padding: var(--bs-card-spacer-y) var(--bs-card-spacer-x);\n color: var(--bs-card-color);\n}\n\n.card-title {\n margin-bottom: var(--bs-card-title-spacer-y);\n color: var(--bs-card-title-color);\n}\n\n.card-subtitle {\n margin-top: calc(-0.5 * var(--bs-card-title-spacer-y));\n margin-bottom: 0;\n color: var(--bs-card-subtitle-color);\n}\n\n.card-text:last-child {\n margin-bottom: 0;\n}\n\n.card-link + .card-link {\n margin-left: var(--bs-card-spacer-x);\n}\n\n.card-header {\n padding: var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);\n margin-bottom: 0;\n color: var(--bs-card-cap-color);\n background-color: var(--bs-card-cap-bg);\n border-bottom: var(--bs-card-border-width) solid var(--bs-card-border-color);\n}\n.card-header:first-child {\n border-radius: var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius) 0 0;\n}\n\n.card-footer {\n padding: var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);\n color: var(--bs-card-cap-color);\n background-color: var(--bs-card-cap-bg);\n border-top: var(--bs-card-border-width) solid var(--bs-card-border-color);\n}\n.card-footer:last-child {\n border-radius: 0 0 var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius);\n}\n\n.card-header-tabs {\n margin-right: calc(-0.5 * var(--bs-card-cap-padding-x));\n margin-bottom: calc(-1 * var(--bs-card-cap-padding-y));\n margin-left: calc(-0.5 * var(--bs-card-cap-padding-x));\n border-bottom: 0;\n}\n.card-header-tabs .nav-link.active {\n background-color: var(--bs-card-bg);\n border-bottom-color: var(--bs-card-bg);\n}\n\n.card-header-pills {\n margin-right: calc(-0.5 * var(--bs-card-cap-padding-x));\n margin-left: calc(-0.5 * var(--bs-card-cap-padding-x));\n}\n\n.card-img-overlay {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n padding: var(--bs-card-img-overlay-padding);\n border-radius: var(--bs-card-inner-border-radius);\n}\n\n.card-img,\n.card-img-top,\n.card-img-bottom {\n width: 100%;\n}\n\n.card-img,\n.card-img-top {\n border-top-left-radius: var(--bs-card-inner-border-radius);\n border-top-right-radius: var(--bs-card-inner-border-radius);\n}\n\n.card-img,\n.card-img-bottom {\n border-bottom-right-radius: var(--bs-card-inner-border-radius);\n border-bottom-left-radius: var(--bs-card-inner-border-radius);\n}\n\n.card-group > .card {\n margin-bottom: var(--bs-card-group-margin);\n}\n@media (min-width: 576px) {\n .card-group {\n display: flex;\n flex-flow: row wrap;\n }\n .card-group > .card {\n flex: 1 0 0%;\n margin-bottom: 0;\n }\n .card-group > .card + .card {\n margin-left: 0;\n border-left: 0;\n }\n .card-group > .card:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n }\n .card-group > .card:not(:last-child) .card-img-top,\n .card-group > .card:not(:last-child) .card-header {\n border-top-right-radius: 0;\n }\n .card-group > .card:not(:last-child) .card-img-bottom,\n .card-group > .card:not(:last-child) .card-footer {\n border-bottom-right-radius: 0;\n }\n .card-group > .card:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n }\n .card-group > .card:not(:first-child) .card-img-top,\n .card-group > .card:not(:first-child) .card-header {\n border-top-left-radius: 0;\n }\n .card-group > .card:not(:first-child) .card-img-bottom,\n .card-group > .card:not(:first-child) .card-footer {\n border-bottom-left-radius: 0;\n }\n}\n\n.accordion {\n --bs-accordion-color: var(--bs-body-color);\n --bs-accordion-bg: var(--bs-body-bg);\n --bs-accordion-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, border-radius 0.15s ease;\n --bs-accordion-border-color: var(--bs-border-color);\n --bs-accordion-border-width: var(--bs-border-width);\n --bs-accordion-border-radius: var(--bs-border-radius);\n --bs-accordion-inner-border-radius: calc(var(--bs-border-radius) - (var(--bs-border-width)));\n --bs-accordion-btn-padding-x: 1.25rem;\n --bs-accordion-btn-padding-y: 1rem;\n --bs-accordion-btn-color: var(--bs-body-color);\n --bs-accordion-btn-bg: var(--bs-accordion-bg);\n --bs-accordion-btn-icon: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e\");\n --bs-accordion-btn-icon-width: 1.25rem;\n --bs-accordion-btn-icon-transform: rotate(-180deg);\n --bs-accordion-btn-icon-transition: transform 0.2s ease-in-out;\n --bs-accordion-btn-active-icon: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23052c65'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e\");\n --bs-accordion-btn-focus-border-color: #86b7fe;\n --bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n --bs-accordion-body-padding-x: 1.25rem;\n --bs-accordion-body-padding-y: 1rem;\n --bs-accordion-active-color: var(--bs-primary-text-emphasis);\n --bs-accordion-active-bg: var(--bs-primary-bg-subtle);\n}\n\n.accordion-button {\n position: relative;\n display: flex;\n align-items: center;\n width: 100%;\n padding: var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x);\n font-size: 1rem;\n color: var(--bs-accordion-btn-color);\n text-align: left;\n background-color: var(--bs-accordion-btn-bg);\n border: 0;\n border-radius: 0;\n overflow-anchor: none;\n transition: var(--bs-accordion-transition);\n}\n@media (prefers-reduced-motion: reduce) {\n .accordion-button {\n transition: none;\n }\n}\n.accordion-button:not(.collapsed) {\n color: var(--bs-accordion-active-color);\n background-color: var(--bs-accordion-active-bg);\n box-shadow: inset 0 calc(-1 * var(--bs-accordion-border-width)) 0 var(--bs-accordion-border-color);\n}\n.accordion-button:not(.collapsed)::after {\n background-image: var(--bs-accordion-btn-active-icon);\n transform: var(--bs-accordion-btn-icon-transform);\n}\n.accordion-button::after {\n flex-shrink: 0;\n width: var(--bs-accordion-btn-icon-width);\n height: var(--bs-accordion-btn-icon-width);\n margin-left: auto;\n content: \"\";\n background-image: var(--bs-accordion-btn-icon);\n background-repeat: no-repeat;\n background-size: var(--bs-accordion-btn-icon-width);\n transition: var(--bs-accordion-btn-icon-transition);\n}\n@media (prefers-reduced-motion: reduce) {\n .accordion-button::after {\n transition: none;\n }\n}\n.accordion-button:hover {\n z-index: 2;\n}\n.accordion-button:focus {\n z-index: 3;\n border-color: var(--bs-accordion-btn-focus-border-color);\n outline: 0;\n box-shadow: var(--bs-accordion-btn-focus-box-shadow);\n}\n\n.accordion-header {\n margin-bottom: 0;\n}\n\n.accordion-item {\n color: var(--bs-accordion-color);\n background-color: var(--bs-accordion-bg);\n border: var(--bs-accordion-border-width) solid var(--bs-accordion-border-color);\n}\n.accordion-item:first-of-type {\n border-top-left-radius: var(--bs-accordion-border-radius);\n border-top-right-radius: var(--bs-accordion-border-radius);\n}\n.accordion-item:first-of-type .accordion-button {\n border-top-left-radius: var(--bs-accordion-inner-border-radius);\n border-top-right-radius: var(--bs-accordion-inner-border-radius);\n}\n.accordion-item:not(:first-of-type) {\n border-top: 0;\n}\n.accordion-item:last-of-type {\n border-bottom-right-radius: var(--bs-accordion-border-radius);\n border-bottom-left-radius: var(--bs-accordion-border-radius);\n}\n.accordion-item:last-of-type .accordion-button.collapsed {\n border-bottom-right-radius: var(--bs-accordion-inner-border-radius);\n border-bottom-left-radius: var(--bs-accordion-inner-border-radius);\n}\n.accordion-item:last-of-type .accordion-collapse {\n border-bottom-right-radius: var(--bs-accordion-border-radius);\n border-bottom-left-radius: var(--bs-accordion-border-radius);\n}\n\n.accordion-body {\n padding: var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x);\n}\n\n.accordion-flush .accordion-collapse {\n border-width: 0;\n}\n.accordion-flush .accordion-item {\n border-right: 0;\n border-left: 0;\n border-radius: 0;\n}\n.accordion-flush .accordion-item:first-child {\n border-top: 0;\n}\n.accordion-flush .accordion-item:last-child {\n border-bottom: 0;\n}\n.accordion-flush .accordion-item .accordion-button, .accordion-flush .accordion-item .accordion-button.collapsed {\n border-radius: 0;\n}\n\n[data-bs-theme=dark] .accordion-button::after {\n --bs-accordion-btn-icon: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236ea8fe'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e\");\n --bs-accordion-btn-active-icon: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236ea8fe'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e\");\n}\n\n.breadcrumb {\n --bs-breadcrumb-padding-x: 0;\n --bs-breadcrumb-padding-y: 0;\n --bs-breadcrumb-margin-bottom: 1rem;\n --bs-breadcrumb-bg: ;\n --bs-breadcrumb-border-radius: ;\n --bs-breadcrumb-divider-color: var(--bs-secondary-color);\n --bs-breadcrumb-item-padding-x: 0.5rem;\n --bs-breadcrumb-item-active-color: var(--bs-secondary-color);\n display: flex;\n flex-wrap: wrap;\n padding: var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x);\n margin-bottom: var(--bs-breadcrumb-margin-bottom);\n font-size: var(--bs-breadcrumb-font-size);\n list-style: none;\n background-color: var(--bs-breadcrumb-bg);\n border-radius: var(--bs-breadcrumb-border-radius);\n}\n\n.breadcrumb-item + .breadcrumb-item {\n padding-left: var(--bs-breadcrumb-item-padding-x);\n}\n.breadcrumb-item + .breadcrumb-item::before {\n float: left;\n padding-right: var(--bs-breadcrumb-item-padding-x);\n color: var(--bs-breadcrumb-divider-color);\n content: var(--bs-breadcrumb-divider, \"/\") /* rtl: var(--bs-breadcrumb-divider, \"/\") */;\n}\n.breadcrumb-item.active {\n color: var(--bs-breadcrumb-item-active-color);\n}\n\n.pagination {\n --bs-pagination-padding-x: 0.75rem;\n --bs-pagination-padding-y: 0.375rem;\n --bs-pagination-font-size: 1rem;\n --bs-pagination-color: var(--bs-link-color);\n --bs-pagination-bg: var(--bs-body-bg);\n --bs-pagination-border-width: var(--bs-border-width);\n --bs-pagination-border-color: var(--bs-border-color);\n --bs-pagination-border-radius: var(--bs-border-radius);\n --bs-pagination-hover-color: var(--bs-link-hover-color);\n --bs-pagination-hover-bg: var(--bs-tertiary-bg);\n --bs-pagination-hover-border-color: var(--bs-border-color);\n --bs-pagination-focus-color: var(--bs-link-hover-color);\n --bs-pagination-focus-bg: var(--bs-secondary-bg);\n --bs-pagination-focus-box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n --bs-pagination-active-color: #fff;\n --bs-pagination-active-bg: #0d6efd;\n --bs-pagination-active-border-color: #0d6efd;\n --bs-pagination-disabled-color: var(--bs-secondary-color);\n --bs-pagination-disabled-bg: var(--bs-secondary-bg);\n --bs-pagination-disabled-border-color: var(--bs-border-color);\n display: flex;\n padding-left: 0;\n list-style: none;\n}\n\n.page-link {\n position: relative;\n display: block;\n padding: var(--bs-pagination-padding-y) var(--bs-pagination-padding-x);\n font-size: var(--bs-pagination-font-size);\n color: var(--bs-pagination-color);\n text-decoration: none;\n background-color: var(--bs-pagination-bg);\n border: var(--bs-pagination-border-width) solid var(--bs-pagination-border-color);\n transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n@media (prefers-reduced-motion: reduce) {\n .page-link {\n transition: none;\n }\n}\n.page-link:hover {\n z-index: 2;\n color: var(--bs-pagination-hover-color);\n background-color: var(--bs-pagination-hover-bg);\n border-color: var(--bs-pagination-hover-border-color);\n}\n.page-link:focus {\n z-index: 3;\n color: var(--bs-pagination-focus-color);\n background-color: var(--bs-pagination-focus-bg);\n outline: 0;\n box-shadow: var(--bs-pagination-focus-box-shadow);\n}\n.page-link.active, .active > .page-link {\n z-index: 3;\n color: var(--bs-pagination-active-color);\n background-color: var(--bs-pagination-active-bg);\n border-color: var(--bs-pagination-active-border-color);\n}\n.page-link.disabled, .disabled > .page-link {\n color: var(--bs-pagination-disabled-color);\n pointer-events: none;\n background-color: var(--bs-pagination-disabled-bg);\n border-color: var(--bs-pagination-disabled-border-color);\n}\n\n.page-item:not(:first-child) .page-link {\n margin-left: calc(var(--bs-border-width) * -1);\n}\n.page-item:first-child .page-link {\n border-top-left-radius: var(--bs-pagination-border-radius);\n border-bottom-left-radius: var(--bs-pagination-border-radius);\n}\n.page-item:last-child .page-link {\n border-top-right-radius: var(--bs-pagination-border-radius);\n border-bottom-right-radius: var(--bs-pagination-border-radius);\n}\n\n.pagination-lg {\n --bs-pagination-padding-x: 1.5rem;\n --bs-pagination-padding-y: 0.75rem;\n --bs-pagination-font-size: 1.25rem;\n --bs-pagination-border-radius: var(--bs-border-radius-lg);\n}\n\n.pagination-sm {\n --bs-pagination-padding-x: 0.5rem;\n --bs-pagination-padding-y: 0.25rem;\n --bs-pagination-font-size: 0.875rem;\n --bs-pagination-border-radius: var(--bs-border-radius-sm);\n}\n\n.badge {\n --bs-badge-padding-x: 0.65em;\n --bs-badge-padding-y: 0.35em;\n --bs-badge-font-size: 0.75em;\n --bs-badge-font-weight: 700;\n --bs-badge-color: #fff;\n --bs-badge-border-radius: var(--bs-border-radius);\n display: inline-block;\n padding: var(--bs-badge-padding-y) var(--bs-badge-padding-x);\n font-size: var(--bs-badge-font-size);\n font-weight: var(--bs-badge-font-weight);\n line-height: 1;\n color: var(--bs-badge-color);\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: var(--bs-badge-border-radius);\n}\n.badge:empty {\n display: none;\n}\n\n.btn .badge {\n position: relative;\n top: -1px;\n}\n\n.alert {\n --bs-alert-bg: transparent;\n --bs-alert-padding-x: 1rem;\n --bs-alert-padding-y: 1rem;\n --bs-alert-margin-bottom: 1rem;\n --bs-alert-color: inherit;\n --bs-alert-border-color: transparent;\n --bs-alert-border: var(--bs-border-width) solid var(--bs-alert-border-color);\n --bs-alert-border-radius: var(--bs-border-radius);\n --bs-alert-link-color: inherit;\n position: relative;\n padding: var(--bs-alert-padding-y) var(--bs-alert-padding-x);\n margin-bottom: var(--bs-alert-margin-bottom);\n color: var(--bs-alert-color);\n background-color: var(--bs-alert-bg);\n border: var(--bs-alert-border);\n border-radius: var(--bs-alert-border-radius);\n}\n\n.alert-heading {\n color: inherit;\n}\n\n.alert-link {\n font-weight: 700;\n color: var(--bs-alert-link-color);\n}\n\n.alert-dismissible {\n padding-right: 3rem;\n}\n.alert-dismissible .btn-close {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2;\n padding: 1.25rem 1rem;\n}\n\n.alert-primary {\n --bs-alert-color: var(--bs-primary-text-emphasis);\n --bs-alert-bg: var(--bs-primary-bg-subtle);\n --bs-alert-border-color: var(--bs-primary-border-subtle);\n --bs-alert-link-color: var(--bs-primary-text-emphasis);\n}\n\n.alert-secondary {\n --bs-alert-color: var(--bs-secondary-text-emphasis);\n --bs-alert-bg: var(--bs-secondary-bg-subtle);\n --bs-alert-border-color: var(--bs-secondary-border-subtle);\n --bs-alert-link-color: var(--bs-secondary-text-emphasis);\n}\n\n.alert-success {\n --bs-alert-color: var(--bs-success-text-emphasis);\n --bs-alert-bg: var(--bs-success-bg-subtle);\n --bs-alert-border-color: var(--bs-success-border-subtle);\n --bs-alert-link-color: var(--bs-success-text-emphasis);\n}\n\n.alert-info {\n --bs-alert-color: var(--bs-info-text-emphasis);\n --bs-alert-bg: var(--bs-info-bg-subtle);\n --bs-alert-border-color: var(--bs-info-border-subtle);\n --bs-alert-link-color: var(--bs-info-text-emphasis);\n}\n\n.alert-warning {\n --bs-alert-color: var(--bs-warning-text-emphasis);\n --bs-alert-bg: var(--bs-warning-bg-subtle);\n --bs-alert-border-color: var(--bs-warning-border-subtle);\n --bs-alert-link-color: var(--bs-warning-text-emphasis);\n}\n\n.alert-danger {\n --bs-alert-color: var(--bs-danger-text-emphasis);\n --bs-alert-bg: var(--bs-danger-bg-subtle);\n --bs-alert-border-color: var(--bs-danger-border-subtle);\n --bs-alert-link-color: var(--bs-danger-text-emphasis);\n}\n\n.alert-light {\n --bs-alert-color: var(--bs-light-text-emphasis);\n --bs-alert-bg: var(--bs-light-bg-subtle);\n --bs-alert-border-color: var(--bs-light-border-subtle);\n --bs-alert-link-color: var(--bs-light-text-emphasis);\n}\n\n.alert-dark {\n --bs-alert-color: var(--bs-dark-text-emphasis);\n --bs-alert-bg: var(--bs-dark-bg-subtle);\n --bs-alert-border-color: var(--bs-dark-border-subtle);\n --bs-alert-link-color: var(--bs-dark-text-emphasis);\n}\n\n@keyframes progress-bar-stripes {\n 0% {\n background-position-x: 1rem;\n }\n}\n.progress,\n.progress-stacked {\n --bs-progress-height: 1rem;\n --bs-progress-font-size: 0.75rem;\n --bs-progress-bg: var(--bs-secondary-bg);\n --bs-progress-border-radius: var(--bs-border-radius);\n --bs-progress-box-shadow: var(--bs-box-shadow-inset);\n --bs-progress-bar-color: #fff;\n --bs-progress-bar-bg: #0d6efd;\n --bs-progress-bar-transition: width 0.6s ease;\n display: flex;\n height: var(--bs-progress-height);\n overflow: hidden;\n font-size: var(--bs-progress-font-size);\n background-color: var(--bs-progress-bg);\n border-radius: var(--bs-progress-border-radius);\n}\n\n.progress-bar {\n display: flex;\n flex-direction: column;\n justify-content: center;\n overflow: hidden;\n color: var(--bs-progress-bar-color);\n text-align: center;\n white-space: nowrap;\n background-color: var(--bs-progress-bar-bg);\n transition: var(--bs-progress-bar-transition);\n}\n@media (prefers-reduced-motion: reduce) {\n .progress-bar {\n transition: none;\n }\n}\n\n.progress-bar-striped {\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-size: var(--bs-progress-height) var(--bs-progress-height);\n}\n\n.progress-stacked > .progress {\n overflow: visible;\n}\n\n.progress-stacked > .progress > .progress-bar {\n width: 100%;\n}\n\n.progress-bar-animated {\n animation: 1s linear infinite progress-bar-stripes;\n}\n@media (prefers-reduced-motion: reduce) {\n .progress-bar-animated {\n animation: none;\n }\n}\n\n.list-group {\n --bs-list-group-color: var(--bs-body-color);\n --bs-list-group-bg: var(--bs-body-bg);\n --bs-list-group-border-color: var(--bs-border-color);\n --bs-list-group-border-width: var(--bs-border-width);\n --bs-list-group-border-radius: var(--bs-border-radius);\n --bs-list-group-item-padding-x: 1rem;\n --bs-list-group-item-padding-y: 0.5rem;\n --bs-list-group-action-color: var(--bs-secondary-color);\n --bs-list-group-action-hover-color: var(--bs-emphasis-color);\n --bs-list-group-action-hover-bg: var(--bs-tertiary-bg);\n --bs-list-group-action-active-color: var(--bs-body-color);\n --bs-list-group-action-active-bg: var(--bs-secondary-bg);\n --bs-list-group-disabled-color: var(--bs-secondary-color);\n --bs-list-group-disabled-bg: var(--bs-body-bg);\n --bs-list-group-active-color: #fff;\n --bs-list-group-active-bg: #0d6efd;\n --bs-list-group-active-border-color: #0d6efd;\n display: flex;\n flex-direction: column;\n padding-left: 0;\n margin-bottom: 0;\n border-radius: var(--bs-list-group-border-radius);\n}\n\n.list-group-numbered {\n list-style-type: none;\n counter-reset: section;\n}\n.list-group-numbered > .list-group-item::before {\n content: counters(section, \".\") \". \";\n counter-increment: section;\n}\n\n.list-group-item-action {\n width: 100%;\n color: var(--bs-list-group-action-color);\n text-align: inherit;\n}\n.list-group-item-action:hover, .list-group-item-action:focus {\n z-index: 1;\n color: var(--bs-list-group-action-hover-color);\n text-decoration: none;\n background-color: var(--bs-list-group-action-hover-bg);\n}\n.list-group-item-action:active {\n color: var(--bs-list-group-action-active-color);\n background-color: var(--bs-list-group-action-active-bg);\n}\n\n.list-group-item {\n position: relative;\n display: block;\n padding: var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x);\n color: var(--bs-list-group-color);\n text-decoration: none;\n background-color: var(--bs-list-group-bg);\n border: var(--bs-list-group-border-width) solid var(--bs-list-group-border-color);\n}\n.list-group-item:first-child {\n border-top-left-radius: inherit;\n border-top-right-radius: inherit;\n}\n.list-group-item:last-child {\n border-bottom-right-radius: inherit;\n border-bottom-left-radius: inherit;\n}\n.list-group-item.disabled, .list-group-item:disabled {\n color: var(--bs-list-group-disabled-color);\n pointer-events: none;\n background-color: var(--bs-list-group-disabled-bg);\n}\n.list-group-item.active {\n z-index: 2;\n color: var(--bs-list-group-active-color);\n background-color: var(--bs-list-group-active-bg);\n border-color: var(--bs-list-group-active-border-color);\n}\n.list-group-item + .list-group-item {\n border-top-width: 0;\n}\n.list-group-item + .list-group-item.active {\n margin-top: calc(-1 * var(--bs-list-group-border-width));\n border-top-width: var(--bs-list-group-border-width);\n}\n\n.list-group-horizontal {\n flex-direction: row;\n}\n.list-group-horizontal > .list-group-item:first-child:not(:last-child) {\n border-bottom-left-radius: var(--bs-list-group-border-radius);\n border-top-right-radius: 0;\n}\n.list-group-horizontal > .list-group-item:last-child:not(:first-child) {\n border-top-right-radius: var(--bs-list-group-border-radius);\n border-bottom-left-radius: 0;\n}\n.list-group-horizontal > .list-group-item.active {\n margin-top: 0;\n}\n.list-group-horizontal > .list-group-item + .list-group-item {\n border-top-width: var(--bs-list-group-border-width);\n border-left-width: 0;\n}\n.list-group-horizontal > .list-group-item + .list-group-item.active {\n margin-left: calc(-1 * var(--bs-list-group-border-width));\n border-left-width: var(--bs-list-group-border-width);\n}\n\n@media (min-width: 576px) {\n .list-group-horizontal-sm {\n flex-direction: row;\n }\n .list-group-horizontal-sm > .list-group-item:first-child:not(:last-child) {\n border-bottom-left-radius: var(--bs-list-group-border-radius);\n border-top-right-radius: 0;\n }\n .list-group-horizontal-sm > .list-group-item:last-child:not(:first-child) {\n border-top-right-radius: var(--bs-list-group-border-radius);\n border-bottom-left-radius: 0;\n }\n .list-group-horizontal-sm > .list-group-item.active {\n margin-top: 0;\n }\n .list-group-horizontal-sm > .list-group-item + .list-group-item {\n border-top-width: var(--bs-list-group-border-width);\n border-left-width: 0;\n }\n .list-group-horizontal-sm > .list-group-item + .list-group-item.active {\n margin-left: calc(-1 * var(--bs-list-group-border-width));\n border-left-width: var(--bs-list-group-border-width);\n }\n}\n@media (min-width: 768px) {\n .list-group-horizontal-md {\n flex-direction: row;\n }\n .list-group-horizontal-md > .list-group-item:first-child:not(:last-child) {\n border-bottom-left-radius: var(--bs-list-group-border-radius);\n border-top-right-radius: 0;\n }\n .list-group-horizontal-md > .list-group-item:last-child:not(:first-child) {\n border-top-right-radius: var(--bs-list-group-border-radius);\n border-bottom-left-radius: 0;\n }\n .list-group-horizontal-md > .list-group-item.active {\n margin-top: 0;\n }\n .list-group-horizontal-md > .list-group-item + .list-group-item {\n border-top-width: var(--bs-list-group-border-width);\n border-left-width: 0;\n }\n .list-group-horizontal-md > .list-group-item + .list-group-item.active {\n margin-left: calc(-1 * var(--bs-list-group-border-width));\n border-left-width: var(--bs-list-group-border-width);\n }\n}\n@media (min-width: 992px) {\n .list-group-horizontal-lg {\n flex-direction: row;\n }\n .list-group-horizontal-lg > .list-group-item:first-child:not(:last-child) {\n border-bottom-left-radius: var(--bs-list-group-border-radius);\n border-top-right-radius: 0;\n }\n .list-group-horizontal-lg > .list-group-item:last-child:not(:first-child) {\n border-top-right-radius: var(--bs-list-group-border-radius);\n border-bottom-left-radius: 0;\n }\n .list-group-horizontal-lg > .list-group-item.active {\n margin-top: 0;\n }\n .list-group-horizontal-lg > .list-group-item + .list-group-item {\n border-top-width: var(--bs-list-group-border-width);\n border-left-width: 0;\n }\n .list-group-horizontal-lg > .list-group-item + .list-group-item.active {\n margin-left: calc(-1 * var(--bs-list-group-border-width));\n border-left-width: var(--bs-list-group-border-width);\n }\n}\n@media (min-width: 1200px) {\n .list-group-horizontal-xl {\n flex-direction: row;\n }\n .list-group-horizontal-xl > .list-group-item:first-child:not(:last-child) {\n border-bottom-left-radius: var(--bs-list-group-border-radius);\n border-top-right-radius: 0;\n }\n .list-group-horizontal-xl > .list-group-item:last-child:not(:first-child) {\n border-top-right-radius: var(--bs-list-group-border-radius);\n border-bottom-left-radius: 0;\n }\n .list-group-horizontal-xl > .list-group-item.active {\n margin-top: 0;\n }\n .list-group-horizontal-xl > .list-group-item + .list-group-item {\n border-top-width: var(--bs-list-group-border-width);\n border-left-width: 0;\n }\n .list-group-horizontal-xl > .list-group-item + .list-group-item.active {\n margin-left: calc(-1 * var(--bs-list-group-border-width));\n border-left-width: var(--bs-list-group-border-width);\n }\n}\n@media (min-width: 1400px) {\n .list-group-horizontal-xxl {\n flex-direction: row;\n }\n .list-group-horizontal-xxl > .list-group-item:first-child:not(:last-child) {\n border-bottom-left-radius: var(--bs-list-group-border-radius);\n border-top-right-radius: 0;\n }\n .list-group-horizontal-xxl > .list-group-item:last-child:not(:first-child) {\n border-top-right-radius: var(--bs-list-group-border-radius);\n border-bottom-left-radius: 0;\n }\n .list-group-horizontal-xxl > .list-group-item.active {\n margin-top: 0;\n }\n .list-group-horizontal-xxl > .list-group-item + .list-group-item {\n border-top-width: var(--bs-list-group-border-width);\n border-left-width: 0;\n }\n .list-group-horizontal-xxl > .list-group-item + .list-group-item.active {\n margin-left: calc(-1 * var(--bs-list-group-border-width));\n border-left-width: var(--bs-list-group-border-width);\n }\n}\n.list-group-flush {\n border-radius: 0;\n}\n.list-group-flush > .list-group-item {\n border-width: 0 0 var(--bs-list-group-border-width);\n}\n.list-group-flush > .list-group-item:last-child {\n border-bottom-width: 0;\n}\n\n.list-group-item-primary {\n --bs-list-group-color: var(--bs-primary-text-emphasis);\n --bs-list-group-bg: var(--bs-primary-bg-subtle);\n --bs-list-group-border-color: var(--bs-primary-border-subtle);\n --bs-list-group-action-hover-color: var(--bs-emphasis-color);\n --bs-list-group-action-hover-bg: var(--bs-primary-border-subtle);\n --bs-list-group-action-active-color: var(--bs-emphasis-color);\n --bs-list-group-action-active-bg: var(--bs-primary-border-subtle);\n --bs-list-group-active-color: var(--bs-primary-bg-subtle);\n --bs-list-group-active-bg: var(--bs-primary-text-emphasis);\n --bs-list-group-active-border-color: var(--bs-primary-text-emphasis);\n}\n\n.list-group-item-secondary {\n --bs-list-group-color: var(--bs-secondary-text-emphasis);\n --bs-list-group-bg: var(--bs-secondary-bg-subtle);\n --bs-list-group-border-color: var(--bs-secondary-border-subtle);\n --bs-list-group-action-hover-color: var(--bs-emphasis-color);\n --bs-list-group-action-hover-bg: var(--bs-secondary-border-subtle);\n --bs-list-group-action-active-color: var(--bs-emphasis-color);\n --bs-list-group-action-active-bg: var(--bs-secondary-border-subtle);\n --bs-list-group-active-color: var(--bs-secondary-bg-subtle);\n --bs-list-group-active-bg: var(--bs-secondary-text-emphasis);\n --bs-list-group-active-border-color: var(--bs-secondary-text-emphasis);\n}\n\n.list-group-item-success {\n --bs-list-group-color: var(--bs-success-text-emphasis);\n --bs-list-group-bg: var(--bs-success-bg-subtle);\n --bs-list-group-border-color: var(--bs-success-border-subtle);\n --bs-list-group-action-hover-color: var(--bs-emphasis-color);\n --bs-list-group-action-hover-bg: var(--bs-success-border-subtle);\n --bs-list-group-action-active-color: var(--bs-emphasis-color);\n --bs-list-group-action-active-bg: var(--bs-success-border-subtle);\n --bs-list-group-active-color: var(--bs-success-bg-subtle);\n --bs-list-group-active-bg: var(--bs-success-text-emphasis);\n --bs-list-group-active-border-color: var(--bs-success-text-emphasis);\n}\n\n.list-group-item-info {\n --bs-list-group-color: var(--bs-info-text-emphasis);\n --bs-list-group-bg: var(--bs-info-bg-subtle);\n --bs-list-group-border-color: var(--bs-info-border-subtle);\n --bs-list-group-action-hover-color: var(--bs-emphasis-color);\n --bs-list-group-action-hover-bg: var(--bs-info-border-subtle);\n --bs-list-group-action-active-color: var(--bs-emphasis-color);\n --bs-list-group-action-active-bg: var(--bs-info-border-subtle);\n --bs-list-group-active-color: var(--bs-info-bg-subtle);\n --bs-list-group-active-bg: var(--bs-info-text-emphasis);\n --bs-list-group-active-border-color: var(--bs-info-text-emphasis);\n}\n\n.list-group-item-warning {\n --bs-list-group-color: var(--bs-warning-text-emphasis);\n --bs-list-group-bg: var(--bs-warning-bg-subtle);\n --bs-list-group-border-color: var(--bs-warning-border-subtle);\n --bs-list-group-action-hover-color: var(--bs-emphasis-color);\n --bs-list-group-action-hover-bg: var(--bs-warning-border-subtle);\n --bs-list-group-action-active-color: var(--bs-emphasis-color);\n --bs-list-group-action-active-bg: var(--bs-warning-border-subtle);\n --bs-list-group-active-color: var(--bs-warning-bg-subtle);\n --bs-list-group-active-bg: var(--bs-warning-text-emphasis);\n --bs-list-group-active-border-color: var(--bs-warning-text-emphasis);\n}\n\n.list-group-item-danger {\n --bs-list-group-color: var(--bs-danger-text-emphasis);\n --bs-list-group-bg: var(--bs-danger-bg-subtle);\n --bs-list-group-border-color: var(--bs-danger-border-subtle);\n --bs-list-group-action-hover-color: var(--bs-emphasis-color);\n --bs-list-group-action-hover-bg: var(--bs-danger-border-subtle);\n --bs-list-group-action-active-color: var(--bs-emphasis-color);\n --bs-list-group-action-active-bg: var(--bs-danger-border-subtle);\n --bs-list-group-active-color: var(--bs-danger-bg-subtle);\n --bs-list-group-active-bg: var(--bs-danger-text-emphasis);\n --bs-list-group-active-border-color: var(--bs-danger-text-emphasis);\n}\n\n.list-group-item-light {\n --bs-list-group-color: var(--bs-light-text-emphasis);\n --bs-list-group-bg: var(--bs-light-bg-subtle);\n --bs-list-group-border-color: var(--bs-light-border-subtle);\n --bs-list-group-action-hover-color: var(--bs-emphasis-color);\n --bs-list-group-action-hover-bg: var(--bs-light-border-subtle);\n --bs-list-group-action-active-color: var(--bs-emphasis-color);\n --bs-list-group-action-active-bg: var(--bs-light-border-subtle);\n --bs-list-group-active-color: var(--bs-light-bg-subtle);\n --bs-list-group-active-bg: var(--bs-light-text-emphasis);\n --bs-list-group-active-border-color: var(--bs-light-text-emphasis);\n}\n\n.list-group-item-dark {\n --bs-list-group-color: var(--bs-dark-text-emphasis);\n --bs-list-group-bg: var(--bs-dark-bg-subtle);\n --bs-list-group-border-color: var(--bs-dark-border-subtle);\n --bs-list-group-action-hover-color: var(--bs-emphasis-color);\n --bs-list-group-action-hover-bg: var(--bs-dark-border-subtle);\n --bs-list-group-action-active-color: var(--bs-emphasis-color);\n --bs-list-group-action-active-bg: var(--bs-dark-border-subtle);\n --bs-list-group-active-color: var(--bs-dark-bg-subtle);\n --bs-list-group-active-bg: var(--bs-dark-text-emphasis);\n --bs-list-group-active-border-color: var(--bs-dark-text-emphasis);\n}\n\n.btn-close {\n --bs-btn-close-color: #000;\n --bs-btn-close-bg: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e\");\n --bs-btn-close-opacity: 0.5;\n --bs-btn-close-hover-opacity: 0.75;\n --bs-btn-close-focus-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n --bs-btn-close-focus-opacity: 1;\n --bs-btn-close-disabled-opacity: 0.25;\n --bs-btn-close-white-filter: invert(1) grayscale(100%) brightness(200%);\n box-sizing: content-box;\n width: 1em;\n height: 1em;\n padding: 0.25em 0.25em;\n color: var(--bs-btn-close-color);\n background: transparent var(--bs-btn-close-bg) center/1em auto no-repeat;\n border: 0;\n border-radius: 0.375rem;\n opacity: var(--bs-btn-close-opacity);\n}\n.btn-close:hover {\n color: var(--bs-btn-close-color);\n text-decoration: none;\n opacity: var(--bs-btn-close-hover-opacity);\n}\n.btn-close:focus {\n outline: 0;\n box-shadow: var(--bs-btn-close-focus-shadow);\n opacity: var(--bs-btn-close-focus-opacity);\n}\n.btn-close:disabled, .btn-close.disabled {\n pointer-events: none;\n -webkit-user-select: none;\n -moz-user-select: none;\n user-select: none;\n opacity: var(--bs-btn-close-disabled-opacity);\n}\n\n.btn-close-white {\n filter: var(--bs-btn-close-white-filter);\n}\n\n[data-bs-theme=dark] .btn-close {\n filter: var(--bs-btn-close-white-filter);\n}\n\n.toast {\n --bs-toast-zindex: 1090;\n --bs-toast-padding-x: 0.75rem;\n --bs-toast-padding-y: 0.5rem;\n --bs-toast-spacing: 1.5rem;\n --bs-toast-max-width: 350px;\n --bs-toast-font-size: 0.875rem;\n --bs-toast-color: ;\n --bs-toast-bg: rgba(var(--bs-body-bg-rgb), 0.85);\n --bs-toast-border-width: var(--bs-border-width);\n --bs-toast-border-color: var(--bs-border-color-translucent);\n --bs-toast-border-radius: var(--bs-border-radius);\n --bs-toast-box-shadow: var(--bs-box-shadow);\n --bs-toast-header-color: var(--bs-secondary-color);\n --bs-toast-header-bg: rgba(var(--bs-body-bg-rgb), 0.85);\n --bs-toast-header-border-color: var(--bs-border-color-translucent);\n width: var(--bs-toast-max-width);\n max-width: 100%;\n font-size: var(--bs-toast-font-size);\n color: var(--bs-toast-color);\n pointer-events: auto;\n background-color: var(--bs-toast-bg);\n background-clip: padding-box;\n border: var(--bs-toast-border-width) solid var(--bs-toast-border-color);\n box-shadow: var(--bs-toast-box-shadow);\n border-radius: var(--bs-toast-border-radius);\n}\n.toast.showing {\n opacity: 0;\n}\n.toast:not(.show) {\n display: none;\n}\n\n.toast-container {\n --bs-toast-zindex: 1090;\n position: absolute;\n z-index: var(--bs-toast-zindex);\n width: -webkit-max-content;\n width: -moz-max-content;\n width: max-content;\n max-width: 100%;\n pointer-events: none;\n}\n.toast-container > :not(:last-child) {\n margin-bottom: var(--bs-toast-spacing);\n}\n\n.toast-header {\n display: flex;\n align-items: center;\n padding: var(--bs-toast-padding-y) var(--bs-toast-padding-x);\n color: var(--bs-toast-header-color);\n background-color: var(--bs-toast-header-bg);\n background-clip: padding-box;\n border-bottom: var(--bs-toast-border-width) solid var(--bs-toast-header-border-color);\n border-top-left-radius: calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));\n border-top-right-radius: calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));\n}\n.toast-header .btn-close {\n margin-right: calc(-0.5 * var(--bs-toast-padding-x));\n margin-left: var(--bs-toast-padding-x);\n}\n\n.toast-body {\n padding: var(--bs-toast-padding-x);\n word-wrap: break-word;\n}\n\n.modal {\n --bs-modal-zindex: 1055;\n --bs-modal-width: 500px;\n --bs-modal-padding: 1rem;\n --bs-modal-margin: 0.5rem;\n --bs-modal-color: ;\n --bs-modal-bg: var(--bs-body-bg);\n --bs-modal-border-color: var(--bs-border-color-translucent);\n --bs-modal-border-width: var(--bs-border-width);\n --bs-modal-border-radius: var(--bs-border-radius-lg);\n --bs-modal-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);\n --bs-modal-inner-border-radius: calc(var(--bs-border-radius-lg) - (var(--bs-border-width)));\n --bs-modal-header-padding-x: 1rem;\n --bs-modal-header-padding-y: 1rem;\n --bs-modal-header-padding: 1rem 1rem;\n --bs-modal-header-border-color: var(--bs-border-color);\n --bs-modal-header-border-width: var(--bs-border-width);\n --bs-modal-title-line-height: 1.5;\n --bs-modal-footer-gap: 0.5rem;\n --bs-modal-footer-bg: ;\n --bs-modal-footer-border-color: var(--bs-border-color);\n --bs-modal-footer-border-width: var(--bs-border-width);\n position: fixed;\n top: 0;\n left: 0;\n z-index: var(--bs-modal-zindex);\n display: none;\n width: 100%;\n height: 100%;\n overflow-x: hidden;\n overflow-y: auto;\n outline: 0;\n}\n\n.modal-dialog {\n position: relative;\n width: auto;\n margin: var(--bs-modal-margin);\n pointer-events: none;\n}\n.modal.fade .modal-dialog {\n transition: transform 0.3s ease-out;\n transform: translate(0, -50px);\n}\n@media (prefers-reduced-motion: reduce) {\n .modal.fade .modal-dialog {\n transition: none;\n }\n}\n.modal.show .modal-dialog {\n transform: none;\n}\n.modal.modal-static .modal-dialog {\n transform: scale(1.02);\n}\n\n.modal-dialog-scrollable {\n height: calc(100% - var(--bs-modal-margin) * 2);\n}\n.modal-dialog-scrollable .modal-content {\n max-height: 100%;\n overflow: hidden;\n}\n.modal-dialog-scrollable .modal-body {\n overflow-y: auto;\n}\n\n.modal-dialog-centered {\n display: flex;\n align-items: center;\n min-height: calc(100% - var(--bs-modal-margin) * 2);\n}\n\n.modal-content {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n color: var(--bs-modal-color);\n pointer-events: auto;\n background-color: var(--bs-modal-bg);\n background-clip: padding-box;\n border: var(--bs-modal-border-width) solid var(--bs-modal-border-color);\n border-radius: var(--bs-modal-border-radius);\n outline: 0;\n}\n\n.modal-backdrop {\n --bs-backdrop-zindex: 1050;\n --bs-backdrop-bg: #000;\n --bs-backdrop-opacity: 0.5;\n position: fixed;\n top: 0;\n left: 0;\n z-index: var(--bs-backdrop-zindex);\n width: 100vw;\n height: 100vh;\n background-color: var(--bs-backdrop-bg);\n}\n.modal-backdrop.fade {\n opacity: 0;\n}\n.modal-backdrop.show {\n opacity: var(--bs-backdrop-opacity);\n}\n\n.modal-header {\n display: flex;\n flex-shrink: 0;\n align-items: center;\n justify-content: space-between;\n padding: var(--bs-modal-header-padding);\n border-bottom: var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);\n border-top-left-radius: var(--bs-modal-inner-border-radius);\n border-top-right-radius: var(--bs-modal-inner-border-radius);\n}\n.modal-header .btn-close {\n padding: calc(var(--bs-modal-header-padding-y) * 0.5) calc(var(--bs-modal-header-padding-x) * 0.5);\n margin: calc(-0.5 * var(--bs-modal-header-padding-y)) calc(-0.5 * var(--bs-modal-header-padding-x)) calc(-0.5 * var(--bs-modal-header-padding-y)) auto;\n}\n\n.modal-title {\n margin-bottom: 0;\n line-height: var(--bs-modal-title-line-height);\n}\n\n.modal-body {\n position: relative;\n flex: 1 1 auto;\n padding: var(--bs-modal-padding);\n}\n\n.modal-footer {\n display: flex;\n flex-shrink: 0;\n flex-wrap: wrap;\n align-items: center;\n justify-content: flex-end;\n padding: calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap) * 0.5);\n background-color: var(--bs-modal-footer-bg);\n border-top: var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color);\n border-bottom-right-radius: var(--bs-modal-inner-border-radius);\n border-bottom-left-radius: var(--bs-modal-inner-border-radius);\n}\n.modal-footer > * {\n margin: calc(var(--bs-modal-footer-gap) * 0.5);\n}\n\n@media (min-width: 576px) {\n .modal {\n --bs-modal-margin: 1.75rem;\n --bs-modal-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);\n }\n .modal-dialog {\n max-width: var(--bs-modal-width);\n margin-right: auto;\n margin-left: auto;\n }\n .modal-sm {\n --bs-modal-width: 300px;\n }\n}\n@media (min-width: 992px) {\n .modal-lg,\n .modal-xl {\n --bs-modal-width: 800px;\n }\n}\n@media (min-width: 1200px) {\n .modal-xl {\n --bs-modal-width: 1140px;\n }\n}\n.modal-fullscreen {\n width: 100vw;\n max-width: none;\n height: 100%;\n margin: 0;\n}\n.modal-fullscreen .modal-content {\n height: 100%;\n border: 0;\n border-radius: 0;\n}\n.modal-fullscreen .modal-header,\n.modal-fullscreen .modal-footer {\n border-radius: 0;\n}\n.modal-fullscreen .modal-body {\n overflow-y: auto;\n}\n\n@media (max-width: 575.98px) {\n .modal-fullscreen-sm-down {\n width: 100vw;\n max-width: none;\n height: 100%;\n margin: 0;\n }\n .modal-fullscreen-sm-down .modal-content {\n height: 100%;\n border: 0;\n border-radius: 0;\n }\n .modal-fullscreen-sm-down .modal-header,\n .modal-fullscreen-sm-down .modal-footer {\n border-radius: 0;\n }\n .modal-fullscreen-sm-down .modal-body {\n overflow-y: auto;\n }\n}\n@media (max-width: 767.98px) {\n .modal-fullscreen-md-down {\n width: 100vw;\n max-width: none;\n height: 100%;\n margin: 0;\n }\n .modal-fullscreen-md-down .modal-content {\n height: 100%;\n border: 0;\n border-radius: 0;\n }\n .modal-fullscreen-md-down .modal-header,\n .modal-fullscreen-md-down .modal-footer {\n border-radius: 0;\n }\n .modal-fullscreen-md-down .modal-body {\n overflow-y: auto;\n }\n}\n@media (max-width: 991.98px) {\n .modal-fullscreen-lg-down {\n width: 100vw;\n max-width: none;\n height: 100%;\n margin: 0;\n }\n .modal-fullscreen-lg-down .modal-content {\n height: 100%;\n border: 0;\n border-radius: 0;\n }\n .modal-fullscreen-lg-down .modal-header,\n .modal-fullscreen-lg-down .modal-footer {\n border-radius: 0;\n }\n .modal-fullscreen-lg-down .modal-body {\n overflow-y: auto;\n }\n}\n@media (max-width: 1199.98px) {\n .modal-fullscreen-xl-down {\n width: 100vw;\n max-width: none;\n height: 100%;\n margin: 0;\n }\n .modal-fullscreen-xl-down .modal-content {\n height: 100%;\n border: 0;\n border-radius: 0;\n }\n .modal-fullscreen-xl-down .modal-header,\n .modal-fullscreen-xl-down .modal-footer {\n border-radius: 0;\n }\n .modal-fullscreen-xl-down .modal-body {\n overflow-y: auto;\n }\n}\n@media (max-width: 1399.98px) {\n .modal-fullscreen-xxl-down {\n width: 100vw;\n max-width: none;\n height: 100%;\n margin: 0;\n }\n .modal-fullscreen-xxl-down .modal-content {\n height: 100%;\n border: 0;\n border-radius: 0;\n }\n .modal-fullscreen-xxl-down .modal-header,\n .modal-fullscreen-xxl-down .modal-footer {\n border-radius: 0;\n }\n .modal-fullscreen-xxl-down .modal-body {\n overflow-y: auto;\n }\n}\n.tooltip {\n --bs-tooltip-zindex: 1080;\n --bs-tooltip-max-width: 200px;\n --bs-tooltip-padding-x: 0.5rem;\n --bs-tooltip-padding-y: 0.25rem;\n --bs-tooltip-margin: ;\n --bs-tooltip-font-size: 0.875rem;\n --bs-tooltip-color: var(--bs-body-bg);\n --bs-tooltip-bg: var(--bs-emphasis-color);\n --bs-tooltip-border-radius: var(--bs-border-radius);\n --bs-tooltip-opacity: 0.9;\n --bs-tooltip-arrow-width: 0.8rem;\n --bs-tooltip-arrow-height: 0.4rem;\n z-index: var(--bs-tooltip-zindex);\n display: block;\n margin: var(--bs-tooltip-margin);\n font-family: var(--bs-font-sans-serif);\n font-style: normal;\n font-weight: 400;\n line-height: 1.5;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n white-space: normal;\n word-spacing: normal;\n line-break: auto;\n font-size: var(--bs-tooltip-font-size);\n word-wrap: break-word;\n opacity: 0;\n}\n.tooltip.show {\n opacity: var(--bs-tooltip-opacity);\n}\n.tooltip .tooltip-arrow {\n display: block;\n width: var(--bs-tooltip-arrow-width);\n height: var(--bs-tooltip-arrow-height);\n}\n.tooltip .tooltip-arrow::before {\n position: absolute;\n content: \"\";\n border-color: transparent;\n border-style: solid;\n}\n\n.bs-tooltip-top .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow {\n bottom: calc(-1 * var(--bs-tooltip-arrow-height));\n}\n.bs-tooltip-top .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before {\n top: -1px;\n border-width: var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * 0.5) 0;\n border-top-color: var(--bs-tooltip-bg);\n}\n\n/* rtl:begin:ignore */\n.bs-tooltip-end .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow {\n left: calc(-1 * var(--bs-tooltip-arrow-height));\n width: var(--bs-tooltip-arrow-height);\n height: var(--bs-tooltip-arrow-width);\n}\n.bs-tooltip-end .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before {\n right: -1px;\n border-width: calc(var(--bs-tooltip-arrow-width) * 0.5) var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * 0.5) 0;\n border-right-color: var(--bs-tooltip-bg);\n}\n\n/* rtl:end:ignore */\n.bs-tooltip-bottom .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow {\n top: calc(-1 * var(--bs-tooltip-arrow-height));\n}\n.bs-tooltip-bottom .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before {\n bottom: -1px;\n border-width: 0 calc(var(--bs-tooltip-arrow-width) * 0.5) var(--bs-tooltip-arrow-height);\n border-bottom-color: var(--bs-tooltip-bg);\n}\n\n/* rtl:begin:ignore */\n.bs-tooltip-start .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow {\n right: calc(-1 * var(--bs-tooltip-arrow-height));\n width: var(--bs-tooltip-arrow-height);\n height: var(--bs-tooltip-arrow-width);\n}\n.bs-tooltip-start .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before {\n left: -1px;\n border-width: calc(var(--bs-tooltip-arrow-width) * 0.5) 0 calc(var(--bs-tooltip-arrow-width) * 0.5) var(--bs-tooltip-arrow-height);\n border-left-color: var(--bs-tooltip-bg);\n}\n\n/* rtl:end:ignore */\n.tooltip-inner {\n max-width: var(--bs-tooltip-max-width);\n padding: var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x);\n color: var(--bs-tooltip-color);\n text-align: center;\n background-color: var(--bs-tooltip-bg);\n border-radius: var(--bs-tooltip-border-radius);\n}\n\n.popover {\n --bs-popover-zindex: 1070;\n --bs-popover-max-width: 276px;\n --bs-popover-font-size: 0.875rem;\n --bs-popover-bg: var(--bs-body-bg);\n --bs-popover-border-width: var(--bs-border-width);\n --bs-popover-border-color: var(--bs-border-color-translucent);\n --bs-popover-border-radius: var(--bs-border-radius-lg);\n --bs-popover-inner-border-radius: calc(var(--bs-border-radius-lg) - var(--bs-border-width));\n --bs-popover-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);\n --bs-popover-header-padding-x: 1rem;\n --bs-popover-header-padding-y: 0.5rem;\n --bs-popover-header-font-size: 1rem;\n --bs-popover-header-color: ;\n --bs-popover-header-bg: var(--bs-secondary-bg);\n --bs-popover-body-padding-x: 1rem;\n --bs-popover-body-padding-y: 1rem;\n --bs-popover-body-color: var(--bs-body-color);\n --bs-popover-arrow-width: 1rem;\n --bs-popover-arrow-height: 0.5rem;\n --bs-popover-arrow-border: var(--bs-popover-border-color);\n z-index: var(--bs-popover-zindex);\n display: block;\n max-width: var(--bs-popover-max-width);\n font-family: var(--bs-font-sans-serif);\n font-style: normal;\n font-weight: 400;\n line-height: 1.5;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n white-space: normal;\n word-spacing: normal;\n line-break: auto;\n font-size: var(--bs-popover-font-size);\n word-wrap: break-word;\n background-color: var(--bs-popover-bg);\n background-clip: padding-box;\n border: var(--bs-popover-border-width) solid var(--bs-popover-border-color);\n border-radius: var(--bs-popover-border-radius);\n}\n.popover .popover-arrow {\n display: block;\n width: var(--bs-popover-arrow-width);\n height: var(--bs-popover-arrow-height);\n}\n.popover .popover-arrow::before, .popover .popover-arrow::after {\n position: absolute;\n display: block;\n content: \"\";\n border-color: transparent;\n border-style: solid;\n border-width: 0;\n}\n\n.bs-popover-top > .popover-arrow, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow {\n bottom: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));\n}\n.bs-popover-top > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::before, .bs-popover-top > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::after {\n border-width: var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * 0.5) 0;\n}\n.bs-popover-top > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::before {\n bottom: 0;\n border-top-color: var(--bs-popover-arrow-border);\n}\n.bs-popover-top > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::after {\n bottom: var(--bs-popover-border-width);\n border-top-color: var(--bs-popover-bg);\n}\n\n/* rtl:begin:ignore */\n.bs-popover-end > .popover-arrow, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow {\n left: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));\n width: var(--bs-popover-arrow-height);\n height: var(--bs-popover-arrow-width);\n}\n.bs-popover-end > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::before, .bs-popover-end > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::after {\n border-width: calc(var(--bs-popover-arrow-width) * 0.5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * 0.5) 0;\n}\n.bs-popover-end > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::before {\n left: 0;\n border-right-color: var(--bs-popover-arrow-border);\n}\n.bs-popover-end > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::after {\n left: var(--bs-popover-border-width);\n border-right-color: var(--bs-popover-bg);\n}\n\n/* rtl:end:ignore */\n.bs-popover-bottom > .popover-arrow, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow {\n top: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));\n}\n.bs-popover-bottom > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::before, .bs-popover-bottom > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::after {\n border-width: 0 calc(var(--bs-popover-arrow-width) * 0.5) var(--bs-popover-arrow-height);\n}\n.bs-popover-bottom > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::before {\n top: 0;\n border-bottom-color: var(--bs-popover-arrow-border);\n}\n.bs-popover-bottom > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::after {\n top: var(--bs-popover-border-width);\n border-bottom-color: var(--bs-popover-bg);\n}\n.bs-popover-bottom .popover-header::before, .bs-popover-auto[data-popper-placement^=bottom] .popover-header::before {\n position: absolute;\n top: 0;\n left: 50%;\n display: block;\n width: var(--bs-popover-arrow-width);\n margin-left: calc(-0.5 * var(--bs-popover-arrow-width));\n content: \"\";\n border-bottom: var(--bs-popover-border-width) solid var(--bs-popover-header-bg);\n}\n\n/* rtl:begin:ignore */\n.bs-popover-start > .popover-arrow, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow {\n right: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));\n width: var(--bs-popover-arrow-height);\n height: var(--bs-popover-arrow-width);\n}\n.bs-popover-start > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::before, .bs-popover-start > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::after {\n border-width: calc(var(--bs-popover-arrow-width) * 0.5) 0 calc(var(--bs-popover-arrow-width) * 0.5) var(--bs-popover-arrow-height);\n}\n.bs-popover-start > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::before {\n right: 0;\n border-left-color: var(--bs-popover-arrow-border);\n}\n.bs-popover-start > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::after {\n right: var(--bs-popover-border-width);\n border-left-color: var(--bs-popover-bg);\n}\n\n/* rtl:end:ignore */\n.popover-header {\n padding: var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x);\n margin-bottom: 0;\n font-size: var(--bs-popover-header-font-size);\n color: var(--bs-popover-header-color);\n background-color: var(--bs-popover-header-bg);\n border-bottom: var(--bs-popover-border-width) solid var(--bs-popover-border-color);\n border-top-left-radius: var(--bs-popover-inner-border-radius);\n border-top-right-radius: var(--bs-popover-inner-border-radius);\n}\n.popover-header:empty {\n display: none;\n}\n\n.popover-body {\n padding: var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x);\n color: var(--bs-popover-body-color);\n}\n\n.carousel {\n position: relative;\n}\n\n.carousel.pointer-event {\n touch-action: pan-y;\n}\n\n.carousel-inner {\n position: relative;\n width: 100%;\n overflow: hidden;\n}\n.carousel-inner::after {\n display: block;\n clear: both;\n content: \"\";\n}\n\n.carousel-item {\n position: relative;\n display: none;\n float: left;\n width: 100%;\n margin-right: -100%;\n -webkit-backface-visibility: hidden;\n backface-visibility: hidden;\n transition: transform 0.6s ease-in-out;\n}\n@media (prefers-reduced-motion: reduce) {\n .carousel-item {\n transition: none;\n }\n}\n\n.carousel-item.active,\n.carousel-item-next,\n.carousel-item-prev {\n display: block;\n}\n\n.carousel-item-next:not(.carousel-item-start),\n.active.carousel-item-end {\n transform: translateX(100%);\n}\n\n.carousel-item-prev:not(.carousel-item-end),\n.active.carousel-item-start {\n transform: translateX(-100%);\n}\n\n.carousel-fade .carousel-item {\n opacity: 0;\n transition-property: opacity;\n transform: none;\n}\n.carousel-fade .carousel-item.active,\n.carousel-fade .carousel-item-next.carousel-item-start,\n.carousel-fade .carousel-item-prev.carousel-item-end {\n z-index: 1;\n opacity: 1;\n}\n.carousel-fade .active.carousel-item-start,\n.carousel-fade .active.carousel-item-end {\n z-index: 0;\n opacity: 0;\n transition: opacity 0s 0.6s;\n}\n@media (prefers-reduced-motion: reduce) {\n .carousel-fade .active.carousel-item-start,\n .carousel-fade .active.carousel-item-end {\n transition: none;\n }\n}\n\n.carousel-control-prev,\n.carousel-control-next {\n position: absolute;\n top: 0;\n bottom: 0;\n z-index: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 15%;\n padding: 0;\n color: #fff;\n text-align: center;\n background: none;\n border: 0;\n opacity: 0.5;\n transition: opacity 0.15s ease;\n}\n@media (prefers-reduced-motion: reduce) {\n .carousel-control-prev,\n .carousel-control-next {\n transition: none;\n }\n}\n.carousel-control-prev:hover, .carousel-control-prev:focus,\n.carousel-control-next:hover,\n.carousel-control-next:focus {\n color: #fff;\n text-decoration: none;\n outline: 0;\n opacity: 0.9;\n}\n\n.carousel-control-prev {\n left: 0;\n}\n\n.carousel-control-next {\n right: 0;\n}\n\n.carousel-control-prev-icon,\n.carousel-control-next-icon {\n display: inline-block;\n width: 2rem;\n height: 2rem;\n background-repeat: no-repeat;\n background-position: 50%;\n background-size: 100% 100%;\n}\n\n/* rtl:options: {\n \"autoRename\": true,\n \"stringMap\":[ {\n \"name\" : \"prev-next\",\n \"search\" : \"prev\",\n \"replace\" : \"next\"\n } ]\n} */\n.carousel-control-prev-icon {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e\");\n}\n\n.carousel-control-next-icon {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e\");\n}\n\n.carousel-indicators {\n position: absolute;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 2;\n display: flex;\n justify-content: center;\n padding: 0;\n margin-right: 15%;\n margin-bottom: 1rem;\n margin-left: 15%;\n}\n.carousel-indicators [data-bs-target] {\n box-sizing: content-box;\n flex: 0 1 auto;\n width: 30px;\n height: 3px;\n padding: 0;\n margin-right: 3px;\n margin-left: 3px;\n text-indent: -999px;\n cursor: pointer;\n background-color: #fff;\n background-clip: padding-box;\n border: 0;\n border-top: 10px solid transparent;\n border-bottom: 10px solid transparent;\n opacity: 0.5;\n transition: opacity 0.6s ease;\n}\n@media (prefers-reduced-motion: reduce) {\n .carousel-indicators [data-bs-target] {\n transition: none;\n }\n}\n.carousel-indicators .active {\n opacity: 1;\n}\n\n.carousel-caption {\n position: absolute;\n right: 15%;\n bottom: 1.25rem;\n left: 15%;\n padding-top: 1.25rem;\n padding-bottom: 1.25rem;\n color: #fff;\n text-align: center;\n}\n\n.carousel-dark .carousel-control-prev-icon,\n.carousel-dark .carousel-control-next-icon {\n filter: invert(1) grayscale(100);\n}\n.carousel-dark .carousel-indicators [data-bs-target] {\n background-color: #000;\n}\n.carousel-dark .carousel-caption {\n color: #000;\n}\n\n[data-bs-theme=dark] .carousel .carousel-control-prev-icon,\n[data-bs-theme=dark] .carousel .carousel-control-next-icon, [data-bs-theme=dark].carousel .carousel-control-prev-icon,\n[data-bs-theme=dark].carousel .carousel-control-next-icon {\n filter: invert(1) grayscale(100);\n}\n[data-bs-theme=dark] .carousel .carousel-indicators [data-bs-target], [data-bs-theme=dark].carousel .carousel-indicators [data-bs-target] {\n background-color: #000;\n}\n[data-bs-theme=dark] .carousel .carousel-caption, [data-bs-theme=dark].carousel .carousel-caption {\n color: #000;\n}\n\n.spinner-grow,\n.spinner-border {\n display: inline-block;\n width: var(--bs-spinner-width);\n height: var(--bs-spinner-height);\n vertical-align: var(--bs-spinner-vertical-align);\n border-radius: 50%;\n animation: var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name);\n}\n\n@keyframes spinner-border {\n to {\n transform: rotate(360deg) /* rtl:ignore */;\n }\n}\n.spinner-border {\n --bs-spinner-width: 2rem;\n --bs-spinner-height: 2rem;\n --bs-spinner-vertical-align: -0.125em;\n --bs-spinner-border-width: 0.25em;\n --bs-spinner-animation-speed: 0.75s;\n --bs-spinner-animation-name: spinner-border;\n border: var(--bs-spinner-border-width) solid currentcolor;\n border-right-color: transparent;\n}\n\n.spinner-border-sm {\n --bs-spinner-width: 1rem;\n --bs-spinner-height: 1rem;\n --bs-spinner-border-width: 0.2em;\n}\n\n@keyframes spinner-grow {\n 0% {\n transform: scale(0);\n }\n 50% {\n opacity: 1;\n transform: none;\n }\n}\n.spinner-grow {\n --bs-spinner-width: 2rem;\n --bs-spinner-height: 2rem;\n --bs-spinner-vertical-align: -0.125em;\n --bs-spinner-animation-speed: 0.75s;\n --bs-spinner-animation-name: spinner-grow;\n background-color: currentcolor;\n opacity: 0;\n}\n\n.spinner-grow-sm {\n --bs-spinner-width: 1rem;\n --bs-spinner-height: 1rem;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .spinner-border,\n .spinner-grow {\n --bs-spinner-animation-speed: 1.5s;\n }\n}\n.offcanvas, .offcanvas-xxl, .offcanvas-xl, .offcanvas-lg, .offcanvas-md, .offcanvas-sm {\n --bs-offcanvas-zindex: 1045;\n --bs-offcanvas-width: 400px;\n --bs-offcanvas-height: 30vh;\n --bs-offcanvas-padding-x: 1rem;\n --bs-offcanvas-padding-y: 1rem;\n --bs-offcanvas-color: var(--bs-body-color);\n --bs-offcanvas-bg: var(--bs-body-bg);\n --bs-offcanvas-border-width: var(--bs-border-width);\n --bs-offcanvas-border-color: var(--bs-border-color-translucent);\n --bs-offcanvas-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);\n --bs-offcanvas-transition: transform 0.3s ease-in-out;\n --bs-offcanvas-title-line-height: 1.5;\n}\n\n@media (max-width: 575.98px) {\n .offcanvas-sm {\n position: fixed;\n bottom: 0;\n z-index: var(--bs-offcanvas-zindex);\n display: flex;\n flex-direction: column;\n max-width: 100%;\n color: var(--bs-offcanvas-color);\n visibility: hidden;\n background-color: var(--bs-offcanvas-bg);\n background-clip: padding-box;\n outline: 0;\n transition: var(--bs-offcanvas-transition);\n }\n}\n@media (max-width: 575.98px) and (prefers-reduced-motion: reduce) {\n .offcanvas-sm {\n transition: none;\n }\n}\n@media (max-width: 575.98px) {\n .offcanvas-sm.offcanvas-start {\n top: 0;\n left: 0;\n width: var(--bs-offcanvas-width);\n border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n transform: translateX(-100%);\n }\n}\n@media (max-width: 575.98px) {\n .offcanvas-sm.offcanvas-end {\n top: 0;\n right: 0;\n width: var(--bs-offcanvas-width);\n border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n transform: translateX(100%);\n }\n}\n@media (max-width: 575.98px) {\n .offcanvas-sm.offcanvas-top {\n top: 0;\n right: 0;\n left: 0;\n height: var(--bs-offcanvas-height);\n max-height: 100%;\n border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n transform: translateY(-100%);\n }\n}\n@media (max-width: 575.98px) {\n .offcanvas-sm.offcanvas-bottom {\n right: 0;\n left: 0;\n height: var(--bs-offcanvas-height);\n max-height: 100%;\n border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n transform: translateY(100%);\n }\n}\n@media (max-width: 575.98px) {\n .offcanvas-sm.showing, .offcanvas-sm.show:not(.hiding) {\n transform: none;\n }\n}\n@media (max-width: 575.98px) {\n .offcanvas-sm.showing, .offcanvas-sm.hiding, .offcanvas-sm.show {\n visibility: visible;\n }\n}\n@media (min-width: 576px) {\n .offcanvas-sm {\n --bs-offcanvas-height: auto;\n --bs-offcanvas-border-width: 0;\n background-color: transparent !important;\n }\n .offcanvas-sm .offcanvas-header {\n display: none;\n }\n .offcanvas-sm .offcanvas-body {\n display: flex;\n flex-grow: 0;\n padding: 0;\n overflow-y: visible;\n background-color: transparent !important;\n }\n}\n\n@media (max-width: 767.98px) {\n .offcanvas-md {\n position: fixed;\n bottom: 0;\n z-index: var(--bs-offcanvas-zindex);\n display: flex;\n flex-direction: column;\n max-width: 100%;\n color: var(--bs-offcanvas-color);\n visibility: hidden;\n background-color: var(--bs-offcanvas-bg);\n background-clip: padding-box;\n outline: 0;\n transition: var(--bs-offcanvas-transition);\n }\n}\n@media (max-width: 767.98px) and (prefers-reduced-motion: reduce) {\n .offcanvas-md {\n transition: none;\n }\n}\n@media (max-width: 767.98px) {\n .offcanvas-md.offcanvas-start {\n top: 0;\n left: 0;\n width: var(--bs-offcanvas-width);\n border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n transform: translateX(-100%);\n }\n}\n@media (max-width: 767.98px) {\n .offcanvas-md.offcanvas-end {\n top: 0;\n right: 0;\n width: var(--bs-offcanvas-width);\n border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n transform: translateX(100%);\n }\n}\n@media (max-width: 767.98px) {\n .offcanvas-md.offcanvas-top {\n top: 0;\n right: 0;\n left: 0;\n height: var(--bs-offcanvas-height);\n max-height: 100%;\n border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n transform: translateY(-100%);\n }\n}\n@media (max-width: 767.98px) {\n .offcanvas-md.offcanvas-bottom {\n right: 0;\n left: 0;\n height: var(--bs-offcanvas-height);\n max-height: 100%;\n border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n transform: translateY(100%);\n }\n}\n@media (max-width: 767.98px) {\n .offcanvas-md.showing, .offcanvas-md.show:not(.hiding) {\n transform: none;\n }\n}\n@media (max-width: 767.98px) {\n .offcanvas-md.showing, .offcanvas-md.hiding, .offcanvas-md.show {\n visibility: visible;\n }\n}\n@media (min-width: 768px) {\n .offcanvas-md {\n --bs-offcanvas-height: auto;\n --bs-offcanvas-border-width: 0;\n background-color: transparent !important;\n }\n .offcanvas-md .offcanvas-header {\n display: none;\n }\n .offcanvas-md .offcanvas-body {\n display: flex;\n flex-grow: 0;\n padding: 0;\n overflow-y: visible;\n background-color: transparent !important;\n }\n}\n\n@media (max-width: 991.98px) {\n .offcanvas-lg {\n position: fixed;\n bottom: 0;\n z-index: var(--bs-offcanvas-zindex);\n display: flex;\n flex-direction: column;\n max-width: 100%;\n color: var(--bs-offcanvas-color);\n visibility: hidden;\n background-color: var(--bs-offcanvas-bg);\n background-clip: padding-box;\n outline: 0;\n transition: var(--bs-offcanvas-transition);\n }\n}\n@media (max-width: 991.98px) and (prefers-reduced-motion: reduce) {\n .offcanvas-lg {\n transition: none;\n }\n}\n@media (max-width: 991.98px) {\n .offcanvas-lg.offcanvas-start {\n top: 0;\n left: 0;\n width: var(--bs-offcanvas-width);\n border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n transform: translateX(-100%);\n }\n}\n@media (max-width: 991.98px) {\n .offcanvas-lg.offcanvas-end {\n top: 0;\n right: 0;\n width: var(--bs-offcanvas-width);\n border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n transform: translateX(100%);\n }\n}\n@media (max-width: 991.98px) {\n .offcanvas-lg.offcanvas-top {\n top: 0;\n right: 0;\n left: 0;\n height: var(--bs-offcanvas-height);\n max-height: 100%;\n border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n transform: translateY(-100%);\n }\n}\n@media (max-width: 991.98px) {\n .offcanvas-lg.offcanvas-bottom {\n right: 0;\n left: 0;\n height: var(--bs-offcanvas-height);\n max-height: 100%;\n border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n transform: translateY(100%);\n }\n}\n@media (max-width: 991.98px) {\n .offcanvas-lg.showing, .offcanvas-lg.show:not(.hiding) {\n transform: none;\n }\n}\n@media (max-width: 991.98px) {\n .offcanvas-lg.showing, .offcanvas-lg.hiding, .offcanvas-lg.show {\n visibility: visible;\n }\n}\n@media (min-width: 992px) {\n .offcanvas-lg {\n --bs-offcanvas-height: auto;\n --bs-offcanvas-border-width: 0;\n background-color: transparent !important;\n }\n .offcanvas-lg .offcanvas-header {\n display: none;\n }\n .offcanvas-lg .offcanvas-body {\n display: flex;\n flex-grow: 0;\n padding: 0;\n overflow-y: visible;\n background-color: transparent !important;\n }\n}\n\n@media (max-width: 1199.98px) {\n .offcanvas-xl {\n position: fixed;\n bottom: 0;\n z-index: var(--bs-offcanvas-zindex);\n display: flex;\n flex-direction: column;\n max-width: 100%;\n color: var(--bs-offcanvas-color);\n visibility: hidden;\n background-color: var(--bs-offcanvas-bg);\n background-clip: padding-box;\n outline: 0;\n transition: var(--bs-offcanvas-transition);\n }\n}\n@media (max-width: 1199.98px) and (prefers-reduced-motion: reduce) {\n .offcanvas-xl {\n transition: none;\n }\n}\n@media (max-width: 1199.98px) {\n .offcanvas-xl.offcanvas-start {\n top: 0;\n left: 0;\n width: var(--bs-offcanvas-width);\n border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n transform: translateX(-100%);\n }\n}\n@media (max-width: 1199.98px) {\n .offcanvas-xl.offcanvas-end {\n top: 0;\n right: 0;\n width: var(--bs-offcanvas-width);\n border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n transform: translateX(100%);\n }\n}\n@media (max-width: 1199.98px) {\n .offcanvas-xl.offcanvas-top {\n top: 0;\n right: 0;\n left: 0;\n height: var(--bs-offcanvas-height);\n max-height: 100%;\n border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n transform: translateY(-100%);\n }\n}\n@media (max-width: 1199.98px) {\n .offcanvas-xl.offcanvas-bottom {\n right: 0;\n left: 0;\n height: var(--bs-offcanvas-height);\n max-height: 100%;\n border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n transform: translateY(100%);\n }\n}\n@media (max-width: 1199.98px) {\n .offcanvas-xl.showing, .offcanvas-xl.show:not(.hiding) {\n transform: none;\n }\n}\n@media (max-width: 1199.98px) {\n .offcanvas-xl.showing, .offcanvas-xl.hiding, .offcanvas-xl.show {\n visibility: visible;\n }\n}\n@media (min-width: 1200px) {\n .offcanvas-xl {\n --bs-offcanvas-height: auto;\n --bs-offcanvas-border-width: 0;\n background-color: transparent !important;\n }\n .offcanvas-xl .offcanvas-header {\n display: none;\n }\n .offcanvas-xl .offcanvas-body {\n display: flex;\n flex-grow: 0;\n padding: 0;\n overflow-y: visible;\n background-color: transparent !important;\n }\n}\n\n@media (max-width: 1399.98px) {\n .offcanvas-xxl {\n position: fixed;\n bottom: 0;\n z-index: var(--bs-offcanvas-zindex);\n display: flex;\n flex-direction: column;\n max-width: 100%;\n color: var(--bs-offcanvas-color);\n visibility: hidden;\n background-color: var(--bs-offcanvas-bg);\n background-clip: padding-box;\n outline: 0;\n transition: var(--bs-offcanvas-transition);\n }\n}\n@media (max-width: 1399.98px) and (prefers-reduced-motion: reduce) {\n .offcanvas-xxl {\n transition: none;\n }\n}\n@media (max-width: 1399.98px) {\n .offcanvas-xxl.offcanvas-start {\n top: 0;\n left: 0;\n width: var(--bs-offcanvas-width);\n border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n transform: translateX(-100%);\n }\n}\n@media (max-width: 1399.98px) {\n .offcanvas-xxl.offcanvas-end {\n top: 0;\n right: 0;\n width: var(--bs-offcanvas-width);\n border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n transform: translateX(100%);\n }\n}\n@media (max-width: 1399.98px) {\n .offcanvas-xxl.offcanvas-top {\n top: 0;\n right: 0;\n left: 0;\n height: var(--bs-offcanvas-height);\n max-height: 100%;\n border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n transform: translateY(-100%);\n }\n}\n@media (max-width: 1399.98px) {\n .offcanvas-xxl.offcanvas-bottom {\n right: 0;\n left: 0;\n height: var(--bs-offcanvas-height);\n max-height: 100%;\n border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n transform: translateY(100%);\n }\n}\n@media (max-width: 1399.98px) {\n .offcanvas-xxl.showing, .offcanvas-xxl.show:not(.hiding) {\n transform: none;\n }\n}\n@media (max-width: 1399.98px) {\n .offcanvas-xxl.showing, .offcanvas-xxl.hiding, .offcanvas-xxl.show {\n visibility: visible;\n }\n}\n@media (min-width: 1400px) {\n .offcanvas-xxl {\n --bs-offcanvas-height: auto;\n --bs-offcanvas-border-width: 0;\n background-color: transparent !important;\n }\n .offcanvas-xxl .offcanvas-header {\n display: none;\n }\n .offcanvas-xxl .offcanvas-body {\n display: flex;\n flex-grow: 0;\n padding: 0;\n overflow-y: visible;\n background-color: transparent !important;\n }\n}\n\n.offcanvas {\n position: fixed;\n bottom: 0;\n z-index: var(--bs-offcanvas-zindex);\n display: flex;\n flex-direction: column;\n max-width: 100%;\n color: var(--bs-offcanvas-color);\n visibility: hidden;\n background-color: var(--bs-offcanvas-bg);\n background-clip: padding-box;\n outline: 0;\n transition: var(--bs-offcanvas-transition);\n}\n@media (prefers-reduced-motion: reduce) {\n .offcanvas {\n transition: none;\n }\n}\n.offcanvas.offcanvas-start {\n top: 0;\n left: 0;\n width: var(--bs-offcanvas-width);\n border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n transform: translateX(-100%);\n}\n.offcanvas.offcanvas-end {\n top: 0;\n right: 0;\n width: var(--bs-offcanvas-width);\n border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n transform: translateX(100%);\n}\n.offcanvas.offcanvas-top {\n top: 0;\n right: 0;\n left: 0;\n height: var(--bs-offcanvas-height);\n max-height: 100%;\n border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n transform: translateY(-100%);\n}\n.offcanvas.offcanvas-bottom {\n right: 0;\n left: 0;\n height: var(--bs-offcanvas-height);\n max-height: 100%;\n border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n transform: translateY(100%);\n}\n.offcanvas.showing, .offcanvas.show:not(.hiding) {\n transform: none;\n}\n.offcanvas.showing, .offcanvas.hiding, .offcanvas.show {\n visibility: visible;\n}\n\n.offcanvas-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n z-index: 1040;\n width: 100vw;\n height: 100vh;\n background-color: #000;\n}\n.offcanvas-backdrop.fade {\n opacity: 0;\n}\n.offcanvas-backdrop.show {\n opacity: 0.5;\n}\n\n.offcanvas-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);\n}\n.offcanvas-header .btn-close {\n padding: calc(var(--bs-offcanvas-padding-y) * 0.5) calc(var(--bs-offcanvas-padding-x) * 0.5);\n margin-top: calc(-0.5 * var(--bs-offcanvas-padding-y));\n margin-right: calc(-0.5 * var(--bs-offcanvas-padding-x));\n margin-bottom: calc(-0.5 * var(--bs-offcanvas-padding-y));\n}\n\n.offcanvas-title {\n margin-bottom: 0;\n line-height: var(--bs-offcanvas-title-line-height);\n}\n\n.offcanvas-body {\n flex-grow: 1;\n padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);\n overflow-y: auto;\n}\n\n.placeholder {\n display: inline-block;\n min-height: 1em;\n vertical-align: middle;\n cursor: wait;\n background-color: currentcolor;\n opacity: 0.5;\n}\n.placeholder.btn::before {\n display: inline-block;\n content: \"\";\n}\n\n.placeholder-xs {\n min-height: 0.6em;\n}\n\n.placeholder-sm {\n min-height: 0.8em;\n}\n\n.placeholder-lg {\n min-height: 1.2em;\n}\n\n.placeholder-glow .placeholder {\n animation: placeholder-glow 2s ease-in-out infinite;\n}\n\n@keyframes placeholder-glow {\n 50% {\n opacity: 0.2;\n }\n}\n.placeholder-wave {\n -webkit-mask-image: linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);\n mask-image: linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);\n -webkit-mask-size: 200% 100%;\n mask-size: 200% 100%;\n animation: placeholder-wave 2s linear infinite;\n}\n\n@keyframes placeholder-wave {\n 100% {\n -webkit-mask-position: -200% 0%;\n mask-position: -200% 0%;\n }\n}\n.clearfix::after {\n display: block;\n clear: both;\n content: \"\";\n}\n\n.text-bg-primary {\n color: #fff !important;\n background-color: RGBA(13, 110, 253, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-secondary {\n color: #fff !important;\n background-color: RGBA(108, 117, 125, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-success {\n color: #fff !important;\n background-color: RGBA(25, 135, 84, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-info {\n color: #000 !important;\n background-color: RGBA(13, 202, 240, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-warning {\n color: #000 !important;\n background-color: RGBA(255, 193, 7, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-danger {\n color: #fff !important;\n background-color: RGBA(220, 53, 69, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-light {\n color: #000 !important;\n background-color: RGBA(248, 249, 250, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-dark {\n color: #fff !important;\n background-color: RGBA(33, 37, 41, var(--bs-bg-opacity, 1)) !important;\n}\n\n.link-primary {\n color: RGBA(var(--bs-primary-rgb, var(--bs-link-opacity, 1)));\n -webkit-text-decoration-color: RGBA(var(--bs-primary-rgb), var(--bs-link-underline-opacity, 1));\n text-decoration-color: RGBA(var(--bs-primary-rgb), var(--bs-link-underline-opacity, 1));\n}\n.link-primary:hover, .link-primary:focus {\n color: RGBA(10, 88, 202, var(--bs-link-opacity, 1));\n -webkit-text-decoration-color: RGBA(10, 88, 202, var(--bs-link-underline-opacity, 1));\n text-decoration-color: RGBA(10, 88, 202, var(--bs-link-underline-opacity, 1));\n}\n\n.link-secondary {\n color: RGBA(var(--bs-secondary-rgb, var(--bs-link-opacity, 1)));\n -webkit-text-decoration-color: RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1));\n text-decoration-color: RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1));\n}\n.link-secondary:hover, .link-secondary:focus {\n color: RGBA(86, 94, 100, var(--bs-link-opacity, 1));\n -webkit-text-decoration-color: RGBA(86, 94, 100, var(--bs-link-underline-opacity, 1));\n text-decoration-color: RGBA(86, 94, 100, var(--bs-link-underline-opacity, 1));\n}\n\n.link-success {\n color: RGBA(var(--bs-success-rgb, var(--bs-link-opacity, 1)));\n -webkit-text-decoration-color: RGBA(var(--bs-success-rgb), var(--bs-link-underline-opacity, 1));\n text-decoration-color: RGBA(var(--bs-success-rgb), var(--bs-link-underline-opacity, 1));\n}\n.link-success:hover, .link-success:focus {\n color: RGBA(20, 108, 67, var(--bs-link-opacity, 1));\n -webkit-text-decoration-color: RGBA(20, 108, 67, var(--bs-link-underline-opacity, 1));\n text-decoration-color: RGBA(20, 108, 67, var(--bs-link-underline-opacity, 1));\n}\n\n.link-info {\n color: RGBA(var(--bs-info-rgb, var(--bs-link-opacity, 1)));\n -webkit-text-decoration-color: RGBA(var(--bs-info-rgb), var(--bs-link-underline-opacity, 1));\n text-decoration-color: RGBA(var(--bs-info-rgb), var(--bs-link-underline-opacity, 1));\n}\n.link-info:hover, .link-info:focus {\n color: RGBA(61, 213, 243, var(--bs-link-opacity, 1));\n -webkit-text-decoration-color: RGBA(61, 213, 243, var(--bs-link-underline-opacity, 1));\n text-decoration-color: RGBA(61, 213, 243, var(--bs-link-underline-opacity, 1));\n}\n\n.link-warning {\n color: RGBA(var(--bs-warning-rgb, var(--bs-link-opacity, 1)));\n -webkit-text-decoration-color: RGBA(var(--bs-warning-rgb), var(--bs-link-underline-opacity, 1));\n text-decoration-color: RGBA(var(--bs-warning-rgb), var(--bs-link-underline-opacity, 1));\n}\n.link-warning:hover, .link-warning:focus {\n color: RGBA(255, 205, 57, var(--bs-link-opacity, 1));\n -webkit-text-decoration-color: RGBA(255, 205, 57, var(--bs-link-underline-opacity, 1));\n text-decoration-color: RGBA(255, 205, 57, var(--bs-link-underline-opacity, 1));\n}\n\n.link-danger {\n color: RGBA(var(--bs-danger-rgb, var(--bs-link-opacity, 1)));\n -webkit-text-decoration-color: RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity, 1));\n text-decoration-color: RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity, 1));\n}\n.link-danger:hover, .link-danger:focus {\n color: RGBA(176, 42, 55, var(--bs-link-opacity, 1));\n -webkit-text-decoration-color: RGBA(176, 42, 55, var(--bs-link-underline-opacity, 1));\n text-decoration-color: RGBA(176, 42, 55, var(--bs-link-underline-opacity, 1));\n}\n\n.link-light {\n color: RGBA(var(--bs-light-rgb, var(--bs-link-opacity, 1)));\n -webkit-text-decoration-color: RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity, 1));\n text-decoration-color: RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity, 1));\n}\n.link-light:hover, .link-light:focus {\n color: RGBA(249, 250, 251, var(--bs-link-opacity, 1));\n -webkit-text-decoration-color: RGBA(249, 250, 251, var(--bs-link-underline-opacity, 1));\n text-decoration-color: RGBA(249, 250, 251, var(--bs-link-underline-opacity, 1));\n}\n\n.link-dark {\n color: RGBA(var(--bs-dark-rgb, var(--bs-link-opacity, 1)));\n -webkit-text-decoration-color: RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity, 1));\n text-decoration-color: RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity, 1));\n}\n.link-dark:hover, .link-dark:focus {\n color: RGBA(26, 30, 33, var(--bs-link-opacity, 1));\n -webkit-text-decoration-color: RGBA(26, 30, 33, var(--bs-link-underline-opacity, 1));\n text-decoration-color: RGBA(26, 30, 33, var(--bs-link-underline-opacity, 1));\n}\n\n.link-body-emphasis {\n color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity, 1));\n -webkit-text-decoration-color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 1));\n text-decoration-color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 1));\n}\n.link-body-emphasis:hover, .link-body-emphasis:focus {\n color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity, 0.75));\n -webkit-text-decoration-color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 0.75));\n text-decoration-color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 0.75));\n}\n\n.focus-ring:focus {\n outline: 0;\n box-shadow: var(--bs-focus-ring-x, 0) var(--bs-focus-ring-y, 0) var(--bs-focus-ring-blur, 0) var(--bs-focus-ring-width) var(--bs-focus-ring-color);\n}\n\n.icon-link {\n display: inline-flex;\n gap: 0.375rem;\n align-items: center;\n -webkit-text-decoration-color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 0.5));\n text-decoration-color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 0.5));\n text-underline-offset: 0.25em;\n -webkit-backface-visibility: hidden;\n backface-visibility: hidden;\n}\n.icon-link > .bi {\n flex-shrink: 0;\n width: 1em;\n height: 1em;\n fill: currentcolor;\n transition: 0.2s ease-in-out transform;\n}\n@media (prefers-reduced-motion: reduce) {\n .icon-link > .bi {\n transition: none;\n }\n}\n\n.icon-link-hover:hover > .bi, .icon-link-hover:focus-visible > .bi {\n transform: var(--bs-icon-link-transform, translate3d(0.25em, 0, 0));\n}\n\n.ratio {\n position: relative;\n width: 100%;\n}\n.ratio::before {\n display: block;\n padding-top: var(--bs-aspect-ratio);\n content: \"\";\n}\n.ratio > * {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n}\n\n.ratio-1x1 {\n --bs-aspect-ratio: 100%;\n}\n\n.ratio-4x3 {\n --bs-aspect-ratio: 75%;\n}\n\n.ratio-16x9 {\n --bs-aspect-ratio: 56.25%;\n}\n\n.ratio-21x9 {\n --bs-aspect-ratio: 42.8571428571%;\n}\n\n.fixed-top {\n position: fixed;\n top: 0;\n right: 0;\n left: 0;\n z-index: 1030;\n}\n\n.fixed-bottom {\n position: fixed;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1030;\n}\n\n.sticky-top {\n position: -webkit-sticky;\n position: sticky;\n top: 0;\n z-index: 1020;\n}\n\n.sticky-bottom {\n position: -webkit-sticky;\n position: sticky;\n bottom: 0;\n z-index: 1020;\n}\n\n@media (min-width: 576px) {\n .sticky-sm-top {\n position: -webkit-sticky;\n position: sticky;\n top: 0;\n z-index: 1020;\n }\n .sticky-sm-bottom {\n position: -webkit-sticky;\n position: sticky;\n bottom: 0;\n z-index: 1020;\n }\n}\n@media (min-width: 768px) {\n .sticky-md-top {\n position: -webkit-sticky;\n position: sticky;\n top: 0;\n z-index: 1020;\n }\n .sticky-md-bottom {\n position: -webkit-sticky;\n position: sticky;\n bottom: 0;\n z-index: 1020;\n }\n}\n@media (min-width: 992px) {\n .sticky-lg-top {\n position: -webkit-sticky;\n position: sticky;\n top: 0;\n z-index: 1020;\n }\n .sticky-lg-bottom {\n position: -webkit-sticky;\n position: sticky;\n bottom: 0;\n z-index: 1020;\n }\n}\n@media (min-width: 1200px) {\n .sticky-xl-top {\n position: -webkit-sticky;\n position: sticky;\n top: 0;\n z-index: 1020;\n }\n .sticky-xl-bottom {\n position: -webkit-sticky;\n position: sticky;\n bottom: 0;\n z-index: 1020;\n }\n}\n@media (min-width: 1400px) {\n .sticky-xxl-top {\n position: -webkit-sticky;\n position: sticky;\n top: 0;\n z-index: 1020;\n }\n .sticky-xxl-bottom {\n position: -webkit-sticky;\n position: sticky;\n bottom: 0;\n z-index: 1020;\n }\n}\n.hstack {\n display: flex;\n flex-direction: row;\n align-items: center;\n align-self: stretch;\n}\n\n.vstack {\n display: flex;\n flex: 1 1 auto;\n flex-direction: column;\n align-self: stretch;\n}\n\n.visually-hidden,\n.visually-hidden-focusable:not(:focus):not(:focus-within) {\n width: 1px !important;\n height: 1px !important;\n padding: 0 !important;\n margin: -1px !important;\n overflow: hidden !important;\n clip: rect(0, 0, 0, 0) !important;\n white-space: nowrap !important;\n border: 0 !important;\n}\n.visually-hidden:not(caption),\n.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption) {\n position: absolute !important;\n}\n\n.stretched-link::after {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1;\n content: \"\";\n}\n\n.text-truncate {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.vr {\n display: inline-block;\n align-self: stretch;\n width: 1px;\n min-height: 1em;\n background-color: currentcolor;\n opacity: 0.25;\n}\n\n.align-baseline {\n vertical-align: baseline !important;\n}\n\n.align-top {\n vertical-align: top !important;\n}\n\n.align-middle {\n vertical-align: middle !important;\n}\n\n.align-bottom {\n vertical-align: bottom !important;\n}\n\n.align-text-bottom {\n vertical-align: text-bottom !important;\n}\n\n.align-text-top {\n vertical-align: text-top !important;\n}\n\n.float-start {\n float: left !important;\n}\n\n.float-end {\n float: right !important;\n}\n\n.float-none {\n float: none !important;\n}\n\n.object-fit-contain {\n -o-object-fit: contain !important;\n object-fit: contain !important;\n}\n\n.object-fit-cover {\n -o-object-fit: cover !important;\n object-fit: cover !important;\n}\n\n.object-fit-fill {\n -o-object-fit: fill !important;\n object-fit: fill !important;\n}\n\n.object-fit-scale {\n -o-object-fit: scale-down !important;\n object-fit: scale-down !important;\n}\n\n.object-fit-none {\n -o-object-fit: none !important;\n object-fit: none !important;\n}\n\n.opacity-0 {\n opacity: 0 !important;\n}\n\n.opacity-25 {\n opacity: 0.25 !important;\n}\n\n.opacity-50 {\n opacity: 0.5 !important;\n}\n\n.opacity-75 {\n opacity: 0.75 !important;\n}\n\n.opacity-100 {\n opacity: 1 !important;\n}\n\n.overflow-auto {\n overflow: auto !important;\n}\n\n.overflow-hidden {\n overflow: hidden !important;\n}\n\n.overflow-visible {\n overflow: visible !important;\n}\n\n.overflow-scroll {\n overflow: scroll !important;\n}\n\n.overflow-x-auto {\n overflow-x: auto !important;\n}\n\n.overflow-x-hidden {\n overflow-x: hidden !important;\n}\n\n.overflow-x-visible {\n overflow-x: visible !important;\n}\n\n.overflow-x-scroll {\n overflow-x: scroll !important;\n}\n\n.overflow-y-auto {\n overflow-y: auto !important;\n}\n\n.overflow-y-hidden {\n overflow-y: hidden !important;\n}\n\n.overflow-y-visible {\n overflow-y: visible !important;\n}\n\n.overflow-y-scroll {\n overflow-y: scroll !important;\n}\n\n.d-inline {\n display: inline !important;\n}\n\n.d-inline-block {\n display: inline-block !important;\n}\n\n.d-block {\n display: block !important;\n}\n\n.d-grid {\n display: grid !important;\n}\n\n.d-inline-grid {\n display: inline-grid !important;\n}\n\n.d-table {\n display: table !important;\n}\n\n.d-table-row {\n display: table-row !important;\n}\n\n.d-table-cell {\n display: table-cell !important;\n}\n\n.d-flex {\n display: flex !important;\n}\n\n.d-inline-flex {\n display: inline-flex !important;\n}\n\n.d-none {\n display: none !important;\n}\n\n.shadow {\n box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;\n}\n\n.shadow-sm {\n box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;\n}\n\n.shadow-lg {\n box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important;\n}\n\n.shadow-none {\n box-shadow: none !important;\n}\n\n.focus-ring-primary {\n --bs-focus-ring-color: rgba(var(--bs-primary-rgb), var(--bs-focus-ring-opacity));\n}\n\n.focus-ring-secondary {\n --bs-focus-ring-color: rgba(var(--bs-secondary-rgb), var(--bs-focus-ring-opacity));\n}\n\n.focus-ring-success {\n --bs-focus-ring-color: rgba(var(--bs-success-rgb), var(--bs-focus-ring-opacity));\n}\n\n.focus-ring-info {\n --bs-focus-ring-color: rgba(var(--bs-info-rgb), var(--bs-focus-ring-opacity));\n}\n\n.focus-ring-warning {\n --bs-focus-ring-color: rgba(var(--bs-warning-rgb), var(--bs-focus-ring-opacity));\n}\n\n.focus-ring-danger {\n --bs-focus-ring-color: rgba(var(--bs-danger-rgb), var(--bs-focus-ring-opacity));\n}\n\n.focus-ring-light {\n --bs-focus-ring-color: rgba(var(--bs-light-rgb), var(--bs-focus-ring-opacity));\n}\n\n.focus-ring-dark {\n --bs-focus-ring-color: rgba(var(--bs-dark-rgb), var(--bs-focus-ring-opacity));\n}\n\n.position-static {\n position: static !important;\n}\n\n.position-relative {\n position: relative !important;\n}\n\n.position-absolute {\n position: absolute !important;\n}\n\n.position-fixed {\n position: fixed !important;\n}\n\n.position-sticky {\n position: -webkit-sticky !important;\n position: sticky !important;\n}\n\n.top-0 {\n top: 0 !important;\n}\n\n.top-50 {\n top: 50% !important;\n}\n\n.top-100 {\n top: 100% !important;\n}\n\n.bottom-0 {\n bottom: 0 !important;\n}\n\n.bottom-50 {\n bottom: 50% !important;\n}\n\n.bottom-100 {\n bottom: 100% !important;\n}\n\n.start-0 {\n left: 0 !important;\n}\n\n.start-50 {\n left: 50% !important;\n}\n\n.start-100 {\n left: 100% !important;\n}\n\n.end-0 {\n right: 0 !important;\n}\n\n.end-50 {\n right: 50% !important;\n}\n\n.end-100 {\n right: 100% !important;\n}\n\n.translate-middle {\n transform: translate(-50%, -50%) !important;\n}\n\n.translate-middle-x {\n transform: translateX(-50%) !important;\n}\n\n.translate-middle-y {\n transform: translateY(-50%) !important;\n}\n\n.border {\n border: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;\n}\n\n.border-0 {\n border: 0 !important;\n}\n\n.border-top {\n border-top: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;\n}\n\n.border-top-0 {\n border-top: 0 !important;\n}\n\n.border-end {\n border-right: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;\n}\n\n.border-end-0 {\n border-right: 0 !important;\n}\n\n.border-bottom {\n border-bottom: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;\n}\n\n.border-bottom-0 {\n border-bottom: 0 !important;\n}\n\n.border-start {\n border-left: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;\n}\n\n.border-start-0 {\n border-left: 0 !important;\n}\n\n.border-primary {\n --bs-border-opacity: 1;\n border-color: rgba(var(--bs-primary-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-secondary {\n --bs-border-opacity: 1;\n border-color: rgba(var(--bs-secondary-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-success {\n --bs-border-opacity: 1;\n border-color: rgba(var(--bs-success-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-info {\n --bs-border-opacity: 1;\n border-color: rgba(var(--bs-info-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-warning {\n --bs-border-opacity: 1;\n border-color: rgba(var(--bs-warning-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-danger {\n --bs-border-opacity: 1;\n border-color: rgba(var(--bs-danger-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-light {\n --bs-border-opacity: 1;\n border-color: rgba(var(--bs-light-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-dark {\n --bs-border-opacity: 1;\n border-color: rgba(var(--bs-dark-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-black {\n --bs-border-opacity: 1;\n border-color: rgba(var(--bs-black-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-white {\n --bs-border-opacity: 1;\n border-color: rgba(var(--bs-white-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-primary-subtle {\n border-color: var(--bs-primary-border-subtle) !important;\n}\n\n.border-secondary-subtle {\n border-color: var(--bs-secondary-border-subtle) !important;\n}\n\n.border-success-subtle {\n border-color: var(--bs-success-border-subtle) !important;\n}\n\n.border-info-subtle {\n border-color: var(--bs-info-border-subtle) !important;\n}\n\n.border-warning-subtle {\n border-color: var(--bs-warning-border-subtle) !important;\n}\n\n.border-danger-subtle {\n border-color: var(--bs-danger-border-subtle) !important;\n}\n\n.border-light-subtle {\n border-color: var(--bs-light-border-subtle) !important;\n}\n\n.border-dark-subtle {\n border-color: var(--bs-dark-border-subtle) !important;\n}\n\n.border-1 {\n border-width: 1px !important;\n}\n\n.border-2 {\n border-width: 2px !important;\n}\n\n.border-3 {\n border-width: 3px !important;\n}\n\n.border-4 {\n border-width: 4px !important;\n}\n\n.border-5 {\n border-width: 5px !important;\n}\n\n.border-opacity-10 {\n --bs-border-opacity: 0.1;\n}\n\n.border-opacity-25 {\n --bs-border-opacity: 0.25;\n}\n\n.border-opacity-50 {\n --bs-border-opacity: 0.5;\n}\n\n.border-opacity-75 {\n --bs-border-opacity: 0.75;\n}\n\n.border-opacity-100 {\n --bs-border-opacity: 1;\n}\n\n.w-25 {\n width: 25% !important;\n}\n\n.w-50 {\n width: 50% !important;\n}\n\n.w-75 {\n width: 75% !important;\n}\n\n.w-100 {\n width: 100% !important;\n}\n\n.w-auto {\n width: auto !important;\n}\n\n.mw-100 {\n max-width: 100% !important;\n}\n\n.vw-100 {\n width: 100vw !important;\n}\n\n.min-vw-100 {\n min-width: 100vw !important;\n}\n\n.h-25 {\n height: 25% !important;\n}\n\n.h-50 {\n height: 50% !important;\n}\n\n.h-75 {\n height: 75% !important;\n}\n\n.h-100 {\n height: 100% !important;\n}\n\n.h-auto {\n height: auto !important;\n}\n\n.mh-100 {\n max-height: 100% !important;\n}\n\n.vh-100 {\n height: 100vh !important;\n}\n\n.min-vh-100 {\n min-height: 100vh !important;\n}\n\n.flex-fill {\n flex: 1 1 auto !important;\n}\n\n.flex-row {\n flex-direction: row !important;\n}\n\n.flex-column {\n flex-direction: column !important;\n}\n\n.flex-row-reverse {\n flex-direction: row-reverse !important;\n}\n\n.flex-column-reverse {\n flex-direction: column-reverse !important;\n}\n\n.flex-grow-0 {\n flex-grow: 0 !important;\n}\n\n.flex-grow-1 {\n flex-grow: 1 !important;\n}\n\n.flex-shrink-0 {\n flex-shrink: 0 !important;\n}\n\n.flex-shrink-1 {\n flex-shrink: 1 !important;\n}\n\n.flex-wrap {\n flex-wrap: wrap !important;\n}\n\n.flex-nowrap {\n flex-wrap: nowrap !important;\n}\n\n.flex-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n}\n\n.justify-content-start {\n justify-content: flex-start !important;\n}\n\n.justify-content-end {\n justify-content: flex-end !important;\n}\n\n.justify-content-center {\n justify-content: center !important;\n}\n\n.justify-content-between {\n justify-content: space-between !important;\n}\n\n.justify-content-around {\n justify-content: space-around !important;\n}\n\n.justify-content-evenly {\n justify-content: space-evenly !important;\n}\n\n.align-items-start {\n align-items: flex-start !important;\n}\n\n.align-items-end {\n align-items: flex-end !important;\n}\n\n.align-items-center {\n align-items: center !important;\n}\n\n.align-items-baseline {\n align-items: baseline !important;\n}\n\n.align-items-stretch {\n align-items: stretch !important;\n}\n\n.align-content-start {\n align-content: flex-start !important;\n}\n\n.align-content-end {\n align-content: flex-end !important;\n}\n\n.align-content-center {\n align-content: center !important;\n}\n\n.align-content-between {\n align-content: space-between !important;\n}\n\n.align-content-around {\n align-content: space-around !important;\n}\n\n.align-content-stretch {\n align-content: stretch !important;\n}\n\n.align-self-auto {\n align-self: auto !important;\n}\n\n.align-self-start {\n align-self: flex-start !important;\n}\n\n.align-self-end {\n align-self: flex-end !important;\n}\n\n.align-self-center {\n align-self: center !important;\n}\n\n.align-self-baseline {\n align-self: baseline !important;\n}\n\n.align-self-stretch {\n align-self: stretch !important;\n}\n\n.order-first {\n order: -1 !important;\n}\n\n.order-0 {\n order: 0 !important;\n}\n\n.order-1 {\n order: 1 !important;\n}\n\n.order-2 {\n order: 2 !important;\n}\n\n.order-3 {\n order: 3 !important;\n}\n\n.order-4 {\n order: 4 !important;\n}\n\n.order-5 {\n order: 5 !important;\n}\n\n.order-last {\n order: 6 !important;\n}\n\n.m-0 {\n margin: 0 !important;\n}\n\n.m-1 {\n margin: 0.25rem !important;\n}\n\n.m-2 {\n margin: 0.5rem !important;\n}\n\n.m-3 {\n margin: 1rem !important;\n}\n\n.m-4 {\n margin: 1.5rem !important;\n}\n\n.m-5 {\n margin: 3rem !important;\n}\n\n.m-auto {\n margin: auto !important;\n}\n\n.mx-0 {\n margin-right: 0 !important;\n margin-left: 0 !important;\n}\n\n.mx-1 {\n margin-right: 0.25rem !important;\n margin-left: 0.25rem !important;\n}\n\n.mx-2 {\n margin-right: 0.5rem !important;\n margin-left: 0.5rem !important;\n}\n\n.mx-3 {\n margin-right: 1rem !important;\n margin-left: 1rem !important;\n}\n\n.mx-4 {\n margin-right: 1.5rem !important;\n margin-left: 1.5rem !important;\n}\n\n.mx-5 {\n margin-right: 3rem !important;\n margin-left: 3rem !important;\n}\n\n.mx-auto {\n margin-right: auto !important;\n margin-left: auto !important;\n}\n\n.my-0 {\n margin-top: 0 !important;\n margin-bottom: 0 !important;\n}\n\n.my-1 {\n margin-top: 0.25rem !important;\n margin-bottom: 0.25rem !important;\n}\n\n.my-2 {\n margin-top: 0.5rem !important;\n margin-bottom: 0.5rem !important;\n}\n\n.my-3 {\n margin-top: 1rem !important;\n margin-bottom: 1rem !important;\n}\n\n.my-4 {\n margin-top: 1.5rem !important;\n margin-bottom: 1.5rem !important;\n}\n\n.my-5 {\n margin-top: 3rem !important;\n margin-bottom: 3rem !important;\n}\n\n.my-auto {\n margin-top: auto !important;\n margin-bottom: auto !important;\n}\n\n.mt-0 {\n margin-top: 0 !important;\n}\n\n.mt-1 {\n margin-top: 0.25rem !important;\n}\n\n.mt-2 {\n margin-top: 0.5rem !important;\n}\n\n.mt-3 {\n margin-top: 1rem !important;\n}\n\n.mt-4 {\n margin-top: 1.5rem !important;\n}\n\n.mt-5 {\n margin-top: 3rem !important;\n}\n\n.mt-auto {\n margin-top: auto !important;\n}\n\n.me-0 {\n margin-right: 0 !important;\n}\n\n.me-1 {\n margin-right: 0.25rem !important;\n}\n\n.me-2 {\n margin-right: 0.5rem !important;\n}\n\n.me-3 {\n margin-right: 1rem !important;\n}\n\n.me-4 {\n margin-right: 1.5rem !important;\n}\n\n.me-5 {\n margin-right: 3rem !important;\n}\n\n.me-auto {\n margin-right: auto !important;\n}\n\n.mb-0 {\n margin-bottom: 0 !important;\n}\n\n.mb-1 {\n margin-bottom: 0.25rem !important;\n}\n\n.mb-2 {\n margin-bottom: 0.5rem !important;\n}\n\n.mb-3 {\n margin-bottom: 1rem !important;\n}\n\n.mb-4 {\n margin-bottom: 1.5rem !important;\n}\n\n.mb-5 {\n margin-bottom: 3rem !important;\n}\n\n.mb-auto {\n margin-bottom: auto !important;\n}\n\n.ms-0 {\n margin-left: 0 !important;\n}\n\n.ms-1 {\n margin-left: 0.25rem !important;\n}\n\n.ms-2 {\n margin-left: 0.5rem !important;\n}\n\n.ms-3 {\n margin-left: 1rem !important;\n}\n\n.ms-4 {\n margin-left: 1.5rem !important;\n}\n\n.ms-5 {\n margin-left: 3rem !important;\n}\n\n.ms-auto {\n margin-left: auto !important;\n}\n\n.p-0 {\n padding: 0 !important;\n}\n\n.p-1 {\n padding: 0.25rem !important;\n}\n\n.p-2 {\n padding: 0.5rem !important;\n}\n\n.p-3 {\n padding: 1rem !important;\n}\n\n.p-4 {\n padding: 1.5rem !important;\n}\n\n.p-5 {\n padding: 3rem !important;\n}\n\n.px-0 {\n padding-right: 0 !important;\n padding-left: 0 !important;\n}\n\n.px-1 {\n padding-right: 0.25rem !important;\n padding-left: 0.25rem !important;\n}\n\n.px-2 {\n padding-right: 0.5rem !important;\n padding-left: 0.5rem !important;\n}\n\n.px-3 {\n padding-right: 1rem !important;\n padding-left: 1rem !important;\n}\n\n.px-4 {\n padding-right: 1.5rem !important;\n padding-left: 1.5rem !important;\n}\n\n.px-5 {\n padding-right: 3rem !important;\n padding-left: 3rem !important;\n}\n\n.py-0 {\n padding-top: 0 !important;\n padding-bottom: 0 !important;\n}\n\n.py-1 {\n padding-top: 0.25rem !important;\n padding-bottom: 0.25rem !important;\n}\n\n.py-2 {\n padding-top: 0.5rem !important;\n padding-bottom: 0.5rem !important;\n}\n\n.py-3 {\n padding-top: 1rem !important;\n padding-bottom: 1rem !important;\n}\n\n.py-4 {\n padding-top: 1.5rem !important;\n padding-bottom: 1.5rem !important;\n}\n\n.py-5 {\n padding-top: 3rem !important;\n padding-bottom: 3rem !important;\n}\n\n.pt-0 {\n padding-top: 0 !important;\n}\n\n.pt-1 {\n padding-top: 0.25rem !important;\n}\n\n.pt-2 {\n padding-top: 0.5rem !important;\n}\n\n.pt-3 {\n padding-top: 1rem !important;\n}\n\n.pt-4 {\n padding-top: 1.5rem !important;\n}\n\n.pt-5 {\n padding-top: 3rem !important;\n}\n\n.pe-0 {\n padding-right: 0 !important;\n}\n\n.pe-1 {\n padding-right: 0.25rem !important;\n}\n\n.pe-2 {\n padding-right: 0.5rem !important;\n}\n\n.pe-3 {\n padding-right: 1rem !important;\n}\n\n.pe-4 {\n padding-right: 1.5rem !important;\n}\n\n.pe-5 {\n padding-right: 3rem !important;\n}\n\n.pb-0 {\n padding-bottom: 0 !important;\n}\n\n.pb-1 {\n padding-bottom: 0.25rem !important;\n}\n\n.pb-2 {\n padding-bottom: 0.5rem !important;\n}\n\n.pb-3 {\n padding-bottom: 1rem !important;\n}\n\n.pb-4 {\n padding-bottom: 1.5rem !important;\n}\n\n.pb-5 {\n padding-bottom: 3rem !important;\n}\n\n.ps-0 {\n padding-left: 0 !important;\n}\n\n.ps-1 {\n padding-left: 0.25rem !important;\n}\n\n.ps-2 {\n padding-left: 0.5rem !important;\n}\n\n.ps-3 {\n padding-left: 1rem !important;\n}\n\n.ps-4 {\n padding-left: 1.5rem !important;\n}\n\n.ps-5 {\n padding-left: 3rem !important;\n}\n\n.gap-0 {\n gap: 0 !important;\n}\n\n.gap-1 {\n gap: 0.25rem !important;\n}\n\n.gap-2 {\n gap: 0.5rem !important;\n}\n\n.gap-3 {\n gap: 1rem !important;\n}\n\n.gap-4 {\n gap: 1.5rem !important;\n}\n\n.gap-5 {\n gap: 3rem !important;\n}\n\n.row-gap-0 {\n row-gap: 0 !important;\n}\n\n.row-gap-1 {\n row-gap: 0.25rem !important;\n}\n\n.row-gap-2 {\n row-gap: 0.5rem !important;\n}\n\n.row-gap-3 {\n row-gap: 1rem !important;\n}\n\n.row-gap-4 {\n row-gap: 1.5rem !important;\n}\n\n.row-gap-5 {\n row-gap: 3rem !important;\n}\n\n.column-gap-0 {\n -moz-column-gap: 0 !important;\n column-gap: 0 !important;\n}\n\n.column-gap-1 {\n -moz-column-gap: 0.25rem !important;\n column-gap: 0.25rem !important;\n}\n\n.column-gap-2 {\n -moz-column-gap: 0.5rem !important;\n column-gap: 0.5rem !important;\n}\n\n.column-gap-3 {\n -moz-column-gap: 1rem !important;\n column-gap: 1rem !important;\n}\n\n.column-gap-4 {\n -moz-column-gap: 1.5rem !important;\n column-gap: 1.5rem !important;\n}\n\n.column-gap-5 {\n -moz-column-gap: 3rem !important;\n column-gap: 3rem !important;\n}\n\n.font-monospace {\n font-family: var(--bs-font-monospace) !important;\n}\n\n.fs-1 {\n font-size: calc(1.375rem + 1.5vw) !important;\n}\n\n.fs-2 {\n font-size: calc(1.325rem + 0.9vw) !important;\n}\n\n.fs-3 {\n font-size: calc(1.3rem + 0.6vw) !important;\n}\n\n.fs-4 {\n font-size: calc(1.275rem + 0.3vw) !important;\n}\n\n.fs-5 {\n font-size: 1.25rem !important;\n}\n\n.fs-6 {\n font-size: 1rem !important;\n}\n\n.fst-italic {\n font-style: italic !important;\n}\n\n.fst-normal {\n font-style: normal !important;\n}\n\n.fw-lighter {\n font-weight: lighter !important;\n}\n\n.fw-light {\n font-weight: 300 !important;\n}\n\n.fw-normal {\n font-weight: 400 !important;\n}\n\n.fw-medium {\n font-weight: 500 !important;\n}\n\n.fw-semibold {\n font-weight: 600 !important;\n}\n\n.fw-bold {\n font-weight: 700 !important;\n}\n\n.fw-bolder {\n font-weight: bolder !important;\n}\n\n.lh-1 {\n line-height: 1 !important;\n}\n\n.lh-sm {\n line-height: 1.25 !important;\n}\n\n.lh-base {\n line-height: 1.5 !important;\n}\n\n.lh-lg {\n line-height: 2 !important;\n}\n\n.text-start {\n text-align: left !important;\n}\n\n.text-end {\n text-align: right !important;\n}\n\n.text-center {\n text-align: center !important;\n}\n\n.text-decoration-none {\n text-decoration: none !important;\n}\n\n.text-decoration-underline {\n text-decoration: underline !important;\n}\n\n.text-decoration-line-through {\n text-decoration: line-through !important;\n}\n\n.text-lowercase {\n text-transform: lowercase !important;\n}\n\n.text-uppercase {\n text-transform: uppercase !important;\n}\n\n.text-capitalize {\n text-transform: capitalize !important;\n}\n\n.text-wrap {\n white-space: normal !important;\n}\n\n.text-nowrap {\n white-space: nowrap !important;\n}\n\n/* rtl:begin:remove */\n.text-break {\n word-wrap: break-word !important;\n word-break: break-word !important;\n}\n\n/* rtl:end:remove */\n.text-primary {\n --bs-text-opacity: 1;\n color: rgba(var(--bs-primary-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-secondary {\n --bs-text-opacity: 1;\n color: rgba(var(--bs-secondary-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-success {\n --bs-text-opacity: 1;\n color: rgba(var(--bs-success-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-info {\n --bs-text-opacity: 1;\n color: rgba(var(--bs-info-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-warning {\n --bs-text-opacity: 1;\n color: rgba(var(--bs-warning-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-danger {\n --bs-text-opacity: 1;\n color: rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-light {\n --bs-text-opacity: 1;\n color: rgba(var(--bs-light-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-dark {\n --bs-text-opacity: 1;\n color: rgba(var(--bs-dark-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-black {\n --bs-text-opacity: 1;\n color: rgba(var(--bs-black-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-white {\n --bs-text-opacity: 1;\n color: rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-body {\n --bs-text-opacity: 1;\n color: rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-muted {\n --bs-text-opacity: 1;\n color: var(--bs-secondary-color) !important;\n}\n\n.text-black-50 {\n --bs-text-opacity: 1;\n color: rgba(0, 0, 0, 0.5) !important;\n}\n\n.text-white-50 {\n --bs-text-opacity: 1;\n color: rgba(255, 255, 255, 0.5) !important;\n}\n\n.text-body-secondary {\n --bs-text-opacity: 1;\n color: var(--bs-secondary-color) !important;\n}\n\n.text-body-tertiary {\n --bs-text-opacity: 1;\n color: var(--bs-tertiary-color) !important;\n}\n\n.text-body-emphasis {\n --bs-text-opacity: 1;\n color: var(--bs-emphasis-color) !important;\n}\n\n.text-reset {\n --bs-text-opacity: 1;\n color: inherit !important;\n}\n\n.text-opacity-25 {\n --bs-text-opacity: 0.25;\n}\n\n.text-opacity-50 {\n --bs-text-opacity: 0.5;\n}\n\n.text-opacity-75 {\n --bs-text-opacity: 0.75;\n}\n\n.text-opacity-100 {\n --bs-text-opacity: 1;\n}\n\n.text-primary-emphasis {\n color: var(--bs-primary-text-emphasis) !important;\n}\n\n.text-secondary-emphasis {\n color: var(--bs-secondary-text-emphasis) !important;\n}\n\n.text-success-emphasis {\n color: var(--bs-success-text-emphasis) !important;\n}\n\n.text-info-emphasis {\n color: var(--bs-info-text-emphasis) !important;\n}\n\n.text-warning-emphasis {\n color: var(--bs-warning-text-emphasis) !important;\n}\n\n.text-danger-emphasis {\n color: var(--bs-danger-text-emphasis) !important;\n}\n\n.text-light-emphasis {\n color: var(--bs-light-text-emphasis) !important;\n}\n\n.text-dark-emphasis {\n color: var(--bs-dark-text-emphasis) !important;\n}\n\n.link-opacity-10 {\n --bs-link-opacity: 0.1;\n}\n\n.link-opacity-10-hover:hover {\n --bs-link-opacity: 0.1;\n}\n\n.link-opacity-25 {\n --bs-link-opacity: 0.25;\n}\n\n.link-opacity-25-hover:hover {\n --bs-link-opacity: 0.25;\n}\n\n.link-opacity-50 {\n --bs-link-opacity: 0.5;\n}\n\n.link-opacity-50-hover:hover {\n --bs-link-opacity: 0.5;\n}\n\n.link-opacity-75 {\n --bs-link-opacity: 0.75;\n}\n\n.link-opacity-75-hover:hover {\n --bs-link-opacity: 0.75;\n}\n\n.link-opacity-100 {\n --bs-link-opacity: 1;\n}\n\n.link-opacity-100-hover:hover {\n --bs-link-opacity: 1;\n}\n\n.link-offset-1 {\n text-underline-offset: 0.125em !important;\n}\n\n.link-offset-1-hover:hover {\n text-underline-offset: 0.125em !important;\n}\n\n.link-offset-2 {\n text-underline-offset: 0.25em !important;\n}\n\n.link-offset-2-hover:hover {\n text-underline-offset: 0.25em !important;\n}\n\n.link-offset-3 {\n text-underline-offset: 0.375em !important;\n}\n\n.link-offset-3-hover:hover {\n text-underline-offset: 0.375em !important;\n}\n\n.link-underline-primary {\n --bs-link-underline-opacity: 1;\n -webkit-text-decoration-color: rgba(var(--bs-primary-rgb), var(--bs-link-underline-opacity)) !important;\n text-decoration-color: rgba(var(--bs-primary-rgb), var(--bs-link-underline-opacity)) !important;\n}\n\n.link-underline-secondary {\n --bs-link-underline-opacity: 1;\n -webkit-text-decoration-color: rgba(var(--bs-secondary-rgb), var(--bs-link-underline-opacity)) !important;\n text-decoration-color: rgba(var(--bs-secondary-rgb), var(--bs-link-underline-opacity)) !important;\n}\n\n.link-underline-success {\n --bs-link-underline-opacity: 1;\n -webkit-text-decoration-color: rgba(var(--bs-success-rgb), var(--bs-link-underline-opacity)) !important;\n text-decoration-color: rgba(var(--bs-success-rgb), var(--bs-link-underline-opacity)) !important;\n}\n\n.link-underline-info {\n --bs-link-underline-opacity: 1;\n -webkit-text-decoration-color: rgba(var(--bs-info-rgb), var(--bs-link-underline-opacity)) !important;\n text-decoration-color: rgba(var(--bs-info-rgb), var(--bs-link-underline-opacity)) !important;\n}\n\n.link-underline-warning {\n --bs-link-underline-opacity: 1;\n -webkit-text-decoration-color: rgba(var(--bs-warning-rgb), var(--bs-link-underline-opacity)) !important;\n text-decoration-color: rgba(var(--bs-warning-rgb), var(--bs-link-underline-opacity)) !important;\n}\n\n.link-underline-danger {\n --bs-link-underline-opacity: 1;\n -webkit-text-decoration-color: rgba(var(--bs-danger-rgb), var(--bs-link-underline-opacity)) !important;\n text-decoration-color: rgba(var(--bs-danger-rgb), var(--bs-link-underline-opacity)) !important;\n}\n\n.link-underline-light {\n --bs-link-underline-opacity: 1;\n -webkit-text-decoration-color: rgba(var(--bs-light-rgb), var(--bs-link-underline-opacity)) !important;\n text-decoration-color: rgba(var(--bs-light-rgb), var(--bs-link-underline-opacity)) !important;\n}\n\n.link-underline-dark {\n --bs-link-underline-opacity: 1;\n -webkit-text-decoration-color: rgba(var(--bs-dark-rgb), var(--bs-link-underline-opacity)) !important;\n text-decoration-color: rgba(var(--bs-dark-rgb), var(--bs-link-underline-opacity)) !important;\n}\n\n.link-underline {\n --bs-link-underline-opacity: 1;\n -webkit-text-decoration-color: rgba(var(--bs-link-color-rgb), var(--bs-link-underline-opacity, 1)) !important;\n text-decoration-color: rgba(var(--bs-link-color-rgb), var(--bs-link-underline-opacity, 1)) !important;\n}\n\n.link-underline-opacity-0 {\n --bs-link-underline-opacity: 0;\n}\n\n.link-underline-opacity-0-hover:hover {\n --bs-link-underline-opacity: 0;\n}\n\n.link-underline-opacity-10 {\n --bs-link-underline-opacity: 0.1;\n}\n\n.link-underline-opacity-10-hover:hover {\n --bs-link-underline-opacity: 0.1;\n}\n\n.link-underline-opacity-25 {\n --bs-link-underline-opacity: 0.25;\n}\n\n.link-underline-opacity-25-hover:hover {\n --bs-link-underline-opacity: 0.25;\n}\n\n.link-underline-opacity-50 {\n --bs-link-underline-opacity: 0.5;\n}\n\n.link-underline-opacity-50-hover:hover {\n --bs-link-underline-opacity: 0.5;\n}\n\n.link-underline-opacity-75 {\n --bs-link-underline-opacity: 0.75;\n}\n\n.link-underline-opacity-75-hover:hover {\n --bs-link-underline-opacity: 0.75;\n}\n\n.link-underline-opacity-100 {\n --bs-link-underline-opacity: 1;\n}\n\n.link-underline-opacity-100-hover:hover {\n --bs-link-underline-opacity: 1;\n}\n\n.bg-primary {\n --bs-bg-opacity: 1;\n background-color: rgba(var(--bs-primary-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-secondary {\n --bs-bg-opacity: 1;\n background-color: rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-success {\n --bs-bg-opacity: 1;\n background-color: rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-info {\n --bs-bg-opacity: 1;\n background-color: rgba(var(--bs-info-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-warning {\n --bs-bg-opacity: 1;\n background-color: rgba(var(--bs-warning-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-danger {\n --bs-bg-opacity: 1;\n background-color: rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-light {\n --bs-bg-opacity: 1;\n background-color: rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-dark {\n --bs-bg-opacity: 1;\n background-color: rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-black {\n --bs-bg-opacity: 1;\n background-color: rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-white {\n --bs-bg-opacity: 1;\n background-color: rgba(var(--bs-white-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-body {\n --bs-bg-opacity: 1;\n background-color: rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-transparent {\n --bs-bg-opacity: 1;\n background-color: transparent !important;\n}\n\n.bg-body-secondary {\n --bs-bg-opacity: 1;\n background-color: rgba(var(--bs-secondary-bg-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-body-tertiary {\n --bs-bg-opacity: 1;\n background-color: rgba(var(--bs-tertiary-bg-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-opacity-10 {\n --bs-bg-opacity: 0.1;\n}\n\n.bg-opacity-25 {\n --bs-bg-opacity: 0.25;\n}\n\n.bg-opacity-50 {\n --bs-bg-opacity: 0.5;\n}\n\n.bg-opacity-75 {\n --bs-bg-opacity: 0.75;\n}\n\n.bg-opacity-100 {\n --bs-bg-opacity: 1;\n}\n\n.bg-primary-subtle {\n background-color: var(--bs-primary-bg-subtle) !important;\n}\n\n.bg-secondary-subtle {\n background-color: var(--bs-secondary-bg-subtle) !important;\n}\n\n.bg-success-subtle {\n background-color: var(--bs-success-bg-subtle) !important;\n}\n\n.bg-info-subtle {\n background-color: var(--bs-info-bg-subtle) !important;\n}\n\n.bg-warning-subtle {\n background-color: var(--bs-warning-bg-subtle) !important;\n}\n\n.bg-danger-subtle {\n background-color: var(--bs-danger-bg-subtle) !important;\n}\n\n.bg-light-subtle {\n background-color: var(--bs-light-bg-subtle) !important;\n}\n\n.bg-dark-subtle {\n background-color: var(--bs-dark-bg-subtle) !important;\n}\n\n.bg-gradient {\n background-image: var(--bs-gradient) !important;\n}\n\n.user-select-all {\n -webkit-user-select: all !important;\n -moz-user-select: all !important;\n user-select: all !important;\n}\n\n.user-select-auto {\n -webkit-user-select: auto !important;\n -moz-user-select: auto !important;\n user-select: auto !important;\n}\n\n.user-select-none {\n -webkit-user-select: none !important;\n -moz-user-select: none !important;\n user-select: none !important;\n}\n\n.pe-none {\n pointer-events: none !important;\n}\n\n.pe-auto {\n pointer-events: auto !important;\n}\n\n.rounded {\n border-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-0 {\n border-radius: 0 !important;\n}\n\n.rounded-1 {\n border-radius: var(--bs-border-radius-sm) !important;\n}\n\n.rounded-2 {\n border-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-3 {\n border-radius: var(--bs-border-radius-lg) !important;\n}\n\n.rounded-4 {\n border-radius: var(--bs-border-radius-xl) !important;\n}\n\n.rounded-5 {\n border-radius: var(--bs-border-radius-xxl) !important;\n}\n\n.rounded-circle {\n border-radius: 50% !important;\n}\n\n.rounded-pill {\n border-radius: var(--bs-border-radius-pill) !important;\n}\n\n.rounded-top {\n border-top-left-radius: var(--bs-border-radius) !important;\n border-top-right-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-top-0 {\n border-top-left-radius: 0 !important;\n border-top-right-radius: 0 !important;\n}\n\n.rounded-top-1 {\n border-top-left-radius: var(--bs-border-radius-sm) !important;\n border-top-right-radius: var(--bs-border-radius-sm) !important;\n}\n\n.rounded-top-2 {\n border-top-left-radius: var(--bs-border-radius) !important;\n border-top-right-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-top-3 {\n border-top-left-radius: var(--bs-border-radius-lg) !important;\n border-top-right-radius: var(--bs-border-radius-lg) !important;\n}\n\n.rounded-top-4 {\n border-top-left-radius: var(--bs-border-radius-xl) !important;\n border-top-right-radius: var(--bs-border-radius-xl) !important;\n}\n\n.rounded-top-5 {\n border-top-left-radius: var(--bs-border-radius-xxl) !important;\n border-top-right-radius: var(--bs-border-radius-xxl) !important;\n}\n\n.rounded-top-circle {\n border-top-left-radius: 50% !important;\n border-top-right-radius: 50% !important;\n}\n\n.rounded-top-pill {\n border-top-left-radius: var(--bs-border-radius-pill) !important;\n border-top-right-radius: var(--bs-border-radius-pill) !important;\n}\n\n.rounded-end {\n border-top-right-radius: var(--bs-border-radius) !important;\n border-bottom-right-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-end-0 {\n border-top-right-radius: 0 !important;\n border-bottom-right-radius: 0 !important;\n}\n\n.rounded-end-1 {\n border-top-right-radius: var(--bs-border-radius-sm) !important;\n border-bottom-right-radius: var(--bs-border-radius-sm) !important;\n}\n\n.rounded-end-2 {\n border-top-right-radius: var(--bs-border-radius) !important;\n border-bottom-right-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-end-3 {\n border-top-right-radius: var(--bs-border-radius-lg) !important;\n border-bottom-right-radius: var(--bs-border-radius-lg) !important;\n}\n\n.rounded-end-4 {\n border-top-right-radius: var(--bs-border-radius-xl) !important;\n border-bottom-right-radius: var(--bs-border-radius-xl) !important;\n}\n\n.rounded-end-5 {\n border-top-right-radius: var(--bs-border-radius-xxl) !important;\n border-bottom-right-radius: var(--bs-border-radius-xxl) !important;\n}\n\n.rounded-end-circle {\n border-top-right-radius: 50% !important;\n border-bottom-right-radius: 50% !important;\n}\n\n.rounded-end-pill {\n border-top-right-radius: var(--bs-border-radius-pill) !important;\n border-bottom-right-radius: var(--bs-border-radius-pill) !important;\n}\n\n.rounded-bottom {\n border-bottom-right-radius: var(--bs-border-radius) !important;\n border-bottom-left-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-bottom-0 {\n border-bottom-right-radius: 0 !important;\n border-bottom-left-radius: 0 !important;\n}\n\n.rounded-bottom-1 {\n border-bottom-right-radius: var(--bs-border-radius-sm) !important;\n border-bottom-left-radius: var(--bs-border-radius-sm) !important;\n}\n\n.rounded-bottom-2 {\n border-bottom-right-radius: var(--bs-border-radius) !important;\n border-bottom-left-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-bottom-3 {\n border-bottom-right-radius: var(--bs-border-radius-lg) !important;\n border-bottom-left-radius: var(--bs-border-radius-lg) !important;\n}\n\n.rounded-bottom-4 {\n border-bottom-right-radius: var(--bs-border-radius-xl) !important;\n border-bottom-left-radius: var(--bs-border-radius-xl) !important;\n}\n\n.rounded-bottom-5 {\n border-bottom-right-radius: var(--bs-border-radius-xxl) !important;\n border-bottom-left-radius: var(--bs-border-radius-xxl) !important;\n}\n\n.rounded-bottom-circle {\n border-bottom-right-radius: 50% !important;\n border-bottom-left-radius: 50% !important;\n}\n\n.rounded-bottom-pill {\n border-bottom-right-radius: var(--bs-border-radius-pill) !important;\n border-bottom-left-radius: var(--bs-border-radius-pill) !important;\n}\n\n.rounded-start {\n border-bottom-left-radius: var(--bs-border-radius) !important;\n border-top-left-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-start-0 {\n border-bottom-left-radius: 0 !important;\n border-top-left-radius: 0 !important;\n}\n\n.rounded-start-1 {\n border-bottom-left-radius: var(--bs-border-radius-sm) !important;\n border-top-left-radius: var(--bs-border-radius-sm) !important;\n}\n\n.rounded-start-2 {\n border-bottom-left-radius: var(--bs-border-radius) !important;\n border-top-left-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-start-3 {\n border-bottom-left-radius: var(--bs-border-radius-lg) !important;\n border-top-left-radius: var(--bs-border-radius-lg) !important;\n}\n\n.rounded-start-4 {\n border-bottom-left-radius: var(--bs-border-radius-xl) !important;\n border-top-left-radius: var(--bs-border-radius-xl) !important;\n}\n\n.rounded-start-5 {\n border-bottom-left-radius: var(--bs-border-radius-xxl) !important;\n border-top-left-radius: var(--bs-border-radius-xxl) !important;\n}\n\n.rounded-start-circle {\n border-bottom-left-radius: 50% !important;\n border-top-left-radius: 50% !important;\n}\n\n.rounded-start-pill {\n border-bottom-left-radius: var(--bs-border-radius-pill) !important;\n border-top-left-radius: var(--bs-border-radius-pill) !important;\n}\n\n.visible {\n visibility: visible !important;\n}\n\n.invisible {\n visibility: hidden !important;\n}\n\n.z-n1 {\n z-index: -1 !important;\n}\n\n.z-0 {\n z-index: 0 !important;\n}\n\n.z-1 {\n z-index: 1 !important;\n}\n\n.z-2 {\n z-index: 2 !important;\n}\n\n.z-3 {\n z-index: 3 !important;\n}\n\n@media (min-width: 576px) {\n .float-sm-start {\n float: left !important;\n }\n .float-sm-end {\n float: right !important;\n }\n .float-sm-none {\n float: none !important;\n }\n .object-fit-sm-contain {\n -o-object-fit: contain !important;\n object-fit: contain !important;\n }\n .object-fit-sm-cover {\n -o-object-fit: cover !important;\n object-fit: cover !important;\n }\n .object-fit-sm-fill {\n -o-object-fit: fill !important;\n object-fit: fill !important;\n }\n .object-fit-sm-scale {\n -o-object-fit: scale-down !important;\n object-fit: scale-down !important;\n }\n .object-fit-sm-none {\n -o-object-fit: none !important;\n object-fit: none !important;\n }\n .d-sm-inline {\n display: inline !important;\n }\n .d-sm-inline-block {\n display: inline-block !important;\n }\n .d-sm-block {\n display: block !important;\n }\n .d-sm-grid {\n display: grid !important;\n }\n .d-sm-inline-grid {\n display: inline-grid !important;\n }\n .d-sm-table {\n display: table !important;\n }\n .d-sm-table-row {\n display: table-row !important;\n }\n .d-sm-table-cell {\n display: table-cell !important;\n }\n .d-sm-flex {\n display: flex !important;\n }\n .d-sm-inline-flex {\n display: inline-flex !important;\n }\n .d-sm-none {\n display: none !important;\n }\n .flex-sm-fill {\n flex: 1 1 auto !important;\n }\n .flex-sm-row {\n flex-direction: row !important;\n }\n .flex-sm-column {\n flex-direction: column !important;\n }\n .flex-sm-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-sm-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-sm-grow-0 {\n flex-grow: 0 !important;\n }\n .flex-sm-grow-1 {\n flex-grow: 1 !important;\n }\n .flex-sm-shrink-0 {\n flex-shrink: 0 !important;\n }\n .flex-sm-shrink-1 {\n flex-shrink: 1 !important;\n }\n .flex-sm-wrap {\n flex-wrap: wrap !important;\n }\n .flex-sm-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-sm-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .justify-content-sm-start {\n justify-content: flex-start !important;\n }\n .justify-content-sm-end {\n justify-content: flex-end !important;\n }\n .justify-content-sm-center {\n justify-content: center !important;\n }\n .justify-content-sm-between {\n justify-content: space-between !important;\n }\n .justify-content-sm-around {\n justify-content: space-around !important;\n }\n .justify-content-sm-evenly {\n justify-content: space-evenly !important;\n }\n .align-items-sm-start {\n align-items: flex-start !important;\n }\n .align-items-sm-end {\n align-items: flex-end !important;\n }\n .align-items-sm-center {\n align-items: center !important;\n }\n .align-items-sm-baseline {\n align-items: baseline !important;\n }\n .align-items-sm-stretch {\n align-items: stretch !important;\n }\n .align-content-sm-start {\n align-content: flex-start !important;\n }\n .align-content-sm-end {\n align-content: flex-end !important;\n }\n .align-content-sm-center {\n align-content: center !important;\n }\n .align-content-sm-between {\n align-content: space-between !important;\n }\n .align-content-sm-around {\n align-content: space-around !important;\n }\n .align-content-sm-stretch {\n align-content: stretch !important;\n }\n .align-self-sm-auto {\n align-self: auto !important;\n }\n .align-self-sm-start {\n align-self: flex-start !important;\n }\n .align-self-sm-end {\n align-self: flex-end !important;\n }\n .align-self-sm-center {\n align-self: center !important;\n }\n .align-self-sm-baseline {\n align-self: baseline !important;\n }\n .align-self-sm-stretch {\n align-self: stretch !important;\n }\n .order-sm-first {\n order: -1 !important;\n }\n .order-sm-0 {\n order: 0 !important;\n }\n .order-sm-1 {\n order: 1 !important;\n }\n .order-sm-2 {\n order: 2 !important;\n }\n .order-sm-3 {\n order: 3 !important;\n }\n .order-sm-4 {\n order: 4 !important;\n }\n .order-sm-5 {\n order: 5 !important;\n }\n .order-sm-last {\n order: 6 !important;\n }\n .m-sm-0 {\n margin: 0 !important;\n }\n .m-sm-1 {\n margin: 0.25rem !important;\n }\n .m-sm-2 {\n margin: 0.5rem !important;\n }\n .m-sm-3 {\n margin: 1rem !important;\n }\n .m-sm-4 {\n margin: 1.5rem !important;\n }\n .m-sm-5 {\n margin: 3rem !important;\n }\n .m-sm-auto {\n margin: auto !important;\n }\n .mx-sm-0 {\n margin-right: 0 !important;\n margin-left: 0 !important;\n }\n .mx-sm-1 {\n margin-right: 0.25rem !important;\n margin-left: 0.25rem !important;\n }\n .mx-sm-2 {\n margin-right: 0.5rem !important;\n margin-left: 0.5rem !important;\n }\n .mx-sm-3 {\n margin-right: 1rem !important;\n margin-left: 1rem !important;\n }\n .mx-sm-4 {\n margin-right: 1.5rem !important;\n margin-left: 1.5rem !important;\n }\n .mx-sm-5 {\n margin-right: 3rem !important;\n margin-left: 3rem !important;\n }\n .mx-sm-auto {\n margin-right: auto !important;\n margin-left: auto !important;\n }\n .my-sm-0 {\n margin-top: 0 !important;\n margin-bottom: 0 !important;\n }\n .my-sm-1 {\n margin-top: 0.25rem !important;\n margin-bottom: 0.25rem !important;\n }\n .my-sm-2 {\n margin-top: 0.5rem !important;\n margin-bottom: 0.5rem !important;\n }\n .my-sm-3 {\n margin-top: 1rem !important;\n margin-bottom: 1rem !important;\n }\n .my-sm-4 {\n margin-top: 1.5rem !important;\n margin-bottom: 1.5rem !important;\n }\n .my-sm-5 {\n margin-top: 3rem !important;\n margin-bottom: 3rem !important;\n }\n .my-sm-auto {\n margin-top: auto !important;\n margin-bottom: auto !important;\n }\n .mt-sm-0 {\n margin-top: 0 !important;\n }\n .mt-sm-1 {\n margin-top: 0.25rem !important;\n }\n .mt-sm-2 {\n margin-top: 0.5rem !important;\n }\n .mt-sm-3 {\n margin-top: 1rem !important;\n }\n .mt-sm-4 {\n margin-top: 1.5rem !important;\n }\n .mt-sm-5 {\n margin-top: 3rem !important;\n }\n .mt-sm-auto {\n margin-top: auto !important;\n }\n .me-sm-0 {\n margin-right: 0 !important;\n }\n .me-sm-1 {\n margin-right: 0.25rem !important;\n }\n .me-sm-2 {\n margin-right: 0.5rem !important;\n }\n .me-sm-3 {\n margin-right: 1rem !important;\n }\n .me-sm-4 {\n margin-right: 1.5rem !important;\n }\n .me-sm-5 {\n margin-right: 3rem !important;\n }\n .me-sm-auto {\n margin-right: auto !important;\n }\n .mb-sm-0 {\n margin-bottom: 0 !important;\n }\n .mb-sm-1 {\n margin-bottom: 0.25rem !important;\n }\n .mb-sm-2 {\n margin-bottom: 0.5rem !important;\n }\n .mb-sm-3 {\n margin-bottom: 1rem !important;\n }\n .mb-sm-4 {\n margin-bottom: 1.5rem !important;\n }\n .mb-sm-5 {\n margin-bottom: 3rem !important;\n }\n .mb-sm-auto {\n margin-bottom: auto !important;\n }\n .ms-sm-0 {\n margin-left: 0 !important;\n }\n .ms-sm-1 {\n margin-left: 0.25rem !important;\n }\n .ms-sm-2 {\n margin-left: 0.5rem !important;\n }\n .ms-sm-3 {\n margin-left: 1rem !important;\n }\n .ms-sm-4 {\n margin-left: 1.5rem !important;\n }\n .ms-sm-5 {\n margin-left: 3rem !important;\n }\n .ms-sm-auto {\n margin-left: auto !important;\n }\n .p-sm-0 {\n padding: 0 !important;\n }\n .p-sm-1 {\n padding: 0.25rem !important;\n }\n .p-sm-2 {\n padding: 0.5rem !important;\n }\n .p-sm-3 {\n padding: 1rem !important;\n }\n .p-sm-4 {\n padding: 1.5rem !important;\n }\n .p-sm-5 {\n padding: 3rem !important;\n }\n .px-sm-0 {\n padding-right: 0 !important;\n padding-left: 0 !important;\n }\n .px-sm-1 {\n padding-right: 0.25rem !important;\n padding-left: 0.25rem !important;\n }\n .px-sm-2 {\n padding-right: 0.5rem !important;\n padding-left: 0.5rem !important;\n }\n .px-sm-3 {\n padding-right: 1rem !important;\n padding-left: 1rem !important;\n }\n .px-sm-4 {\n padding-right: 1.5rem !important;\n padding-left: 1.5rem !important;\n }\n .px-sm-5 {\n padding-right: 3rem !important;\n padding-left: 3rem !important;\n }\n .py-sm-0 {\n padding-top: 0 !important;\n padding-bottom: 0 !important;\n }\n .py-sm-1 {\n padding-top: 0.25rem !important;\n padding-bottom: 0.25rem !important;\n }\n .py-sm-2 {\n padding-top: 0.5rem !important;\n padding-bottom: 0.5rem !important;\n }\n .py-sm-3 {\n padding-top: 1rem !important;\n padding-bottom: 1rem !important;\n }\n .py-sm-4 {\n padding-top: 1.5rem !important;\n padding-bottom: 1.5rem !important;\n }\n .py-sm-5 {\n padding-top: 3rem !important;\n padding-bottom: 3rem !important;\n }\n .pt-sm-0 {\n padding-top: 0 !important;\n }\n .pt-sm-1 {\n padding-top: 0.25rem !important;\n }\n .pt-sm-2 {\n padding-top: 0.5rem !important;\n }\n .pt-sm-3 {\n padding-top: 1rem !important;\n }\n .pt-sm-4 {\n padding-top: 1.5rem !important;\n }\n .pt-sm-5 {\n padding-top: 3rem !important;\n }\n .pe-sm-0 {\n padding-right: 0 !important;\n }\n .pe-sm-1 {\n padding-right: 0.25rem !important;\n }\n .pe-sm-2 {\n padding-right: 0.5rem !important;\n }\n .pe-sm-3 {\n padding-right: 1rem !important;\n }\n .pe-sm-4 {\n padding-right: 1.5rem !important;\n }\n .pe-sm-5 {\n padding-right: 3rem !important;\n }\n .pb-sm-0 {\n padding-bottom: 0 !important;\n }\n .pb-sm-1 {\n padding-bottom: 0.25rem !important;\n }\n .pb-sm-2 {\n padding-bottom: 0.5rem !important;\n }\n .pb-sm-3 {\n padding-bottom: 1rem !important;\n }\n .pb-sm-4 {\n padding-bottom: 1.5rem !important;\n }\n .pb-sm-5 {\n padding-bottom: 3rem !important;\n }\n .ps-sm-0 {\n padding-left: 0 !important;\n }\n .ps-sm-1 {\n padding-left: 0.25rem !important;\n }\n .ps-sm-2 {\n padding-left: 0.5rem !important;\n }\n .ps-sm-3 {\n padding-left: 1rem !important;\n }\n .ps-sm-4 {\n padding-left: 1.5rem !important;\n }\n .ps-sm-5 {\n padding-left: 3rem !important;\n }\n .gap-sm-0 {\n gap: 0 !important;\n }\n .gap-sm-1 {\n gap: 0.25rem !important;\n }\n .gap-sm-2 {\n gap: 0.5rem !important;\n }\n .gap-sm-3 {\n gap: 1rem !important;\n }\n .gap-sm-4 {\n gap: 1.5rem !important;\n }\n .gap-sm-5 {\n gap: 3rem !important;\n }\n .row-gap-sm-0 {\n row-gap: 0 !important;\n }\n .row-gap-sm-1 {\n row-gap: 0.25rem !important;\n }\n .row-gap-sm-2 {\n row-gap: 0.5rem !important;\n }\n .row-gap-sm-3 {\n row-gap: 1rem !important;\n }\n .row-gap-sm-4 {\n row-gap: 1.5rem !important;\n }\n .row-gap-sm-5 {\n row-gap: 3rem !important;\n }\n .column-gap-sm-0 {\n -moz-column-gap: 0 !important;\n column-gap: 0 !important;\n }\n .column-gap-sm-1 {\n -moz-column-gap: 0.25rem !important;\n column-gap: 0.25rem !important;\n }\n .column-gap-sm-2 {\n -moz-column-gap: 0.5rem !important;\n column-gap: 0.5rem !important;\n }\n .column-gap-sm-3 {\n -moz-column-gap: 1rem !important;\n column-gap: 1rem !important;\n }\n .column-gap-sm-4 {\n -moz-column-gap: 1.5rem !important;\n column-gap: 1.5rem !important;\n }\n .column-gap-sm-5 {\n -moz-column-gap: 3rem !important;\n column-gap: 3rem !important;\n }\n .text-sm-start {\n text-align: left !important;\n }\n .text-sm-end {\n text-align: right !important;\n }\n .text-sm-center {\n text-align: center !important;\n }\n}\n@media (min-width: 768px) {\n .float-md-start {\n float: left !important;\n }\n .float-md-end {\n float: right !important;\n }\n .float-md-none {\n float: none !important;\n }\n .object-fit-md-contain {\n -o-object-fit: contain !important;\n object-fit: contain !important;\n }\n .object-fit-md-cover {\n -o-object-fit: cover !important;\n object-fit: cover !important;\n }\n .object-fit-md-fill {\n -o-object-fit: fill !important;\n object-fit: fill !important;\n }\n .object-fit-md-scale {\n -o-object-fit: scale-down !important;\n object-fit: scale-down !important;\n }\n .object-fit-md-none {\n -o-object-fit: none !important;\n object-fit: none !important;\n }\n .d-md-inline {\n display: inline !important;\n }\n .d-md-inline-block {\n display: inline-block !important;\n }\n .d-md-block {\n display: block !important;\n }\n .d-md-grid {\n display: grid !important;\n }\n .d-md-inline-grid {\n display: inline-grid !important;\n }\n .d-md-table {\n display: table !important;\n }\n .d-md-table-row {\n display: table-row !important;\n }\n .d-md-table-cell {\n display: table-cell !important;\n }\n .d-md-flex {\n display: flex !important;\n }\n .d-md-inline-flex {\n display: inline-flex !important;\n }\n .d-md-none {\n display: none !important;\n }\n .flex-md-fill {\n flex: 1 1 auto !important;\n }\n .flex-md-row {\n flex-direction: row !important;\n }\n .flex-md-column {\n flex-direction: column !important;\n }\n .flex-md-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-md-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-md-grow-0 {\n flex-grow: 0 !important;\n }\n .flex-md-grow-1 {\n flex-grow: 1 !important;\n }\n .flex-md-shrink-0 {\n flex-shrink: 0 !important;\n }\n .flex-md-shrink-1 {\n flex-shrink: 1 !important;\n }\n .flex-md-wrap {\n flex-wrap: wrap !important;\n }\n .flex-md-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-md-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .justify-content-md-start {\n justify-content: flex-start !important;\n }\n .justify-content-md-end {\n justify-content: flex-end !important;\n }\n .justify-content-md-center {\n justify-content: center !important;\n }\n .justify-content-md-between {\n justify-content: space-between !important;\n }\n .justify-content-md-around {\n justify-content: space-around !important;\n }\n .justify-content-md-evenly {\n justify-content: space-evenly !important;\n }\n .align-items-md-start {\n align-items: flex-start !important;\n }\n .align-items-md-end {\n align-items: flex-end !important;\n }\n .align-items-md-center {\n align-items: center !important;\n }\n .align-items-md-baseline {\n align-items: baseline !important;\n }\n .align-items-md-stretch {\n align-items: stretch !important;\n }\n .align-content-md-start {\n align-content: flex-start !important;\n }\n .align-content-md-end {\n align-content: flex-end !important;\n }\n .align-content-md-center {\n align-content: center !important;\n }\n .align-content-md-between {\n align-content: space-between !important;\n }\n .align-content-md-around {\n align-content: space-around !important;\n }\n .align-content-md-stretch {\n align-content: stretch !important;\n }\n .align-self-md-auto {\n align-self: auto !important;\n }\n .align-self-md-start {\n align-self: flex-start !important;\n }\n .align-self-md-end {\n align-self: flex-end !important;\n }\n .align-self-md-center {\n align-self: center !important;\n }\n .align-self-md-baseline {\n align-self: baseline !important;\n }\n .align-self-md-stretch {\n align-self: stretch !important;\n }\n .order-md-first {\n order: -1 !important;\n }\n .order-md-0 {\n order: 0 !important;\n }\n .order-md-1 {\n order: 1 !important;\n }\n .order-md-2 {\n order: 2 !important;\n }\n .order-md-3 {\n order: 3 !important;\n }\n .order-md-4 {\n order: 4 !important;\n }\n .order-md-5 {\n order: 5 !important;\n }\n .order-md-last {\n order: 6 !important;\n }\n .m-md-0 {\n margin: 0 !important;\n }\n .m-md-1 {\n margin: 0.25rem !important;\n }\n .m-md-2 {\n margin: 0.5rem !important;\n }\n .m-md-3 {\n margin: 1rem !important;\n }\n .m-md-4 {\n margin: 1.5rem !important;\n }\n .m-md-5 {\n margin: 3rem !important;\n }\n .m-md-auto {\n margin: auto !important;\n }\n .mx-md-0 {\n margin-right: 0 !important;\n margin-left: 0 !important;\n }\n .mx-md-1 {\n margin-right: 0.25rem !important;\n margin-left: 0.25rem !important;\n }\n .mx-md-2 {\n margin-right: 0.5rem !important;\n margin-left: 0.5rem !important;\n }\n .mx-md-3 {\n margin-right: 1rem !important;\n margin-left: 1rem !important;\n }\n .mx-md-4 {\n margin-right: 1.5rem !important;\n margin-left: 1.5rem !important;\n }\n .mx-md-5 {\n margin-right: 3rem !important;\n margin-left: 3rem !important;\n }\n .mx-md-auto {\n margin-right: auto !important;\n margin-left: auto !important;\n }\n .my-md-0 {\n margin-top: 0 !important;\n margin-bottom: 0 !important;\n }\n .my-md-1 {\n margin-top: 0.25rem !important;\n margin-bottom: 0.25rem !important;\n }\n .my-md-2 {\n margin-top: 0.5rem !important;\n margin-bottom: 0.5rem !important;\n }\n .my-md-3 {\n margin-top: 1rem !important;\n margin-bottom: 1rem !important;\n }\n .my-md-4 {\n margin-top: 1.5rem !important;\n margin-bottom: 1.5rem !important;\n }\n .my-md-5 {\n margin-top: 3rem !important;\n margin-bottom: 3rem !important;\n }\n .my-md-auto {\n margin-top: auto !important;\n margin-bottom: auto !important;\n }\n .mt-md-0 {\n margin-top: 0 !important;\n }\n .mt-md-1 {\n margin-top: 0.25rem !important;\n }\n .mt-md-2 {\n margin-top: 0.5rem !important;\n }\n .mt-md-3 {\n margin-top: 1rem !important;\n }\n .mt-md-4 {\n margin-top: 1.5rem !important;\n }\n .mt-md-5 {\n margin-top: 3rem !important;\n }\n .mt-md-auto {\n margin-top: auto !important;\n }\n .me-md-0 {\n margin-right: 0 !important;\n }\n .me-md-1 {\n margin-right: 0.25rem !important;\n }\n .me-md-2 {\n margin-right: 0.5rem !important;\n }\n .me-md-3 {\n margin-right: 1rem !important;\n }\n .me-md-4 {\n margin-right: 1.5rem !important;\n }\n .me-md-5 {\n margin-right: 3rem !important;\n }\n .me-md-auto {\n margin-right: auto !important;\n }\n .mb-md-0 {\n margin-bottom: 0 !important;\n }\n .mb-md-1 {\n margin-bottom: 0.25rem !important;\n }\n .mb-md-2 {\n margin-bottom: 0.5rem !important;\n }\n .mb-md-3 {\n margin-bottom: 1rem !important;\n }\n .mb-md-4 {\n margin-bottom: 1.5rem !important;\n }\n .mb-md-5 {\n margin-bottom: 3rem !important;\n }\n .mb-md-auto {\n margin-bottom: auto !important;\n }\n .ms-md-0 {\n margin-left: 0 !important;\n }\n .ms-md-1 {\n margin-left: 0.25rem !important;\n }\n .ms-md-2 {\n margin-left: 0.5rem !important;\n }\n .ms-md-3 {\n margin-left: 1rem !important;\n }\n .ms-md-4 {\n margin-left: 1.5rem !important;\n }\n .ms-md-5 {\n margin-left: 3rem !important;\n }\n .ms-md-auto {\n margin-left: auto !important;\n }\n .p-md-0 {\n padding: 0 !important;\n }\n .p-md-1 {\n padding: 0.25rem !important;\n }\n .p-md-2 {\n padding: 0.5rem !important;\n }\n .p-md-3 {\n padding: 1rem !important;\n }\n .p-md-4 {\n padding: 1.5rem !important;\n }\n .p-md-5 {\n padding: 3rem !important;\n }\n .px-md-0 {\n padding-right: 0 !important;\n padding-left: 0 !important;\n }\n .px-md-1 {\n padding-right: 0.25rem !important;\n padding-left: 0.25rem !important;\n }\n .px-md-2 {\n padding-right: 0.5rem !important;\n padding-left: 0.5rem !important;\n }\n .px-md-3 {\n padding-right: 1rem !important;\n padding-left: 1rem !important;\n }\n .px-md-4 {\n padding-right: 1.5rem !important;\n padding-left: 1.5rem !important;\n }\n .px-md-5 {\n padding-right: 3rem !important;\n padding-left: 3rem !important;\n }\n .py-md-0 {\n padding-top: 0 !important;\n padding-bottom: 0 !important;\n }\n .py-md-1 {\n padding-top: 0.25rem !important;\n padding-bottom: 0.25rem !important;\n }\n .py-md-2 {\n padding-top: 0.5rem !important;\n padding-bottom: 0.5rem !important;\n }\n .py-md-3 {\n padding-top: 1rem !important;\n padding-bottom: 1rem !important;\n }\n .py-md-4 {\n padding-top: 1.5rem !important;\n padding-bottom: 1.5rem !important;\n }\n .py-md-5 {\n padding-top: 3rem !important;\n padding-bottom: 3rem !important;\n }\n .pt-md-0 {\n padding-top: 0 !important;\n }\n .pt-md-1 {\n padding-top: 0.25rem !important;\n }\n .pt-md-2 {\n padding-top: 0.5rem !important;\n }\n .pt-md-3 {\n padding-top: 1rem !important;\n }\n .pt-md-4 {\n padding-top: 1.5rem !important;\n }\n .pt-md-5 {\n padding-top: 3rem !important;\n }\n .pe-md-0 {\n padding-right: 0 !important;\n }\n .pe-md-1 {\n padding-right: 0.25rem !important;\n }\n .pe-md-2 {\n padding-right: 0.5rem !important;\n }\n .pe-md-3 {\n padding-right: 1rem !important;\n }\n .pe-md-4 {\n padding-right: 1.5rem !important;\n }\n .pe-md-5 {\n padding-right: 3rem !important;\n }\n .pb-md-0 {\n padding-bottom: 0 !important;\n }\n .pb-md-1 {\n padding-bottom: 0.25rem !important;\n }\n .pb-md-2 {\n padding-bottom: 0.5rem !important;\n }\n .pb-md-3 {\n padding-bottom: 1rem !important;\n }\n .pb-md-4 {\n padding-bottom: 1.5rem !important;\n }\n .pb-md-5 {\n padding-bottom: 3rem !important;\n }\n .ps-md-0 {\n padding-left: 0 !important;\n }\n .ps-md-1 {\n padding-left: 0.25rem !important;\n }\n .ps-md-2 {\n padding-left: 0.5rem !important;\n }\n .ps-md-3 {\n padding-left: 1rem !important;\n }\n .ps-md-4 {\n padding-left: 1.5rem !important;\n }\n .ps-md-5 {\n padding-left: 3rem !important;\n }\n .gap-md-0 {\n gap: 0 !important;\n }\n .gap-md-1 {\n gap: 0.25rem !important;\n }\n .gap-md-2 {\n gap: 0.5rem !important;\n }\n .gap-md-3 {\n gap: 1rem !important;\n }\n .gap-md-4 {\n gap: 1.5rem !important;\n }\n .gap-md-5 {\n gap: 3rem !important;\n }\n .row-gap-md-0 {\n row-gap: 0 !important;\n }\n .row-gap-md-1 {\n row-gap: 0.25rem !important;\n }\n .row-gap-md-2 {\n row-gap: 0.5rem !important;\n }\n .row-gap-md-3 {\n row-gap: 1rem !important;\n }\n .row-gap-md-4 {\n row-gap: 1.5rem !important;\n }\n .row-gap-md-5 {\n row-gap: 3rem !important;\n }\n .column-gap-md-0 {\n -moz-column-gap: 0 !important;\n column-gap: 0 !important;\n }\n .column-gap-md-1 {\n -moz-column-gap: 0.25rem !important;\n column-gap: 0.25rem !important;\n }\n .column-gap-md-2 {\n -moz-column-gap: 0.5rem !important;\n column-gap: 0.5rem !important;\n }\n .column-gap-md-3 {\n -moz-column-gap: 1rem !important;\n column-gap: 1rem !important;\n }\n .column-gap-md-4 {\n -moz-column-gap: 1.5rem !important;\n column-gap: 1.5rem !important;\n }\n .column-gap-md-5 {\n -moz-column-gap: 3rem !important;\n column-gap: 3rem !important;\n }\n .text-md-start {\n text-align: left !important;\n }\n .text-md-end {\n text-align: right !important;\n }\n .text-md-center {\n text-align: center !important;\n }\n}\n@media (min-width: 992px) {\n .float-lg-start {\n float: left !important;\n }\n .float-lg-end {\n float: right !important;\n }\n .float-lg-none {\n float: none !important;\n }\n .object-fit-lg-contain {\n -o-object-fit: contain !important;\n object-fit: contain !important;\n }\n .object-fit-lg-cover {\n -o-object-fit: cover !important;\n object-fit: cover !important;\n }\n .object-fit-lg-fill {\n -o-object-fit: fill !important;\n object-fit: fill !important;\n }\n .object-fit-lg-scale {\n -o-object-fit: scale-down !important;\n object-fit: scale-down !important;\n }\n .object-fit-lg-none {\n -o-object-fit: none !important;\n object-fit: none !important;\n }\n .d-lg-inline {\n display: inline !important;\n }\n .d-lg-inline-block {\n display: inline-block !important;\n }\n .d-lg-block {\n display: block !important;\n }\n .d-lg-grid {\n display: grid !important;\n }\n .d-lg-inline-grid {\n display: inline-grid !important;\n }\n .d-lg-table {\n display: table !important;\n }\n .d-lg-table-row {\n display: table-row !important;\n }\n .d-lg-table-cell {\n display: table-cell !important;\n }\n .d-lg-flex {\n display: flex !important;\n }\n .d-lg-inline-flex {\n display: inline-flex !important;\n }\n .d-lg-none {\n display: none !important;\n }\n .flex-lg-fill {\n flex: 1 1 auto !important;\n }\n .flex-lg-row {\n flex-direction: row !important;\n }\n .flex-lg-column {\n flex-direction: column !important;\n }\n .flex-lg-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-lg-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-lg-grow-0 {\n flex-grow: 0 !important;\n }\n .flex-lg-grow-1 {\n flex-grow: 1 !important;\n }\n .flex-lg-shrink-0 {\n flex-shrink: 0 !important;\n }\n .flex-lg-shrink-1 {\n flex-shrink: 1 !important;\n }\n .flex-lg-wrap {\n flex-wrap: wrap !important;\n }\n .flex-lg-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-lg-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .justify-content-lg-start {\n justify-content: flex-start !important;\n }\n .justify-content-lg-end {\n justify-content: flex-end !important;\n }\n .justify-content-lg-center {\n justify-content: center !important;\n }\n .justify-content-lg-between {\n justify-content: space-between !important;\n }\n .justify-content-lg-around {\n justify-content: space-around !important;\n }\n .justify-content-lg-evenly {\n justify-content: space-evenly !important;\n }\n .align-items-lg-start {\n align-items: flex-start !important;\n }\n .align-items-lg-end {\n align-items: flex-end !important;\n }\n .align-items-lg-center {\n align-items: center !important;\n }\n .align-items-lg-baseline {\n align-items: baseline !important;\n }\n .align-items-lg-stretch {\n align-items: stretch !important;\n }\n .align-content-lg-start {\n align-content: flex-start !important;\n }\n .align-content-lg-end {\n align-content: flex-end !important;\n }\n .align-content-lg-center {\n align-content: center !important;\n }\n .align-content-lg-between {\n align-content: space-between !important;\n }\n .align-content-lg-around {\n align-content: space-around !important;\n }\n .align-content-lg-stretch {\n align-content: stretch !important;\n }\n .align-self-lg-auto {\n align-self: auto !important;\n }\n .align-self-lg-start {\n align-self: flex-start !important;\n }\n .align-self-lg-end {\n align-self: flex-end !important;\n }\n .align-self-lg-center {\n align-self: center !important;\n }\n .align-self-lg-baseline {\n align-self: baseline !important;\n }\n .align-self-lg-stretch {\n align-self: stretch !important;\n }\n .order-lg-first {\n order: -1 !important;\n }\n .order-lg-0 {\n order: 0 !important;\n }\n .order-lg-1 {\n order: 1 !important;\n }\n .order-lg-2 {\n order: 2 !important;\n }\n .order-lg-3 {\n order: 3 !important;\n }\n .order-lg-4 {\n order: 4 !important;\n }\n .order-lg-5 {\n order: 5 !important;\n }\n .order-lg-last {\n order: 6 !important;\n }\n .m-lg-0 {\n margin: 0 !important;\n }\n .m-lg-1 {\n margin: 0.25rem !important;\n }\n .m-lg-2 {\n margin: 0.5rem !important;\n }\n .m-lg-3 {\n margin: 1rem !important;\n }\n .m-lg-4 {\n margin: 1.5rem !important;\n }\n .m-lg-5 {\n margin: 3rem !important;\n }\n .m-lg-auto {\n margin: auto !important;\n }\n .mx-lg-0 {\n margin-right: 0 !important;\n margin-left: 0 !important;\n }\n .mx-lg-1 {\n margin-right: 0.25rem !important;\n margin-left: 0.25rem !important;\n }\n .mx-lg-2 {\n margin-right: 0.5rem !important;\n margin-left: 0.5rem !important;\n }\n .mx-lg-3 {\n margin-right: 1rem !important;\n margin-left: 1rem !important;\n }\n .mx-lg-4 {\n margin-right: 1.5rem !important;\n margin-left: 1.5rem !important;\n }\n .mx-lg-5 {\n margin-right: 3rem !important;\n margin-left: 3rem !important;\n }\n .mx-lg-auto {\n margin-right: auto !important;\n margin-left: auto !important;\n }\n .my-lg-0 {\n margin-top: 0 !important;\n margin-bottom: 0 !important;\n }\n .my-lg-1 {\n margin-top: 0.25rem !important;\n margin-bottom: 0.25rem !important;\n }\n .my-lg-2 {\n margin-top: 0.5rem !important;\n margin-bottom: 0.5rem !important;\n }\n .my-lg-3 {\n margin-top: 1rem !important;\n margin-bottom: 1rem !important;\n }\n .my-lg-4 {\n margin-top: 1.5rem !important;\n margin-bottom: 1.5rem !important;\n }\n .my-lg-5 {\n margin-top: 3rem !important;\n margin-bottom: 3rem !important;\n }\n .my-lg-auto {\n margin-top: auto !important;\n margin-bottom: auto !important;\n }\n .mt-lg-0 {\n margin-top: 0 !important;\n }\n .mt-lg-1 {\n margin-top: 0.25rem !important;\n }\n .mt-lg-2 {\n margin-top: 0.5rem !important;\n }\n .mt-lg-3 {\n margin-top: 1rem !important;\n }\n .mt-lg-4 {\n margin-top: 1.5rem !important;\n }\n .mt-lg-5 {\n margin-top: 3rem !important;\n }\n .mt-lg-auto {\n margin-top: auto !important;\n }\n .me-lg-0 {\n margin-right: 0 !important;\n }\n .me-lg-1 {\n margin-right: 0.25rem !important;\n }\n .me-lg-2 {\n margin-right: 0.5rem !important;\n }\n .me-lg-3 {\n margin-right: 1rem !important;\n }\n .me-lg-4 {\n margin-right: 1.5rem !important;\n }\n .me-lg-5 {\n margin-right: 3rem !important;\n }\n .me-lg-auto {\n margin-right: auto !important;\n }\n .mb-lg-0 {\n margin-bottom: 0 !important;\n }\n .mb-lg-1 {\n margin-bottom: 0.25rem !important;\n }\n .mb-lg-2 {\n margin-bottom: 0.5rem !important;\n }\n .mb-lg-3 {\n margin-bottom: 1rem !important;\n }\n .mb-lg-4 {\n margin-bottom: 1.5rem !important;\n }\n .mb-lg-5 {\n margin-bottom: 3rem !important;\n }\n .mb-lg-auto {\n margin-bottom: auto !important;\n }\n .ms-lg-0 {\n margin-left: 0 !important;\n }\n .ms-lg-1 {\n margin-left: 0.25rem !important;\n }\n .ms-lg-2 {\n margin-left: 0.5rem !important;\n }\n .ms-lg-3 {\n margin-left: 1rem !important;\n }\n .ms-lg-4 {\n margin-left: 1.5rem !important;\n }\n .ms-lg-5 {\n margin-left: 3rem !important;\n }\n .ms-lg-auto {\n margin-left: auto !important;\n }\n .p-lg-0 {\n padding: 0 !important;\n }\n .p-lg-1 {\n padding: 0.25rem !important;\n }\n .p-lg-2 {\n padding: 0.5rem !important;\n }\n .p-lg-3 {\n padding: 1rem !important;\n }\n .p-lg-4 {\n padding: 1.5rem !important;\n }\n .p-lg-5 {\n padding: 3rem !important;\n }\n .px-lg-0 {\n padding-right: 0 !important;\n padding-left: 0 !important;\n }\n .px-lg-1 {\n padding-right: 0.25rem !important;\n padding-left: 0.25rem !important;\n }\n .px-lg-2 {\n padding-right: 0.5rem !important;\n padding-left: 0.5rem !important;\n }\n .px-lg-3 {\n padding-right: 1rem !important;\n padding-left: 1rem !important;\n }\n .px-lg-4 {\n padding-right: 1.5rem !important;\n padding-left: 1.5rem !important;\n }\n .px-lg-5 {\n padding-right: 3rem !important;\n padding-left: 3rem !important;\n }\n .py-lg-0 {\n padding-top: 0 !important;\n padding-bottom: 0 !important;\n }\n .py-lg-1 {\n padding-top: 0.25rem !important;\n padding-bottom: 0.25rem !important;\n }\n .py-lg-2 {\n padding-top: 0.5rem !important;\n padding-bottom: 0.5rem !important;\n }\n .py-lg-3 {\n padding-top: 1rem !important;\n padding-bottom: 1rem !important;\n }\n .py-lg-4 {\n padding-top: 1.5rem !important;\n padding-bottom: 1.5rem !important;\n }\n .py-lg-5 {\n padding-top: 3rem !important;\n padding-bottom: 3rem !important;\n }\n .pt-lg-0 {\n padding-top: 0 !important;\n }\n .pt-lg-1 {\n padding-top: 0.25rem !important;\n }\n .pt-lg-2 {\n padding-top: 0.5rem !important;\n }\n .pt-lg-3 {\n padding-top: 1rem !important;\n }\n .pt-lg-4 {\n padding-top: 1.5rem !important;\n }\n .pt-lg-5 {\n padding-top: 3rem !important;\n }\n .pe-lg-0 {\n padding-right: 0 !important;\n }\n .pe-lg-1 {\n padding-right: 0.25rem !important;\n }\n .pe-lg-2 {\n padding-right: 0.5rem !important;\n }\n .pe-lg-3 {\n padding-right: 1rem !important;\n }\n .pe-lg-4 {\n padding-right: 1.5rem !important;\n }\n .pe-lg-5 {\n padding-right: 3rem !important;\n }\n .pb-lg-0 {\n padding-bottom: 0 !important;\n }\n .pb-lg-1 {\n padding-bottom: 0.25rem !important;\n }\n .pb-lg-2 {\n padding-bottom: 0.5rem !important;\n }\n .pb-lg-3 {\n padding-bottom: 1rem !important;\n }\n .pb-lg-4 {\n padding-bottom: 1.5rem !important;\n }\n .pb-lg-5 {\n padding-bottom: 3rem !important;\n }\n .ps-lg-0 {\n padding-left: 0 !important;\n }\n .ps-lg-1 {\n padding-left: 0.25rem !important;\n }\n .ps-lg-2 {\n padding-left: 0.5rem !important;\n }\n .ps-lg-3 {\n padding-left: 1rem !important;\n }\n .ps-lg-4 {\n padding-left: 1.5rem !important;\n }\n .ps-lg-5 {\n padding-left: 3rem !important;\n }\n .gap-lg-0 {\n gap: 0 !important;\n }\n .gap-lg-1 {\n gap: 0.25rem !important;\n }\n .gap-lg-2 {\n gap: 0.5rem !important;\n }\n .gap-lg-3 {\n gap: 1rem !important;\n }\n .gap-lg-4 {\n gap: 1.5rem !important;\n }\n .gap-lg-5 {\n gap: 3rem !important;\n }\n .row-gap-lg-0 {\n row-gap: 0 !important;\n }\n .row-gap-lg-1 {\n row-gap: 0.25rem !important;\n }\n .row-gap-lg-2 {\n row-gap: 0.5rem !important;\n }\n .row-gap-lg-3 {\n row-gap: 1rem !important;\n }\n .row-gap-lg-4 {\n row-gap: 1.5rem !important;\n }\n .row-gap-lg-5 {\n row-gap: 3rem !important;\n }\n .column-gap-lg-0 {\n -moz-column-gap: 0 !important;\n column-gap: 0 !important;\n }\n .column-gap-lg-1 {\n -moz-column-gap: 0.25rem !important;\n column-gap: 0.25rem !important;\n }\n .column-gap-lg-2 {\n -moz-column-gap: 0.5rem !important;\n column-gap: 0.5rem !important;\n }\n .column-gap-lg-3 {\n -moz-column-gap: 1rem !important;\n column-gap: 1rem !important;\n }\n .column-gap-lg-4 {\n -moz-column-gap: 1.5rem !important;\n column-gap: 1.5rem !important;\n }\n .column-gap-lg-5 {\n -moz-column-gap: 3rem !important;\n column-gap: 3rem !important;\n }\n .text-lg-start {\n text-align: left !important;\n }\n .text-lg-end {\n text-align: right !important;\n }\n .text-lg-center {\n text-align: center !important;\n }\n}\n@media (min-width: 1200px) {\n .float-xl-start {\n float: left !important;\n }\n .float-xl-end {\n float: right !important;\n }\n .float-xl-none {\n float: none !important;\n }\n .object-fit-xl-contain {\n -o-object-fit: contain !important;\n object-fit: contain !important;\n }\n .object-fit-xl-cover {\n -o-object-fit: cover !important;\n object-fit: cover !important;\n }\n .object-fit-xl-fill {\n -o-object-fit: fill !important;\n object-fit: fill !important;\n }\n .object-fit-xl-scale {\n -o-object-fit: scale-down !important;\n object-fit: scale-down !important;\n }\n .object-fit-xl-none {\n -o-object-fit: none !important;\n object-fit: none !important;\n }\n .d-xl-inline {\n display: inline !important;\n }\n .d-xl-inline-block {\n display: inline-block !important;\n }\n .d-xl-block {\n display: block !important;\n }\n .d-xl-grid {\n display: grid !important;\n }\n .d-xl-inline-grid {\n display: inline-grid !important;\n }\n .d-xl-table {\n display: table !important;\n }\n .d-xl-table-row {\n display: table-row !important;\n }\n .d-xl-table-cell {\n display: table-cell !important;\n }\n .d-xl-flex {\n display: flex !important;\n }\n .d-xl-inline-flex {\n display: inline-flex !important;\n }\n .d-xl-none {\n display: none !important;\n }\n .flex-xl-fill {\n flex: 1 1 auto !important;\n }\n .flex-xl-row {\n flex-direction: row !important;\n }\n .flex-xl-column {\n flex-direction: column !important;\n }\n .flex-xl-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-xl-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-xl-grow-0 {\n flex-grow: 0 !important;\n }\n .flex-xl-grow-1 {\n flex-grow: 1 !important;\n }\n .flex-xl-shrink-0 {\n flex-shrink: 0 !important;\n }\n .flex-xl-shrink-1 {\n flex-shrink: 1 !important;\n }\n .flex-xl-wrap {\n flex-wrap: wrap !important;\n }\n .flex-xl-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-xl-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .justify-content-xl-start {\n justify-content: flex-start !important;\n }\n .justify-content-xl-end {\n justify-content: flex-end !important;\n }\n .justify-content-xl-center {\n justify-content: center !important;\n }\n .justify-content-xl-between {\n justify-content: space-between !important;\n }\n .justify-content-xl-around {\n justify-content: space-around !important;\n }\n .justify-content-xl-evenly {\n justify-content: space-evenly !important;\n }\n .align-items-xl-start {\n align-items: flex-start !important;\n }\n .align-items-xl-end {\n align-items: flex-end !important;\n }\n .align-items-xl-center {\n align-items: center !important;\n }\n .align-items-xl-baseline {\n align-items: baseline !important;\n }\n .align-items-xl-stretch {\n align-items: stretch !important;\n }\n .align-content-xl-start {\n align-content: flex-start !important;\n }\n .align-content-xl-end {\n align-content: flex-end !important;\n }\n .align-content-xl-center {\n align-content: center !important;\n }\n .align-content-xl-between {\n align-content: space-between !important;\n }\n .align-content-xl-around {\n align-content: space-around !important;\n }\n .align-content-xl-stretch {\n align-content: stretch !important;\n }\n .align-self-xl-auto {\n align-self: auto !important;\n }\n .align-self-xl-start {\n align-self: flex-start !important;\n }\n .align-self-xl-end {\n align-self: flex-end !important;\n }\n .align-self-xl-center {\n align-self: center !important;\n }\n .align-self-xl-baseline {\n align-self: baseline !important;\n }\n .align-self-xl-stretch {\n align-self: stretch !important;\n }\n .order-xl-first {\n order: -1 !important;\n }\n .order-xl-0 {\n order: 0 !important;\n }\n .order-xl-1 {\n order: 1 !important;\n }\n .order-xl-2 {\n order: 2 !important;\n }\n .order-xl-3 {\n order: 3 !important;\n }\n .order-xl-4 {\n order: 4 !important;\n }\n .order-xl-5 {\n order: 5 !important;\n }\n .order-xl-last {\n order: 6 !important;\n }\n .m-xl-0 {\n margin: 0 !important;\n }\n .m-xl-1 {\n margin: 0.25rem !important;\n }\n .m-xl-2 {\n margin: 0.5rem !important;\n }\n .m-xl-3 {\n margin: 1rem !important;\n }\n .m-xl-4 {\n margin: 1.5rem !important;\n }\n .m-xl-5 {\n margin: 3rem !important;\n }\n .m-xl-auto {\n margin: auto !important;\n }\n .mx-xl-0 {\n margin-right: 0 !important;\n margin-left: 0 !important;\n }\n .mx-xl-1 {\n margin-right: 0.25rem !important;\n margin-left: 0.25rem !important;\n }\n .mx-xl-2 {\n margin-right: 0.5rem !important;\n margin-left: 0.5rem !important;\n }\n .mx-xl-3 {\n margin-right: 1rem !important;\n margin-left: 1rem !important;\n }\n .mx-xl-4 {\n margin-right: 1.5rem !important;\n margin-left: 1.5rem !important;\n }\n .mx-xl-5 {\n margin-right: 3rem !important;\n margin-left: 3rem !important;\n }\n .mx-xl-auto {\n margin-right: auto !important;\n margin-left: auto !important;\n }\n .my-xl-0 {\n margin-top: 0 !important;\n margin-bottom: 0 !important;\n }\n .my-xl-1 {\n margin-top: 0.25rem !important;\n margin-bottom: 0.25rem !important;\n }\n .my-xl-2 {\n margin-top: 0.5rem !important;\n margin-bottom: 0.5rem !important;\n }\n .my-xl-3 {\n margin-top: 1rem !important;\n margin-bottom: 1rem !important;\n }\n .my-xl-4 {\n margin-top: 1.5rem !important;\n margin-bottom: 1.5rem !important;\n }\n .my-xl-5 {\n margin-top: 3rem !important;\n margin-bottom: 3rem !important;\n }\n .my-xl-auto {\n margin-top: auto !important;\n margin-bottom: auto !important;\n }\n .mt-xl-0 {\n margin-top: 0 !important;\n }\n .mt-xl-1 {\n margin-top: 0.25rem !important;\n }\n .mt-xl-2 {\n margin-top: 0.5rem !important;\n }\n .mt-xl-3 {\n margin-top: 1rem !important;\n }\n .mt-xl-4 {\n margin-top: 1.5rem !important;\n }\n .mt-xl-5 {\n margin-top: 3rem !important;\n }\n .mt-xl-auto {\n margin-top: auto !important;\n }\n .me-xl-0 {\n margin-right: 0 !important;\n }\n .me-xl-1 {\n margin-right: 0.25rem !important;\n }\n .me-xl-2 {\n margin-right: 0.5rem !important;\n }\n .me-xl-3 {\n margin-right: 1rem !important;\n }\n .me-xl-4 {\n margin-right: 1.5rem !important;\n }\n .me-xl-5 {\n margin-right: 3rem !important;\n }\n .me-xl-auto {\n margin-right: auto !important;\n }\n .mb-xl-0 {\n margin-bottom: 0 !important;\n }\n .mb-xl-1 {\n margin-bottom: 0.25rem !important;\n }\n .mb-xl-2 {\n margin-bottom: 0.5rem !important;\n }\n .mb-xl-3 {\n margin-bottom: 1rem !important;\n }\n .mb-xl-4 {\n margin-bottom: 1.5rem !important;\n }\n .mb-xl-5 {\n margin-bottom: 3rem !important;\n }\n .mb-xl-auto {\n margin-bottom: auto !important;\n }\n .ms-xl-0 {\n margin-left: 0 !important;\n }\n .ms-xl-1 {\n margin-left: 0.25rem !important;\n }\n .ms-xl-2 {\n margin-left: 0.5rem !important;\n }\n .ms-xl-3 {\n margin-left: 1rem !important;\n }\n .ms-xl-4 {\n margin-left: 1.5rem !important;\n }\n .ms-xl-5 {\n margin-left: 3rem !important;\n }\n .ms-xl-auto {\n margin-left: auto !important;\n }\n .p-xl-0 {\n padding: 0 !important;\n }\n .p-xl-1 {\n padding: 0.25rem !important;\n }\n .p-xl-2 {\n padding: 0.5rem !important;\n }\n .p-xl-3 {\n padding: 1rem !important;\n }\n .p-xl-4 {\n padding: 1.5rem !important;\n }\n .p-xl-5 {\n padding: 3rem !important;\n }\n .px-xl-0 {\n padding-right: 0 !important;\n padding-left: 0 !important;\n }\n .px-xl-1 {\n padding-right: 0.25rem !important;\n padding-left: 0.25rem !important;\n }\n .px-xl-2 {\n padding-right: 0.5rem !important;\n padding-left: 0.5rem !important;\n }\n .px-xl-3 {\n padding-right: 1rem !important;\n padding-left: 1rem !important;\n }\n .px-xl-4 {\n padding-right: 1.5rem !important;\n padding-left: 1.5rem !important;\n }\n .px-xl-5 {\n padding-right: 3rem !important;\n padding-left: 3rem !important;\n }\n .py-xl-0 {\n padding-top: 0 !important;\n padding-bottom: 0 !important;\n }\n .py-xl-1 {\n padding-top: 0.25rem !important;\n padding-bottom: 0.25rem !important;\n }\n .py-xl-2 {\n padding-top: 0.5rem !important;\n padding-bottom: 0.5rem !important;\n }\n .py-xl-3 {\n padding-top: 1rem !important;\n padding-bottom: 1rem !important;\n }\n .py-xl-4 {\n padding-top: 1.5rem !important;\n padding-bottom: 1.5rem !important;\n }\n .py-xl-5 {\n padding-top: 3rem !important;\n padding-bottom: 3rem !important;\n }\n .pt-xl-0 {\n padding-top: 0 !important;\n }\n .pt-xl-1 {\n padding-top: 0.25rem !important;\n }\n .pt-xl-2 {\n padding-top: 0.5rem !important;\n }\n .pt-xl-3 {\n padding-top: 1rem !important;\n }\n .pt-xl-4 {\n padding-top: 1.5rem !important;\n }\n .pt-xl-5 {\n padding-top: 3rem !important;\n }\n .pe-xl-0 {\n padding-right: 0 !important;\n }\n .pe-xl-1 {\n padding-right: 0.25rem !important;\n }\n .pe-xl-2 {\n padding-right: 0.5rem !important;\n }\n .pe-xl-3 {\n padding-right: 1rem !important;\n }\n .pe-xl-4 {\n padding-right: 1.5rem !important;\n }\n .pe-xl-5 {\n padding-right: 3rem !important;\n }\n .pb-xl-0 {\n padding-bottom: 0 !important;\n }\n .pb-xl-1 {\n padding-bottom: 0.25rem !important;\n }\n .pb-xl-2 {\n padding-bottom: 0.5rem !important;\n }\n .pb-xl-3 {\n padding-bottom: 1rem !important;\n }\n .pb-xl-4 {\n padding-bottom: 1.5rem !important;\n }\n .pb-xl-5 {\n padding-bottom: 3rem !important;\n }\n .ps-xl-0 {\n padding-left: 0 !important;\n }\n .ps-xl-1 {\n padding-left: 0.25rem !important;\n }\n .ps-xl-2 {\n padding-left: 0.5rem !important;\n }\n .ps-xl-3 {\n padding-left: 1rem !important;\n }\n .ps-xl-4 {\n padding-left: 1.5rem !important;\n }\n .ps-xl-5 {\n padding-left: 3rem !important;\n }\n .gap-xl-0 {\n gap: 0 !important;\n }\n .gap-xl-1 {\n gap: 0.25rem !important;\n }\n .gap-xl-2 {\n gap: 0.5rem !important;\n }\n .gap-xl-3 {\n gap: 1rem !important;\n }\n .gap-xl-4 {\n gap: 1.5rem !important;\n }\n .gap-xl-5 {\n gap: 3rem !important;\n }\n .row-gap-xl-0 {\n row-gap: 0 !important;\n }\n .row-gap-xl-1 {\n row-gap: 0.25rem !important;\n }\n .row-gap-xl-2 {\n row-gap: 0.5rem !important;\n }\n .row-gap-xl-3 {\n row-gap: 1rem !important;\n }\n .row-gap-xl-4 {\n row-gap: 1.5rem !important;\n }\n .row-gap-xl-5 {\n row-gap: 3rem !important;\n }\n .column-gap-xl-0 {\n -moz-column-gap: 0 !important;\n column-gap: 0 !important;\n }\n .column-gap-xl-1 {\n -moz-column-gap: 0.25rem !important;\n column-gap: 0.25rem !important;\n }\n .column-gap-xl-2 {\n -moz-column-gap: 0.5rem !important;\n column-gap: 0.5rem !important;\n }\n .column-gap-xl-3 {\n -moz-column-gap: 1rem !important;\n column-gap: 1rem !important;\n }\n .column-gap-xl-4 {\n -moz-column-gap: 1.5rem !important;\n column-gap: 1.5rem !important;\n }\n .column-gap-xl-5 {\n -moz-column-gap: 3rem !important;\n column-gap: 3rem !important;\n }\n .text-xl-start {\n text-align: left !important;\n }\n .text-xl-end {\n text-align: right !important;\n }\n .text-xl-center {\n text-align: center !important;\n }\n}\n@media (min-width: 1400px) {\n .float-xxl-start {\n float: left !important;\n }\n .float-xxl-end {\n float: right !important;\n }\n .float-xxl-none {\n float: none !important;\n }\n .object-fit-xxl-contain {\n -o-object-fit: contain !important;\n object-fit: contain !important;\n }\n .object-fit-xxl-cover {\n -o-object-fit: cover !important;\n object-fit: cover !important;\n }\n .object-fit-xxl-fill {\n -o-object-fit: fill !important;\n object-fit: fill !important;\n }\n .object-fit-xxl-scale {\n -o-object-fit: scale-down !important;\n object-fit: scale-down !important;\n }\n .object-fit-xxl-none {\n -o-object-fit: none !important;\n object-fit: none !important;\n }\n .d-xxl-inline {\n display: inline !important;\n }\n .d-xxl-inline-block {\n display: inline-block !important;\n }\n .d-xxl-block {\n display: block !important;\n }\n .d-xxl-grid {\n display: grid !important;\n }\n .d-xxl-inline-grid {\n display: inline-grid !important;\n }\n .d-xxl-table {\n display: table !important;\n }\n .d-xxl-table-row {\n display: table-row !important;\n }\n .d-xxl-table-cell {\n display: table-cell !important;\n }\n .d-xxl-flex {\n display: flex !important;\n }\n .d-xxl-inline-flex {\n display: inline-flex !important;\n }\n .d-xxl-none {\n display: none !important;\n }\n .flex-xxl-fill {\n flex: 1 1 auto !important;\n }\n .flex-xxl-row {\n flex-direction: row !important;\n }\n .flex-xxl-column {\n flex-direction: column !important;\n }\n .flex-xxl-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-xxl-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-xxl-grow-0 {\n flex-grow: 0 !important;\n }\n .flex-xxl-grow-1 {\n flex-grow: 1 !important;\n }\n .flex-xxl-shrink-0 {\n flex-shrink: 0 !important;\n }\n .flex-xxl-shrink-1 {\n flex-shrink: 1 !important;\n }\n .flex-xxl-wrap {\n flex-wrap: wrap !important;\n }\n .flex-xxl-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-xxl-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .justify-content-xxl-start {\n justify-content: flex-start !important;\n }\n .justify-content-xxl-end {\n justify-content: flex-end !important;\n }\n .justify-content-xxl-center {\n justify-content: center !important;\n }\n .justify-content-xxl-between {\n justify-content: space-between !important;\n }\n .justify-content-xxl-around {\n justify-content: space-around !important;\n }\n .justify-content-xxl-evenly {\n justify-content: space-evenly !important;\n }\n .align-items-xxl-start {\n align-items: flex-start !important;\n }\n .align-items-xxl-end {\n align-items: flex-end !important;\n }\n .align-items-xxl-center {\n align-items: center !important;\n }\n .align-items-xxl-baseline {\n align-items: baseline !important;\n }\n .align-items-xxl-stretch {\n align-items: stretch !important;\n }\n .align-content-xxl-start {\n align-content: flex-start !important;\n }\n .align-content-xxl-end {\n align-content: flex-end !important;\n }\n .align-content-xxl-center {\n align-content: center !important;\n }\n .align-content-xxl-between {\n align-content: space-between !important;\n }\n .align-content-xxl-around {\n align-content: space-around !important;\n }\n .align-content-xxl-stretch {\n align-content: stretch !important;\n }\n .align-self-xxl-auto {\n align-self: auto !important;\n }\n .align-self-xxl-start {\n align-self: flex-start !important;\n }\n .align-self-xxl-end {\n align-self: flex-end !important;\n }\n .align-self-xxl-center {\n align-self: center !important;\n }\n .align-self-xxl-baseline {\n align-self: baseline !important;\n }\n .align-self-xxl-stretch {\n align-self: stretch !important;\n }\n .order-xxl-first {\n order: -1 !important;\n }\n .order-xxl-0 {\n order: 0 !important;\n }\n .order-xxl-1 {\n order: 1 !important;\n }\n .order-xxl-2 {\n order: 2 !important;\n }\n .order-xxl-3 {\n order: 3 !important;\n }\n .order-xxl-4 {\n order: 4 !important;\n }\n .order-xxl-5 {\n order: 5 !important;\n }\n .order-xxl-last {\n order: 6 !important;\n }\n .m-xxl-0 {\n margin: 0 !important;\n }\n .m-xxl-1 {\n margin: 0.25rem !important;\n }\n .m-xxl-2 {\n margin: 0.5rem !important;\n }\n .m-xxl-3 {\n margin: 1rem !important;\n }\n .m-xxl-4 {\n margin: 1.5rem !important;\n }\n .m-xxl-5 {\n margin: 3rem !important;\n }\n .m-xxl-auto {\n margin: auto !important;\n }\n .mx-xxl-0 {\n margin-right: 0 !important;\n margin-left: 0 !important;\n }\n .mx-xxl-1 {\n margin-right: 0.25rem !important;\n margin-left: 0.25rem !important;\n }\n .mx-xxl-2 {\n margin-right: 0.5rem !important;\n margin-left: 0.5rem !important;\n }\n .mx-xxl-3 {\n margin-right: 1rem !important;\n margin-left: 1rem !important;\n }\n .mx-xxl-4 {\n margin-right: 1.5rem !important;\n margin-left: 1.5rem !important;\n }\n .mx-xxl-5 {\n margin-right: 3rem !important;\n margin-left: 3rem !important;\n }\n .mx-xxl-auto {\n margin-right: auto !important;\n margin-left: auto !important;\n }\n .my-xxl-0 {\n margin-top: 0 !important;\n margin-bottom: 0 !important;\n }\n .my-xxl-1 {\n margin-top: 0.25rem !important;\n margin-bottom: 0.25rem !important;\n }\n .my-xxl-2 {\n margin-top: 0.5rem !important;\n margin-bottom: 0.5rem !important;\n }\n .my-xxl-3 {\n margin-top: 1rem !important;\n margin-bottom: 1rem !important;\n }\n .my-xxl-4 {\n margin-top: 1.5rem !important;\n margin-bottom: 1.5rem !important;\n }\n .my-xxl-5 {\n margin-top: 3rem !important;\n margin-bottom: 3rem !important;\n }\n .my-xxl-auto {\n margin-top: auto !important;\n margin-bottom: auto !important;\n }\n .mt-xxl-0 {\n margin-top: 0 !important;\n }\n .mt-xxl-1 {\n margin-top: 0.25rem !important;\n }\n .mt-xxl-2 {\n margin-top: 0.5rem !important;\n }\n .mt-xxl-3 {\n margin-top: 1rem !important;\n }\n .mt-xxl-4 {\n margin-top: 1.5rem !important;\n }\n .mt-xxl-5 {\n margin-top: 3rem !important;\n }\n .mt-xxl-auto {\n margin-top: auto !important;\n }\n .me-xxl-0 {\n margin-right: 0 !important;\n }\n .me-xxl-1 {\n margin-right: 0.25rem !important;\n }\n .me-xxl-2 {\n margin-right: 0.5rem !important;\n }\n .me-xxl-3 {\n margin-right: 1rem !important;\n }\n .me-xxl-4 {\n margin-right: 1.5rem !important;\n }\n .me-xxl-5 {\n margin-right: 3rem !important;\n }\n .me-xxl-auto {\n margin-right: auto !important;\n }\n .mb-xxl-0 {\n margin-bottom: 0 !important;\n }\n .mb-xxl-1 {\n margin-bottom: 0.25rem !important;\n }\n .mb-xxl-2 {\n margin-bottom: 0.5rem !important;\n }\n .mb-xxl-3 {\n margin-bottom: 1rem !important;\n }\n .mb-xxl-4 {\n margin-bottom: 1.5rem !important;\n }\n .mb-xxl-5 {\n margin-bottom: 3rem !important;\n }\n .mb-xxl-auto {\n margin-bottom: auto !important;\n }\n .ms-xxl-0 {\n margin-left: 0 !important;\n }\n .ms-xxl-1 {\n margin-left: 0.25rem !important;\n }\n .ms-xxl-2 {\n margin-left: 0.5rem !important;\n }\n .ms-xxl-3 {\n margin-left: 1rem !important;\n }\n .ms-xxl-4 {\n margin-left: 1.5rem !important;\n }\n .ms-xxl-5 {\n margin-left: 3rem !important;\n }\n .ms-xxl-auto {\n margin-left: auto !important;\n }\n .p-xxl-0 {\n padding: 0 !important;\n }\n .p-xxl-1 {\n padding: 0.25rem !important;\n }\n .p-xxl-2 {\n padding: 0.5rem !important;\n }\n .p-xxl-3 {\n padding: 1rem !important;\n }\n .p-xxl-4 {\n padding: 1.5rem !important;\n }\n .p-xxl-5 {\n padding: 3rem !important;\n }\n .px-xxl-0 {\n padding-right: 0 !important;\n padding-left: 0 !important;\n }\n .px-xxl-1 {\n padding-right: 0.25rem !important;\n padding-left: 0.25rem !important;\n }\n .px-xxl-2 {\n padding-right: 0.5rem !important;\n padding-left: 0.5rem !important;\n }\n .px-xxl-3 {\n padding-right: 1rem !important;\n padding-left: 1rem !important;\n }\n .px-xxl-4 {\n padding-right: 1.5rem !important;\n padding-left: 1.5rem !important;\n }\n .px-xxl-5 {\n padding-right: 3rem !important;\n padding-left: 3rem !important;\n }\n .py-xxl-0 {\n padding-top: 0 !important;\n padding-bottom: 0 !important;\n }\n .py-xxl-1 {\n padding-top: 0.25rem !important;\n padding-bottom: 0.25rem !important;\n }\n .py-xxl-2 {\n padding-top: 0.5rem !important;\n padding-bottom: 0.5rem !important;\n }\n .py-xxl-3 {\n padding-top: 1rem !important;\n padding-bottom: 1rem !important;\n }\n .py-xxl-4 {\n padding-top: 1.5rem !important;\n padding-bottom: 1.5rem !important;\n }\n .py-xxl-5 {\n padding-top: 3rem !important;\n padding-bottom: 3rem !important;\n }\n .pt-xxl-0 {\n padding-top: 0 !important;\n }\n .pt-xxl-1 {\n padding-top: 0.25rem !important;\n }\n .pt-xxl-2 {\n padding-top: 0.5rem !important;\n }\n .pt-xxl-3 {\n padding-top: 1rem !important;\n }\n .pt-xxl-4 {\n padding-top: 1.5rem !important;\n }\n .pt-xxl-5 {\n padding-top: 3rem !important;\n }\n .pe-xxl-0 {\n padding-right: 0 !important;\n }\n .pe-xxl-1 {\n padding-right: 0.25rem !important;\n }\n .pe-xxl-2 {\n padding-right: 0.5rem !important;\n }\n .pe-xxl-3 {\n padding-right: 1rem !important;\n }\n .pe-xxl-4 {\n padding-right: 1.5rem !important;\n }\n .pe-xxl-5 {\n padding-right: 3rem !important;\n }\n .pb-xxl-0 {\n padding-bottom: 0 !important;\n }\n .pb-xxl-1 {\n padding-bottom: 0.25rem !important;\n }\n .pb-xxl-2 {\n padding-bottom: 0.5rem !important;\n }\n .pb-xxl-3 {\n padding-bottom: 1rem !important;\n }\n .pb-xxl-4 {\n padding-bottom: 1.5rem !important;\n }\n .pb-xxl-5 {\n padding-bottom: 3rem !important;\n }\n .ps-xxl-0 {\n padding-left: 0 !important;\n }\n .ps-xxl-1 {\n padding-left: 0.25rem !important;\n }\n .ps-xxl-2 {\n padding-left: 0.5rem !important;\n }\n .ps-xxl-3 {\n padding-left: 1rem !important;\n }\n .ps-xxl-4 {\n padding-left: 1.5rem !important;\n }\n .ps-xxl-5 {\n padding-left: 3rem !important;\n }\n .gap-xxl-0 {\n gap: 0 !important;\n }\n .gap-xxl-1 {\n gap: 0.25rem !important;\n }\n .gap-xxl-2 {\n gap: 0.5rem !important;\n }\n .gap-xxl-3 {\n gap: 1rem !important;\n }\n .gap-xxl-4 {\n gap: 1.5rem !important;\n }\n .gap-xxl-5 {\n gap: 3rem !important;\n }\n .row-gap-xxl-0 {\n row-gap: 0 !important;\n }\n .row-gap-xxl-1 {\n row-gap: 0.25rem !important;\n }\n .row-gap-xxl-2 {\n row-gap: 0.5rem !important;\n }\n .row-gap-xxl-3 {\n row-gap: 1rem !important;\n }\n .row-gap-xxl-4 {\n row-gap: 1.5rem !important;\n }\n .row-gap-xxl-5 {\n row-gap: 3rem !important;\n }\n .column-gap-xxl-0 {\n -moz-column-gap: 0 !important;\n column-gap: 0 !important;\n }\n .column-gap-xxl-1 {\n -moz-column-gap: 0.25rem !important;\n column-gap: 0.25rem !important;\n }\n .column-gap-xxl-2 {\n -moz-column-gap: 0.5rem !important;\n column-gap: 0.5rem !important;\n }\n .column-gap-xxl-3 {\n -moz-column-gap: 1rem !important;\n column-gap: 1rem !important;\n }\n .column-gap-xxl-4 {\n -moz-column-gap: 1.5rem !important;\n column-gap: 1.5rem !important;\n }\n .column-gap-xxl-5 {\n -moz-column-gap: 3rem !important;\n column-gap: 3rem !important;\n }\n .text-xxl-start {\n text-align: left !important;\n }\n .text-xxl-end {\n text-align: right !important;\n }\n .text-xxl-center {\n text-align: center !important;\n }\n}\n@media (min-width: 1200px) {\n .fs-1 {\n font-size: 2.5rem !important;\n }\n .fs-2 {\n font-size: 2rem !important;\n }\n .fs-3 {\n font-size: 1.75rem !important;\n }\n .fs-4 {\n font-size: 1.5rem !important;\n }\n}\n@media print {\n .d-print-inline {\n display: inline !important;\n }\n .d-print-inline-block {\n display: inline-block !important;\n }\n .d-print-block {\n display: block !important;\n }\n .d-print-grid {\n display: grid !important;\n }\n .d-print-inline-grid {\n display: inline-grid !important;\n }\n .d-print-table {\n display: table !important;\n }\n .d-print-table-row {\n display: table-row !important;\n }\n .d-print-table-cell {\n display: table-cell !important;\n }\n .d-print-flex {\n display: flex !important;\n }\n .d-print-inline-flex {\n display: inline-flex !important;\n }\n .d-print-none {\n display: none !important;\n }\n}\n\n/*# sourceMappingURL=bootstrap.css.map */","// stylelint-disable declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix\n\n\n// Reboot\n//\n// Normalization of HTML elements, manually forked from Normalize.css to remove\n// styles targeting irrelevant browsers while applying new styles.\n//\n// Normalize is licensed MIT. https://github.com/necolas/normalize.css\n\n\n// Document\n//\n// Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`.\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n\n// Root\n//\n// Ability to the value of the root font sizes, affecting the value of `rem`.\n// null by default, thus nothing is generated.\n\n:root {\n @if $font-size-root != null {\n @include font-size(var(--#{$prefix}root-font-size));\n }\n\n @if $enable-smooth-scroll {\n @media (prefers-reduced-motion: no-preference) {\n scroll-behavior: smooth;\n }\n }\n}\n\n\n// Body\n//\n// 1. Remove the margin in all browsers.\n// 2. As a best practice, apply a default `background-color`.\n// 3. Prevent adjustments of font size after orientation changes in iOS.\n// 4. Change the default tap highlight to be completely transparent in iOS.\n\n// scss-docs-start reboot-body-rules\nbody {\n margin: 0; // 1\n font-family: var(--#{$prefix}body-font-family);\n @include font-size(var(--#{$prefix}body-font-size));\n font-weight: var(--#{$prefix}body-font-weight);\n line-height: var(--#{$prefix}body-line-height);\n color: var(--#{$prefix}body-color);\n text-align: var(--#{$prefix}body-text-align);\n background-color: var(--#{$prefix}body-bg); // 2\n -webkit-text-size-adjust: 100%; // 3\n -webkit-tap-highlight-color: rgba($black, 0); // 4\n}\n// scss-docs-end reboot-body-rules\n\n\n// Content grouping\n//\n// 1. Reset Firefox's gray color\n\nhr {\n margin: $hr-margin-y 0;\n color: $hr-color; // 1\n border: 0;\n border-top: $hr-border-width solid $hr-border-color;\n opacity: $hr-opacity;\n}\n\n\n// Typography\n//\n// 1. Remove top margins from headings\n// By default, `

`-`

` all receive top and bottom margins. We nuke the top\n// margin for easier control within type scales as it avoids margin collapsing.\n\n%heading {\n margin-top: 0; // 1\n margin-bottom: $headings-margin-bottom;\n font-family: $headings-font-family;\n font-style: $headings-font-style;\n font-weight: $headings-font-weight;\n line-height: $headings-line-height;\n color: var(--#{$prefix}heading-color, inherit);\n}\n\nh1 {\n @extend %heading;\n @include font-size($h1-font-size);\n}\n\nh2 {\n @extend %heading;\n @include font-size($h2-font-size);\n}\n\nh3 {\n @extend %heading;\n @include font-size($h3-font-size);\n}\n\nh4 {\n @extend %heading;\n @include font-size($h4-font-size);\n}\n\nh5 {\n @extend %heading;\n @include font-size($h5-font-size);\n}\n\nh6 {\n @extend %heading;\n @include font-size($h6-font-size);\n}\n\n\n// Reset margins on paragraphs\n//\n// Similarly, the top margin on `

`s get reset. However, we also reset the\n// bottom margin to use `rem` units instead of `em`.\n\np {\n margin-top: 0;\n margin-bottom: $paragraph-margin-bottom;\n}\n\n\n// Abbreviations\n//\n// 1. Add the correct text decoration in Chrome, Edge, Opera, and Safari.\n// 2. Add explicit cursor to indicate changed behavior.\n// 3. Prevent the text-decoration to be skipped.\n\nabbr[title] {\n text-decoration: underline dotted; // 1\n cursor: help; // 2\n text-decoration-skip-ink: none; // 3\n}\n\n\n// Address\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\n\n// Lists\n\nol,\nul {\n padding-left: 2rem;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: $dt-font-weight;\n}\n\n// 1. Undo browser default\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0; // 1\n}\n\n\n// Blockquote\n\nblockquote {\n margin: 0 0 1rem;\n}\n\n\n// Strong\n//\n// Add the correct font weight in Chrome, Edge, and Safari\n\nb,\nstrong {\n font-weight: $font-weight-bolder;\n}\n\n\n// Small\n//\n// Add the correct font size in all browsers\n\nsmall {\n @include font-size($small-font-size);\n}\n\n\n// Mark\n\nmark {\n padding: $mark-padding;\n background-color: var(--#{$prefix}highlight-bg);\n}\n\n\n// Sub and Sup\n//\n// Prevent `sub` and `sup` elements from affecting the line height in\n// all browsers.\n\nsub,\nsup {\n position: relative;\n @include font-size($sub-sup-font-size);\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub { bottom: -.25em; }\nsup { top: -.5em; }\n\n\n// Links\n\na {\n color: rgba(var(--#{$prefix}link-color-rgb), var(--#{$prefix}link-opacity, 1));\n text-decoration: $link-decoration;\n\n &:hover {\n --#{$prefix}link-color-rgb: var(--#{$prefix}link-hover-color-rgb);\n text-decoration: $link-hover-decoration;\n }\n}\n\n// And undo these styles for placeholder links/named anchors (without href).\n// It would be more straightforward to just use a[href] in previous block, but that\n// causes specificity issues in many other styles that are too complex to fix.\n// See https://github.com/twbs/bootstrap/issues/19402\n\na:not([href]):not([class]) {\n &,\n &:hover {\n color: inherit;\n text-decoration: none;\n }\n}\n\n\n// Code\n\npre,\ncode,\nkbd,\nsamp {\n font-family: $font-family-code;\n @include font-size(1em); // Correct the odd `em` font sizing in all browsers.\n}\n\n// 1. Remove browser default top margin\n// 2. Reset browser default of `1em` to use `rem`s\n// 3. Don't allow content to break outside\n\npre {\n display: block;\n margin-top: 0; // 1\n margin-bottom: 1rem; // 2\n overflow: auto; // 3\n @include font-size($code-font-size);\n color: $pre-color;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n @include font-size(inherit);\n color: inherit;\n word-break: normal;\n }\n}\n\ncode {\n @include font-size($code-font-size);\n color: var(--#{$prefix}code-color);\n word-wrap: break-word;\n\n // Streamline the style when inside anchors to avoid broken underline and more\n a > & {\n color: inherit;\n }\n}\n\nkbd {\n padding: $kbd-padding-y $kbd-padding-x;\n @include font-size($kbd-font-size);\n color: $kbd-color;\n background-color: $kbd-bg;\n @include border-radius($border-radius-sm);\n\n kbd {\n padding: 0;\n @include font-size(1em);\n font-weight: $nested-kbd-font-weight;\n }\n}\n\n\n// Figures\n//\n// Apply a consistent margin strategy (matches our type styles).\n\nfigure {\n margin: 0 0 1rem;\n}\n\n\n// Images and content\n\nimg,\nsvg {\n vertical-align: middle;\n}\n\n\n// Tables\n//\n// Prevent double borders\n\ntable {\n caption-side: bottom;\n border-collapse: collapse;\n}\n\ncaption {\n padding-top: $table-cell-padding-y;\n padding-bottom: $table-cell-padding-y;\n color: $table-caption-color;\n text-align: left;\n}\n\n// 1. Removes font-weight bold by inheriting\n// 2. Matches default `` alignment by inheriting `text-align`.\n// 3. Fix alignment for Safari\n\nth {\n font-weight: $table-th-font-weight; // 1\n text-align: inherit; // 2\n text-align: -webkit-match-parent; // 3\n}\n\nthead,\ntbody,\ntfoot,\ntr,\ntd,\nth {\n border-color: inherit;\n border-style: solid;\n border-width: 0;\n}\n\n\n// Forms\n//\n// 1. Allow labels to use `margin` for spacing.\n\nlabel {\n display: inline-block; // 1\n}\n\n// Remove the default `border-radius` that macOS Chrome adds.\n// See https://github.com/twbs/bootstrap/issues/24093\n\nbutton {\n // stylelint-disable-next-line property-disallowed-list\n border-radius: 0;\n}\n\n// Explicitly remove focus outline in Chromium when it shouldn't be\n// visible (e.g. as result of mouse click or touch tap). It already\n// should be doing this automatically, but seems to currently be\n// confused and applies its very visible two-tone outline anyway.\n\nbutton:focus:not(:focus-visible) {\n outline: 0;\n}\n\n// 1. Remove the margin in Firefox and Safari\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0; // 1\n font-family: inherit;\n @include font-size(inherit);\n line-height: inherit;\n}\n\n// Remove the inheritance of text transform in Firefox\nbutton,\nselect {\n text-transform: none;\n}\n// Set the cursor for non-`\n * \n * );\n * }\n * ```\n *\n * When the button is clicked the component will shift to the `'entering'` state\n * and stay there for 500ms (the value of `timeout`) before it finally switches\n * to `'entered'`.\n *\n * When `in` is `false` the same thing happens except the state moves from\n * `'exiting'` to `'exited'`.\n */\n\nvar Transition = /*#__PURE__*/function (_React$Component) {\n _inheritsLoose(Transition, _React$Component);\n\n function Transition(props, context) {\n var _this;\n\n _this = _React$Component.call(this, props, context) || this;\n var parentGroup = context; // In the context of a TransitionGroup all enters are really appears\n\n var appear = parentGroup && !parentGroup.isMounting ? props.enter : props.appear;\n var initialStatus;\n _this.appearStatus = null;\n\n if (props.in) {\n if (appear) {\n initialStatus = EXITED;\n _this.appearStatus = ENTERING;\n } else {\n initialStatus = ENTERED;\n }\n } else {\n if (props.unmountOnExit || props.mountOnEnter) {\n initialStatus = UNMOUNTED;\n } else {\n initialStatus = EXITED;\n }\n }\n\n _this.state = {\n status: initialStatus\n };\n _this.nextCallback = null;\n return _this;\n }\n\n Transition.getDerivedStateFromProps = function getDerivedStateFromProps(_ref, prevState) {\n var nextIn = _ref.in;\n\n if (nextIn && prevState.status === UNMOUNTED) {\n return {\n status: EXITED\n };\n }\n\n return null;\n } // getSnapshotBeforeUpdate(prevProps) {\n // let nextStatus = null\n // if (prevProps !== this.props) {\n // const { status } = this.state\n // if (this.props.in) {\n // if (status !== ENTERING && status !== ENTERED) {\n // nextStatus = ENTERING\n // }\n // } else {\n // if (status === ENTERING || status === ENTERED) {\n // nextStatus = EXITING\n // }\n // }\n // }\n // return { nextStatus }\n // }\n ;\n\n var _proto = Transition.prototype;\n\n _proto.componentDidMount = function componentDidMount() {\n this.updateStatus(true, this.appearStatus);\n };\n\n _proto.componentDidUpdate = function componentDidUpdate(prevProps) {\n var nextStatus = null;\n\n if (prevProps !== this.props) {\n var status = this.state.status;\n\n if (this.props.in) {\n if (status !== ENTERING && status !== ENTERED) {\n nextStatus = ENTERING;\n }\n } else {\n if (status === ENTERING || status === ENTERED) {\n nextStatus = EXITING;\n }\n }\n }\n\n this.updateStatus(false, nextStatus);\n };\n\n _proto.componentWillUnmount = function componentWillUnmount() {\n this.cancelNextCallback();\n };\n\n _proto.getTimeouts = function getTimeouts() {\n var timeout = this.props.timeout;\n var exit, enter, appear;\n exit = enter = appear = timeout;\n\n if (timeout != null && typeof timeout !== 'number') {\n exit = timeout.exit;\n enter = timeout.enter; // TODO: remove fallback for next major\n\n appear = timeout.appear !== undefined ? timeout.appear : enter;\n }\n\n return {\n exit: exit,\n enter: enter,\n appear: appear\n };\n };\n\n _proto.updateStatus = function updateStatus(mounting, nextStatus) {\n if (mounting === void 0) {\n mounting = false;\n }\n\n if (nextStatus !== null) {\n // nextStatus will always be ENTERING or EXITING.\n this.cancelNextCallback();\n\n if (nextStatus === ENTERING) {\n if (this.props.unmountOnExit || this.props.mountOnEnter) {\n var node = this.props.nodeRef ? this.props.nodeRef.current : ReactDOM.findDOMNode(this); // https://github.com/reactjs/react-transition-group/pull/749\n // With unmountOnExit or mountOnEnter, the enter animation should happen at the transition between `exited` and `entering`.\n // To make the animation happen, we have to separate each rendering and avoid being processed as batched.\n\n if (node) forceReflow(node);\n }\n\n this.performEnter(mounting);\n } else {\n this.performExit();\n }\n } else if (this.props.unmountOnExit && this.state.status === EXITED) {\n this.setState({\n status: UNMOUNTED\n });\n }\n };\n\n _proto.performEnter = function performEnter(mounting) {\n var _this2 = this;\n\n var enter = this.props.enter;\n var appearing = this.context ? this.context.isMounting : mounting;\n\n var _ref2 = this.props.nodeRef ? [appearing] : [ReactDOM.findDOMNode(this), appearing],\n maybeNode = _ref2[0],\n maybeAppearing = _ref2[1];\n\n var timeouts = this.getTimeouts();\n var enterTimeout = appearing ? timeouts.appear : timeouts.enter; // no enter animation skip right to ENTERED\n // if we are mounting and running this it means appear _must_ be set\n\n if (!mounting && !enter || config.disabled) {\n this.safeSetState({\n status: ENTERED\n }, function () {\n _this2.props.onEntered(maybeNode);\n });\n return;\n }\n\n this.props.onEnter(maybeNode, maybeAppearing);\n this.safeSetState({\n status: ENTERING\n }, function () {\n _this2.props.onEntering(maybeNode, maybeAppearing);\n\n _this2.onTransitionEnd(enterTimeout, function () {\n _this2.safeSetState({\n status: ENTERED\n }, function () {\n _this2.props.onEntered(maybeNode, maybeAppearing);\n });\n });\n });\n };\n\n _proto.performExit = function performExit() {\n var _this3 = this;\n\n var exit = this.props.exit;\n var timeouts = this.getTimeouts();\n var maybeNode = this.props.nodeRef ? undefined : ReactDOM.findDOMNode(this); // no exit animation skip right to EXITED\n\n if (!exit || config.disabled) {\n this.safeSetState({\n status: EXITED\n }, function () {\n _this3.props.onExited(maybeNode);\n });\n return;\n }\n\n this.props.onExit(maybeNode);\n this.safeSetState({\n status: EXITING\n }, function () {\n _this3.props.onExiting(maybeNode);\n\n _this3.onTransitionEnd(timeouts.exit, function () {\n _this3.safeSetState({\n status: EXITED\n }, function () {\n _this3.props.onExited(maybeNode);\n });\n });\n });\n };\n\n _proto.cancelNextCallback = function cancelNextCallback() {\n if (this.nextCallback !== null) {\n this.nextCallback.cancel();\n this.nextCallback = null;\n }\n };\n\n _proto.safeSetState = function safeSetState(nextState, callback) {\n // This shouldn't be necessary, but there are weird race conditions with\n // setState callbacks and unmounting in testing, so always make sure that\n // we can cancel any pending setState callbacks after we unmount.\n callback = this.setNextCallback(callback);\n this.setState(nextState, callback);\n };\n\n _proto.setNextCallback = function setNextCallback(callback) {\n var _this4 = this;\n\n var active = true;\n\n this.nextCallback = function (event) {\n if (active) {\n active = false;\n _this4.nextCallback = null;\n callback(event);\n }\n };\n\n this.nextCallback.cancel = function () {\n active = false;\n };\n\n return this.nextCallback;\n };\n\n _proto.onTransitionEnd = function onTransitionEnd(timeout, handler) {\n this.setNextCallback(handler);\n var node = this.props.nodeRef ? this.props.nodeRef.current : ReactDOM.findDOMNode(this);\n var doesNotHaveTimeoutOrListener = timeout == null && !this.props.addEndListener;\n\n if (!node || doesNotHaveTimeoutOrListener) {\n setTimeout(this.nextCallback, 0);\n return;\n }\n\n if (this.props.addEndListener) {\n var _ref3 = this.props.nodeRef ? [this.nextCallback] : [node, this.nextCallback],\n maybeNode = _ref3[0],\n maybeNextCallback = _ref3[1];\n\n this.props.addEndListener(maybeNode, maybeNextCallback);\n }\n\n if (timeout != null) {\n setTimeout(this.nextCallback, timeout);\n }\n };\n\n _proto.render = function render() {\n var status = this.state.status;\n\n if (status === UNMOUNTED) {\n return null;\n }\n\n var _this$props = this.props,\n children = _this$props.children,\n _in = _this$props.in,\n _mountOnEnter = _this$props.mountOnEnter,\n _unmountOnExit = _this$props.unmountOnExit,\n _appear = _this$props.appear,\n _enter = _this$props.enter,\n _exit = _this$props.exit,\n _timeout = _this$props.timeout,\n _addEndListener = _this$props.addEndListener,\n _onEnter = _this$props.onEnter,\n _onEntering = _this$props.onEntering,\n _onEntered = _this$props.onEntered,\n _onExit = _this$props.onExit,\n _onExiting = _this$props.onExiting,\n _onExited = _this$props.onExited,\n _nodeRef = _this$props.nodeRef,\n childProps = _objectWithoutPropertiesLoose(_this$props, [\"children\", \"in\", \"mountOnEnter\", \"unmountOnExit\", \"appear\", \"enter\", \"exit\", \"timeout\", \"addEndListener\", \"onEnter\", \"onEntering\", \"onEntered\", \"onExit\", \"onExiting\", \"onExited\", \"nodeRef\"]);\n\n return (\n /*#__PURE__*/\n // allows for nested Transitions\n React.createElement(TransitionGroupContext.Provider, {\n value: null\n }, typeof children === 'function' ? children(status, childProps) : React.cloneElement(React.Children.only(children), childProps))\n );\n };\n\n return Transition;\n}(React.Component);\n\nTransition.contextType = TransitionGroupContext;\nTransition.propTypes = process.env.NODE_ENV !== \"production\" ? {\n /**\n * A React reference to DOM element that need to transition:\n * https://stackoverflow.com/a/51127130/4671932\n *\n * - When `nodeRef` prop is used, `node` is not passed to callback functions\n * (e.g. `onEnter`) because user already has direct access to the node.\n * - When changing `key` prop of `Transition` in a `TransitionGroup` a new\n * `nodeRef` need to be provided to `Transition` with changed `key` prop\n * (see\n * [test/CSSTransition-test.js](https://github.com/reactjs/react-transition-group/blob/13435f897b3ab71f6e19d724f145596f5910581c/test/CSSTransition-test.js#L362-L437)).\n */\n nodeRef: PropTypes.shape({\n current: typeof Element === 'undefined' ? PropTypes.any : function (propValue, key, componentName, location, propFullName, secret) {\n var value = propValue[key];\n return PropTypes.instanceOf(value && 'ownerDocument' in value ? value.ownerDocument.defaultView.Element : Element)(propValue, key, componentName, location, propFullName, secret);\n }\n }),\n\n /**\n * A `function` child can be used instead of a React element. This function is\n * called with the current transition status (`'entering'`, `'entered'`,\n * `'exiting'`, `'exited'`), which can be used to apply context\n * specific props to a component.\n *\n * ```jsx\n * \n * {state => (\n * \n * )}\n * \n * ```\n */\n children: PropTypes.oneOfType([PropTypes.func.isRequired, PropTypes.element.isRequired]).isRequired,\n\n /**\n * Show the component; triggers the enter or exit states\n */\n in: PropTypes.bool,\n\n /**\n * By default the child component is mounted immediately along with\n * the parent `Transition` component. If you want to \"lazy mount\" the component on the\n * first `in={true}` you can set `mountOnEnter`. After the first enter transition the component will stay\n * mounted, even on \"exited\", unless you also specify `unmountOnExit`.\n */\n mountOnEnter: PropTypes.bool,\n\n /**\n * By default the child component stays mounted after it reaches the `'exited'` state.\n * Set `unmountOnExit` if you'd prefer to unmount the component after it finishes exiting.\n */\n unmountOnExit: PropTypes.bool,\n\n /**\n * By default the child component does not perform the enter transition when\n * it first mounts, regardless of the value of `in`. If you want this\n * behavior, set both `appear` and `in` to `true`.\n *\n * > **Note**: there are no special appear states like `appearing`/`appeared`, this prop\n * > only adds an additional enter transition. However, in the\n * > `` component that first enter transition does result in\n * > additional `.appear-*` classes, that way you can choose to style it\n * > differently.\n */\n appear: PropTypes.bool,\n\n /**\n * Enable or disable enter transitions.\n */\n enter: PropTypes.bool,\n\n /**\n * Enable or disable exit transitions.\n */\n exit: PropTypes.bool,\n\n /**\n * The duration of the transition, in milliseconds.\n * Required unless `addEndListener` is provided.\n *\n * You may specify a single timeout for all transitions:\n *\n * ```jsx\n * timeout={500}\n * ```\n *\n * or individually:\n *\n * ```jsx\n * timeout={{\n * appear: 500,\n * enter: 300,\n * exit: 500,\n * }}\n * ```\n *\n * - `appear` defaults to the value of `enter`\n * - `enter` defaults to `0`\n * - `exit` defaults to `0`\n *\n * @type {number | { enter?: number, exit?: number, appear?: number }}\n */\n timeout: function timeout(props) {\n var pt = timeoutsShape;\n if (!props.addEndListener) pt = pt.isRequired;\n\n for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n args[_key - 1] = arguments[_key];\n }\n\n return pt.apply(void 0, [props].concat(args));\n },\n\n /**\n * Add a custom transition end trigger. Called with the transitioning\n * DOM node and a `done` callback. Allows for more fine grained transition end\n * logic. Timeouts are still used as a fallback if provided.\n *\n * **Note**: when `nodeRef` prop is passed, `node` is not passed.\n *\n * ```jsx\n * addEndListener={(node, done) => {\n * // use the css transitionend event to mark the finish of a transition\n * node.addEventListener('transitionend', done, false);\n * }}\n * ```\n */\n addEndListener: PropTypes.func,\n\n /**\n * Callback fired before the \"entering\" status is applied. An extra parameter\n * `isAppearing` is supplied to indicate if the enter stage is occurring on the initial mount\n *\n * **Note**: when `nodeRef` prop is passed, `node` is not passed.\n *\n * @type Function(node: HtmlElement, isAppearing: bool) -> void\n */\n onEnter: PropTypes.func,\n\n /**\n * Callback fired after the \"entering\" status is applied. An extra parameter\n * `isAppearing` is supplied to indicate if the enter stage is occurring on the initial mount\n *\n * **Note**: when `nodeRef` prop is passed, `node` is not passed.\n *\n * @type Function(node: HtmlElement, isAppearing: bool)\n */\n onEntering: PropTypes.func,\n\n /**\n * Callback fired after the \"entered\" status is applied. An extra parameter\n * `isAppearing` is supplied to indicate if the enter stage is occurring on the initial mount\n *\n * **Note**: when `nodeRef` prop is passed, `node` is not passed.\n *\n * @type Function(node: HtmlElement, isAppearing: bool) -> void\n */\n onEntered: PropTypes.func,\n\n /**\n * Callback fired before the \"exiting\" status is applied.\n *\n * **Note**: when `nodeRef` prop is passed, `node` is not passed.\n *\n * @type Function(node: HtmlElement) -> void\n */\n onExit: PropTypes.func,\n\n /**\n * Callback fired after the \"exiting\" status is applied.\n *\n * **Note**: when `nodeRef` prop is passed, `node` is not passed.\n *\n * @type Function(node: HtmlElement) -> void\n */\n onExiting: PropTypes.func,\n\n /**\n * Callback fired after the \"exited\" status is applied.\n *\n * **Note**: when `nodeRef` prop is passed, `node` is not passed\n *\n * @type Function(node: HtmlElement) -> void\n */\n onExited: PropTypes.func\n} : {}; // Name the function so it is clearer in the documentation\n\nfunction noop() {}\n\nTransition.defaultProps = {\n in: false,\n mountOnEnter: false,\n unmountOnExit: false,\n appear: false,\n enter: true,\n exit: true,\n onEnter: noop,\n onEntering: noop,\n onEntered: noop,\n onExit: noop,\n onExiting: noop,\n onExited: noop\n};\nTransition.UNMOUNTED = UNMOUNTED;\nTransition.EXITED = EXITED;\nTransition.ENTERING = ENTERING;\nTransition.ENTERED = ENTERED;\nTransition.EXITING = EXITING;\nexport default Transition;","import setPrototypeOf from \"./setPrototypeOf.js\";\nexport default function _inheritsLoose(subClass, superClass) {\n subClass.prototype = Object.create(superClass.prototype);\n subClass.prototype.constructor = subClass;\n setPrototypeOf(subClass, superClass);\n}","export var forceReflow = function forceReflow(node) {\n return node.scrollTop;\n};","import ownerWindow from './ownerWindow';\n/**\n * Returns one or all computed style properties of an element.\n * \n * @param node the element\n * @param psuedoElement the style property\n */\n\nexport default function getComputedStyle(node, psuedoElement) {\n return ownerWindow(node).getComputedStyle(node, psuedoElement);\n}","import ownerDocument from './ownerDocument';\n/**\n * Returns the owner window of a given element.\n * \n * @param node the element\n */\n\nexport default function ownerWindow(node) {\n var doc = ownerDocument(node);\n return doc && doc.defaultView || window;\n}","var rUpper = /([A-Z])/g;\nexport default function hyphenate(string) {\n return string.replace(rUpper, '-$1').toLowerCase();\n}","/**\n * Copyright 2013-2014, Facebook, Inc.\n * All rights reserved.\n * https://github.com/facebook/react/blob/2aeb8a2a6beb00617a4217f7f8284924fa2ad819/src/vendor/core/hyphenateStyleName.js\n */\nimport hyphenate from './hyphenate';\nvar msPattern = /^ms-/;\nexport default function hyphenateStyleName(string) {\n return hyphenate(string).replace(msPattern, '-ms-');\n}","var supportedTransforms = /^((translate|rotate|scale)(X|Y|Z|3d)?|matrix(3d)?|perspective|skew(X|Y)?)$/i;\nexport default function isTransform(value) {\n return !!(value && supportedTransforms.test(value));\n}","import getComputedStyle from './getComputedStyle';\nimport hyphenate from './hyphenateStyle';\nimport isTransform from './isTransform';\n\nfunction style(node, property) {\n var css = '';\n var transforms = '';\n\n if (typeof property === 'string') {\n return node.style.getPropertyValue(hyphenate(property)) || getComputedStyle(node).getPropertyValue(hyphenate(property));\n }\n\n Object.keys(property).forEach(function (key) {\n var value = property[key];\n\n if (!value && value !== 0) {\n node.style.removeProperty(hyphenate(key));\n } else if (isTransform(key)) {\n transforms += key + \"(\" + value + \") \";\n } else {\n css += hyphenate(key) + \": \" + value + \";\";\n }\n });\n\n if (transforms) {\n css += \"transform: \" + transforms + \";\";\n }\n\n node.style.cssText += \";\" + css;\n}\n\nexport default style;","import css from './css';\nimport listen from './listen';\nimport triggerEvent from './triggerEvent';\n\nfunction parseDuration(node) {\n var str = css(node, 'transitionDuration') || '';\n var mult = str.indexOf('ms') === -1 ? 1000 : 1;\n return parseFloat(str) * mult;\n}\n\nfunction emulateTransitionEnd(element, duration, padding) {\n if (padding === void 0) {\n padding = 5;\n }\n\n var called = false;\n var handle = setTimeout(function () {\n if (!called) triggerEvent(element, 'transitionend', true);\n }, duration + padding);\n var remove = listen(element, 'transitionend', function () {\n called = true;\n }, {\n once: true\n });\n return function () {\n clearTimeout(handle);\n remove();\n };\n}\n\nexport default function transitionEnd(element, handler, duration, padding) {\n if (duration == null) duration = parseDuration(element) || 0;\n var removeEmulate = emulateTransitionEnd(element, duration, padding);\n var remove = listen(element, 'transitionend', handler);\n return function () {\n removeEmulate();\n remove();\n };\n}","/**\n * Triggers an event on a given element.\n * \n * @param node the element\n * @param eventName the event name to trigger\n * @param bubbles whether the event should bubble up\n * @param cancelable whether the event should be cancelable\n */\nexport default function triggerEvent(node, eventName, bubbles, cancelable) {\n if (bubbles === void 0) {\n bubbles = false;\n }\n\n if (cancelable === void 0) {\n cancelable = true;\n }\n\n if (node) {\n var event = document.createEvent('HTMLEvents');\n event.initEvent(eventName, bubbles, cancelable);\n node.dispatchEvent(event);\n }\n}","import css from 'dom-helpers/css';\nimport transitionEnd from 'dom-helpers/transitionEnd';\nfunction parseDuration(node, property) {\n const str = css(node, property) || '';\n const mult = str.indexOf('ms') === -1 ? 1000 : 1;\n return parseFloat(str) * mult;\n}\nexport default function transitionEndListener(element, handler) {\n const duration = parseDuration(element, 'transitionDuration');\n const delay = parseDuration(element, 'transitionDelay');\n const remove = transitionEnd(element, e => {\n if (e.target === element) {\n remove();\n handler(e);\n }\n }, duration + delay);\n}","import ReactDOM from 'react-dom';\nexport default function safeFindDOMNode(componentOrElement) {\n if (componentOrElement && 'setState' in componentOrElement) {\n return ReactDOM.findDOMNode(componentOrElement);\n }\n return componentOrElement != null ? componentOrElement : null;\n}","import React, { useCallback, useRef } from 'react';\nimport Transition from 'react-transition-group/Transition';\nimport useMergedRefs from '@restart/hooks/useMergedRefs';\nimport safeFindDOMNode from './safeFindDOMNode';\nimport { jsx as _jsx } from \"react/jsx-runtime\";\n// Normalizes Transition callbacks when nodeRef is used.\nconst TransitionWrapper = /*#__PURE__*/React.forwardRef(({\n onEnter,\n onEntering,\n onEntered,\n onExit,\n onExiting,\n onExited,\n addEndListener,\n children,\n childRef,\n ...props\n}, ref) => {\n const nodeRef = useRef(null);\n const mergedRef = useMergedRefs(nodeRef, childRef);\n const attachRef = r => {\n mergedRef(safeFindDOMNode(r));\n };\n const normalize = callback => param => {\n if (callback && nodeRef.current) {\n callback(nodeRef.current, param);\n }\n };\n\n /* eslint-disable react-hooks/exhaustive-deps */\n const handleEnter = useCallback(normalize(onEnter), [onEnter]);\n const handleEntering = useCallback(normalize(onEntering), [onEntering]);\n const handleEntered = useCallback(normalize(onEntered), [onEntered]);\n const handleExit = useCallback(normalize(onExit), [onExit]);\n const handleExiting = useCallback(normalize(onExiting), [onExiting]);\n const handleExited = useCallback(normalize(onExited), [onExited]);\n const handleAddEndListener = useCallback(normalize(addEndListener), [addEndListener]);\n /* eslint-enable react-hooks/exhaustive-deps */\n\n return /*#__PURE__*/_jsx(Transition, {\n ref: ref,\n ...props,\n onEnter: handleEnter,\n onEntered: handleEntered,\n onEntering: handleEntering,\n onExit: handleExit,\n onExited: handleExited,\n onExiting: handleExiting,\n addEndListener: handleAddEndListener,\n nodeRef: nodeRef,\n children: typeof children === 'function' ? (status, innerProps) => children(status, {\n ...innerProps,\n ref: attachRef\n }) : /*#__PURE__*/React.cloneElement(children, {\n ref: attachRef\n })\n });\n});\nexport default TransitionWrapper;","import classNames from 'classnames';\nimport * as React from 'react';\nimport { useCallback } from 'react';\nimport { ENTERED, ENTERING } from 'react-transition-group/Transition';\nimport transitionEndListener from './transitionEndListener';\nimport triggerBrowserReflow from './triggerBrowserReflow';\nimport TransitionWrapper from './TransitionWrapper';\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nconst fadeStyles = {\n [ENTERING]: 'show',\n [ENTERED]: 'show'\n};\nconst Fade = /*#__PURE__*/React.forwardRef(({\n className,\n children,\n transitionClasses = {},\n onEnter,\n ...rest\n}, ref) => {\n const props = {\n in: false,\n timeout: 300,\n mountOnEnter: false,\n unmountOnExit: false,\n appear: false,\n ...rest\n };\n const handleEnter = useCallback((node, isAppearing) => {\n triggerBrowserReflow(node);\n onEnter == null ? void 0 : onEnter(node, isAppearing);\n }, [onEnter]);\n return /*#__PURE__*/_jsx(TransitionWrapper, {\n ref: ref,\n addEndListener: transitionEndListener,\n ...props,\n onEnter: handleEnter,\n childRef: children.ref,\n children: (status, innerProps) => /*#__PURE__*/React.cloneElement(children, {\n ...innerProps,\n className: classNames('fade', className, children.props.className, fadeStyles[status], transitionClasses[status])\n })\n });\n});\nFade.displayName = 'Fade';\nexport default Fade;","// reading a dimension prop will cause the browser to recalculate,\n// which will let our animations work\nexport default function triggerBrowserReflow(node) {\n // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n node.offsetHeight;\n}","import * as React from 'react';\nimport { useEffect, useRef, useState } from 'react';\nimport classNames from 'classnames';\nimport BaseOverlay from '@restart/ui/Overlay';\nimport useEventCallback from '@restart/hooks/useEventCallback';\nimport useIsomorphicEffect from '@restart/hooks/useIsomorphicEffect';\nimport useMergedRefs from '@restart/hooks/useMergedRefs';\nimport useOverlayOffset from './useOverlayOffset';\nimport Fade from './Fade';\nimport safeFindDOMNode from './safeFindDOMNode';\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nfunction wrapRefs(props, arrowProps) {\n const {\n ref\n } = props;\n const {\n ref: aRef\n } = arrowProps;\n props.ref = ref.__wrapped || (ref.__wrapped = r => ref(safeFindDOMNode(r)));\n arrowProps.ref = aRef.__wrapped || (aRef.__wrapped = r => aRef(safeFindDOMNode(r)));\n}\nconst Overlay = /*#__PURE__*/React.forwardRef(({\n children: overlay,\n transition = Fade,\n popperConfig = {},\n rootClose = false,\n placement = 'top',\n show: outerShow = false,\n ...outerProps\n}, outerRef) => {\n const popperRef = useRef({});\n const [firstRenderedState, setFirstRenderedState] = useState(null);\n const [ref, modifiers] = useOverlayOffset(outerProps.offset);\n const mergedRef = useMergedRefs(outerRef, ref);\n const actualTransition = transition === true ? Fade : transition || undefined;\n const handleFirstUpdate = useEventCallback(state => {\n setFirstRenderedState(state);\n popperConfig == null ? void 0 : popperConfig.onFirstUpdate == null ? void 0 : popperConfig.onFirstUpdate(state);\n });\n useIsomorphicEffect(() => {\n if (firstRenderedState) {\n popperRef.current.scheduleUpdate == null ? void 0 : popperRef.current.scheduleUpdate();\n }\n }, [firstRenderedState]);\n useEffect(() => {\n if (!outerShow) {\n setFirstRenderedState(null);\n }\n }, [outerShow]);\n return /*#__PURE__*/_jsx(BaseOverlay, {\n ...outerProps,\n ref: mergedRef,\n popperConfig: {\n ...popperConfig,\n modifiers: modifiers.concat(popperConfig.modifiers || []),\n onFirstUpdate: handleFirstUpdate\n },\n transition: actualTransition,\n rootClose: rootClose,\n placement: placement,\n show: outerShow,\n children: (overlayProps, {\n arrowProps,\n popper: popperObj,\n show\n }) => {\n var _popperObj$state, _popperObj$state$modi;\n wrapRefs(overlayProps, arrowProps);\n // Need to get placement from popper object, handling case when overlay is flipped using 'flip' prop\n const updatedPlacement = popperObj == null ? void 0 : popperObj.placement;\n const popper = Object.assign(popperRef.current, {\n state: popperObj == null ? void 0 : popperObj.state,\n scheduleUpdate: popperObj == null ? void 0 : popperObj.update,\n placement: updatedPlacement,\n outOfBoundaries: (popperObj == null ? void 0 : (_popperObj$state = popperObj.state) == null ? void 0 : (_popperObj$state$modi = _popperObj$state.modifiersData.hide) == null ? void 0 : _popperObj$state$modi.isReferenceHidden) || false,\n strategy: popperConfig.strategy\n });\n const hasDoneInitialMeasure = !!firstRenderedState;\n if (typeof overlay === 'function') return overlay({\n ...overlayProps,\n placement: updatedPlacement,\n show,\n ...(!transition && show && {\n className: 'show'\n }),\n popper,\n arrowProps,\n hasDoneInitialMeasure\n });\n return /*#__PURE__*/React.cloneElement(overlay, {\n ...overlayProps,\n placement: updatedPlacement,\n arrowProps,\n popper,\n hasDoneInitialMeasure,\n className: classNames(overlay.props.className, !transition && show && 'show'),\n style: {\n ...overlay.props.style,\n ...overlayProps.style\n }\n });\n }\n });\n});\nOverlay.displayName = 'Overlay';\nexport default Overlay;","import { useMemo, useRef } from 'react';\nimport hasClass from 'dom-helpers/hasClass';\nimport { useBootstrapPrefix } from './ThemeProvider';\nimport Popover from './Popover';\n\n// This is meant for internal use.\n// This applies a custom offset to the overlay if it's a popover.\nexport default function useOverlayOffset(customOffset) {\n const overlayRef = useRef(null);\n const popoverClass = useBootstrapPrefix(undefined, 'popover');\n const offset = useMemo(() => ({\n name: 'offset',\n options: {\n offset: () => {\n if (overlayRef.current && hasClass(overlayRef.current, popoverClass)) {\n return customOffset || Popover.POPPER_OFFSET;\n }\n return customOffset || [0, 0];\n }\n }\n }), [customOffset, popoverClass]);\n return [overlayRef, [offset]];\n}","import contains from 'dom-helpers/contains';\nimport * as React from 'react';\nimport { cloneElement, useCallback, useRef } from 'react';\nimport useTimeout from '@restart/hooks/useTimeout';\nimport warning from 'warning';\nimport { useUncontrolledProp } from 'uncontrollable';\nimport useMergedRefs from '@restart/hooks/useMergedRefs';\nimport Overlay from './Overlay';\nimport safeFindDOMNode from './safeFindDOMNode';\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nimport { Fragment as _Fragment } from \"react/jsx-runtime\";\nimport { jsxs as _jsxs } from \"react/jsx-runtime\";\nfunction normalizeDelay(delay) {\n return delay && typeof delay === 'object' ? delay : {\n show: delay,\n hide: delay\n };\n}\n\n// Simple implementation of mouseEnter and mouseLeave.\n// React's built version is broken: https://github.com/facebook/react/issues/4251\n// for cases when the trigger is disabled and mouseOut/Over can cause flicker\n// moving from one child element to another.\nfunction handleMouseOverOut(\n// eslint-disable-next-line @typescript-eslint/no-shadow\nhandler, args, relatedNative) {\n const [e] = args;\n const target = e.currentTarget;\n const related = e.relatedTarget || e.nativeEvent[relatedNative];\n if ((!related || related !== target) && !contains(target, related)) {\n handler(...args);\n }\n}\nfunction OverlayTrigger({\n trigger = ['hover', 'focus'],\n overlay,\n children,\n popperConfig = {},\n show: propsShow,\n defaultShow = false,\n onToggle,\n delay: propsDelay,\n placement,\n flip = placement && placement.indexOf('auto') !== -1,\n ...props\n}) {\n const triggerNodeRef = useRef(null);\n const mergedRef = useMergedRefs(triggerNodeRef, children.ref);\n const timeout = useTimeout();\n const hoverStateRef = useRef('');\n const [show, setShow] = useUncontrolledProp(propsShow, defaultShow, onToggle);\n const delay = normalizeDelay(propsDelay);\n const {\n onFocus,\n onBlur,\n onClick\n } = typeof children !== 'function' ? React.Children.only(children).props : {};\n const attachRef = r => {\n mergedRef(safeFindDOMNode(r));\n };\n const handleShow = useCallback(() => {\n timeout.clear();\n hoverStateRef.current = 'show';\n if (!delay.show) {\n setShow(true);\n return;\n }\n timeout.set(() => {\n if (hoverStateRef.current === 'show') setShow(true);\n }, delay.show);\n }, [delay.show, setShow, timeout]);\n const handleHide = useCallback(() => {\n timeout.clear();\n hoverStateRef.current = 'hide';\n if (!delay.hide) {\n setShow(false);\n return;\n }\n timeout.set(() => {\n if (hoverStateRef.current === 'hide') setShow(false);\n }, delay.hide);\n }, [delay.hide, setShow, timeout]);\n const handleFocus = useCallback((...args) => {\n handleShow();\n onFocus == null ? void 0 : onFocus(...args);\n }, [handleShow, onFocus]);\n const handleBlur = useCallback((...args) => {\n handleHide();\n onBlur == null ? void 0 : onBlur(...args);\n }, [handleHide, onBlur]);\n const handleClick = useCallback((...args) => {\n setShow(!show);\n onClick == null ? void 0 : onClick(...args);\n }, [onClick, setShow, show]);\n const handleMouseOver = useCallback((...args) => {\n handleMouseOverOut(handleShow, args, 'fromElement');\n }, [handleShow]);\n const handleMouseOut = useCallback((...args) => {\n handleMouseOverOut(handleHide, args, 'toElement');\n }, [handleHide]);\n const triggers = trigger == null ? [] : [].concat(trigger);\n const triggerProps = {\n ref: attachRef\n };\n if (triggers.indexOf('click') !== -1) {\n triggerProps.onClick = handleClick;\n }\n if (triggers.indexOf('focus') !== -1) {\n triggerProps.onFocus = handleFocus;\n triggerProps.onBlur = handleBlur;\n }\n if (triggers.indexOf('hover') !== -1) {\n process.env.NODE_ENV !== \"production\" ? warning(triggers.length > 1, '[react-bootstrap] Specifying only the `\"hover\"` trigger limits the visibility of the overlay to just mouse users. Consider also including the `\"focus\"` trigger so that touch and keyboard only users can see the overlay as well.') : void 0;\n triggerProps.onMouseOver = handleMouseOver;\n triggerProps.onMouseOut = handleMouseOut;\n }\n return /*#__PURE__*/_jsxs(_Fragment, {\n children: [typeof children === 'function' ? children(triggerProps) : /*#__PURE__*/cloneElement(children, triggerProps), /*#__PURE__*/_jsx(Overlay, {\n ...props,\n show: show,\n onHide: handleHide,\n flip: flip,\n placement: placement,\n popperConfig: popperConfig,\n target: triggerNodeRef.current,\n children: overlay\n })]\n });\n}\nexport default OverlayTrigger;","const _excluded = [\"as\", \"disabled\"];\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\nimport * as React from 'react';\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport function isTrivialHref(href) {\n return !href || href.trim() === '#';\n}\nexport function useButtonProps({\n tagName,\n disabled,\n href,\n target,\n rel,\n role,\n onClick,\n tabIndex = 0,\n type\n}) {\n if (!tagName) {\n if (href != null || target != null || rel != null) {\n tagName = 'a';\n } else {\n tagName = 'button';\n }\n }\n const meta = {\n tagName\n };\n if (tagName === 'button') {\n return [{\n type: type || 'button',\n disabled\n }, meta];\n }\n const handleClick = event => {\n if (disabled || tagName === 'a' && isTrivialHref(href)) {\n event.preventDefault();\n }\n if (disabled) {\n event.stopPropagation();\n return;\n }\n onClick == null ? void 0 : onClick(event);\n };\n const handleKeyDown = event => {\n if (event.key === ' ') {\n event.preventDefault();\n handleClick(event);\n }\n };\n if (tagName === 'a') {\n // Ensure there's a href so Enter can trigger anchor button.\n href || (href = '#');\n if (disabled) {\n href = undefined;\n }\n }\n return [{\n role: role != null ? role : 'button',\n // explicitly undefined so that it overrides the props disabled in a spread\n // e.g. \n disabled: undefined,\n tabIndex: disabled ? undefined : tabIndex,\n href,\n target: tagName === 'a' ? target : undefined,\n 'aria-disabled': !disabled ? undefined : disabled,\n rel: tagName === 'a' ? rel : undefined,\n onClick: handleClick,\n onKeyDown: handleKeyDown\n }, meta];\n}\nconst Button = /*#__PURE__*/React.forwardRef((_ref, ref) => {\n let {\n as: asProp,\n disabled\n } = _ref,\n props = _objectWithoutPropertiesLoose(_ref, _excluded);\n const [buttonProps, {\n tagName: Component\n }] = useButtonProps(Object.assign({\n tagName: asProp,\n disabled\n }, props));\n return /*#__PURE__*/_jsx(Component, Object.assign({}, props, buttonProps, {\n ref: ref\n }));\n});\nButton.displayName = 'Button';\nexport default Button;","import classNames from 'classnames';\nimport * as React from 'react';\nimport { useButtonProps } from '@restart/ui/Button';\nimport { useBootstrapPrefix } from './ThemeProvider';\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nconst Button = /*#__PURE__*/React.forwardRef(({\n as,\n bsPrefix,\n variant = 'primary',\n size,\n active = false,\n disabled = false,\n className,\n ...props\n}, ref) => {\n const prefix = useBootstrapPrefix(bsPrefix, 'btn');\n const [buttonProps, {\n tagName\n }] = useButtonProps({\n tagName: as,\n disabled,\n ...props\n });\n const Component = tagName;\n return /*#__PURE__*/_jsx(Component, {\n ...buttonProps,\n ...props,\n ref: ref,\n disabled: disabled,\n className: classNames(className, prefix, active && 'active', variant && `${prefix}-${variant}`, size && `${prefix}-${size}`, props.href && disabled && 'disabled')\n });\n});\nButton.displayName = 'Button';\nexport default Button;","import canUseDOM from './canUseDOM';\nvar size;\nexport default function scrollbarSize(recalc) {\n if (!size && size !== 0 || recalc) {\n if (canUseDOM) {\n var scrollDiv = document.createElement('div');\n scrollDiv.style.position = 'absolute';\n scrollDiv.style.top = '-9999px';\n scrollDiv.style.width = '50px';\n scrollDiv.style.height = '50px';\n scrollDiv.style.overflow = 'scroll';\n document.body.appendChild(scrollDiv);\n size = scrollDiv.offsetWidth - scrollDiv.clientWidth;\n document.body.removeChild(scrollDiv);\n }\n }\n\n return size;\n}","import ownerDocument from './ownerDocument';\n/**\n * Returns the actively focused element safely.\n *\n * @param doc the document to check\n */\n\nexport default function activeElement(doc) {\n if (doc === void 0) {\n doc = ownerDocument();\n }\n\n // Support: IE 9 only\n // IE9 throws an \"Unspecified error\" accessing document.activeElement from an