diff --git a/.gitignore b/.gitignore index 10c755b..02ad545 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,14 @@ vc/.vs vc/enc_temp_folder build/ + +**/cmake-build-debug +**/CMakeCache.txt +**/cmake_install.cmake +**/install_manifest.txt +**/CMakeFiles/ +**/CTestTestfile.cmake +**/Makefile +**/*.cbp +**/CMakeScripts +**/compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..efd79c3 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,150 @@ +# +# YaPB - Counter-Strike Bot based on PODBot by Markus Klinge. +# Copyright © 2023-2024 Velaron. +# +# SPDX-License-Identifier: MIT +# + +cmake_minimum_required(VERSION 3.15) +project(yapb VERSION 4.4 LANGUAGES CXX) + +if(NOT ANDROID) + set(CMAKE_SHARED_MODULE_PREFIX "") +endif() + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(YAPB_SRC + "src/analyze.cpp" + "src/botlib.cpp" + "src/chatlib.cpp" + "src/combat.cpp" + "src/config.cpp" + "src/control.cpp" + "src/engine.cpp" + "src/graph.cpp" + "src/hooks.cpp" + "src/linkage.cpp" + "src/manager.cpp" + "src/module.cpp" + "src/message.cpp" + "src/navigate.cpp" + "src/planner.cpp" + "src/practice.cpp" + "src/sounds.cpp" + "src/storage.cpp" + "src/support.cpp" + "src/tasks.cpp" + "src/vision.cpp" + "src/vistable.cpp" +) + +add_library(yapb MODULE ${YAPB_SRC}) +find_package(Git QUIET) + +if(GIT_FOUND) + execute_process(COMMAND hostname OUTPUT_VARIABLE BUILD_MACHINE OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) + execute_process(COMMAND git rev-parse --short HEAD OUTPUT_VARIABLE BUILD_HASH OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) + execute_process(COMMAND git rev-list --count HEAD OUTPUT_VARIABLE BUILD_COUNT OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) + execute_process(COMMAND git log --pretty="%ae" -1 OUTPUT_VARIABLE BUILD_AUTHOR OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) + + set(BUILD_COMPILER ${CMAKE_CXX_COMPILER_ID}\ ${CMAKE_CXX_COMPILER_VERSION}) + set(BUILD_VERSION ${CMAKE_PROJECT_VERSION}) + + string(REPLACE . , BUILD_WINVER ${CMAKE_PROJECT_VERSION}) + + configure_file(inc/version.h.in version.build.h @ONLY) + target_compile_definitions(yapb PRIVATE VERSION_GENERATED) +endif() + +if((CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") AND NOT MSVC) + target_compile_options(yapb PRIVATE -flto=auto -fno-exceptions -fno-rtti -fno-threadsafe-statics -pthread) + + if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64") + target_compile_options(yapb PRIVATE -march=armv8-a+fp+simd) + elseif(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "arm" AND NOT CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64" AND NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^ppc") + target_compile_options(yapb PRIVATE -mmmx -msse -msse2 -msse3 -mssse3 -mfpmath=sse) + endif() + + if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + target_compile_options(yapb PRIVATE -funroll-loops -fomit-frame-pointer -fno-stack-protector -fvisibility=hidden -fvisibility-inlines-hidden) + + if(NOT WIN32 AND NOT CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64" AND NOT CMAKE_SYSTEM_PROCESSOR STREQUAL "arm" AND NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^ppc") + target_compile_options(yapb PRIVATE -fdata-sections -ffunction-sections -fcf-protection=none) + target_link_options(yapb PRIVATE -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/ext/ldscripts/version.lds -Wl,--gc-sections) + + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + target_compile_options(yapb PRIVATE -fgraphite-identity -floop-nest-optimize) + target_link_options(yapb PRIVATE -fgraphite-identity -floop-nest-optimize) + endif() + endif() + + if(NOT WIN32 AND CMAKE_CXX_COMPILER_ID MATCHES "GNU") + target_link_options(yapb PRIVATE -flto-partition=none) + endif() + else() + target_compile_options(yapb PRIVATE -g3 -ggdb -DDEBUG -D_FORTIFY_SOURCE=2) + endif() + + if(WIN32 AND CMAKE_CXX_COMPILER_ID MATCHES "GNU") + target_link_options(yapb PRIVATE -Wl,--kill-at) + + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "12") + target_link_options(yapb PRIVATE -Xlinker --script -Xlinker ${CMAKE_CURRENT_SOURCE_DIR}/ext/ldscripts/i386pe.lds) + endif() + endif() + + if(WIN32 AND NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang") + target_link_options(yapb PRIVATE -static-libgcc) + endif() + + if(WIN32 AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set_property(TARGET yapb PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + endif() + + if(CMAKE_SIZEOF_VOID_P EQUAL 8 OR CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "arm" OR CMAKE_SYSTEM_PROCESSOR MATCHES "^ppc") + target_compile_options(yapb PRIVATE -fPIC) + target_link_options(yapb PRIVATE -fPIC) + endif() +elseif(WIN32 AND MSVC) + set_property(TARGET yapb PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + + if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + target_compile_options(yapb PRIVATE /Zc:threadSafeInit- /GS- /Ob2 /Oy /Oi /Ot /fp:precise /GF /Gw /arch:SSE2 /Zi /guard:ehcont- /guard:cf- /DEBUG) + target_link_options(yapb PRIVATE /OPT:REF,ICF /GUARD:NO /LTCG delayimp.lib /DELAYLOAD:user32.dll /DELAYLOAD:ws2_32.dll) + + if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang") + target_compile_options(yapb PRIVATE /GL) + endif() + endif() +endif() + +if(WIN32 OR MINGW) + target_link_libraries(yapb PRIVATE user32 ws2_32) + target_sources(yapb PRIVATE "vc/yapb.rc") +elseif(ANDROID) + target_link_libraries(yapb PRIVATE m dl log) +else() + target_link_libraries(yapb PRIVATE m dl pthread) +endif() + +target_include_directories(yapb PRIVATE + ${PROJECT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + "inc" + "ext" + "ext/crlib" + "ext/linkage" +) + +install(TARGETS yapb + DESTINATION "${GAME_DIR}/${SERVER_INSTALL_DIR}/" + PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE + GROUP_READ GROUP_EXECUTE + WORLD_READ WORLD_EXECUTE) + +if(MSVC) + install(FILES $ + DESTINATION "${GAME_DIR}/${SERVER_INSTALL_DIR}/" OPTIONAL) +endif() diff --git a/cfg/addons/yapb/conf/chatter.cfg b/cfg/addons/yapb/conf/chatter.cfg index 7529fe8..7b98328 100644 --- a/cfg/addons/yapb/conf/chatter.cfg +++ b/cfg/addons/yapb/conf/chatter.cfg @@ -9,76 +9,76 @@ RewritePath sound/radio/bot -Event Radio_CoverMe = ("cover_me", "cover_me2"); -// Event Radio_YouTakePoint = (""); -// Event Radio_HoldPosition = (""); -// Event Radio_RegroupTeam = (""); -Event Radio_FollowMe = ("lead_on_sir", "lead_the_way_sir", "lead_the_way", "ok_sir_lets_go", "lead_on_commander", "lead_the_way_commander", "ok_cmdr_lets_go"); -Event Radio_TakingFire = ("taking_fire_need_assistance2", "i_could_use_some_help", "i_could_use_some_help_over_here", "help", "need_help", "need_help2", "im_in_trouble"); +Event Radio_CoverMe = cover_me, cover_me2 +// Event Radio_YouTakePoint = +// Event Radio_HoldPosition = +// Event Radio_RegroupTeam = +Event Radio_FollowMe = lead_on_sir, lead_the_way_sir, lead_the_way, ok_sir_lets_go, lead_on_commander, lead_the_way_commander, ok_cmdr_lets_go +Event Radio_TakingFire = taking_fire_need_assistance2, i_could_use_some_help, i_could_use_some_help_over_here, help, need_help, need_help2, im_in_trouble -// Event Radio_GoGoGo = (""); -// Event Radio_Fallback = (""); -// Event Radio_StickTogether = (""); -// Event Radio_GetInPosition = (""); -// Event Radio_StormTheFront = (""); -Event Radio_ReportTeam = ("report_in_team", "anyone_see_them", "anyone_see_anything", "where_are_they", "where_could_they_be"); +// Event Radio_GoGoGo = +// Event Radio_Fallback = +// Event Radio_StickTogether = +// Event Radio_GetInPosition = +// Event Radio_StormTheFront = +Event Radio_ReportTeam = report_in_team, anyone_see_them, anyone_see_anything, where_are_they, where_could_they_be -Event Radio_Affirmative = ("affirmative", "roger_that", "me_too", "ill_come_with_you", "ill_go_with_you", "ill_go_too", "i_got_your_back", "i_got_your_back2", "im_with_you", "im_with_you", "sounds_like_a_plan", "good_idea"); -Event Radio_EnemySpotted = ("one_guy", "two_of_them", "three", "three_of_them", "a_bunch_of_them", "theyre_all_over_the_place2", "theyre_everywhere2", "theres_too_many_of_them", "theres_too_many", "too_many2", "the_actions_hot_here", "its_a_party"); -Event Radio_NeedBackup = ("taking_fire_need_assistance2", "i_could_use_some_help", "i_could_use_some_help_over_here", "help", "need_help", "need_help2", "im_in_trouble"); -Event Radio_SectorClear = ("clear", "clear2", "clear3", "clear4", "area_clear", "all_clear_here", "nothing_moving_over_here", "all_quiet", "nothing_happening_over_here", "i_got_nothing", "nothing", "nothing_here", "theres_nobody_home"); -Event Radio_InPosition = ("lets_wait_here", "lets_hold_up_here_for_a_minute", "im_gonna_hang_back", "im_going_to_wait_here", "im_waiting_here"); -Event Radio_ReportingIn = ("reporting_in"); -// Event Radio_ShesGonnaBlow = (""); -Event Radio_Negative = ("ahh_negative", "negative", "no2", "negative2", "i_dont_think_so", "naa", "no_thanks", "no", "nnno_sir", "no_sir"); -Event Radio_EnemyDown = ("enemy_down", "enemy_down2"); +Event Radio_Affirmative = affirmative, roger_that, me_too, ill_come_with_you, ill_go_with_you, ill_go_too, i_got_your_back, i_got_your_back2, im_with_you, im_with_you, sounds_like_a_plan, good_idea +Event Radio_EnemySpotted = one_guy, two_of_them, three, three_of_them, a_bunch_of_them, theyre_all_over_the_place2, theyre_everywhere2, theres_too_many_of_them, theres_too_many, too_many2, the_actions_hot_here, its_a_party +Event Radio_NeedBackup = taking_fire_need_assistance2, i_could_use_some_help, i_could_use_some_help_over_here, help, need_help, need_help2, im_in_trouble +Event Radio_SectorClear = clear, clear2, clear3, clear4, area_clear, all_clear_here, nothing_moving_over_here, all_quiet, nothing_happening_over_here, i_got_nothing, nothing, nothing_here, theres_nobody_home +Event Radio_InPosition = lets_wait_here, lets_hold_up_here_for_a_minute, im_gonna_hang_back, im_going_to_wait_here, im_waiting_here +Event Radio_ReportingIn = reporting_in +// Event Radio_ShesGonnaBlow = +Event Radio_Negative = ahh_negative, negative, no2, negative2, i_dont_think_so, naa, no_thanks, no, nnno_sir, no_sir +Event Radio_EnemyDown = enemy_down, enemy_down2 // end of radio, begin some voices (NOT SORTED) -Event Chatter_SpotTheBomber = ("i_see_the_bomber", "theres_the_bomber", "hes_got_the_bomb", "hes_got_the_bomb2", "hes_got_the_package", "spotted_the_delivery_boy"); -Event Chatter_FriendlyFire = ("cut_it_out", "what_are_you_doing", "stop_it", "ow_its_me", "ow", "ouch", "im_on_your_side", "hold_your_fire", "hey", "hey2", "ouch", "ouch", "ouch"); -Event Chatter_DiePain = ("pain2", "pain4", "pain5", "pain8", "pain9", "pain10"); -Event Chatter_GotBlinded = ("ive_been_blinded", "my_eyes", "i_cant_see", "im_blind"); -Event Chatter_GoingToPlantBomb = ("im_gonna_go_plant", "im_gonna_go_plant_the_bomb"); -Event Chatter_RescuingHostages = ("the_hostages_are_with_me", "taking_the_hostages_to_safety", "ive_got_the_hostages", "i_have_the_hostages"); -Event Chatter_GoingToCamp = ("im_going_to_camp"); -Event Chatter_HearSomething = ("hang_on_i_heard_something", "i_hear_something", "i_hear_them", "i_heard_them", "i_heard_something_over_there"); -Event Chatter_TeamKill = ("what_happened", "noo", "oh_my_god", "oh_man", "oh_no_sad", "what_have_you_done"); -Event Chatter_ReportingIn = ("reporting_in"); -Event Chatter_GuardDroppedC4 = ("bombsite", "bombsite2"); -Event Chatter_Camp = ("im_waiting_here"); -Event Chatter_PlantingC4 = ("planting_the_bomb", "planting"); -Event Chatter_DefusingC4 = ("defusing", "defusing_bomb", "defusing_bomb_now"); -Event Chatter_InCombat = ("attacking", "attacking_enemies", "engaging_enemies", "in_combat", "in_combat2", "returning_fire"); -Event Chatter_SeeksEnemy = ("lets_wait_here", "lets_hold_up_here_for_a_minute", "im_gonna_hang_back", "im_going_to_wait_here", "im_waiting_here"); -Event Chatter_Nothing = ("nothing_here", "nothing"); -Event Chatter_EnemyDown = ("hes_dead", "hes_down", "got_him", "dropped_him", "killed_him", "ruined_his_day", "wasted_him", "made_him_cry", "took_him_down", "took_him_out2", "took_him_out", "hes_broken", "hes_done"); -Event Chatter_UseHostage = ("talking_to_hostages", "rescuing_hostages"); -Event Chatter_FoundC4 = ("bombs_on_the_ground", "bombs_on_the_ground_here", "the_bomb_is_down", "the_bomb_is_on_the_ground", "they_dropped_the_bomb"); -Event Chatter_WonTheRound = ("good_job_team", "nice_work_team", "way_to_be_team", "well_done"); -Event Chatter_QuicklyWonTheRound = ("i_am_dangerous", "do_not_mess_with_me", "we_owned_them", "they_never_knew_what_hit_them", "thats_the_way_this_is_done", "and_thats_how_its_done", "owned", "yesss", "yesss2", "yea_baby", "whoo", "whoo2", "oh_yea", "oh_yea2"); -Event Chatter_ScaredEmotion = ("whoa", "uh_oh", "oh_no", "yikes", "oh", "oh_boy", "oh_boy2", "aah"); -Event Chatter_HeardEnemy = ("i_hear_them", "hang_on_i_heard_something", "i_hear_something", "i_heard_them", "i_heard_something_over_there"); -Event Chatter_SniperWarning = ("sniper", "sniper2", "watch_it_theres_a_sniper"); -Event Chatter_SniperKilled = ("got_the_sniper", "got_the_sniper2", "sniper_down", "took_out_the_sniper", "the_sniper_is_dead"); -Event Chatter_VIPSpotted = ("i_see_our_target", "target_spotted", "target_acquired"); -Event Chatter_GuardingVipSafety = ("watching_the_escape_route", "im_at_the_escape_zone", "watching_the_escape_zone", "guarding_the_escape_zone", "guarding_the_escape_zone2"); -Event Chatter_GoingToGuardVIPSafety = ("im_going_to_cover_the_escape_zone", "im_going_to_watch_the_escape_zone", "im_going_to_keep_an_eye_on_the_escape", "heading_to_the_escape_zone"); -Event Chatter_OneEnemyLeft = ("one_guy_left", "theres_one_left"); -Event Chatter_TwoEnemiesLeft = ("two_enemies_left", "two_to_go"); -Event Chatter_ThreeEnemiesLeft = ("three_left", "three_to_go", "three_to_go2"); -Event Chatter_NoEnemiesLeft = ("that_was_the_last_one", "that_was_it", "that_was_the_last_guy"); -Event Chatter_FoundBombPlace = ("theres_the_bomb", "theres_the_bomb2"); -Event Chatter_WhereIsTheBomb = ("wheres_the_bomb", "wheres_the_bomb2", "wheres_the_bomb3", "where_is_it"); -Event Chatter_DefendingBombSite = ("bombsite", "bombsite2"); -Event Chatter_BarelyDefused = ("i_wasnt_worried_for_a_minute", "that_was_a_close_one", "well_done", "whew_that_was_close"); -Event Chatter_NiceshotCommander = ("good_one_sir", "good_one_sir2", "nice_shot_sir", "nice_one_sir"); -Event Chatter_NiceshotPall = ("good_one", "good_one2", "nice_shot", "nice_shot2", "good_shot", "good_shot2", "nice", "nice2", "very_nice"); -Event Chatter_GoingToGuardHostages = ("camping_hostages", "im_going_to_camp_the_hostages", "im_going_to_guard_the_hostages", "im_going_to_guard_the_hostages2"); -Event Chatter_GoingToGuardDoppedBomb = ("im_going_to_guard_the_bomb", "im_going_to_guard_the_bomb2", "im_going_to_keep_an_eye_on_the_bomb", "im_going_to_watch_the_bomb"); -Event Chatter_OnMyWay = ("on_my_way", "on_my_way2", "im_coming", "hang_on_im_coming", "be_right_there"); -Event Chatter_LeadOnSir = ("lead_on_sir", "lead_the_way_sir", "lead_the_way", "ok_sir_lets_go", "lead_on_commander", "lead_the_way_commander", "ok_cmdr_lets_go"); -Event Chatter_Pinned_Down = ("they_got_me_pinned_down_here", "im_pinned_down"); -Event Chatter_GottaFindTheBomb = ("theres_the_bomb", "theres_the_bomb2"); -Event Chatter_Lost_The_Commander = ("weve_lost_the_commander", "the_commander_is_down", "the_commander_is_down_repeat"); -Event Chatter_CoverMe = ("cover_me", "cover_me2"); -Event Chatter_BombSiteSecured = ("i_wasnt_worried_for_a_minute", "that_was_a_close_one", "well_done", "whew_that_was_close"); +Event Chatter_SpotTheBomber = i_see_the_bomber, theres_the_bomber, hes_got_the_bomb, hes_got_the_bomb2, hes_got_the_package, spotted_the_delivery_boy +Event Chatter_FriendlyFire = cut_it_out, what_are_you_doing, stop_it, ow_its_me, ow, ouch, im_on_your_side, hold_your_fire, hey, hey2, ouch, ouch, ouch +Event Chatter_DiePain = pain2, pain4, pain5, pain8, pain9, pain10 +Event Chatter_GotBlinded = ive_been_blinded, my_eyes, i_cant_see, im_blind +Event Chatter_GoingToPlantBomb = im_gonna_go_plant, im_gonna_go_plant_the_bomb +Event Chatter_RescuingHostages = the_hostages_are_with_me, taking_the_hostages_to_safety, ive_got_the_hostages, i_have_the_hostages +Event Chatter_GoingToCamp = im_going_to_camp +Event Chatter_HearSomething = hang_on_i_heard_something, i_hear_something, i_hear_them, i_heard_them, i_heard_something_over_there +Event Chatter_TeamKill = what_happened, noo, oh_my_god, oh_man, oh_no_sad, what_have_you_done +Event Chatter_ReportingIn = reporting_in +Event Chatter_GuardDroppedC4 = bombsite, bombsite2 +Event Chatter_Camp = im_waiting_here +Event Chatter_PlantingC4 = planting_the_bomb, planting +Event Chatter_DefusingC4 = defusing, defusing_bomb, defusing_bomb_now +Event Chatter_InCombat = attacking, attacking_enemies, engaging_enemies, in_combat, in_combat2, returning_fire +Event Chatter_SeeksEnemy = lets_wait_here, lets_hold_up_here_for_a_minute, im_gonna_hang_back, im_going_to_wait_here, im_waiting_here +Event Chatter_Nothing = nothing_here, nothing +Event Chatter_EnemyDown = hes_dead, hes_down, got_him, dropped_him, killed_him, ruined_his_day, wasted_him, made_him_cry, took_him_down, took_him_out2, took_him_out, hes_broken, hes_done +Event Chatter_UseHostage = talking_to_hostages, rescuing_hostages +Event Chatter_FoundC4 = bombs_on_the_ground, bombs_on_the_ground_here, the_bomb_is_down, the_bomb_is_on_the_ground, they_dropped_the_bomb +Event Chatter_WonTheRound = good_job_team, nice_work_team, way_to_be_team, well_done +Event Chatter_QuicklyWonTheRound = i_am_dangerous, do_not_mess_with_me, we_owned_them, they_never_knew_what_hit_them, thats_the_way_this_is_done, and_thats_how_its_done, owned, yesss, yesss2, yea_baby, whoo, whoo2, oh_yea, oh_yea2 +Event Chatter_ScaredEmotion = whoa, uh_oh, oh_no, yikes, oh, oh_boy, oh_boy2, aah +Event Chatter_HeardEnemy = i_hear_them, hang_on_i_heard_something, i_hear_something, i_heard_them, i_heard_something_over_there +Event Chatter_SniperWarning = sniper, sniper2, watch_it_theres_a_sniper +Event Chatter_SniperKilled = got_the_sniper, got_the_sniper2, sniper_down, took_out_the_sniper, the_sniper_is_dead +Event Chatter_VIPSpotted = i_see_our_target, target_spotted, target_acquired +Event Chatter_GuardingVipSafety = watching_the_escape_route, im_at_the_escape_zone, watching_the_escape_zone, guarding_the_escape_zone, guarding_the_escape_zone2 +Event Chatter_GoingToGuardVIPSafety = im_going_to_cover_the_escape_zone, im_going_to_watch_the_escape_zone, im_going_to_keep_an_eye_on_the_escape, heading_to_the_escape_zone +Event Chatter_OneEnemyLeft = one_guy_left, theres_one_left +Event Chatter_TwoEnemiesLeft = two_enemies_left, two_to_go +Event Chatter_ThreeEnemiesLeft = three_left, three_to_go, three_to_go2 +Event Chatter_NoEnemiesLeft = that_was_the_last_one, that_was_it, that_was_the_last_guy +Event Chatter_FoundBombPlace = theres_the_bomb, theres_the_bomb2 +Event Chatter_WhereIsTheBomb = wheres_the_bomb, wheres_the_bomb2, wheres_the_bomb3, where_is_it +Event Chatter_DefendingBombSite = bombsite, bombsite2 +Event Chatter_BarelyDefused = i_wasnt_worried_for_a_minute, that_was_a_close_one, well_done, whew_that_was_close +Event Chatter_NiceshotCommander = good_one_sir, good_one_sir2, nice_shot_sir, nice_one_sir +Event Chatter_NiceshotPall = good_one, good_one2, nice_shot, nice_shot2, good_shot, good_shot2, nice, nice2, very_nice +Event Chatter_GoingToGuardHostages = camping_hostages, im_going_to_camp_the_hostages, im_going_to_guard_the_hostages, im_going_to_guard_the_hostages2 +Event Chatter_GoingToGuardDoppedBomb = im_going_to_guard_the_bomb, im_going_to_guard_the_bomb2, im_going_to_keep_an_eye_on_the_bomb, im_going_to_watch_the_bomb +Event Chatter_OnMyWay = on_my_way, on_my_way2, im_coming, hang_on_im_coming, be_right_there +Event Chatter_LeadOnSir = lead_on_sir, lead_the_way_sir, lead_the_way, ok_sir_lets_go, lead_on_commander, lead_the_way_commander, ok_cmdr_lets_go +Event Chatter_Pinned_Down = they_got_me_pinned_down_here, im_pinned_down +Event Chatter_GottaFindTheBomb = theres_the_bomb, theres_the_bomb2 +Event Chatter_Lost_The_Commander = weve_lost_the_commander, the_commander_is_down, the_commander_is_down_repeat +Event Chatter_CoverMe = cover_me, cover_me2 +Event Chatter_BombSiteSecured = i_wasnt_worried_for_a_minute, that_was_a_close_one, well_done, whew_that_was_close diff --git a/ext/crlib b/ext/crlib index f188cfd..07c5413 160000 --- a/ext/crlib +++ b/ext/crlib @@ -1 +1 @@ -Subproject commit f188cfd399594cdcbca8dda42db842cd0796b7fb +Subproject commit 07c54138879ea360913a594b3c11557361e20fed diff --git a/ext/linkage b/ext/linkage index c91a51d..12a6dbd 160000 --- a/ext/linkage +++ b/ext/linkage @@ -1 +1 @@ -Subproject commit c91a51d769f10a38637af7ee9f03ed76d1fc206c +Subproject commit 12a6dbd1388052c0d98cd892ccd9d61e6b043854 diff --git a/inc/analyze.h b/inc/analyze.h index 8cc97a1..575c090 100644 --- a/inc/analyze.h +++ b/inc/analyze.h @@ -52,6 +52,9 @@ private: // cleanup bad nodes void cleanup (); + // show overlay message about analyzing + void displayOverlayMessage (); + public: // node should be created as crouch diff --git a/inc/chatlib.h b/inc/chatlib.h new file mode 100644 index 0000000..63c5c33 --- /dev/null +++ b/inc/chatlib.h @@ -0,0 +1,64 @@ +// +// YaPB, based on PODBot by Markus Klinge ("CountFloyd"). +// Copyright © YaPB Project Developers . +// +// SPDX-License-Identifier: MIT +// + +#pragma once + +// links keywords and replies together +struct ChatKeywords { + StringArray keywords; + StringArray replies; + StringArray usedReplies; + +public: + ChatKeywords () = default; + + ChatKeywords (const StringArray &keywords, const StringArray &replies) { + this->keywords.clear (); + this->replies.clear (); + this->usedReplies.clear (); + + this->keywords.insert (0, keywords); + this->replies.insert (0, replies); + } +}; + +// define chatting collection structure +struct ChatCollection { + int chatProbability {}; + float chatDelay {}; + float timeNextChat {}; + int entityIndex {}; + String sayText {}; + StringArray lastUsedSentences {}; +}; + + +// bot's chat manager +class BotChatManager : public Singleton { +private: + SmallArray > m_clanTags {}; // strippable clan tags + +public: + BotChatManager (); + ~BotChatManager () = default; + +public: + // chat helper to strip the clantags out of the string + void stripTags (String &line); + + // chat helper to make player name more human-like + void humanizePlayerName (String &playerName); + + // chat helper to add errors to the bot chat string + void addChatErrors (String &line); + + // chat helper to find keywords for given string + bool checkKeywords (StringRef line, String &reply); +}; + +// expose global +CR_EXPOSE_GLOBAL_SINGLETON (BotChatManager, chatlib); diff --git a/inc/config.h b/inc/config.h index 9c54b92..26dc9e0 100644 --- a/inc/config.h +++ b/inc/config.h @@ -249,7 +249,7 @@ public: } // get random name by index - StringRef getRandomLogoName (int index) { + StringRef getLogoName (int index) { return m_logos[index]; } @@ -258,7 +258,7 @@ public: if (m_custom.exists (name)) { return m_custom[name]; } - SimpleLogger::instance ().error ("Trying to fetch unknown custom variable: %s", name); + logger.error ("Trying to fetch unknown custom variable: %s", name); return ""; } diff --git a/inc/control.h b/inc/control.h index d4048a5..2450601 100644 --- a/inc/control.h +++ b/inc/control.h @@ -152,7 +152,6 @@ private: int menuKickPage4 (int item); private: - void enableDrawModels (bool enable); void createMenus (); public: @@ -166,6 +165,7 @@ public: void assignAdminRights (edict_t *ent, char *infobuffer); void maintainAdminRights (); void flushPrintQueue (); + void enableDrawModels (bool enable); public: void setFromConsole (bool console) { diff --git a/inc/engine.h b/inc/engine.h index 17cae5f..c870cdf 100644 --- a/inc/engine.h +++ b/inc/engine.h @@ -46,7 +46,8 @@ CR_DECLARE_SCOPED_ENUM (GameFlags, ReGameDLL = cr::bit (9), // server dll is a regamedll HasFakePings = cr::bit (10), // on that game version we can fake bots pings HasBotVoice = cr::bit (11), // on that game version we can use chatter - AnniversaryHL25 = cr::bit (12) // half-life 25th anniversary engine + AnniversaryHL25 = cr::bit (12), // half-life 25th anniversary engine + Xash3DLegacy = cr::bit (13) // old xash3d-branch ) // defines map type @@ -83,7 +84,7 @@ struct ConVarReg { }; // entity prototype -using EntityFunction = void (*) (entvars_t *); +using EntityProto = void (*) (entvars_t *); // rehlds has this fixed, but original hlds doesn't allocate string space passed to precache* argument, so game will crash when unloading module using metamod class EngineWrap final { @@ -128,6 +129,7 @@ private: Array m_breakables {}; SmallArray m_cvars {}; SharedLibrary m_gameLib {}; + SharedLibrary m_engineLib {}; EngineWrap m_engineWrap {}; bool m_precached {}; @@ -343,6 +345,11 @@ public: return m_gameLib; } + // get loaded engine lib + const SharedLibrary &elib () { + return m_engineLib; + } + // get registered cvars list const SmallArray &getCvars () { return m_cvars; @@ -369,6 +376,9 @@ public: // helper to sending the server message void sendServerMessage (StringRef message); + // helper for sending hud messages to client + void sendHudMessage (edict_t *ent, const hudtextparms_t &htp, StringRef message); + // send server command template void serverCommand (const char *fmt, Args &&...args) { engfuncs.pfnServerCommand (strings.concat (strings.format (fmt, cr::forward (args)...), "\n", StringBuffer::StaticBufferSize)); @@ -459,6 +469,7 @@ public: Game::instance ().addNewCvar (name_.chars (), initval, info, bounded, min, max, type, regMissing, regVal, this); } +public: template constexpr U get () const { if constexpr (cr::is_same ::value) { return ptr->value; @@ -467,25 +478,41 @@ public: return ptr->value > 0.0f; } else if constexpr (cr::is_same ::value) { - return static_cast (ptr->value); + return static_cast (ptr->value); + } + else if constexpr (cr::is_same ::value) { + return ptr->string; } - assert ("!Invalid type requested."); } +public: + operator bool () const { + return bool_ (); + } + + operator float () const { + return float_ (); + } + + operator StringRef () { + return str (); + } + +public: bool bool_ () const { - return ptr->value > 0.0f; + return get (); } int int_ () const { - return static_cast (ptr->value); + return get (); } float float_ () const { - return ptr->value; + return get (); } StringRef str () const { - return ptr->string; + return get (); } StringRef name () const { @@ -626,164 +653,6 @@ public: } }; -// simple handler for parsing and rewriting queries (fake queries) -class QueryBuffer { - SmallArray m_buffer {}; - size_t m_cursor {}; - -public: - QueryBuffer (const uint8_t *msg, size_t length, size_t shift) : m_cursor (0) { - m_buffer.insert (0, msg, length); - m_cursor += shift; - } - -public: - template T read () { - T result {}; - constexpr auto size = sizeof (T); - - if (m_cursor + size > m_buffer.length ()) { - return 0; - } - - memcpy (&result, m_buffer.data () + m_cursor, size); - m_cursor += size; - - return result; - } - - // must be called right after read - template void write (T value) { - constexpr auto size = sizeof (value); - memcpy (m_buffer.data () + m_cursor - size, &value, size); - } - - template void skip () { - constexpr auto size = sizeof (T); - - if (m_cursor + size > m_buffer.length ()) { - return; - } - m_cursor += size; - } - - void skipString () { - if (m_buffer.length () < m_cursor) { - return; - } - for (; m_cursor < m_buffer.length () && m_buffer[m_cursor] != kNullChar; ++m_cursor) { } - ++m_cursor; - } - - - String readString () { - if (m_buffer.length () < m_cursor) { - return ""; - } - String out; - - for (; m_cursor < m_buffer.length () && m_buffer[m_cursor] != kNullChar; ++m_cursor) { - out += m_buffer[m_cursor]; - } - ++m_cursor; - - return out; - } - - void shiftToEnd () { - m_cursor = m_buffer.length (); - } - -public: - Twin data () { - return { m_buffer.data (), m_buffer.length () }; - } -}; - -class EntityLinkage : public Singleton { -private: -#if defined (CR_WINDOWS) -# define DLSYM_FUNCTION GetProcAddress -# define DLCLOSE_FUNCTION FreeLibrary -# define DLSYM_RETURN SharedLibrary::Handle -# define DLSYM_HANDLE HMODULE -#else -# define DLSYM_FUNCTION dlsym -# define DLCLOSE_FUNCTION dlclose -# define DLSYM_RETURN SharedLibrary::Handle -# define DLSYM_HANDLE SharedLibrary::Handle -#endif - -private: - bool m_paused { false }; - - Detour m_dlsym; - Detour m_dlclose; - HashMap m_exports; - - SharedLibrary m_self; - -public: - EntityLinkage () = default; - -public: - void initialize (); - SharedLibrary::Func lookup (SharedLibrary::Handle module, const char *function); - - int close (DLSYM_HANDLE module) { - if (m_self.handle () == module) { - disable (); - return m_dlclose (module); - } - return m_dlclose (module); - } - -public: - void callPlayerFunction (edict_t *ent); - -public: - void enable () { - if (m_dlsym.detoured ()) { - return; - } - m_dlsym.detour (); - } - - void disable () { - if (!m_dlsym.detoured ()) { - return; - } - m_dlsym.restore (); - } - - void setPaused (bool what) { - m_paused = what; - } - - bool isPaused () const { - return m_paused; - } - -public: - static SharedLibrary::Func CR_STDCALL lookupHandler (SharedLibrary::Handle module, const char *function) { - return EntityLinkage::instance ().lookup (module, function); - } - - static int CR_STDCALL closeHandler (DLSYM_HANDLE module) { - return EntityLinkage::instance ().close (module); - } - -public: - void flush () { - m_exports.clear (); - } - - bool needsBypass () const { - return !plat.win && !Game::instance ().isDedicated (); - } -}; - // expose globals CR_EXPOSE_GLOBAL_SINGLETON (Game, game); CR_EXPOSE_GLOBAL_SINGLETON (LightMeasure, illum); -CR_EXPOSE_GLOBAL_SINGLETON (EntityLinkage, ents); diff --git a/inc/graph.h b/inc/graph.h index a854030..afdfb80 100644 --- a/inc/graph.h +++ b/inc/graph.h @@ -204,7 +204,7 @@ public: int getForAnalyzer (const Vector &origin, const float maxRange); int getNearest (const Vector &origin, const float range = kInfiniteDistance, int flags = -1); int getNearestNoBuckets (const Vector &origin, const float range = kInfiniteDistance, int flags = -1); - int getEditorNearest (); + int getEditorNearest (const float maxRange = 50.0f); int clearConnections (int index); int getBspSize (); int locateBucket (const Vector &pos); @@ -302,8 +302,8 @@ public: } // check nodes range - bool exists (int index) const { - return index >= 0 && index < length (); + template bool exists (U index) const { + return index >= 0 && index < static_cast (length ()); } // get real nodes num diff --git a/inc/hooks.h b/inc/hooks.h new file mode 100644 index 0000000..0e81d11 --- /dev/null +++ b/inc/hooks.h @@ -0,0 +1,193 @@ +// +// YaPB, based on PODBot by Markus Klinge ("CountFloyd"). +// Copyright © YaPB Project Developers . +// +// SPDX-License-Identifier: MIT +// + +#pragma once + +// simple handler for parsing and rewriting queries (fake queries) +class QueryBuffer { + SmallArray m_buffer {}; + size_t m_cursor {}; + +public: + QueryBuffer (const uint8_t *msg, size_t length, size_t shift) : m_cursor (0) { + m_buffer.insert (0, msg, length); + m_cursor += shift; + } + +public: + template T read () { + T result {}; + constexpr auto size = sizeof (T); + + if (m_cursor + size > m_buffer.length ()) { + return 0; + } + + memcpy (&result, m_buffer.data () + m_cursor, size); + m_cursor += size; + + return result; + } + + // must be called right after read + template void write (T value) { + constexpr auto size = sizeof (value); + memcpy (m_buffer.data () + m_cursor - size, &value, size); + } + + template void skip () { + constexpr auto size = sizeof (T); + + if (m_cursor + size > m_buffer.length ()) { + return; + } + m_cursor += size; + } + + void skipString () { + if (m_buffer.length () < m_cursor) { + return; + } + for (; m_cursor < m_buffer.length () && m_buffer[m_cursor] != kNullChar; ++m_cursor) {} + ++m_cursor; + } + + + String readString () { + if (m_buffer.length () < m_cursor) { + return ""; + } + String out; + + for (; m_cursor < m_buffer.length () && m_buffer[m_cursor] != kNullChar; ++m_cursor) { + out += m_buffer[m_cursor]; + } + ++m_cursor; + + return out; + } + + void shiftToEnd () { + m_cursor = m_buffer.length (); + } + +public: + Twin data () { + return { m_buffer.data (), m_buffer.length () }; + } +}; + +// used for response with fake timestamps and bots count in server responses +class ServerQueryHook : public Singleton { +private: + using SendToProto = decltype (sendto); + +private: + Detour m_sendToDetour { }; + +public: + ServerQueryHook () = default; + ~ServerQueryHook () = default; + +public: + // initialzie and install hook + void init (); + +public: + // disables send hook + bool disable () { + return m_sendToDetour.restore (); + } + +}; + +// used for transit calls between game dll and engine without all needed functions on bot side +class DynamicLinkerHook : public Singleton { +private: +#if defined (CR_WINDOWS) +# define DLSYM_FUNCTION GetProcAddress +# define DLCLOSE_FUNCTION FreeLibrary +#else +# define DLSYM_FUNCTION dlsym +# define DLCLOSE_FUNCTION dlclose +#endif + +private: + using DlsymProto = SharedLibrary::Func CR_STDCALL (SharedLibrary::Handle, const char *); + using DlcloseProto = int CR_STDCALL (SharedLibrary::Handle); + +private: + bool m_paused { false }; + + Detour m_dlsym; + Detour m_dlclose; + HashMap m_exports; + + SharedLibrary m_self; + +public: + DynamicLinkerHook () = default; + ~DynamicLinkerHook () = default; + +public: + void initialize (); + bool needsBypass () const; + + SharedLibrary::Func lookup (SharedLibrary::Handle module, const char *function); + + decltype (auto) close (SharedLibrary::Handle module) { + if (m_self.handle () == module) { + disable (); + return m_dlclose (module); + } + return m_dlclose (module); + } + +public: + bool callPlayerFunction (edict_t *ent); + +public: + void enable () { + if (m_dlsym.detoured ()) { + return; + } + m_dlsym.detour (); + } + + void disable () { + if (!m_dlsym.detoured ()) { + return; + } + m_dlsym.restore (); + } + + void setPaused (bool what) { + m_paused = what; + } + + bool isPaused () const { + return m_paused; + } + +public: + static SharedLibrary::Func CR_STDCALL lookupHandler (SharedLibrary::Handle module, const char *function) { + return instance ().lookup (module, function); + } + + static int CR_STDCALL closeHandler (SharedLibrary::Handle module) { + return instance ().close (module); + } + +public: + void flush () { + m_exports.clear (); + } +}; + +// expose global +CR_EXPOSE_GLOBAL_SINGLETON (DynamicLinkerHook, entlink); +CR_EXPOSE_GLOBAL_SINGLETON (ServerQueryHook, fakequeries); diff --git a/inc/planner.h b/inc/planner.h index 1c8d0c2..1dd2d62 100644 --- a/inc/planner.h +++ b/inc/planner.h @@ -111,9 +111,6 @@ private: // clears the currently built route void clearRoute (); - // can the node can be skipped? - bool cantSkipNode (const int a, const int b); - // do a post-smoothing after a* finished constructing path void postSmooth (NodeAdderFn onAddedNode); @@ -156,6 +153,10 @@ public: size_t getMaxLength () const { return m_length / 2; } + +public: + // can the node can be skipped? + static bool cantSkipNode (const int a, const int b, bool skipVisCheck = false); }; // floyd-warshall shortest path algorithm diff --git a/inc/support.h b/inc/support.h index 4dbe457..18908be 100644 --- a/inc/support.h +++ b/inc/support.h @@ -17,10 +17,8 @@ private: StringArray m_sentences {}; SmallArray m_clients {}; - SmallArray > m_tags {}; HashMap m_weaponAlias {}; - Detour m_sendToDetour { }; public: BotSupport (); @@ -67,23 +65,11 @@ public: bool findNearestPlayer (void **holder, edict_t *to, float searchDistance = 4096.0, bool sameTeam = false, bool needBot = false, bool needAlive = false, bool needDrawn = false, bool needBotWithC4 = false); // tracing decals for bots spraying logos - void traceDecals (entvars_t *pev, TraceResult *trace, int logotypeIndex); + void decalTrace (entvars_t *pev, TraceResult *trace, int logotypeIndex); // update stats on clients void updateClients (); - // chat helper to strip the clantags out of the string - void stripTags (String &line); - - // chat helper to make player name more human-like - void humanizePlayerName (String &playerName); - - // chat helper to add errors to the bot chat string - void addChatErrors (String &line); - - // chat helper to find keywords for given string - bool checkKeywords (StringRef line, String &reply); - // generates ping bitmask for SVC_PINGS message int getPingBitmask (edict_t *ent, int loss, int ping); @@ -96,9 +82,6 @@ public: // send modified pings to all the clients void emitPings (edict_t *to); - // installs the sendto function interception - void installSendTo (); - // checks if same model omitting the models directory bool isModel (const edict_t *ent, StringRef model); @@ -113,6 +96,7 @@ public: // re-show welcome after changelevel ? void setNeedForWelcome (bool need) { m_needToSendWelcome = need; + m_welcomeReceiveTime = -1.0f; } // get array of clients @@ -130,11 +114,6 @@ public: return m_clients[index]; } - // disables send hook - bool disableSendTo () { - return m_sendToDetour.restore (); - } - // gets the shooting cone deviation float getShootingCone (edict_t *ent, const Vector &pos) { return ent->v.v_angle.forward () | (pos - (ent->v.origin + ent->v.view_ofs)).normalize (); // he's facing it, he meant it diff --git a/inc/version.h.in b/inc/version.h.in index 01047c4..7a2aa4c 100644 --- a/inc/version.h.in +++ b/inc/version.h.in @@ -7,14 +7,14 @@ #pragma once -// generated by meson build system +// generated by meson/cmake build system #ifndef MODULE_BUILD_HASH -# define MODULE_COMMIT_COUNT "@count@" -# define MODULE_COMMIT_HASH "@hash@" -# define MODULE_AUTHOR @author@ -# define MODULE_MACHINE "@machine@" -# define MODULE_COMPILER "@compiler@" -# define MODULE_VERSION "@version@" -# define MODULE_VERSION_FILE @winver@,@count@ -# define MODULE_BUILD_ID "@count@:@hash@" +# define MODULE_COMMIT_COUNT "@BUILD_COUNT@" +# define MODULE_COMMIT_HASH "@BUILD_HASH@" +# define MODULE_AUTHOR @BUILD_AUTHOR@ +# define MODULE_MACHINE "@BUILD_MACHINE@" +# define MODULE_COMPILER "@BUILD_COMPILER@" +# define MODULE_VERSION "@BUILD_VERSION@" +# define MODULE_VERSION_FILE @BUILD_WINVER@,@BUILD_COUNT@ +# define MODULE_BUILD_ID "@BUILD_COUNT@:@BUILD_HASH@" #endif diff --git a/inc/yapb.h b/inc/yapb.h index f2c6664..66a6429 100644 --- a/inc/yapb.h +++ b/inc/yapb.h @@ -19,25 +19,7 @@ using namespace cr; #include #include #include - -// links keywords and replies together -struct ChatKeywords { - StringArray keywords; - StringArray replies; - StringArray usedReplies; - -public: - ChatKeywords () = default; - - ChatKeywords (const StringArray &keywords, const StringArray &replies) { - this->keywords.clear (); - this->replies.clear (); - this->usedReplies.clear (); - - this->keywords.insert (0, keywords); - this->replies.insert (0, replies); - } -}; +#include // tasks definition struct BotTask { @@ -127,16 +109,6 @@ struct Client { ClientNoise noise; }; -// define chatting collection structure -struct ChatCollection { - int chatProbability {}; - float chatDelay {}; - float timeNextChat {}; - int entityIndex {}; - String sayText {}; - StringArray lastUsedSentences {}; -}; - // include bot graph stuff #include #include @@ -855,6 +827,7 @@ private: #include "config.h" #include "support.h" +#include "hooks.h" #include "sounds.h" #include "message.h" #include "engine.h" diff --git a/meson.build b/meson.build index 3b4d954..7ff0538 100644 --- a/meson.build +++ b/meson.build @@ -6,7 +6,7 @@ # # version is now passed into the bot dll -project ( +project( 'yapb', 'cpp', version: '4.4', @@ -80,15 +80,15 @@ if git.found() cxxflags += flags_versioned version_data = configuration_data() - version_data.set('count', count) - version_data.set('hash', hash) - version_data.set('author', author) - version_data.set('machine', machine) - version_data.set('version', bot_version) - version_data.set('winver', winver) - version_data.set('compiler', '@0@ @1@'.format (cxx, cxx_version)) + version_data.set('BUILD_COUNT', count) + version_data.set('BUILD_HASH', hash) + version_data.set('BUILD_AUTHOR', author) + version_data.set('BUILD_MACHINE', machine) + version_data.set('BUILD_VERSION', bot_version) + version_data.set('BUILD_WINVER', winver) + version_data.set('BUILD_COMPILER', '@0@ @1@'.format(cxx, cxx_version)) - configure_file (input: 'inc/version.h.in', output: 'version.build.h', configuration: version_data) + configure_file(input: 'inc/version.h.in', output: 'version.build.h', configuration: version_data) endif # define crlib native build @@ -102,7 +102,7 @@ if cxx == 'clang' or cxx == 'gcc' '-fno-threadsafe-statics', '-pthread' ] - if not opt_native and cpu != 'arm' + if not opt_native and cpu != 'arm' and not cpu.startswith('ppc') cxxflags += '-mtune=generic' endif @@ -110,7 +110,7 @@ if cxx == 'clang' or cxx == 'gcc' cxxflags += [ '-march=armv8-a+fp+simd', ] - elif cpu != 'arm' + elif cpu != 'arm' and not cpu.startswith('ppc') cxxflags += [ '-mmmx', '-msse', '-msse2', '-msse3', '-mssse3', '-mfpmath=sse' ] @@ -128,7 +128,7 @@ if cxx == 'clang' or cxx == 'gcc' '-funroll-loops', '-fomit-frame-pointer', '-fno-stack-protector', '-fvisibility=hidden', '-fvisibility-inlines-hidden' ] - if os != 'darwin' and os != 'windows' and cpu != 'aarch64' and cpu != 'arm' + if os != 'darwin' and os != 'windows' and cpu != 'aarch64' and cpu != 'arm' and not cpu.startswith('ppc') cxxflags += [ '-fdata-sections', '-ffunction-sections', @@ -181,7 +181,7 @@ if cxx == 'clang' or cxx == 'gcc' endif # by default we buid 32bit binaries - if not opt_64bit and cpu != 'aarch64' and cpu != 'arm' + if not opt_64bit and cpu != 'aarch64' and cpu != 'arm' and not cpu.startswith('ppc') cxxflags += '-m32' ldflags += '-m32' @@ -261,6 +261,7 @@ sources = files( 'src/control.cpp', 'src/engine.cpp', 'src/graph.cpp', + 'src/hooks.cpp', 'src/linkage.cpp', 'src/manager.cpp', 'src/module.cpp', diff --git a/src/analyze.cpp b/src/analyze.cpp index 7e2f5e7..88b7a9d 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -52,6 +52,9 @@ void GraphAnalyze::update () { if (m_updateInterval >= game.time ()) { return; } + else { + displayOverlayMessage (); + } // add basic nodes if (!m_basicsCreated) { @@ -157,6 +160,8 @@ void GraphAnalyze::finish () { return; } vistab.startRebuild (); + ctrl.enableDrawModels (false); + cv_quota.revert (); } } @@ -171,13 +176,6 @@ void GraphAnalyze::optimize () { } cleanup (); - // clear the useless connections - if (cv_graph_analyze_clean_paths_on_finish.bool_ ()) { - for (auto i = 0; i < graph.length (); ++i) { - graph.clearConnections (i); - } - } - auto smooth = [] (const Array &nodes) { Vector result; @@ -203,7 +201,7 @@ void GraphAnalyze::optimize () { Array indexes; for (const auto &link : path.links) { - if (graph.exists (link.index) && !m_optimizedNodes[link.index] && cr::fequal (path.origin.z, graph[link.index].origin.z)) { + if (graph.exists (link.index) && !m_optimizedNodes[link.index] && !AStarAlgo::cantSkipNode (path.number, link.index, true)) { indexes.emplace (link.index); } } @@ -218,6 +216,13 @@ void GraphAnalyze::optimize () { graph.add (NodeAddFlag::Normal, pos); } } + + // clear the useless connections + if (cv_graph_analyze_clean_paths_on_finish.bool_ ()) { + for (auto i = 0; i < graph.length (); ++i) { + graph.clearConnections (i); + } + } } void GraphAnalyze::cleanup () { @@ -262,6 +267,37 @@ void GraphAnalyze::cleanup () { } } +void GraphAnalyze::displayOverlayMessage () { + auto listenserverEdict = game.getLocalEntity (); + + if (game.isNullEntity (listenserverEdict) || !m_isAnalyzing) { + return; + } + constexpr StringRef analyzeHudMesssage = + "+--------------------------------------------------------+\n" + " Map analysis for bots is in progress. Please Wait.. \n" + "+--------------------------------------------------------+\n"; + + static hudtextparms_t textParams {}; + + textParams.channel = 1; + textParams.x = -1.0f; + textParams.y = -1.0f; + textParams.effect = 1; + + textParams.r1 = textParams.r2 = static_cast (255); + textParams.g1 = textParams.g2 = static_cast (31); + textParams.b1 = textParams.b2 = static_cast (75); + textParams.a1 = textParams.a2 = static_cast (0); + + textParams.fadeinTime = 0.0078125f; + textParams.fadeoutTime = 0.0078125f; + textParams.holdTime = m_updateInterval; + textParams.fxTime = 0.25f; + + game.sendHudMessage (listenserverEdict, textParams, analyzeHudMesssage); +} + void GraphAnalyze::flood (const Vector &pos, const Vector &next, float range) { range *= 0.75f; diff --git a/src/botlib.cpp b/src/botlib.cpp index 0727636..d3cb24d 100644 --- a/src/botlib.cpp +++ b/src/botlib.cpp @@ -1686,7 +1686,7 @@ void Bot::refreshEnemyPredict () { if (distanceToLastEnemySq > cr::sqrf (128.0f) && (distanceToLastEnemySq < cr::sqrf (2048.0f) || usesSniper ())) { m_aimFlags |= AimFlags::PredictPath; } - const bool denyLastEnemy = pev->velocity.lengthSq2d () > 0.0f && distanceToLastEnemySq < cr::sqrf (256.0f); + const bool denyLastEnemy = pev->velocity.lengthSq2d () > 0.0f && distanceToLastEnemySq < cr::sqrf (256.0f) && m_shootTime + 2.5f > game.time (); if (!denyLastEnemy && seesEntity (m_lastEnemyOrigin, true)) { m_aimFlags |= AimFlags::LastEnemy; @@ -1778,19 +1778,14 @@ void Bot::setConditions () { m_numFriendsLeft = numFriendsNear (pev->origin, kInfiniteDistance); m_numEnemiesLeft = numEnemiesNear (pev->origin, kInfiniteDistance); - auto clearLastEnemy = [&] () { - m_lastEnemyOrigin = nullptr; - m_lastEnemy = nullptr; - }; - // check if our current enemy is still valid if (!game.isNullEntity (m_lastEnemy)) { if (!util.isAlive (m_lastEnemy) && m_shootAtDeadTime < game.time ()) { - clearLastEnemy (); + m_lastEnemy = nullptr; } } else { - clearLastEnemy (); + m_lastEnemy = nullptr; } // don't listen if seeing enemy, just checked for sounds or being blinded (because its inhuman) @@ -1798,8 +1793,20 @@ void Bot::setConditions () { updateHearing (); m_soundUpdateTime = game.time () + 0.25f; } - else if (m_heardSoundTime < game.time ()) { + else if (m_heardSoundTime + 10.0f < game.time ()) { m_states &= ~Sense::HearingEnemy; + + // clear the last enemy pointers if time has passed or enemy far away + if (!m_lastEnemyOrigin.empty ()) { + auto distanceSq = pev->origin.distanceSq (m_lastEnemyOrigin); + + if (distanceSq > cr::sqrf (2048.0f) || (game.isNullEntity (m_enemy) && m_seeEnemyTime + 10.0f < game.time ())) { + m_lastEnemyOrigin = nullptr; + m_lastEnemy = nullptr; + + m_aimFlags &= ~AimFlags::LastEnemy; + } + } } refreshEnemyPredict (); @@ -2001,11 +2008,11 @@ void Bot::filterTasks () { offensive = subsumeDesire (offensive, pickup); // if offensive task, don't allow picking up stuff auto sub = maxDesire (offensive, def); // default normal & careful tasks against offensive actions - auto final = subsumeDesire (&filter[Task::Blind], maxDesire (survive, sub)); // reason about fleeing instead + auto finalTask = subsumeDesire (&filter[Task::Blind], maxDesire (survive, sub)); // reason about fleeing instead if (!m_tasks.empty ()) { - final = maxDesire (final, getTask ()); - startTask (final->id, final->desire, final->data, final->time, final->resume); // push the final behavior in our task stack to carry out + finalTask = maxDesire (finalTask, getTask ()); + startTask (finalTask->id, finalTask->desire, finalTask->data, finalTask->time, finalTask->resume); // push the final behavior in our task stack to carry out } } @@ -2733,12 +2740,6 @@ void Bot::frame () { kick (); return; } - - // clear enemy far away - if (!m_lastEnemyOrigin.empty () && !game.isNullEntity (m_lastEnemy) && pev->origin.distanceSq (m_lastEnemyOrigin) >= cr::sqrf (2048.0f)) { - m_lastEnemy = nullptr; - m_lastEnemyOrigin = nullptr; - } m_slowFrameTimestamp = game.time () + 0.5f; } @@ -3097,7 +3098,7 @@ void Bot::showDebugOverlay () { } auto overlayEntity = graph.getEditor (); - if (overlayEntity->v.iuser2 == entindex ()) { + if (overlayEntity->v.iuser2 == entindex () && overlayEntity->v.origin.distanceSq (pev->origin) < cr::sqrf (256.0f)) { displayDebugOverlay = true; } @@ -3162,12 +3163,11 @@ void Bot::showDebugOverlay () { if (m_tasks.empty ()) { return; } - const auto drawTime = globals->frametime * 500.0f; if (tid != getCurrentTaskId () || index != m_currentNodeIndex || goal != getTask ()->data || m_timeDebugUpdateTime < game.time ()) { tid = getCurrentTaskId (); - index = m_currentNodeIndex; goal = getTask ()->data; + index = m_currentNodeIndex; String enemy = "(none)"; @@ -3191,31 +3191,38 @@ void Bot::showDebugOverlay () { aimFlags.appendf (" %s", flags[static_cast (bit)]); } } - auto weapon = util.weaponIdToAlias (m_currentWeapon); + StringRef weapon = util.weaponIdToAlias (m_currentWeapon); + StringRef debugData = strings.format ( + "\n\n\n\n\n%s (H:%.1f/A:%.1f)- Task: %d=%s Desire:%.02f\n" + "Item: %s Clip: %d Ammo: %d%s Money: %d AimFlags: %s\n" + "SP=%.02f SSP=%.02f I=%d PG=%d G=%d T: %.02f MT: %d\n" + "Enemy=%s Pickup=%s Type=%s Terrain=%s Stuck=%s\n", + pev->netname.str (), m_healthValue, pev->armorvalue, + tid, tasks[tid], getTask ()->desire, weapon, getAmmoInClip (), + getAmmo (), m_isReloading ? " (R)" : "", m_moneyAmount, aimFlags.trim (), + m_moveSpeed, m_strafeSpeed, index, m_prevGoalIndex, goal, m_navTimeset - game.time (), + pev->movetype, enemy, pickup, personalities[m_personality], boolValue (m_checkTerrain), + boolValue (m_isStuck)); - String debugData; - debugData.assignf ("\n\n\n\n\n%s (H:%.1f/A:%.1f)- Task: %d=%s Desire:%.02f\nItem: %s Clip: %d Ammo: %d%s Money: %d AimFlags: %s\nSP=%.02f SSP=%.02f I=%d PG=%d G=%d T: %.02f MT: %d\nEnemy=%s Pickup=%s Type=%s Terrain=%s Stuck=%s\n", pev->netname.str (), m_healthValue, pev->armorvalue, tid, tasks[tid], getTask ()->desire, weapon, getAmmoInClip (), getAmmo (), m_isReloading ? " (R)" : "", m_moneyAmount, aimFlags.trim (), m_moveSpeed, m_strafeSpeed, index, m_prevGoalIndex, goal, m_navTimeset - game.time (), pev->movetype, enemy, pickup, personalities[m_personality], boolValue (m_checkTerrain), boolValue (m_isStuck)); + static hudtextparms_t textParams {}; - MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullptr, overlayEntity) - .writeByte (TE_TEXTMESSAGE) - .writeByte (1) - .writeShort (MessageWriter::fs16 (-1.0f, 13.0f)) - .writeShort (MessageWriter::fs16 (0.0f, 13.0f)) - .writeByte (0) - .writeByte (m_team == Team::CT ? 0 : 255) - .writeByte (100) - .writeByte (m_team != Team::CT ? 0 : 255) - .writeByte (0) - .writeByte (255) - .writeByte (255) - .writeByte (255) - .writeByte (0) - .writeShort (MessageWriter::fu16 (0.0f, 8.0f)) - .writeShort (MessageWriter::fu16 (0.0f, 8.0f)) - .writeShort (MessageWriter::fu16 (drawTime, 8.0f)) - .writeString (debugData.chars ()); + textParams.channel = 1; + textParams.x = -1.0f; + textParams.y = 0.0f; + textParams.effect = 0; - m_timeDebugUpdateTime = game.time () + drawTime; + textParams.r1 = textParams.r2 = static_cast (m_team == Team::CT ? 0 : 255); + textParams.g1 = textParams.g2 = static_cast (100); + textParams.b1 = textParams.b2 = static_cast (m_team != Team::CT ? 0 : 255); + textParams.a1 = textParams.a2 = static_cast (1); + + textParams.fadeinTime = 0.0f; + textParams.fadeoutTime = 0.0f; + textParams.holdTime = 0.5f; + textParams.fxTime = 0.0f; + + game.sendHudMessage (overlayEntity, textParams, debugData); + m_timeDebugUpdateTime = game.time () + 0.5f; } // green = destination origin @@ -3714,7 +3721,7 @@ void Bot::updateHearing () { } // didn't bot already have an enemy ? take this one... - if (m_lastEnemyOrigin.empty () || m_lastEnemy == nullptr) { + if (m_lastEnemyOrigin.empty () || game.isNullEntity (m_lastEnemy)) { m_lastEnemy = hearedEnemy; m_lastEnemyOrigin = hearedEnemy->v.origin; } diff --git a/src/chatlib.cpp b/src/chatlib.cpp index f591c7d..9bec73c 100644 --- a/src/chatlib.cpp +++ b/src/chatlib.cpp @@ -10,12 +10,37 @@ ConVar cv_chat ("chat", "1", "Enables or disables bots chat functionality."); ConVar cv_chat_percent ("chat_percent", "30", "Bot chances to send random dead chat when killed.", true, 0.0f, 100.0f); -void BotSupport::stripTags (String &line) { +BotChatManager::BotChatManager () { + m_clanTags.emplace ("[[", "]]"); + m_clanTags.emplace ("-=", "=-"); + m_clanTags.emplace ("-[", "]-"); + m_clanTags.emplace ("-]", "[-"); + m_clanTags.emplace ("-}", "{-"); + m_clanTags.emplace ("-{", "}-"); + m_clanTags.emplace ("<[", "]>"); + m_clanTags.emplace ("<]", "[>"); + m_clanTags.emplace ("[-", "-]"); + m_clanTags.emplace ("]-", "-["); + m_clanTags.emplace ("{-", "-}"); + m_clanTags.emplace ("}-", "-{"); + m_clanTags.emplace ("[", "]"); + m_clanTags.emplace ("{", "}"); + m_clanTags.emplace ("<", "["); + m_clanTags.emplace (">", "<"); + m_clanTags.emplace ("-", "-"); + m_clanTags.emplace ("|", "|"); + m_clanTags.emplace ("=", "="); + m_clanTags.emplace ("+", "+"); + m_clanTags.emplace ("(", ")"); + m_clanTags.emplace (")", "("); +} + +void BotChatManager::stripTags (String &line) { if (line.empty ()) { return; } - for (const auto &tag : m_tags) { + for (const auto &tag : m_clanTags) { const size_t start = line.find (tag.first, 0); if (start != String::InvalidIndex) { @@ -30,7 +55,7 @@ void BotSupport::stripTags (String &line) { } } -void BotSupport::humanizePlayerName (String &playerName) { +void BotChatManager::humanizePlayerName (String &playerName) { if (playerName.empty ()) { return; } @@ -49,7 +74,7 @@ void BotSupport::humanizePlayerName (String &playerName) { } } -void BotSupport::addChatErrors (String &line) { +void BotChatManager::addChatErrors (String &line) { // sometimes switch name to lower characters, only valid for the english languge if (rg.chance (8) && cv_language.str () == "en") { line.lowercase (); @@ -72,7 +97,7 @@ void BotSupport::addChatErrors (String &line) { } } -bool BotSupport::checkKeywords (StringRef line, String &reply) { +bool BotChatManager::checkKeywords (StringRef line, String &reply) { // this function checks is string contain keyword, and generates reply to it if (!cv_chat.bool_ () || line.empty ()) { @@ -132,7 +157,7 @@ void Bot::prepareChatMessage (StringRef message) { // must be called before return or on the end auto finishPreparation = [&] () { if (!m_chatBuffer.empty ()) { - util.addChatErrors (m_chatBuffer); + chatlib.addChatErrors (m_chatBuffer); } }; @@ -153,7 +178,7 @@ void Bot::prepareChatMessage (StringRef message) { return "unknown"; } String playerName = ent->v.netname.chars (); - util.humanizePlayerName (playerName); + chatlib.humanizePlayerName (playerName); return playerName; }; @@ -287,7 +312,7 @@ void Bot::prepareChatMessage (StringRef message) { bool Bot::checkChatKeywords (String &reply) { // this function parse chat buffer, and prepare buffer to keyword searching - return util.checkKeywords (utf8tools.strToUpper (m_sayTextBuffer.sayText), reply); + return chatlib.checkKeywords (utf8tools.strToUpper (m_sayTextBuffer.sayText), reply); } bool Bot::isReplyingToChat () { @@ -316,8 +341,8 @@ bool Bot::isReplyingToChat () { } void Bot::checkForChat () { - // say a text every now and then + if (m_isAlive || !cv_chat.bool_ () || game.is (GameFlags::CSDM)) { return; } diff --git a/src/combat.cpp b/src/combat.cpp index e513b62..632b57a 100644 --- a/src/combat.cpp +++ b/src/combat.cpp @@ -386,7 +386,7 @@ bool Bot::lookupEnemies () { if (other->m_seeEnemyTime + 2.0f < game.time () && game.isNullEntity (other->m_lastEnemy) && util.isVisible (pev->origin, other->ent ()) && other->isInViewCone (pev->origin)) { other->m_lastEnemy = newEnemy; - other->m_lastEnemyOrigin = m_lastEnemyOrigin; + other->m_lastEnemyOrigin = newEnemy->v.origin; other->m_seeEnemyTime = game.time (); other->m_states |= (Sense::SuspectEnemy | Sense::HearingEnemy); other->m_aimFlags |= AimFlags::LastEnemy; @@ -405,7 +405,7 @@ bool Bot::lookupEnemies () { // shoot at dying players if no new enemy to give some more human-like illusion if (m_seeEnemyTime + 0.1f > game.time ()) { if (!usesSniper ()) { - m_shootAtDeadTime = game.time () + cr::clamp (m_agressionLevel * 1.25f, 0.25f, 0.45f); + m_shootAtDeadTime = game.time () + cr::clamp (m_agressionLevel * 1.25f, 0.15f, 0.25f); m_actualReactionTime = 0.0f; m_states |= Sense::SuspectEnemy; @@ -424,7 +424,11 @@ bool Bot::lookupEnemies () { } // if no enemy visible check if last one shoot able through wall - if (cv_shoots_thru_walls.bool_ () && rg.chance (conf.getDifficultyTweaks (m_difficulty)->seenThruPct) && m_difficulty >= Difficulty::Normal && isPenetrableObstacle (newEnemy->v.origin)) { + if (cv_shoots_thru_walls.bool_ () + && m_difficulty >= Difficulty::Normal + && rg.chance (conf.getDifficultyTweaks (m_difficulty)->seenThruPct) + && isPenetrableObstacle (newEnemy->v.origin)) { + m_seeEnemyTime = game.time (); m_states |= Sense::SuspectEnemy; @@ -439,7 +443,15 @@ bool Bot::lookupEnemies () { } // check if bots should reload... - if ((m_aimFlags <= AimFlags::PredictPath && m_seeEnemyTime + 3.0f < game.time () && !(m_states & (Sense::SeeingEnemy | Sense::HearingEnemy)) && game.isNullEntity (m_lastEnemy) && game.isNullEntity (m_enemy) && getCurrentTaskId () != Task::ShootBreakable && getCurrentTaskId () != Task::PlantBomb && getCurrentTaskId () != Task::DefuseBomb) || bots.isRoundOver ()) { + if ((m_aimFlags <= AimFlags::PredictPath + && m_seeEnemyTime + 3.0f < game.time () + && !(m_states & (Sense::SeeingEnemy | Sense::HearingEnemy)) + && game.isNullEntity (m_lastEnemy) + && game.isNullEntity (m_enemy) + && getCurrentTaskId () != Task::ShootBreakable + && getCurrentTaskId () != Task::PlantBomb + && getCurrentTaskId () != Task::DefuseBomb) || bots.isRoundOver ()) { + if (!m_reloadState) { m_reloadState = Reload::Primary; } @@ -458,20 +470,20 @@ bool Bot::lookupEnemies () { } Vector Bot::getBodyOffsetError (float distance) { - if (game.isNullEntity (m_enemy)) { + if (game.isNullEntity (m_enemy) || distance < kSprayDistance) { return nullptr; } if (m_aimErrorTime < game.time ()) { - const float hitError = distance / (cr::clamp (static_cast (m_difficulty), 1.0f, 4.0f) * 1000.0f); + const float hitError = distance / (cr::clamp (static_cast (m_difficulty), 1.0f, 4.0f) * 1280.0f); const auto &maxs = m_enemy->v.maxs, &mins = m_enemy->v.mins; - m_aimLastError = Vector (rg.get (mins.x * hitError, maxs.x * hitError), rg.get (mins.y * hitError, maxs.y * hitError), rg.get (mins.z * hitError, maxs.z * hitError)); + m_aimLastError = Vector (rg.get (mins.x * hitError, maxs.x * hitError), rg.get (mins.y * hitError, maxs.y * hitError), rg.get (mins.z * hitError * 0.5f, maxs.z * hitError * 0.5f)); const auto &aimError = conf.getDifficultyTweaks (m_difficulty) ->aimError; m_aimLastError += Vector (rg.get (-aimError.x, aimError.x), rg.get (-aimError.y, aimError.y), rg.get (-aimError.z, aimError.z)); - m_aimErrorTime = game.time () + rg.get (1.5f, 2.0f); + m_aimErrorTime = game.time () + rg.get (0.4f, 0.8f); } return m_aimLastError; } @@ -514,7 +526,7 @@ Vector Bot::getBodyOffsetError (float distance) { else if (util.isPlayer (m_enemy)) { // now take in account different parts of enemy body if (m_enemyParts & (Visibility::Head | Visibility::Body)) { - auto headshotPct = conf.getDifficultyTweaks (m_difficulty)->headshotPct; + const auto headshotPct = conf.getDifficultyTweaks (m_difficulty)->headshotPct; // now check is our skill match to aim at head, else aim at enemy body if (rg.chance (headshotPct)) { @@ -570,7 +582,7 @@ Vector Bot::getCustomHeight (float distance) { { 1.5f, -4.0f, -9.0f } // heavy }; - // only highskilled bots do that + // only high-skilled bots do that if (m_difficulty != Difficulty::Expert || (m_enemy->v.flags & FL_DUCKING)) { return 0.0f; } @@ -987,12 +999,19 @@ void Bot::fireWeapons () { // loop through all the weapons until terminator is found... while (tab[selectIndex].id) { + const auto wid = tab[selectIndex].id; + // is the bot carrying this weapon? - if (weapons & cr::bit (tab[selectIndex].id)) { + if (weapons & cr::bit (wid)) { // is enough ammo available to fire AND check is better to use pistol in our current situation... - if (m_ammoInClip[tab[selectIndex].id] > 0 && !isWeaponBadAtDistance (selectIndex, distance)) { - choosenWeapon = selectIndex; + if (m_ammoInClip[wid] > 0 && !isWeaponBadAtDistance (selectIndex, distance)) { + const auto &prop = conf.getWeaponProp (wid); + + // skip the weapons that cannot be used underwater (regamedll addition) + if (!(pev->waterlevel == 3 && (prop.flags & ITEM_FLAG_NOFIREUNDERWATER))) { + choosenWeapon = selectIndex; + } } } selectIndex++; @@ -1005,11 +1024,11 @@ void Bot::fireWeapons () { // loop through all the weapons until terminator is found... while (tab[selectIndex].id) { - const int id = tab[selectIndex].id; + const int wid = tab[selectIndex].id; // is the bot carrying this weapon? - if (weapons & cr::bit (id)) { - if (getAmmo (id) >= tab[selectIndex].minPrimaryAmmo) { + if (weapons & cr::bit (wid)) { + if (getAmmo (wid) >= tab[selectIndex].minPrimaryAmmo) { // available ammo found, reload weapon if (m_reloadState == Reload::None || m_reloadCheckTime > game.time ()) { @@ -1195,7 +1214,7 @@ void Bot::attackMovement () { } } } - else if (rg.get (0, 100) < (isInNarrowPlace () ? 25 : 75) || usesKnife ()) { + else if (usesKnife ()) { m_fightStyle = Fight::Strafe; } else { @@ -1217,10 +1236,6 @@ void Bot::attackMovement () { m_fightStyle = Fight::Strafe; } - if (usesPistol () && distance < 768.0f) { - m_fightStyle = Fight::Strafe; - } - if (m_fightStyle == Fight::Strafe) { auto swapStrafeCombatDir = [&] () { m_combatStrafeDir = (m_combatStrafeDir == Dodge::Left ? Dodge::Right : Dodge::Left); diff --git a/src/config.cpp b/src/config.cpp index bf0cc8c..fb724af 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -144,6 +144,7 @@ void BotConfig::loadNamesConfig () { } file.close (); } + m_botNames.shuffle (); } void BotConfig::loadWeaponsConfig () { @@ -345,6 +346,7 @@ void BotConfig::loadChatterConfig () { if (event.str == items.first ()) { // this does common work of parsing comma-separated chatter line auto sentences = items[1].split (","); + sentences.shuffle (); for (auto &sound : sentences) { sound.trim ().trim ("\""); @@ -480,6 +482,10 @@ void BotConfig::loadLanguageConfig () { String temp; Twin lang; + auto pushTranslatedMsg = [&] () { + m_language[hashLangString (lang.first.trim ().chars ())] = lang.second.trim (); + }; + // clear all the translations before new load m_language.clear (); @@ -494,7 +500,7 @@ void BotConfig::loadLanguageConfig () { } if (!lang.second.empty () && !lang.first.empty ()) { - m_language[hashLangString (lang.first.trim ().chars ())] = lang.second.trim (); + pushTranslatedMsg (); } } else if (line.startsWith ("[TRANSLATED]") && !temp.empty ()) { @@ -503,6 +509,12 @@ void BotConfig::loadLanguageConfig () { else { temp += line; } + + // make sure last string is translated + if (file.eof () && !lang.first.empty ()) { + lang.second = line.trim (); + pushTranslatedMsg (); + } } file.close (); } diff --git a/src/control.cpp b/src/control.cpp index 7a74ff1..4f6563d 100644 --- a/src/control.cpp +++ b/src/control.cpp @@ -195,7 +195,29 @@ int BotControl::cmdList () { int BotControl::cmdCvars () { enum args { alias = 1, pattern }; - const auto &match = strValue (pattern); + auto match = strValue (pattern); + + // revert all the cvars to their default values + if (match == "defaults") { + msg ("Bots cvars has been reverted to their default values."); + + for (const auto &cvar : game.getCvars ()) { + if (!cvar.self || !cvar.self->ptr || cvar.type == Var::GameRef) { + continue; + } + + // set depending on cvar type + if (cvar.bounded) { + cvar.self->set (cvar.initial); + } + else { + cvar.self->set (cvar.init.chars ()); + } + } + cv_quota.revert (); // quota should be reverted instead of regval + + return BotCommandResult::Handled; + } const bool isSaveMain = match == "save"; const bool isSaveMap = match == "save_map"; @@ -2162,11 +2184,9 @@ bool BotControl::handleMenuCommands (edict_t *ent) { } void BotControl::enableDrawModels (bool enable) { - StringArray entities; - - entities.push ("info_player_start"); - entities.push ("info_player_deathmatch"); - entities.push ("info_vip_start"); + static StringArray entities { + "info_player_start", "info_player_deathmatch", "info_vip_start" + }; if (enable) { game.setPlayerStartDrawModels (); diff --git a/src/engine.cpp b/src/engine.cpp index 46c8262..490c6cf 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -81,7 +81,7 @@ void Game::levelInitialize (edict_t *entities, int max) { bots.initQuota (); // install the sendto hook to fake queries - util.installSendTo (); + fakequeries.init (); // flush any print queue ctrl.resetFlushTimestamp (); @@ -332,11 +332,13 @@ void Game::registerEngineCommand (const char *command, void func ()) { // that for every "command_name" server command it receives, it should call the function // pointed to by "function" in order to handle it. - // check for hl pre 1.1.0.4, as it's doesn't have pfnAddServerCommand + // check for hl pre 1.1.0.4, as it's doesn't have pfnAddServerCommand and many more stuff we need to work if (!plat.isValidPtr (engfuncs.pfnAddServerCommand)) { logger.fatal ("%s's minimum HL engine version is 1.1.0.4 and minimum Counter-Strike is Beta 6.5. Please update your engine / game version.", product.name); } - engfuncs.pfnAddServerCommand (const_cast (command), func); + else { + engfuncs.pfnAddServerCommand (command, func); + } } void Game::playSound (edict_t *ent, const char *sound) { @@ -392,8 +394,8 @@ bool Game::checkVisibility (edict_t *ent, uint8_t *set) { } uint8_t *Game::getVisibilitySet (Bot *bot, bool pvs) { - if (is (GameFlags::Xash3D)) { - return nullptr; // TODO: bug fixed in upstream xash3d, should be removed + if (is (GameFlags::Xash3DLegacy)) { + return nullptr; } auto eyes = bot->getEyesPos (); @@ -460,6 +462,37 @@ void Game::sendServerMessage (StringRef message) { engfuncs.pfnServerPrint (message.chars ()); } +void Game::sendHudMessage (edict_t *ent, const hudtextparms_t &htp, StringRef message) { + constexpr size_t maxSendLength = 512; + + if (game.isNullEntity (ent)) { + return; + } + MessageWriter msg (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullptr, ent); + + msg.writeByte (TE_TEXTMESSAGE); + msg.writeByte (htp.channel & 0xff); + msg.writeShort (MessageWriter::fs16 (htp.x, 13.0f)); + msg.writeShort (MessageWriter::fs16 (htp.y, 13.0f)); + msg.writeByte (htp.effect); + msg.writeByte (htp.r1); + msg.writeByte (htp.g1); + msg.writeByte (htp.b1); + msg.writeByte (htp.a1); + msg.writeByte (htp.r2); + msg.writeByte (htp.g2); + msg.writeByte (htp.b2); + msg.writeByte (htp.a2); + msg.writeShort (MessageWriter::fu16 (htp.fadeinTime, 8.0f)); + msg.writeShort (MessageWriter::fu16 (htp.fadeoutTime, 8.0f)); + msg.writeShort (MessageWriter::fu16 (htp.holdTime, 8.0f)); + + if (htp.effect == 2) { + msg.writeShort (MessageWriter::fu16 (htp.fxTime, 8.0f)); + } + msg.writeString (message.substr (0, maxSendLength).chars ()); +} + void Game::prepareBotArgs (edict_t *ent, String str) { // the purpose of this function is to provide fakeclients (bots) with the same client // command-scripting advantages (putting multiple commands in one line between semicolons) @@ -726,7 +759,7 @@ bool Game::loadCSBinary () { if (!m_gameLib) { logger.fatal ("Unable to load gamedll \"%s\". Exiting... (gamedir: %s)", dll, mod); } - auto ent = m_gameLib.resolve ("trigger_random_unique"); + auto ent = m_gameLib.resolve ("trigger_random_unique"); // detect regamedll by addon entity they provide if (ent != nullptr) { @@ -765,7 +798,12 @@ bool Game::loadCSBinary () { } // detect if we're running modern game - auto entity = m_gameLib.resolve ("weapon_famas"); + auto entity = m_gameLib.resolve ("weapon_famas"); + + // detect legacy xash3d branch + if (engfuncs.pfnCVarGetPointer ("build") != nullptr) { + m_gameFlags |= GameFlags::Xash3DLegacy; + } // detect xash engine if (engfuncs.pfnCVarGetPointer ("host_ver") != nullptr) { @@ -855,6 +893,9 @@ bool Game::postload () { // initialize weapons conf.initWeapons (); + // register engine lib handle + m_engineLib.locate (reinterpret_cast (engfuncs.pfnPrecacheModel)); + if (plat.android) { m_gameFlags |= (GameFlags::Xash3D | GameFlags::Mobility | GameFlags::HasBotVoice | GameFlags::ReGameDLL); @@ -1245,7 +1286,7 @@ float LightMeasure::getLightLevel (const Vector &point) { auto recursiveCheck = [&] () -> bool { if (!isSoftRenderer) { if (is25Anniversary) { - return recursiveLightPoint (reinterpret_cast (m_worldModel->nodes), point, endPoint); + return recursiveLightPoint (reinterpret_cast (m_worldModel->nodes), point, endPoint); } return recursiveLightPoint (reinterpret_cast (m_worldModel->nodes), point, endPoint); } diff --git a/src/entities.cpp b/src/entities.cpp index 293de68..f655f18 100644 --- a/src/entities.cpp +++ b/src/entities.cpp @@ -12,9 +12,9 @@ // other platforms, and you want to run bot on it without metamod, consider enabling LINKENT_STATIC_THUNKS // when compiling the bot, to get it supported. #if defined(LINKENT_STATIC_THUNKS) -void forwardEntity_helper (EntityFunction &addr, const char *name, entvars_t *pev) { +void forwardEntity_helper (EntityProto &addr, const char *name, entvars_t *pev) { if (!addr) { - addr = game.lib ().resolve (name); + addr = game.lib ().resolve (name); } if (!addr) { return; @@ -24,7 +24,7 @@ void forwardEntity_helper (EntityFunction &addr, const char *name, entvars_t *pe #define LINK_ENTITY(entityName) \ CR_EXPORT void entityName (entvars_t *pev) { \ - static EntityFunction addr; \ + static EntityProto addr; \ forwardEntity_helper (addr, __FUNCTION__, pev); \ } diff --git a/src/graph.cpp b/src/graph.cpp index b9642d0..a41e207 100644 --- a/src/graph.cpp +++ b/src/graph.cpp @@ -412,13 +412,14 @@ void BotGraph::addPath (int addIndex, int pathIndex, float distance) { return; } } + auto integerDistance = cr::abs (static_cast (distance)); // check for free space in the connection indices for (auto &link : path.links) { if (link.index == kInvalidNodeIndex) { link.index = static_cast (pathIndex); - link.distance = cr::abs (static_cast (distance)); - + link.distance = integerDistance; + msg ("Path added from %d to %d.", addIndex, pathIndex); return; } @@ -439,7 +440,7 @@ void BotGraph::addPath (int addIndex, int pathIndex, float distance) { msg ("Path added from %d to %d.", addIndex, pathIndex); path.links[slot].index = static_cast (pathIndex); - path.links[slot].distance = cr::abs (static_cast (distance)); + path.links[slot].distance = integerDistance; } } @@ -498,11 +499,11 @@ int BotGraph::getNearestNoBuckets (const Vector &origin, const float range, int return index; } -int BotGraph::getEditorNearest () { +int BotGraph::getEditorNearest (const float maxRange) { if (!hasEditFlag (GraphEdit::On)) { return kInvalidNodeIndex; } - return getNearestNoBuckets (m_editor->v.origin, 50.0f); + return getNearestNoBuckets (m_editor->v.origin, maxRange); } int BotGraph::getNearest (const Vector &origin, const float range, int flags) { @@ -625,12 +626,12 @@ void BotGraph::add (int type, const Vector &pos) { return; case NodeAddFlag::JumpStart: - index = getEditorNearest (); + index = getEditorNearest (25.0f); if (index != kInvalidNodeIndex && m_paths[index].number >= 0) { const float distanceSq = m_editor->v.origin.distanceSq (m_paths[index].origin); - if (distanceSq < cr::sqrf (50.0f)) { + if (distanceSq < cr::sqrf (25.0f)) { addNewNode = false; path = &m_paths[index]; @@ -643,12 +644,12 @@ void BotGraph::add (int type, const Vector &pos) { break; case NodeAddFlag::JumpEnd: - index = getEditorNearest (); + index = getEditorNearest (25.0f); if (index != kInvalidNodeIndex && m_paths[index].number >= 0) { const float distanceSq = m_editor->v.origin.distanceSq (m_paths[index].origin); - if (distanceSq < cr::sqrf (50.0f)) { + if (distanceSq < cr::sqrf (25.0f)) { addNewNode = false; path = &m_paths[index]; @@ -1064,14 +1065,16 @@ void BotGraph::pathCreate (char dir) { if (!isConnected (nodeFrom, nodeTo)) { addPath (nodeFrom, nodeTo, distance); } + for (auto &link : m_paths[nodeFrom].links) { if (link.index == nodeTo && !(link.flags & PathFlag::Jump)) { link.flags |= PathFlag::Jump; m_paths[nodeFrom].radius = 0.0f; + msg ("Path added from %d to %d.", nodeFrom, nodeTo); } else if (link.index == nodeTo && (link.flags & PathFlag::Jump)) { - msg ("Denied path creation from %d to %d (path already exists).", nodeFrom, nodeTo); + msg ("Denied path creation from %d to %d (path already exists).", nodeFrom, nodeTo); } } } @@ -1415,7 +1418,7 @@ void BotGraph::initNarrowPlaces () { constexpr int32_t kNarrowPlacesMinGraphVersion = 2; // if version 2 or higher, narrow places already initialized and saved into file - if (m_graphHeader.version >= kNarrowPlacesMinGraphVersion) { + if (m_graphHeader.version >= kNarrowPlacesMinGraphVersion && !hasEditFlag (GraphEdit::On)) { m_narrowChecked = true; return; } @@ -2141,25 +2144,25 @@ void BotGraph::frame () { } static int channel = 0; - auto sendHudMessage = [] (Color color, float x, float y, edict_t *to, StringRef text) { - MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullptr, to) - .writeByte (TE_TEXTMESSAGE) - .writeByte (channel++ & 0xff) // channel - .writeShort (MessageWriter::fs16 (x, 13.0f)) // x - .writeShort (MessageWriter::fs16 (y, 13.0f)) // y - .writeByte (0) // effect - .writeByte (color.red) // r1 - .writeByte (color.green) // g1 - .writeByte (color.blue) // b1 - .writeByte (1) // a1 - .writeByte (color.red) // r2 - .writeByte (color.green) // g2 - .writeByte (color.blue) // b2 - .writeByte (1) // a2 - .writeShort (0) // fadeintime - .writeShort (0) // fadeouttime - .writeShort (MessageWriter::fu16 (1.0f, 8.0f)) // holdtime - .writeString (text.chars ()); + auto sendHudMessage = [&] (Color color, float x, float y, StringRef text) { + static hudtextparms_t textParams {}; + + textParams.channel = channel; + textParams.x = x; + textParams.y = y; + textParams.effect = 0; + + textParams.r1 = textParams.r2 = static_cast (color.red); + textParams.g1 = textParams.g2 = static_cast (color.green); + textParams.b1 = textParams.b2 = static_cast (color.blue); + textParams.a1 = textParams.a2 = static_cast (1); + + textParams.fadeinTime = 0.0f; + textParams.fadeoutTime = 0.0f; + textParams.holdTime = m_pathDisplayTime; + textParams.fxTime = 0.0f; + + game.sendHudMessage (m_editor, textParams, text); if (channel > 3) { channel = 0; @@ -2207,16 +2210,16 @@ void BotGraph::frame () { }; // display some information - sendHudMessage ({ 255, 255, 255 }, 0.0f, 0.025f, m_editor, getNodeData ("Current", nearestIndex)); + sendHudMessage ({ 255, 255, 255 }, 0.0f, 0.025f, getNodeData ("Current", nearestIndex)); // check if we need to show the cached point index if (m_cacheNodeIndex != kInvalidNodeIndex) { - sendHudMessage ({ 255, 255, 255 }, 0.28f, 0.16f, m_editor, getNodeData ("Cached", m_cacheNodeIndex)); + sendHudMessage ({ 255, 255, 255 }, 0.28f, 0.16f, getNodeData ("Cached", m_cacheNodeIndex)); } // check if we need to show the facing point index if (m_facingAtIndex != kInvalidNodeIndex) { - sendHudMessage ({ 255, 255, 255 }, 0.28f, 0.025f, m_editor, getNodeData ("Facing", m_facingAtIndex)); + sendHudMessage ({ 255, 255, 255 }, 0.28f, 0.025f, getNodeData ("Facing", m_facingAtIndex)); } String timeMessage = strings.format (" Map: %s, Time: %s\n", game.getMapName (), util.getCurrentDateTime ()); @@ -2230,10 +2233,10 @@ void BotGraph::frame () { " CT: %d / %d\n" " T: %d / %d\n\n", dangerIndexCT, dangerIndexCT != kInvalidNodeIndex ? practice.getDamage (Team::CT, nearestIndex, dangerIndexCT) : 0, dangerIndexT, dangerIndexT != kInvalidNodeIndex ? practice.getDamage (Team::Terrorist, nearestIndex, dangerIndexT) : 0); - sendHudMessage ({ 255, 255, 255 }, 0.0f, 0.16f, m_editor, practiceText + timeMessage); + sendHudMessage ({ 255, 255, 255 }, 0.0f, 0.16f, practiceText + timeMessage); } else { - sendHudMessage ({ 255, 255, 255 }, 0.0f, 0.16f, m_editor, timeMessage); + sendHudMessage ({ 255, 255, 255 }, 0.0f, 0.16f, timeMessage); } } } @@ -2350,10 +2353,10 @@ bool BotGraph::checkNodes (bool teleportPlayer) { } // perform DFS instead of floyd-warshall, this shit speedup this process in a bit - const auto length = cr::min (static_cast (kMaxNodes), m_paths.length ()); + const auto length = cr::min (static_cast (kMaxNodes), m_paths.length ()); // ensure valid capacity - assert (length > 8 && length < static_cast (kMaxNodes)); + assert (length > 8 && length < static_cast (kMaxNodes)); PathWalk walk; walk.init (length); diff --git a/src/hooks.cpp b/src/hooks.cpp new file mode 100644 index 0000000..9e3e7e0 --- /dev/null +++ b/src/hooks.cpp @@ -0,0 +1,179 @@ +// +// YaPB, based on PODBot by Markus Klinge ("CountFloyd"). +// Copyright © YaPB Project Developers . +// +// SPDX-License-Identifier: MIT +// + +#include + +int32_t BotSupport::sendTo (int socket, const void *message, size_t length, int flags, const sockaddr *dest, int destLength) { + const auto send = [&] (const Twin &msg) -> int32_t { + return Socket::sendto (socket, msg.first, msg.second, flags, dest, destLength); + }; + + auto packet = reinterpret_cast (message); + constexpr int32_t packetLength = 5; + + // player replies response + if (length > packetLength && memcmp (packet, "\xff\xff\xff\xff", packetLength - 1) == 0) { + if (packet[4] == 'D') { + QueryBuffer buffer { packet, length, packetLength }; + auto count = buffer.read (); + + for (uint8_t i = 0; i < count; ++i) { + buffer.skip (); // number + auto name = buffer.readString (); // name + buffer.skip (); // score + + auto ctime = buffer.read (); // override connection time + buffer.write (bots.getConnectTime (name, ctime)); + } + return send (buffer.data ()); + } + else if (packet[4] == 'I') { + QueryBuffer buffer { packet, length, packetLength }; + buffer.skip (); // protocol + + // skip server name, folder, map game + for (size_t i = 0; i < 4; ++i) { + buffer.skipString (); + } + buffer.skip (); // steam app id + buffer.skip (); // players + buffer.skip (); // maxplayers + buffer.skip (); // bots + buffer.write (0); // zero out bot count + + return send (buffer.data ()); + } + else if (packet[4] == 'm') { + QueryBuffer buffer { packet, length, packetLength }; + + buffer.shiftToEnd (); // shift to the end of buffer + buffer.write (0); // zero out bot count + + return send (buffer.data ()); + } + } + return send ({ packet, length }); +} + +void ServerQueryHook::init () { + // if previously requested to disable? + if (!cv_enable_query_hook.bool_ ()) { + if (m_sendToDetour.detoured ()) { + disable (); + } + return; + } + + // do not enable on not dedicated server + if (!game.isDedicated ()) { + return; + } + SendToProto *sendToAddress = sendto; + + // linux workaround with sendto + if (!plat.win && !plat.isNonX86 ()) { + if (game.elib ()) { + auto address = game.elib ().resolve ("sendto"); + + if (address != nullptr) { + sendToAddress = address; + } + } + } + m_sendToDetour.initialize ("ws2_32.dll", "sendto", sendToAddress); + + // enable only on modern games + if (!game.is (GameFlags::Legacy) && (plat.nix || plat.win) && !plat.isNonX86 () && !m_sendToDetour.detoured ()) { + m_sendToDetour.install (reinterpret_cast (BotSupport::sendTo), true); + } +} + +SharedLibrary::Func DynamicLinkerHook::lookup (SharedLibrary::Handle module, const char *function) { + static const auto &gamedll = game.lib ().handle (); + static const auto &self = m_self.handle (); + + const auto resolve = [&] (SharedLibrary::Handle handle) { + return m_dlsym (handle, function); + }; + + if (entlink.needsBypass () && !strcmp (function, "CreateInterface")) { + entlink.setPaused (true); + auto ret = resolve (module); + + entlink.disable (); + + return ret; + } + + // if requested module is yapb module, put in cache the looked up symbol + if (self != module) { + return resolve (module); + } + +#if defined (CR_WINDOWS) + if (HIWORD (function) == 0) { + return resolve (module); + } +#endif + + if (m_exports.exists (function)) { + return m_exports[function]; + } + auto botAddr = resolve (self); + + if (!botAddr) { + auto gameAddr = resolve (gamedll); + + if (gameAddr) { + return m_exports[function] = gameAddr; + } + } + else { + return m_exports[function] = botAddr; + } + return nullptr; +} + +bool DynamicLinkerHook::callPlayerFunction (edict_t *ent) { + auto callPlayer = [&] () { + reinterpret_cast (m_exports["player"]) (&ent->v); + }; + + if (m_exports.exists ("player")) { + callPlayer (); + return true; + } + auto playerFunction = game.lib ().resolve ("player"); + + if (!playerFunction) { + logger.error ("Cannot resolve player() function in GameDLL."); + return false; + } + m_exports["player"] = reinterpret_cast (playerFunction); + callPlayer (); + + return true; +} + +bool DynamicLinkerHook::needsBypass () const { + return !plat.win && !game.isDedicated (); +} + +void DynamicLinkerHook::initialize () { + if (plat.isNonX86 () || game.is (GameFlags::Metamod)) { + return; + } + + m_dlsym.initialize ("kernel32.dll", "GetProcAddress", DLSYM_FUNCTION); + m_dlsym.install (reinterpret_cast (lookupHandler), true); + + if (needsBypass ()) { + m_dlclose.initialize ("kernel32.dll", "FreeLibrary", DLCLOSE_FUNCTION); + m_dlclose.install (reinterpret_cast (closeHandler), true); + } + m_self.locate (&engfuncs); +} diff --git a/src/linkage.cpp b/src/linkage.cpp index 1be4151..9133321 100644 --- a/src/linkage.cpp +++ b/src/linkage.cpp @@ -32,7 +32,58 @@ plugin_info_t Plugin_info = { PT_ANYTIME, // when unloadable }; -CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int) { +// compilers can't create lambdas with vaargs, so put this one in it's own namespace +namespace Hooks { + void handler_engClientCommand (edict_t *ent, char const *format, ...) { + // this function forces the client whose player entity is ent to issue a client command. + // How it works is that clients all have a argv global string in their client DLL that + // stores the command string; if ever that string is filled with characters, the client DLL + // sends it to the engine as a command to be executed. When the engine has executed that + // command, this argv string is reset to zero. Here is somehow a curious implementation of + // ClientCommand: the engine sets the command it wants the client to issue in his argv, then + // the client DLL sends it back to the engine, the engine receives it then executes the + // command therein. Don't ask me why we need all this complicated crap. Anyhow since bots have + // no client DLL, be certain never to call this function upon a bot entity, else it will just + // make the server crash. Since hordes of uncautious, not to say stupid, programmers don't + // even imagine some players on their servers could be bots, this check is performed less than + // sometimes actually by their side, that's why we strongly recommend to check it here too. In + // case it's a bot asking for a client command, we handle it like we do for bot commands + + if (game.isNullEntity (ent)) { + if (game.is (GameFlags::Metamod)) { + RETURN_META (MRES_SUPERCEDE); + } + return; + } + + va_list ap; + auto buffer = strings.chars (); + + va_start (ap, format); + vsnprintf (buffer, StringBuffer::StaticBufferSize, format, ap); + va_end (ap); + + if (util.isFakeClient (ent) && !(ent->v.flags & FL_DORMANT)) { + auto bot = bots[ent]; + + if (bot) { + game.botCommand (bot->pev->pContainingEntity, buffer); + } + + if (game.is (GameFlags::Metamod)) { + RETURN_META (MRES_SUPERCEDE); // prevent bots to be forced to issue client commands + } + return; + } + + if (game.is (GameFlags::Metamod)) { + RETURN_META (MRES_IGNORED); + } + engfuncs.pfnClientCommand (ent, buffer); + } +} + +CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) { // this function is called right after GiveFnptrsToDll() by the engine in the game DLL (or // what it BELIEVES to be the game DLL), in order to copy the list of MOD functions that can // be called by the engine, into a memory block pointed to by the functionTable pointer @@ -49,7 +100,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int) { auto api_GetEntityAPI = game.lib ().resolve (__func__); // pass other DLLs engine callbacks to function table... - if (!api_GetEntityAPI || api_GetEntityAPI (&dllapi, INTERFACE_VERSION) == 0) { + if (!api_GetEntityAPI || api_GetEntityAPI (&dllapi, interfaceVersion) == 0) { logger.fatal ("Could not resolve symbol \"%s\" in the game dll.", __func__); } dllfuncs.dllapi_table = &dllapi; @@ -294,7 +345,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int) { dllapi.pfnServerDeactivate (); // refill export table - ents.flush (); + entlink.flush (); }; table->pfnStartFrame = [] () { @@ -447,12 +498,12 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) { plat.bzero (table, sizeof (enginefuncs_t)); } - if (ents.needsBypass () && !game.is (GameFlags::Metamod)) { + if (entlink.needsBypass () && !game.is (GameFlags::Metamod)) { table->pfnCreateNamedEntity = [] (string_t classname) -> edict_t *{ - if (ents.isPaused ()) { - ents.enable (); - ents.setPaused (false); + if (entlink.isPaused ()) { + entlink.enable (); + entlink.setPaused (false); } return engfuncs.pfnCreateNamedEntity (classname); }; @@ -601,6 +652,9 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) { engfuncs.pfnWriteEntity (value); }; + // very ancient engine versions (pre 2xxx builds) needs this to work correctly + table->pfnClientCommand = Hooks::handler_engClientCommand; + if (!game.is (GameFlags::Metamod)) { table->pfnRegUserMsg = [] (const char *name, int size) { // this function registers a "user message" by the engine side. User messages are network @@ -726,18 +780,19 @@ CR_EXPORT int GetNewDLLFunctions (newgamefuncs_t *table, int *interfaceVersion) // pass them too, else the DLL interfacing wouldn't be complete and the game possibly wouldn't // run properly. - plat.bzero (table, sizeof (newgamefuncs_t)); - if (!(game.is (GameFlags::Metamod))) { auto api_GetNewDLLFunctions = game.lib ().resolve (__func__); // pass other DLLs engine callbacks to function table... if (!api_GetNewDLLFunctions || api_GetNewDLLFunctions (&newapi, interfaceVersion) == 0) { - logger.error ("Could not resolve symbol \"%s\" in the game dll.", __func__); + logger.error ("Could not resolve symbol \"%s\" in the game dll. Continuing...", __func__); + + return HLFalse; } dllfuncs.newapi_table = &newapi; memcpy (table, &newapi, sizeof (newgamefuncs_t)); } + plat.bzero (table, sizeof (newgamefuncs_t)); if (!game.is (GameFlags::Legacy)) { table->pfnOnFreeEntPrivateData = [] (edict_t *ent) { @@ -847,7 +902,7 @@ CR_EXPORT int Meta_Detach (PLUG_LOADTIME now, PL_UNLOAD_REASON reason) { practice.save (); // disable hooks - util.disableSendTo (); + fakequeries.disable (); // make sure all stuff cleared bots.destroy (); @@ -912,7 +967,7 @@ DLL_GIVEFNPTRSTODLL GiveFnptrsToDll (enginefuncs_t *table, globalvars_t *glob) { // initialize dynamic linkents (no memory hacking with xash3d) if (!game.is (GameFlags::Xash3D)) { - ents.initialize (); + entlink.initialize (); } // give the engine functions to the other DLL... @@ -943,7 +998,7 @@ CR_EXPORT int Server_GetPhysicsInterface (int version, server_physics_api_t *phy table->version = SV_PHYSICS_INTERFACE_VERSION; table->SV_CreateEntity = [] (edict_t *ent, const char *name) -> int { - auto func = game.lib ().resolve (name); // lookup symbol in game dll + auto func = game.lib ().resolve (name); // lookup symbol in game dll // found one in game dll ? if (func) { @@ -960,85 +1015,6 @@ CR_EXPORT int Server_GetPhysicsInterface (int version, server_physics_api_t *phy return HLTrue; } -SharedLibrary::Func EntityLinkage::lookup (SharedLibrary::Handle module, const char *function) { - static const auto &gamedll = game.lib ().handle (); - static const auto &self = m_self.handle (); - - const auto resolve = [&] (SharedLibrary::Handle handle) { - return m_dlsym (handle, function); - }; - - if (ents.needsBypass () && !strcmp (function, "CreateInterface")) { - ents.setPaused (true); - auto ret = resolve (module); - - ents.disable (); - - return ret; - } - - // if requested module is yapb module, put in cache the looked up symbol - if (self != module) { - return resolve (module); - } - -#if defined (CR_WINDOWS) - if (HIWORD (function) == 0) { - return resolve (module); - } -#endif - - if (m_exports.exists (function)) { - return m_exports[function]; - } - auto botAddr = resolve (self); - - if (!botAddr) { - auto gameAddr = resolve (gamedll); - - if (gameAddr) { - return m_exports[function] = gameAddr; - } - } - else { - return m_exports[function] = botAddr; - } - return nullptr; -} - -void EntityLinkage::callPlayerFunction (edict_t *ent) { - EntityFunction playerFunction = nullptr; - - if (game.is (GameFlags::Xash3D)) { - playerFunction = game.lib ().resolve ("player"); - } - else { - playerFunction = reinterpret_cast (reinterpret_cast (lookup (game.lib ().handle (), "player"))); - } - - if (!playerFunction) { - logger.fatal ("Cannot resolve player () function in gamedll."); - } - else { - playerFunction (&ent->v); - } -} - -void EntityLinkage::initialize () { - if (plat.arm || game.is (GameFlags::Metamod)) { - return; - } - - m_dlsym.initialize ("kernel32.dll", "GetProcAddress", DLSYM_FUNCTION); - m_dlsym.install (reinterpret_cast (EntityLinkage::lookupHandler), true); - - if (needsBypass ()) { - m_dlclose.initialize ("kernel32.dll", "FreeLibrary", DLCLOSE_FUNCTION); - m_dlclose.install (reinterpret_cast (EntityLinkage::closeHandler), true); - } - m_self.locate (&engfuncs); -} - // add linkents for android #include "entities.cpp" diff --git a/src/manager.cpp b/src/manager.cpp index 51487d1..2a4c5b7 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -8,7 +8,7 @@ #include ConVar cv_autovacate ("autovacate", "1", "Kick bots to automatically make room for human players."); -ConVar cv_autovacate_keep_slots ("autovacate_keep_slots", "1", "How many slots autovacate feature should keep for human players", true, 1.0f, 8.0f); +ConVar cv_autovacate_keep_slots ("autovacate_keep_slots", "1", "How many slots autovacate feature should keep for human players.", true, 1.0f, 8.0f); ConVar cv_kick_after_player_connect ("kick_after_player_connect", "1", "Kick the bot immediately when a human player joins the server (yb_autovacate must be enabled)."); ConVar cv_quota ("quota", "9", "Specifies the number bots to be added to the game.", true, 0.0f, static_cast (kGameMaxPlayers)); @@ -37,6 +37,7 @@ ConVar cv_save_bots_names ("save_bots_names", "1", "Allows to save bot names upo ConVar cv_botskin_t ("botskin_t", "0", "Specifies the bots wanted skin for Terrorist team.", true, 0.0f, 5.0f); ConVar cv_botskin_ct ("botskin_ct", "0", "Specifies the bots wanted skin for CT team.", true, 0.0f, 5.0f); +ConVar cv_preferred_personality ("preferred_personality", "none", "Sets the default personality when creating bots with quota management.\nAllowed values: 'none', 'normal', 'careful', 'rusher'.\nIf 'none' is specified personality chosen randomly.", false); ConVar cv_ping_base_min ("ping_base_min", "7", "Lower bound for base bot ping shown in scoreboard upon creation.", true, 0.0f, 100.0f); ConVar cv_ping_base_max ("ping_base_max", "34", "Upper bound for base bot ping shown in scoreboard upon creation.", true, 0.0f, 100.0f); @@ -149,7 +150,15 @@ void BotManager::execGameEntity (edict_t *ent) { MUTIL_CallGameEntity (PLID, "player", &ent->v); return; } - ents.callPlayerFunction (ent); + + if (!entlink.callPlayerFunction (ent)) { + for (const auto &bot : m_bots) { + if (bot->ent () == ent) { + bot->kick (); + break; + } + } + } } void BotManager::forEach (ForEachBot handler) { @@ -182,25 +191,42 @@ BotCreateResult BotManager::create (StringRef name, int difficulty, int personal ctrl.msg ("Desired team is stacked. Unable to proceed with bot creation."); return BotCreateResult::TeamStacked; } - if (difficulty < 0 || difficulty > 4) { + if (difficulty < Difficulty::Noob || difficulty > Difficulty::Expert) { difficulty = cv_difficulty.int_ (); - if (difficulty < 0 || difficulty > 4) { + if (difficulty < Difficulty::Noob || difficulty > Difficulty::Expert) { difficulty = rg.get (3, 4); cv_difficulty.set (difficulty); } } + // try to set proffered personality + static HashMap personalityMap { + {"normal", Personality::Normal }, + {"careful", Personality::Careful }, + {"rusher", Personality::Rusher }, + }; + + // set personality if requested if (personality < Personality::Normal || personality > Personality::Careful) { - if (rg.chance (50)) { - personality = Personality::Normal; + + // assign preferred if we're forced with cvar + if (personalityMap.exists (cv_preferred_personality.str ())) { + personality = personalityMap[cv_preferred_personality.str ()]; } + + // do a holy random else { if (rg.chance (50)) { - personality = Personality::Rusher; + personality = Personality::Normal; } else { - personality = Personality::Careful; + if (rg.chance (50)) { + personality = Personality::Rusher; + } + else { + personality = Personality::Careful; + } } } } @@ -685,8 +711,9 @@ bool BotManager::kickRandom (bool decQuota, Team fromTeam) { // first try to kick the bot that is currently dead for (const auto &bot : m_bots) { - if (!bot->m_isAlive && belongsTeam (bot.get ())) // is this slot used? - { + + // is this slot used? + if (!bot->m_isAlive && belongsTeam (bot.get ())) { updateQuota (); bot->kick (); @@ -721,8 +748,9 @@ bool BotManager::kickRandom (bool decQuota, Team fromTeam) { // worst case, just kick some random bot for (const auto &bot : m_bots) { - if (belongsTeam (bot.get ())) // is this slot used? - { + + // is this slot used? + if (belongsTeam (bot.get ())) { updateQuota (); bot->kick (); @@ -824,7 +852,8 @@ void BotManager::listBots () { }; for (const auto &bot : bots) { - ctrl.msg ("[%-3.1d]\t%-19.16s\t%-10.12s\t%-3.4s\t%-3.1d\t%-3.1d\t%-3.4s\t%-3.0f secs", bot->index (), bot->pev->netname.chars (), bot->m_personality == Personality::Rusher ? "rusher" : bot->m_personality == Personality::Normal ? "normal" : "careful", botTeam (bot->m_team), bot->m_difficulty, static_cast (bot->pev->frags), bot->m_isAlive ? "yes" : "no", cv_rotate_bots.bool_ () ? bot->m_stayTime - game.time () : 0.0f); + auto timelimitStr = cv_rotate_bots.bool_ () ? strings.format ("%-3.0f secs", bot->m_stayTime - game.time ()) : "unlimited"; + ctrl.msg ("[%-2.1d]\t%-22.16s\t%-10.12s\t%-3.4s\t%-3.1d\t%-3.1d\t%-3.4s\t%s", bot->index (), bot->pev->netname.chars (), bot->m_personality == Personality::Rusher ? "rusher" : bot->m_personality == Personality::Normal ? "normal" : "careful", botTeam (bot->m_team), bot->m_difficulty, static_cast (bot->pev->frags), bot->m_isAlive ? "yes" : "no", timelimitStr); } ctrl.msg ("%d bots", m_bots.length ()); } @@ -1007,7 +1036,9 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int skin) { // set all info buffer keys for this bot auto buffer = engfuncs.pfnGetInfoKeyBuffer (bot); + engfuncs.pfnSetClientKeyValue (clientIndex, buffer, "_vgui_menus", "0"); + engfuncs.pfnSetClientKeyValue (clientIndex, buffer, "_ah", "0"); if (!game.is (GameFlags::Legacy)) { if (cv_show_latency.int_ () == 1) { @@ -1260,7 +1291,16 @@ void BotManager::handleDeath (edict_t *killer, edict_t *victim) { // notice nearby to victim teammates, that attacker is near for (const auto ¬ify : bots) { - if (notify->m_seeEnemyTime + 2.0f < game.time () && notify->m_isAlive && notify->m_team == victimTeam && game.isNullEntity (notify->m_enemy) && killerTeam != victimTeam && util.isVisible (killer->v.origin, notify->ent ())) { + if (notify->m_difficulty >= Difficulty::Hard + && killerTeam != victimTeam + && notify->m_seeEnemyTime + 2.0f < game.time () + && notify->m_isAlive + && notify->m_team == victimTeam + && game.isNullEntity (notify->m_enemy) + && game.isNullEntity (notify->m_lastEnemy) + && util.isVisible (killer->v.origin, notify->ent ())) { + + // make bot look at last e nemy position notify->m_actualReactionTime = 0.0f; notify->m_seeEnemyTime = game.time (); notify->m_enemy = killer; diff --git a/src/navigate.cpp b/src/navigate.cpp index 540b632..13a228d 100644 --- a/src/navigate.cpp +++ b/src/navigate.cpp @@ -1842,7 +1842,6 @@ int Bot::findBombNode () { return graph.getNearest (bomb, 512.0f); // reliability check } - int goal = 0, count = 0; float lastDistanceSq = kInfiniteDistance; diff --git a/src/planner.cpp b/src/planner.cpp index cb25370..69b5400 100644 --- a/src/planner.cpp +++ b/src/planner.cpp @@ -7,10 +7,11 @@ #include -ConVar cv_path_heuristic_mode ("path_heuristic_mode", "3", "Selects the heuristic function mode. For debug purposes only.", true, 0.0f, 4.0f); +ConVar cv_path_heuristic_mode ("path_heuristic_mode", "0", "Selects the heuristic function mode. For debug purposes only.", true, 0.0f, 4.0f); ConVar cv_path_floyd_memory_limit ("path_floyd_memory_limit", "6", "Limit maximum floyd-warshall memory (megabytes). Use Dijkstra if memory exceeds.", true, 0.0, 32.0f); ConVar cv_path_dijkstra_simple_distance ("path_dijkstra_simple_distance", "1", "Use simple distance path calculation instead of running full Dijkstra path cycle. Used only when Floyd matrices unavailable due to memory limit."); ConVar cv_path_astar_post_smooth ("path_astar_post_smooth", "0", "Enables post-smoothing for A*. Reduces zig-zags on paths at cost of some CPU cycles."); +ConVar cv_path_randomize_on_round_start ("path_randomize_on_round_start", "1", "Randomize pathfinding on each round start."); float Heuristic::gfunctionKillsDist (int team, int currentIndex, int parentIndex) { if (parentIndex == kInvalidNodeIndex) { @@ -172,7 +173,7 @@ void AStarAlgo::clearRoute () { m_routes.clear (); } -bool AStarAlgo::cantSkipNode (const int a, const int b) { +bool AStarAlgo::cantSkipNode (const int a, const int b, bool skipVisCheck) { const auto &ag = graph[a]; const auto &bg = graph[b]; @@ -181,12 +182,14 @@ bool AStarAlgo::cantSkipNode (const int a, const int b) { if (hasZeroRadius) { return true; } - const bool notVisible = !vistab.visible (ag.number, bg.number) || !vistab.visible (bg.number, ag.number); - if (notVisible) { - return true; + if (!skipVisCheck) { + const bool notVisible = !vistab.visible (ag.number, bg.number) || !vistab.visible (bg.number, ag.number); + + if (notVisible) { + return true; + } } - const bool tooHigh = cr::abs (ag.origin.z - bg.origin.z) > 17.0f; if (tooHigh) { @@ -257,6 +260,14 @@ AStarResult AStarAlgo::find (int botTeam, int srcIndex, int destIndex, NodeAdder // always clear constructed path m_constructedPath.clear (); + // round start randomizer offset + auto rsRandomizer = 1.0f; + + // randomize path on round start now and then + if (cv_path_randomize_on_round_start.bool_ () && bots.getRoundStartTime () + mp_freezetime.float_ () + 2.0f > game.time ()) { + rsRandomizer = rg.get (0.5f, static_cast (botTeam) * 2.0f + 5.0f); + } + while (!m_routeQue.empty ()) { // remove the first node from the open list int currentIndex = m_routeQue.pop ().index; @@ -302,7 +313,7 @@ AStarResult AStarAlgo::find (int botTeam, int srcIndex, int destIndex, NodeAdder auto childRoute = &m_routes[child.index]; // calculate the F value as F = G + H - const float g = curRoute->g + m_gcalc (botTeam, child.index, currentIndex); + const float g = curRoute->g + m_gcalc (botTeam, child.index, currentIndex) * rsRandomizer; const float h = m_hcalc (child.index, kInvalidNodeIndex, destIndex); const float f = g + h; diff --git a/src/support.cpp b/src/support.cpp index 78c3f92..3de7dfe 100644 --- a/src/support.cpp +++ b/src/support.cpp @@ -32,29 +32,6 @@ BotSupport::BotSupport () { m_sentences.push ("attention, expect experimental armed hostile presence"); m_sentences.push ("warning, medical attention required"); - m_tags.emplace ("[[", "]]"); - m_tags.emplace ("-=", "=-"); - m_tags.emplace ("-[", "]-"); - m_tags.emplace ("-]", "[-"); - m_tags.emplace ("-}", "{-"); - m_tags.emplace ("-{", "}-"); - m_tags.emplace ("<[", "]>"); - m_tags.emplace ("<]", "[>"); - m_tags.emplace ("[-", "-]"); - m_tags.emplace ("]-", "-["); - m_tags.emplace ("{-", "-}"); - m_tags.emplace ("}-", "-{"); - m_tags.emplace ("[", "]"); - m_tags.emplace ("{", "}"); - m_tags.emplace ("<", "["); - m_tags.emplace (">", "<"); - m_tags.emplace ("-", "-"); - m_tags.emplace ("|", "|"); - m_tags.emplace ("=", "="); - m_tags.emplace ("+", "+"); - m_tags.emplace ("(", ")"); - m_tags.emplace (")", "("); - // register weapon aliases m_weaponAlias[Weapon::USP] = "usp"; // HK USP .45 Tactical m_weaponAlias[Weapon::Glock18] = "glock"; // Glock18 Select Fire @@ -112,10 +89,10 @@ bool BotSupport::isVisible (const Vector &origin, edict_t *ent) { return true; } -void BotSupport::traceDecals (entvars_t *pev, TraceResult *trace, int logotypeIndex) { +void BotSupport::decalTrace (entvars_t *pev, TraceResult *trace, int logotypeIndex) { // this function draw spraypaint depending on the tracing results. - auto logo = conf.getRandomLogoName (logotypeIndex); + auto logo = conf.getLogoName (logotypeIndex); int entityIndex = -1, message = TE_DECAL; int decalIndex = engfuncs.pfnDecalIndex (logo.chars ()); @@ -127,6 +104,7 @@ void BotSupport::traceDecals (entvars_t *pev, TraceResult *trace, int logotypeIn if (cr::fequal (trace->flFraction, 1.0f)) { return; } + if (!game.isNullEntity (trace->pHit)) { if (trace->pHit->v.solid == SOLID_BSP || trace->pHit->v.movetype == MOVETYPE_PUSHSTEP) { entityIndex = game.indexOfEntity (trace->pHit); @@ -208,7 +186,6 @@ bool BotSupport::isMonster (edict_t *ent) { if (isHostageEntity (ent)) { return false; } - return true; } @@ -283,24 +260,30 @@ void BotSupport::checkWelcome () { if (game.isDedicated () || !cv_display_welcome_text.bool_ () || !m_needToSendWelcome) { return; } - m_welcomeReceiveTime = 0.0f; - const bool needToSendMsg = (graph.length () > 0 ? m_needToSendWelcome : true); - auto receiveEntity = game.getLocalEntity (); + auto receiveEnt = game.getLocalEntity (); - if (isAlive (receiveEntity) && m_welcomeReceiveTime < 1.0f && needToSendMsg) { - m_welcomeReceiveTime = game.time () + 4.0f; // receive welcome message in four seconds after game has commencing + if (isAlive (receiveEnt) && m_welcomeReceiveTime < 1.0f && needToSendMsg) { + m_welcomeReceiveTime = game.time () + 2.0f + mp_freezetime.float_ (); // receive welcome message in four seconds after game has commencing } - if (m_welcomeReceiveTime > 0.0f && needToSendMsg) { + // legacy welcome message, to respect the original code + constexpr StringRef legacyWelcomeMessage = "Welcome to POD-Bot V2.5 by Count Floyd\n" + "Visit http://www.nuclearbox.com/podbot/ or\n" + " http://www.botepidemic.com/podbot for Updates\n"; + + // it's should be send in very rare cases + const bool sendLegacyWelcome = rg.chance (2); + + if (m_welcomeReceiveTime > 0.0f && m_welcomeReceiveTime < game.time () && needToSendMsg) { if (!game.is (GameFlags::Mobility | GameFlags::Xash3D)) { game.serverCommand ("speak \"%s\"", m_sentences.random ()); } String authorStr = "Official Navigation Graph"; - StringRef graphAuthor = graph.getAuthor (); - StringRef graphModified = graph.getModifiedBy (); + auto graphAuthor = graph.getAuthor (); + auto graphModified = graph.getModifiedBy (); if (!graphAuthor.startsWith (product.name)) { authorStr.assignf ("Navigation Graph by: %s", graphAuthor); @@ -309,30 +292,39 @@ void BotSupport::checkWelcome () { authorStr.appendf (" (Modified by: %s)", graphModified); } } + StringRef modernWelcomeMessage = strings.format ("\nHello! You are playing with %s v%s\nDevised by %s\n\n%s", product.name, product.version, product.author, authorStr); + StringRef modernChatWelcomeMessage = strings.format ("----- %s v%s {%s}, (c) %s, by %s (%s)-----", product.name, product.version, product.date, product.year, product.author, product.url); - MessageWriter (MSG_ONE, msgs.id (NetMsg::TextMsg), nullptr, receiveEntity) + // send a chat-position message + MessageWriter (MSG_ONE, msgs.id (NetMsg::TextMsg), nullptr, receiveEnt) .writeByte (HUD_PRINTTALK) - .writeString (strings.format ("----- %s v%s {%s}, (c) %s, by %s (%s)-----", product.name, product.version, product.date, product.year, product.author, product.url)); + .writeString (modernChatWelcomeMessage.chars ()); - MessageWriter (MSG_ONE, SVC_TEMPENTITY, nullptr, receiveEntity) - .writeByte (TE_TEXTMESSAGE) - .writeByte (1) - .writeShort (MessageWriter::fs16 (-1.0f, 13.0f)) - .writeShort (MessageWriter::fs16 (-1.0f, 13.0f)) - .writeByte (2) - .writeByte (rg.get (33, 255)) - .writeByte (rg.get (33, 255)) - .writeByte (rg.get (33, 255)) - .writeByte (0) - .writeByte (rg.get (230, 255)) - .writeByte (rg.get (230, 255)) - .writeByte (rg.get (230, 255)) - .writeByte (200) - .writeShort (MessageWriter::fu16 (0.0078125f, 8.0f)) - .writeShort (MessageWriter::fu16 (2.0f, 8.0f)) - .writeShort (MessageWriter::fu16 (6.0f, 8.0f)) - .writeShort (MessageWriter::fu16 (0.1f, 8.0f)) - .writeString (strings.format ("\nHello! You are playing with %s v%s\nDevised by %s\n\n%s", product.name, product.version, product.author, authorStr)); + static hudtextparms_t textParams {}; + + textParams.channel = 1; + textParams.x = -1.0f; + textParams.y = sendLegacyWelcome ? 0.0f : -1.0f; + textParams.effect = rg.get (1, 2); + + textParams.r1 = static_cast (sendLegacyWelcome ? 255 : rg.get (33, 255)); + textParams.g1 = static_cast (sendLegacyWelcome ? 0 : rg.get (33, 255)); + textParams.b1 = static_cast (sendLegacyWelcome ? 0 : rg.get (33, 255)); + textParams.a1 = static_cast (0); + + textParams.r2 = static_cast (sendLegacyWelcome ? 255 : rg.get (230, 255)); + textParams.g2 = static_cast (sendLegacyWelcome ? 255 : rg.get (230, 255)); + textParams.b2 = static_cast (sendLegacyWelcome ? 255 : rg.get (230, 255)); + textParams.a2 = static_cast (200); + + textParams.fadeinTime = 0.0078125f; + textParams.fadeoutTime = 2.0f; + textParams.holdTime = 6.0f; + textParams.fxTime = 0.25f; + + // send the hud message + game.sendHudMessage (receiveEnt, textParams, + sendLegacyWelcome ? legacyWelcomeMessage.chars () : modernWelcomeMessage.chars ()); m_welcomeReceiveTime = 0.0f; m_needToSendWelcome = false; @@ -488,10 +480,10 @@ void BotSupport::syncCalculatePings () { int botPing = bot->m_basePing + rg.get (average.first - part, average.first + part) + rg.get (bot->m_difficulty / 2, bot->m_difficulty); const int botLoss = rg.get (average.second / 2, average.second); - if (botPing <= 5) { + if (botPing < 2) { botPing = rg.get (10, 23); } - else if (botPing > 70) { + else if (botPing > 300) { botPing = rg.get (30, 40); } client.ping = getPingBitmask (client.ent, botLoss, botPing); @@ -522,43 +514,6 @@ void BotSupport::emitPings (edict_t *to) { return; } -void BotSupport::installSendTo () { - // if previously requested to disable? - if (!cv_enable_query_hook.bool_ ()) { - if (m_sendToDetour.detoured ()) { - disableSendTo (); - } - return; - } - - // do not enable on not dedicated server - if (!game.isDedicated ()) { - return; - } - using SendToHandle = decltype (sendto); - SendToHandle *sendToAddress = sendto; - - // linux workaround with sendto - if (!plat.win && !plat.arm) { - SharedLibrary engineLib {}; - engineLib.locate (reinterpret_cast (engfuncs.pfnPrecacheModel)); - - if (engineLib) { - auto address = engineLib.resolve ("sendto"); - - if (address != nullptr) { - sendToAddress = address; - } - } - } - m_sendToDetour.initialize ("ws2_32.dll", "sendto", sendToAddress); - - // enable only on modern games - if (!game.is (GameFlags::Legacy) && (plat.nix || plat.win) && !plat.arm && !m_sendToDetour.detoured ()) { - m_sendToDetour.install (reinterpret_cast (BotSupport::sendTo), true); - } -} - bool BotSupport::isModel (const edict_t *ent, StringRef model) { return model.startsWith (ent->v.model.chars (9)); } @@ -575,58 +530,6 @@ String BotSupport::getCurrentDateTime () { return String (timebuf); } -int32_t BotSupport::sendTo (int socket, const void *message, size_t length, int flags, const sockaddr *dest, int destLength) { - const auto send = [&] (const Twin &msg) -> int32_t { - return Socket::sendto (socket, msg.first, msg.second, flags, dest, destLength); - }; - - auto packet = reinterpret_cast (message); - constexpr int32_t packetLength = 5; - - // player replies response - if (length > packetLength && memcmp (packet, "\xff\xff\xff\xff", packetLength - 1) == 0) { - if (packet[4] == 'D') { - QueryBuffer buffer { packet, length, packetLength }; - auto count = buffer.read (); - - for (uint8_t i = 0; i < count; ++i) { - buffer.skip (); // number - auto name = buffer.readString (); // name - buffer.skip (); // score - - auto ctime = buffer.read (); // override connection time - buffer.write (bots.getConnectTime (name, ctime)); - } - return send (buffer.data ()); - } - else if (packet[4] == 'I') { - QueryBuffer buffer { packet, length, packetLength }; - buffer.skip (); // protocol - - // skip server name, folder, map game - for (size_t i = 0; i < 4; ++i) { - buffer.skipString (); - } - buffer.skip (); // steam app id - buffer.skip (); // players - buffer.skip (); // maxplayers - buffer.skip (); // bots - buffer.write (0); // zero out bot count - - return send (buffer.data ()); - } - else if (packet[4] == 'm') { - QueryBuffer buffer { packet, length, packetLength }; - - buffer.shiftToEnd (); // shift to the end of buffer - buffer.write (0); // zero out bot count - - return send (buffer.data ()); - } - } - return send ({ packet, length }); -} - StringRef BotSupport::weaponIdToAlias (int32_t id) { StringRef none = "none"; @@ -636,7 +539,6 @@ StringRef BotSupport::weaponIdToAlias (int32_t id) { return none; } - // helper class for reading wave header class WaveEndianessHelper final : public NonCopyable { private: diff --git a/src/tasks.cpp b/src/tasks.cpp index 3da92ec..4672199 100644 --- a/src/tasks.cpp +++ b/src/tasks.cpp @@ -270,7 +270,7 @@ void Bot::spraypaint_ () { game.testLine (getEyesPos (), getEyesPos () + forward * 128.0f, TraceIgnore::Monsters, ent (), &tr); // paint the actual logo decal - util.traceDecals (pev, &tr, m_logotypeIndex); + util.decalTrace (pev, &tr, m_logotypeIndex); m_timeLogoSpray = game.time () + rg.get (60.0f, 90.0f); } } diff --git a/vc/yapb.vcxproj b/vc/yapb.vcxproj index 9baa0ef..855fbc0 100644 --- a/vc/yapb.vcxproj +++ b/vc/yapb.vcxproj @@ -55,11 +55,13 @@ + + @@ -69,26 +71,35 @@ + - + + + + false + false + false + false + - - true - true - true - true - + + true + true + true + true + + @@ -103,14 +114,6 @@ - - - false - false - false - false - - diff --git a/vc/yapb.vcxproj.filters b/vc/yapb.vcxproj.filters index 03d434e..7c35720 100644 --- a/vc/yapb.vcxproj.filters +++ b/vc/yapb.vcxproj.filters @@ -186,6 +186,12 @@ inc + + inc + + + inc + @@ -254,6 +260,9 @@ src + + src +