fix: bots at difficulty 0 unable to do anything useful

fix: lang configs unable to parse last translated line (fixes #340)
fix: last enemy isn't  cleared instantly with dead entity anymore
fix: bot weakness in pistol rounds
analyzer: improved optimization of useless nodes
linkage: make inability to call gamedll player( non-fatal
linkage: fixed bot boot  on WON engines pre 2000 builds (support for beta 6.5 restored)
cvars: added suupport to revert all cvars to defaults via 'yb cvars defaults'
cvars: added cv_preferred_personality  to select bot default personality
refactor: use single function to send hud messages over the bot code
bot: added random original podbot welcome message to preserve origins of this bot
conf: shuffle bot names and chatter items on conflig load
conf: simplified a bit chatter.cfg syntax (old syntax  still works
build: added support for building with CMake (thanks @Velaron)
refactor: rall the memory hooks moved into their one cpp file
This commit is contained in:
jeefo 2024-01-19 00:03:45 +03:00
commit bf91ef2831
No known key found for this signature in database
GPG key ID: 927BCA0779BEA8ED
35 changed files with 1256 additions and 734 deletions

11
.gitignore vendored
View file

@ -10,3 +10,14 @@ vc/.vs
vc/enc_temp_folder vc/enc_temp_folder
build/ build/
**/cmake-build-debug
**/CMakeCache.txt
**/cmake_install.cmake
**/install_manifest.txt
**/CMakeFiles/
**/CTestTestfile.cmake
**/Makefile
**/*.cbp
**/CMakeScripts
**/compile_commands.json

150
CMakeLists.txt Normal file
View file

@ -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$<$<CONFIG:Debug>: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$<$<CONFIG:Debug>: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 $<TARGET_PDB_FILE:yapb>
DESTINATION "${GAME_DIR}/${SERVER_INSTALL_DIR}/" OPTIONAL)
endif()

View file

@ -9,76 +9,76 @@
RewritePath sound/radio/bot RewritePath sound/radio/bot
Event Radio_CoverMe = ("cover_me", "cover_me2"); Event Radio_CoverMe = cover_me, cover_me2
// Event Radio_YouTakePoint = (""); // Event Radio_YouTakePoint =
// Event Radio_HoldPosition = (""); // Event Radio_HoldPosition =
// Event Radio_RegroupTeam = (""); // 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_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_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_GoGoGo =
// Event Radio_Fallback = (""); // Event Radio_Fallback =
// Event Radio_StickTogether = (""); // Event Radio_StickTogether =
// Event Radio_GetInPosition = (""); // Event Radio_GetInPosition =
// Event Radio_StormTheFront = (""); // Event Radio_StormTheFront =
Event Radio_ReportTeam = ("report_in_team", "anyone_see_them", "anyone_see_anything", "where_are_they", "where_could_they_be"); 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_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_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_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_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_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_ReportingIn = reporting_in
// Event Radio_ShesGonnaBlow = (""); // 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_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_EnemyDown = enemy_down, enemy_down2
// end of radio, begin some voices (NOT SORTED) // 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_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_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_DiePain = pain2, pain4, pain5, pain8, pain9, pain10
Event Chatter_GotBlinded = ("ive_been_blinded", "my_eyes", "i_cant_see", "im_blind"); 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_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_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_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_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_TeamKill = what_happened, noo, oh_my_god, oh_man, oh_no_sad, what_have_you_done
Event Chatter_ReportingIn = ("reporting_in"); Event Chatter_ReportingIn = reporting_in
Event Chatter_GuardDroppedC4 = ("bombsite", "bombsite2"); Event Chatter_GuardDroppedC4 = bombsite, bombsite2
Event Chatter_Camp = ("im_waiting_here"); Event Chatter_Camp = im_waiting_here
Event Chatter_PlantingC4 = ("planting_the_bomb", "planting"); Event Chatter_PlantingC4 = planting_the_bomb, planting
Event Chatter_DefusingC4 = ("defusing", "defusing_bomb", "defusing_bomb_now"); 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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_OneEnemyLeft = one_guy_left, theres_one_left
Event Chatter_TwoEnemiesLeft = ("two_enemies_left", "two_to_go"); Event Chatter_TwoEnemiesLeft = two_enemies_left, two_to_go
Event Chatter_ThreeEnemiesLeft = ("three_left", "three_to_go", "three_to_go2"); 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_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_FoundBombPlace = theres_the_bomb, theres_the_bomb2
Event Chatter_WhereIsTheBomb = ("wheres_the_bomb", "wheres_the_bomb2", "wheres_the_bomb3", "where_is_it"); Event Chatter_WhereIsTheBomb = wheres_the_bomb, wheres_the_bomb2, wheres_the_bomb3, where_is_it
Event Chatter_DefendingBombSite = ("bombsite", "bombsite2"); 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_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_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_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_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_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_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_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_Pinned_Down = they_got_me_pinned_down_here, im_pinned_down
Event Chatter_GottaFindTheBomb = ("theres_the_bomb", "theres_the_bomb2"); 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_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_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_BombSiteSecured = i_wasnt_worried_for_a_minute, that_was_a_close_one, well_done, whew_that_was_close

@ -1 +1 @@
Subproject commit f188cfd399594cdcbca8dda42db842cd0796b7fb Subproject commit 07c54138879ea360913a594b3c11557361e20fed

@ -1 +1 @@
Subproject commit c91a51d769f10a38637af7ee9f03ed76d1fc206c Subproject commit 12a6dbd1388052c0d98cd892ccd9d61e6b043854

View file

@ -52,6 +52,9 @@ private:
// cleanup bad nodes // cleanup bad nodes
void cleanup (); void cleanup ();
// show overlay message about analyzing
void displayOverlayMessage ();
public: public:
// node should be created as crouch // node should be created as crouch

64
inc/chatlib.h Normal file
View file

@ -0,0 +1,64 @@
//
// YaPB, based on PODBot by Markus Klinge ("CountFloyd").
// Copyright © YaPB Project Developers <yapb@jeefo.net>.
//
// 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 <BotChatManager> {
private:
SmallArray <Twin <String, String>> 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);

View file

@ -249,7 +249,7 @@ public:
} }
// get random name by index // get random name by index
StringRef getRandomLogoName (int index) { StringRef getLogoName (int index) {
return m_logos[index]; return m_logos[index];
} }
@ -258,7 +258,7 @@ public:
if (m_custom.exists (name)) { if (m_custom.exists (name)) {
return m_custom[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 ""; return "";
} }

View file

@ -152,7 +152,6 @@ private:
int menuKickPage4 (int item); int menuKickPage4 (int item);
private: private:
void enableDrawModels (bool enable);
void createMenus (); void createMenus ();
public: public:
@ -166,6 +165,7 @@ public:
void assignAdminRights (edict_t *ent, char *infobuffer); void assignAdminRights (edict_t *ent, char *infobuffer);
void maintainAdminRights (); void maintainAdminRights ();
void flushPrintQueue (); void flushPrintQueue ();
void enableDrawModels (bool enable);
public: public:
void setFromConsole (bool console) { void setFromConsole (bool console) {

View file

@ -46,7 +46,8 @@ CR_DECLARE_SCOPED_ENUM (GameFlags,
ReGameDLL = cr::bit (9), // server dll is a regamedll ReGameDLL = cr::bit (9), // server dll is a regamedll
HasFakePings = cr::bit (10), // on that game version we can fake bots pings 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 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 // defines map type
@ -83,7 +84,7 @@ struct ConVarReg {
}; };
// entity prototype // 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 // 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 { class EngineWrap final {
@ -128,6 +129,7 @@ private:
Array <edict_t *> m_breakables {}; Array <edict_t *> m_breakables {};
SmallArray <ConVarReg> m_cvars {}; SmallArray <ConVarReg> m_cvars {};
SharedLibrary m_gameLib {}; SharedLibrary m_gameLib {};
SharedLibrary m_engineLib {};
EngineWrap m_engineWrap {}; EngineWrap m_engineWrap {};
bool m_precached {}; bool m_precached {};
@ -343,6 +345,11 @@ public:
return m_gameLib; return m_gameLib;
} }
// get loaded engine lib
const SharedLibrary &elib () {
return m_engineLib;
}
// get registered cvars list // get registered cvars list
const SmallArray <ConVarReg> &getCvars () { const SmallArray <ConVarReg> &getCvars () {
return m_cvars; return m_cvars;
@ -369,6 +376,9 @@ public:
// helper to sending the server message // helper to sending the server message
void sendServerMessage (StringRef 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 // send server command
template <typename ...Args> void serverCommand (const char *fmt, Args &&...args) { template <typename ...Args> void serverCommand (const char *fmt, Args &&...args) {
engfuncs.pfnServerCommand (strings.concat (strings.format (fmt, cr::forward <Args> (args)...), "\n", StringBuffer::StaticBufferSize)); engfuncs.pfnServerCommand (strings.concat (strings.format (fmt, cr::forward <Args> (args)...), "\n", StringBuffer::StaticBufferSize));
@ -459,6 +469,7 @@ public:
Game::instance ().addNewCvar (name_.chars (), initval, info, bounded, min, max, type, regMissing, regVal, this); Game::instance ().addNewCvar (name_.chars (), initval, info, bounded, min, max, type, regMissing, regVal, this);
} }
public:
template <typename U> constexpr U get () const { template <typename U> constexpr U get () const {
if constexpr (cr::is_same <U, float>::value) { if constexpr (cr::is_same <U, float>::value) {
return ptr->value; return ptr->value;
@ -467,25 +478,41 @@ public:
return ptr->value > 0.0f; return ptr->value > 0.0f;
} }
else if constexpr (cr::is_same <U, int>::value) { else if constexpr (cr::is_same <U, int>::value) {
return static_cast <int> (ptr->value); return static_cast <U> (ptr->value);
}
else if constexpr (cr::is_same <U, StringRef>::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 { bool bool_ () const {
return ptr->value > 0.0f; return get <bool> ();
} }
int int_ () const { int int_ () const {
return static_cast <int> (ptr->value); return get <int> ();
} }
float float_ () const { float float_ () const {
return ptr->value; return get <float> ();
} }
StringRef str () const { StringRef str () const {
return ptr->string; return get <StringRef> ();
} }
StringRef name () const { StringRef name () const {
@ -626,164 +653,6 @@ public:
} }
}; };
// simple handler for parsing and rewriting queries (fake queries)
class QueryBuffer {
SmallArray <uint8_t> 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 <typename T> 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 <typename T> void write (T value) {
constexpr auto size = sizeof (value);
memcpy (m_buffer.data () + m_cursor - size, &value, size);
}
template <typename T> 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 <const uint8_t *, size_t> data () {
return { m_buffer.data (), m_buffer.length () };
}
};
class EntityLinkage : public Singleton <EntityLinkage> {
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 <decltype (DLSYM_FUNCTION)> m_dlsym;
Detour <decltype (DLCLOSE_FUNCTION)> m_dlclose;
HashMap <StringRef, SharedLibrary::Func> 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 // expose globals
CR_EXPOSE_GLOBAL_SINGLETON (Game, game); CR_EXPOSE_GLOBAL_SINGLETON (Game, game);
CR_EXPOSE_GLOBAL_SINGLETON (LightMeasure, illum); CR_EXPOSE_GLOBAL_SINGLETON (LightMeasure, illum);
CR_EXPOSE_GLOBAL_SINGLETON (EntityLinkage, ents);

View file

@ -204,7 +204,7 @@ public:
int getForAnalyzer (const Vector &origin, const float maxRange); int getForAnalyzer (const Vector &origin, const float maxRange);
int getNearest (const Vector &origin, const float range = kInfiniteDistance, int flags = -1); 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 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 clearConnections (int index);
int getBspSize (); int getBspSize ();
int locateBucket (const Vector &pos); int locateBucket (const Vector &pos);
@ -302,8 +302,8 @@ public:
} }
// check nodes range // check nodes range
bool exists (int index) const { template <typename U> bool exists (U index) const {
return index >= 0 && index < length (); return index >= 0 && index < static_cast <U> (length ());
} }
// get real nodes num // get real nodes num

193
inc/hooks.h Normal file
View file

@ -0,0 +1,193 @@
//
// YaPB, based on PODBot by Markus Klinge ("CountFloyd").
// Copyright © YaPB Project Developers <yapb@jeefo.net>.
//
// SPDX-License-Identifier: MIT
//
#pragma once
// simple handler for parsing and rewriting queries (fake queries)
class QueryBuffer {
SmallArray <uint8_t> 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 <typename T> 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 <typename T> void write (T value) {
constexpr auto size = sizeof (value);
memcpy (m_buffer.data () + m_cursor - size, &value, size);
}
template <typename T> 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 <const uint8_t *, size_t> data () {
return { m_buffer.data (), m_buffer.length () };
}
};
// used for response with fake timestamps and bots count in server responses
class ServerQueryHook : public Singleton <ServerQueryHook> {
private:
using SendToProto = decltype (sendto);
private:
Detour <SendToProto> 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 <DynamicLinkerHook> {
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 <DlsymProto> m_dlsym;
Detour <DlcloseProto> m_dlclose;
HashMap <StringRef, SharedLibrary::Func> 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);

View file

@ -111,9 +111,6 @@ private:
// clears the currently built route // clears the currently built route
void clearRoute (); 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 // do a post-smoothing after a* finished constructing path
void postSmooth (NodeAdderFn onAddedNode); void postSmooth (NodeAdderFn onAddedNode);
@ -156,6 +153,10 @@ public:
size_t getMaxLength () const { size_t getMaxLength () const {
return m_length / 2; 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 // floyd-warshall shortest path algorithm

View file

@ -17,10 +17,8 @@ private:
StringArray m_sentences {}; StringArray m_sentences {};
SmallArray <Client> m_clients {}; SmallArray <Client> m_clients {};
SmallArray <Twin <String, String>> m_tags {};
HashMap <int32_t, String> m_weaponAlias {}; HashMap <int32_t, String> m_weaponAlias {};
Detour <decltype (sendto)> m_sendToDetour { };
public: public:
BotSupport (); 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); 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 // 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 // update stats on clients
void updateClients (); 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 // generates ping bitmask for SVC_PINGS message
int getPingBitmask (edict_t *ent, int loss, int ping); int getPingBitmask (edict_t *ent, int loss, int ping);
@ -96,9 +82,6 @@ public:
// send modified pings to all the clients // send modified pings to all the clients
void emitPings (edict_t *to); void emitPings (edict_t *to);
// installs the sendto function interception
void installSendTo ();
// checks if same model omitting the models directory // checks if same model omitting the models directory
bool isModel (const edict_t *ent, StringRef model); bool isModel (const edict_t *ent, StringRef model);
@ -113,6 +96,7 @@ public:
// re-show welcome after changelevel ? // re-show welcome after changelevel ?
void setNeedForWelcome (bool need) { void setNeedForWelcome (bool need) {
m_needToSendWelcome = need; m_needToSendWelcome = need;
m_welcomeReceiveTime = -1.0f;
} }
// get array of clients // get array of clients
@ -130,11 +114,6 @@ public:
return m_clients[index]; return m_clients[index];
} }
// disables send hook
bool disableSendTo () {
return m_sendToDetour.restore ();
}
// gets the shooting cone deviation // gets the shooting cone deviation
float getShootingCone (edict_t *ent, const Vector &pos) { 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 return ent->v.v_angle.forward () | (pos - (ent->v.origin + ent->v.view_ofs)).normalize (); // he's facing it, he meant it

View file

@ -7,14 +7,14 @@
#pragma once #pragma once
// generated by meson build system // generated by meson/cmake build system
#ifndef MODULE_BUILD_HASH #ifndef MODULE_BUILD_HASH
# define MODULE_COMMIT_COUNT "@count@" # define MODULE_COMMIT_COUNT "@BUILD_COUNT@"
# define MODULE_COMMIT_HASH "@hash@" # define MODULE_COMMIT_HASH "@BUILD_HASH@"
# define MODULE_AUTHOR @author@ # define MODULE_AUTHOR @BUILD_AUTHOR@
# define MODULE_MACHINE "@machine@" # define MODULE_MACHINE "@BUILD_MACHINE@"
# define MODULE_COMPILER "@compiler@" # define MODULE_COMPILER "@BUILD_COMPILER@"
# define MODULE_VERSION "@version@" # define MODULE_VERSION "@BUILD_VERSION@"
# define MODULE_VERSION_FILE @winver@,@count@ # define MODULE_VERSION_FILE @BUILD_WINVER@,@BUILD_COUNT@
# define MODULE_BUILD_ID "@count@:@hash@" # define MODULE_BUILD_ID "@BUILD_COUNT@:@BUILD_HASH@"
#endif #endif

View file

@ -19,25 +19,7 @@ using namespace cr;
#include <product.h> #include <product.h>
#include <module.h> #include <module.h>
#include <constant.h> #include <constant.h>
#include <chatlib.h>
// 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);
}
};
// tasks definition // tasks definition
struct BotTask { struct BotTask {
@ -127,16 +109,6 @@ struct Client {
ClientNoise noise; 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 bot graph stuff
#include <graph.h> #include <graph.h>
#include <vision.h> #include <vision.h>
@ -855,6 +827,7 @@ private:
#include "config.h" #include "config.h"
#include "support.h" #include "support.h"
#include "hooks.h"
#include "sounds.h" #include "sounds.h"
#include "message.h" #include "message.h"
#include "engine.h" #include "engine.h"

View file

@ -6,7 +6,7 @@
# #
# version is now passed into the bot dll # version is now passed into the bot dll
project ( project(
'yapb', 'yapb',
'cpp', 'cpp',
version: '4.4', version: '4.4',
@ -80,15 +80,15 @@ if git.found()
cxxflags += flags_versioned cxxflags += flags_versioned
version_data = configuration_data() version_data = configuration_data()
version_data.set('count', count) version_data.set('BUILD_COUNT', count)
version_data.set('hash', hash) version_data.set('BUILD_HASH', hash)
version_data.set('author', author) version_data.set('BUILD_AUTHOR', author)
version_data.set('machine', machine) version_data.set('BUILD_MACHINE', machine)
version_data.set('version', bot_version) version_data.set('BUILD_VERSION', bot_version)
version_data.set('winver', winver) version_data.set('BUILD_WINVER', winver)
version_data.set('compiler', '@0@ @1@'.format (cxx, cxx_version)) 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 endif
# define crlib native build # define crlib native build
@ -102,7 +102,7 @@ if cxx == 'clang' or cxx == 'gcc'
'-fno-threadsafe-statics', '-pthread' '-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' cxxflags += '-mtune=generic'
endif endif
@ -110,7 +110,7 @@ if cxx == 'clang' or cxx == 'gcc'
cxxflags += [ cxxflags += [
'-march=armv8-a+fp+simd', '-march=armv8-a+fp+simd',
] ]
elif cpu != 'arm' elif cpu != 'arm' and not cpu.startswith('ppc')
cxxflags += [ cxxflags += [
'-mmmx', '-msse', '-msse2', '-msse3', '-mssse3', '-mfpmath=sse' '-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' '-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 += [ cxxflags += [
'-fdata-sections', '-fdata-sections',
'-ffunction-sections', '-ffunction-sections',
@ -181,7 +181,7 @@ if cxx == 'clang' or cxx == 'gcc'
endif endif
# by default we buid 32bit binaries # 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' cxxflags += '-m32'
ldflags += '-m32' ldflags += '-m32'
@ -261,6 +261,7 @@ sources = files(
'src/control.cpp', 'src/control.cpp',
'src/engine.cpp', 'src/engine.cpp',
'src/graph.cpp', 'src/graph.cpp',
'src/hooks.cpp',
'src/linkage.cpp', 'src/linkage.cpp',
'src/manager.cpp', 'src/manager.cpp',
'src/module.cpp', 'src/module.cpp',

View file

@ -52,6 +52,9 @@ void GraphAnalyze::update () {
if (m_updateInterval >= game.time ()) { if (m_updateInterval >= game.time ()) {
return; return;
} }
else {
displayOverlayMessage ();
}
// add basic nodes // add basic nodes
if (!m_basicsCreated) { if (!m_basicsCreated) {
@ -157,6 +160,8 @@ void GraphAnalyze::finish () {
return; return;
} }
vistab.startRebuild (); vistab.startRebuild ();
ctrl.enableDrawModels (false);
cv_quota.revert (); cv_quota.revert ();
} }
} }
@ -171,13 +176,6 @@ void GraphAnalyze::optimize () {
} }
cleanup (); 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 <int> &nodes) { auto smooth = [] (const Array <int> &nodes) {
Vector result; Vector result;
@ -203,7 +201,7 @@ void GraphAnalyze::optimize () {
Array <int> indexes; Array <int> indexes;
for (const auto &link : path.links) { 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); indexes.emplace (link.index);
} }
} }
@ -218,6 +216,13 @@ void GraphAnalyze::optimize () {
graph.add (NodeAddFlag::Normal, pos); 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 () { 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 <uint8_t> (255);
textParams.g1 = textParams.g2 = static_cast <uint8_t> (31);
textParams.b1 = textParams.b2 = static_cast <uint8_t> (75);
textParams.a1 = textParams.a2 = static_cast <uint8_t> (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) { void GraphAnalyze::flood (const Vector &pos, const Vector &next, float range) {
range *= 0.75f; range *= 0.75f;

View file

@ -1686,7 +1686,7 @@ void Bot::refreshEnemyPredict () {
if (distanceToLastEnemySq > cr::sqrf (128.0f) && (distanceToLastEnemySq < cr::sqrf (2048.0f) || usesSniper ())) { if (distanceToLastEnemySq > cr::sqrf (128.0f) && (distanceToLastEnemySq < cr::sqrf (2048.0f) || usesSniper ())) {
m_aimFlags |= AimFlags::PredictPath; 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)) { if (!denyLastEnemy && seesEntity (m_lastEnemyOrigin, true)) {
m_aimFlags |= AimFlags::LastEnemy; m_aimFlags |= AimFlags::LastEnemy;
@ -1778,19 +1778,14 @@ void Bot::setConditions () {
m_numFriendsLeft = numFriendsNear (pev->origin, kInfiniteDistance); m_numFriendsLeft = numFriendsNear (pev->origin, kInfiniteDistance);
m_numEnemiesLeft = numEnemiesNear (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 // check if our current enemy is still valid
if (!game.isNullEntity (m_lastEnemy)) { if (!game.isNullEntity (m_lastEnemy)) {
if (!util.isAlive (m_lastEnemy) && m_shootAtDeadTime < game.time ()) { if (!util.isAlive (m_lastEnemy) && m_shootAtDeadTime < game.time ()) {
clearLastEnemy (); m_lastEnemy = nullptr;
} }
} }
else { else {
clearLastEnemy (); m_lastEnemy = nullptr;
} }
// don't listen if seeing enemy, just checked for sounds or being blinded (because its inhuman) // don't listen if seeing enemy, just checked for sounds or being blinded (because its inhuman)
@ -1798,8 +1793,20 @@ void Bot::setConditions () {
updateHearing (); updateHearing ();
m_soundUpdateTime = game.time () + 0.25f; m_soundUpdateTime = game.time () + 0.25f;
} }
else if (m_heardSoundTime < game.time ()) { else if (m_heardSoundTime + 10.0f < game.time ()) {
m_states &= ~Sense::HearingEnemy; 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 (); refreshEnemyPredict ();
@ -2001,11 +2008,11 @@ void Bot::filterTasks () {
offensive = subsumeDesire (offensive, pickup); // if offensive task, don't allow picking up stuff 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 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 ()) { if (!m_tasks.empty ()) {
final = maxDesire (final, getTask ()); finalTask = maxDesire (finalTask, getTask ());
startTask (final->id, final->desire, final->data, final->time, final->resume); // push the final behavior in our task stack to carry out 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 (); kick ();
return; 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; m_slowFrameTimestamp = game.time () + 0.5f;
} }
@ -3097,7 +3098,7 @@ void Bot::showDebugOverlay () {
} }
auto overlayEntity = graph.getEditor (); 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; displayDebugOverlay = true;
} }
@ -3162,12 +3163,11 @@ void Bot::showDebugOverlay () {
if (m_tasks.empty ()) { if (m_tasks.empty ()) {
return; return;
} }
const auto drawTime = globals->frametime * 500.0f;
if (tid != getCurrentTaskId () || index != m_currentNodeIndex || goal != getTask ()->data || m_timeDebugUpdateTime < game.time ()) { if (tid != getCurrentTaskId () || index != m_currentNodeIndex || goal != getTask ()->data || m_timeDebugUpdateTime < game.time ()) {
tid = getCurrentTaskId (); tid = getCurrentTaskId ();
index = m_currentNodeIndex;
goal = getTask ()->data; goal = getTask ()->data;
index = m_currentNodeIndex;
String enemy = "(none)"; String enemy = "(none)";
@ -3191,31 +3191,38 @@ void Bot::showDebugOverlay () {
aimFlags.appendf (" %s", flags[static_cast <int32_t> (bit)]); aimFlags.appendf (" %s", flags[static_cast <int32_t> (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; static hudtextparms_t textParams {};
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));
MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullptr, overlayEntity) textParams.channel = 1;
.writeByte (TE_TEXTMESSAGE) textParams.x = -1.0f;
.writeByte (1) textParams.y = 0.0f;
.writeShort (MessageWriter::fs16 (-1.0f, 13.0f)) textParams.effect = 0;
.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 ());
m_timeDebugUpdateTime = game.time () + drawTime; textParams.r1 = textParams.r2 = static_cast <uint8_t> (m_team == Team::CT ? 0 : 255);
textParams.g1 = textParams.g2 = static_cast <uint8_t> (100);
textParams.b1 = textParams.b2 = static_cast <uint8_t> (m_team != Team::CT ? 0 : 255);
textParams.a1 = textParams.a2 = static_cast <uint8_t> (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 // green = destination origin
@ -3714,7 +3721,7 @@ void Bot::updateHearing () {
} }
// didn't bot already have an enemy ? take this one... // 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_lastEnemy = hearedEnemy;
m_lastEnemyOrigin = hearedEnemy->v.origin; m_lastEnemyOrigin = hearedEnemy->v.origin;
} }

View file

@ -10,12 +10,37 @@
ConVar cv_chat ("chat", "1", "Enables or disables bots chat functionality."); 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); 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 ()) { if (line.empty ()) {
return; return;
} }
for (const auto &tag : m_tags) { for (const auto &tag : m_clanTags) {
const size_t start = line.find (tag.first, 0); const size_t start = line.find (tag.first, 0);
if (start != String::InvalidIndex) { 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 ()) { if (playerName.empty ()) {
return; 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 // sometimes switch name to lower characters, only valid for the english languge
if (rg.chance (8) && cv_language.str () == "en") { if (rg.chance (8) && cv_language.str () == "en") {
line.lowercase (); 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 // this function checks is string contain keyword, and generates reply to it
if (!cv_chat.bool_ () || line.empty ()) { if (!cv_chat.bool_ () || line.empty ()) {
@ -132,7 +157,7 @@ void Bot::prepareChatMessage (StringRef message) {
// must be called before return or on the end // must be called before return or on the end
auto finishPreparation = [&] () { auto finishPreparation = [&] () {
if (!m_chatBuffer.empty ()) { if (!m_chatBuffer.empty ()) {
util.addChatErrors (m_chatBuffer); chatlib.addChatErrors (m_chatBuffer);
} }
}; };
@ -153,7 +178,7 @@ void Bot::prepareChatMessage (StringRef message) {
return "unknown"; return "unknown";
} }
String playerName = ent->v.netname.chars (); String playerName = ent->v.netname.chars ();
util.humanizePlayerName (playerName); chatlib.humanizePlayerName (playerName);
return playerName; return playerName;
}; };
@ -287,7 +312,7 @@ void Bot::prepareChatMessage (StringRef message) {
bool Bot::checkChatKeywords (String &reply) { bool Bot::checkChatKeywords (String &reply) {
// this function parse chat buffer, and prepare buffer to keyword searching // 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 () { bool Bot::isReplyingToChat () {
@ -316,8 +341,8 @@ bool Bot::isReplyingToChat () {
} }
void Bot::checkForChat () { void Bot::checkForChat () {
// say a text every now and then // say a text every now and then
if (m_isAlive || !cv_chat.bool_ () || game.is (GameFlags::CSDM)) { if (m_isAlive || !cv_chat.bool_ () || game.is (GameFlags::CSDM)) {
return; return;
} }

View file

@ -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)) { 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_lastEnemy = newEnemy;
other->m_lastEnemyOrigin = m_lastEnemyOrigin; other->m_lastEnemyOrigin = newEnemy->v.origin;
other->m_seeEnemyTime = game.time (); other->m_seeEnemyTime = game.time ();
other->m_states |= (Sense::SuspectEnemy | Sense::HearingEnemy); other->m_states |= (Sense::SuspectEnemy | Sense::HearingEnemy);
other->m_aimFlags |= AimFlags::LastEnemy; 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 // shoot at dying players if no new enemy to give some more human-like illusion
if (m_seeEnemyTime + 0.1f > game.time ()) { if (m_seeEnemyTime + 0.1f > game.time ()) {
if (!usesSniper ()) { 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_actualReactionTime = 0.0f;
m_states |= Sense::SuspectEnemy; m_states |= Sense::SuspectEnemy;
@ -424,7 +424,11 @@ bool Bot::lookupEnemies () {
} }
// if no enemy visible check if last one shoot able through wall // 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_seeEnemyTime = game.time ();
m_states |= Sense::SuspectEnemy; m_states |= Sense::SuspectEnemy;
@ -439,7 +443,15 @@ bool Bot::lookupEnemies () {
} }
// check if bots should reload... // 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) { if (!m_reloadState) {
m_reloadState = Reload::Primary; m_reloadState = Reload::Primary;
} }
@ -458,20 +470,20 @@ bool Bot::lookupEnemies () {
} }
Vector Bot::getBodyOffsetError (float distance) { Vector Bot::getBodyOffsetError (float distance) {
if (game.isNullEntity (m_enemy)) { if (game.isNullEntity (m_enemy) || distance < kSprayDistance) {
return nullptr; return nullptr;
} }
if (m_aimErrorTime < game.time ()) { if (m_aimErrorTime < game.time ()) {
const float hitError = distance / (cr::clamp (static_cast <float> (m_difficulty), 1.0f, 4.0f) * 1000.0f); const float hitError = distance / (cr::clamp (static_cast <float> (m_difficulty), 1.0f, 4.0f) * 1280.0f);
const auto &maxs = m_enemy->v.maxs, &mins = m_enemy->v.mins; 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; 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_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; return m_aimLastError;
} }
@ -514,7 +526,7 @@ Vector Bot::getBodyOffsetError (float distance) {
else if (util.isPlayer (m_enemy)) { else if (util.isPlayer (m_enemy)) {
// now take in account different parts of enemy body // now take in account different parts of enemy body
if (m_enemyParts & (Visibility::Head | Visibility::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 // now check is our skill match to aim at head, else aim at enemy body
if (rg.chance (headshotPct)) { if (rg.chance (headshotPct)) {
@ -570,7 +582,7 @@ Vector Bot::getCustomHeight (float distance) {
{ 1.5f, -4.0f, -9.0f } // heavy { 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)) { if (m_difficulty != Difficulty::Expert || (m_enemy->v.flags & FL_DUCKING)) {
return 0.0f; return 0.0f;
} }
@ -987,12 +999,19 @@ void Bot::fireWeapons () {
// loop through all the weapons until terminator is found... // loop through all the weapons until terminator is found...
while (tab[selectIndex].id) { while (tab[selectIndex].id) {
const auto wid = tab[selectIndex].id;
// is the bot carrying this weapon? // 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... // 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)) { if (m_ammoInClip[wid] > 0 && !isWeaponBadAtDistance (selectIndex, distance)) {
choosenWeapon = selectIndex; 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++; selectIndex++;
@ -1005,11 +1024,11 @@ void Bot::fireWeapons () {
// loop through all the weapons until terminator is found... // loop through all the weapons until terminator is found...
while (tab[selectIndex].id) { while (tab[selectIndex].id) {
const int id = tab[selectIndex].id; const int wid = tab[selectIndex].id;
// is the bot carrying this weapon? // is the bot carrying this weapon?
if (weapons & cr::bit (id)) { if (weapons & cr::bit (wid)) {
if (getAmmo (id) >= tab[selectIndex].minPrimaryAmmo) { if (getAmmo (wid) >= tab[selectIndex].minPrimaryAmmo) {
// available ammo found, reload weapon // available ammo found, reload weapon
if (m_reloadState == Reload::None || m_reloadCheckTime > game.time ()) { 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; m_fightStyle = Fight::Strafe;
} }
else { else {
@ -1217,10 +1236,6 @@ void Bot::attackMovement () {
m_fightStyle = Fight::Strafe; m_fightStyle = Fight::Strafe;
} }
if (usesPistol () && distance < 768.0f) {
m_fightStyle = Fight::Strafe;
}
if (m_fightStyle == Fight::Strafe) { if (m_fightStyle == Fight::Strafe) {
auto swapStrafeCombatDir = [&] () { auto swapStrafeCombatDir = [&] () {
m_combatStrafeDir = (m_combatStrafeDir == Dodge::Left ? Dodge::Right : Dodge::Left); m_combatStrafeDir = (m_combatStrafeDir == Dodge::Left ? Dodge::Right : Dodge::Left);

View file

@ -144,6 +144,7 @@ void BotConfig::loadNamesConfig () {
} }
file.close (); file.close ();
} }
m_botNames.shuffle ();
} }
void BotConfig::loadWeaponsConfig () { void BotConfig::loadWeaponsConfig () {
@ -345,6 +346,7 @@ void BotConfig::loadChatterConfig () {
if (event.str == items.first ()) { if (event.str == items.first ()) {
// this does common work of parsing comma-separated chatter line // this does common work of parsing comma-separated chatter line
auto sentences = items[1].split (","); auto sentences = items[1].split (",");
sentences.shuffle ();
for (auto &sound : sentences) { for (auto &sound : sentences) {
sound.trim ().trim ("\""); sound.trim ().trim ("\"");
@ -480,6 +482,10 @@ void BotConfig::loadLanguageConfig () {
String temp; String temp;
Twin <String, String> lang; Twin <String, String> lang;
auto pushTranslatedMsg = [&] () {
m_language[hashLangString (lang.first.trim ().chars ())] = lang.second.trim ();
};
// clear all the translations before new load // clear all the translations before new load
m_language.clear (); m_language.clear ();
@ -494,7 +500,7 @@ void BotConfig::loadLanguageConfig () {
} }
if (!lang.second.empty () && !lang.first.empty ()) { 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 ()) { else if (line.startsWith ("[TRANSLATED]") && !temp.empty ()) {
@ -503,6 +509,12 @@ void BotConfig::loadLanguageConfig () {
else { else {
temp += line; temp += line;
} }
// make sure last string is translated
if (file.eof () && !lang.first.empty ()) {
lang.second = line.trim ();
pushTranslatedMsg ();
}
} }
file.close (); file.close ();
} }

View file

@ -195,7 +195,29 @@ int BotControl::cmdList () {
int BotControl::cmdCvars () { int BotControl::cmdCvars () {
enum args { alias = 1, pattern }; 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 isSaveMain = match == "save";
const bool isSaveMap = match == "save_map"; const bool isSaveMap = match == "save_map";
@ -2162,11 +2184,9 @@ bool BotControl::handleMenuCommands (edict_t *ent) {
} }
void BotControl::enableDrawModels (bool enable) { void BotControl::enableDrawModels (bool enable) {
StringArray entities; static StringArray entities {
"info_player_start", "info_player_deathmatch", "info_vip_start"
entities.push ("info_player_start"); };
entities.push ("info_player_deathmatch");
entities.push ("info_vip_start");
if (enable) { if (enable) {
game.setPlayerStartDrawModels (); game.setPlayerStartDrawModels ();

View file

@ -81,7 +81,7 @@ void Game::levelInitialize (edict_t *entities, int max) {
bots.initQuota (); bots.initQuota ();
// install the sendto hook to fake queries // install the sendto hook to fake queries
util.installSendTo (); fakequeries.init ();
// flush any print queue // flush any print queue
ctrl.resetFlushTimestamp (); 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 // that for every "command_name" server command it receives, it should call the function
// pointed to by "function" in order to handle it. // 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)) { 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); 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 <char *> (command), func); else {
engfuncs.pfnAddServerCommand (command, func);
}
} }
void Game::playSound (edict_t *ent, const char *sound) { 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) { uint8_t *Game::getVisibilitySet (Bot *bot, bool pvs) {
if (is (GameFlags::Xash3D)) { if (is (GameFlags::Xash3DLegacy)) {
return nullptr; // TODO: bug fixed in upstream xash3d, should be removed return nullptr;
} }
auto eyes = bot->getEyesPos (); auto eyes = bot->getEyesPos ();
@ -460,6 +462,37 @@ void Game::sendServerMessage (StringRef message) {
engfuncs.pfnServerPrint (message.chars ()); 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) { void Game::prepareBotArgs (edict_t *ent, String str) {
// the purpose of this function is to provide fakeclients (bots) with the same client // 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) // command-scripting advantages (putting multiple commands in one line between semicolons)
@ -726,7 +759,7 @@ bool Game::loadCSBinary () {
if (!m_gameLib) { if (!m_gameLib) {
logger.fatal ("Unable to load gamedll \"%s\". Exiting... (gamedir: %s)", dll, mod); logger.fatal ("Unable to load gamedll \"%s\". Exiting... (gamedir: %s)", dll, mod);
} }
auto ent = m_gameLib.resolve <EntityFunction> ("trigger_random_unique"); auto ent = m_gameLib.resolve <EntityProto> ("trigger_random_unique");
// detect regamedll by addon entity they provide // detect regamedll by addon entity they provide
if (ent != nullptr) { if (ent != nullptr) {
@ -765,7 +798,12 @@ bool Game::loadCSBinary () {
} }
// detect if we're running modern game // detect if we're running modern game
auto entity = m_gameLib.resolve <EntityFunction> ("weapon_famas"); auto entity = m_gameLib.resolve <EntityProto> ("weapon_famas");
// detect legacy xash3d branch
if (engfuncs.pfnCVarGetPointer ("build") != nullptr) {
m_gameFlags |= GameFlags::Xash3DLegacy;
}
// detect xash engine // detect xash engine
if (engfuncs.pfnCVarGetPointer ("host_ver") != nullptr) { if (engfuncs.pfnCVarGetPointer ("host_ver") != nullptr) {
@ -855,6 +893,9 @@ bool Game::postload () {
// initialize weapons // initialize weapons
conf.initWeapons (); conf.initWeapons ();
// register engine lib handle
m_engineLib.locate (reinterpret_cast <void *> (engfuncs.pfnPrecacheModel));
if (plat.android) { if (plat.android) {
m_gameFlags |= (GameFlags::Xash3D | GameFlags::Mobility | GameFlags::HasBotVoice | GameFlags::ReGameDLL); m_gameFlags |= (GameFlags::Xash3D | GameFlags::Mobility | GameFlags::HasBotVoice | GameFlags::ReGameDLL);
@ -1245,7 +1286,7 @@ float LightMeasure::getLightLevel (const Vector &point) {
auto recursiveCheck = [&] () -> bool { auto recursiveCheck = [&] () -> bool {
if (!isSoftRenderer) { if (!isSoftRenderer) {
if (is25Anniversary) { if (is25Anniversary) {
return recursiveLightPoint <msurface_hw_25anniversary_t, mnode_hw_t> (reinterpret_cast <mnode_hw_t *> (m_worldModel->nodes), point, endPoint); return recursiveLightPoint <msurface_hw_hl25_t, mnode_hw_t> (reinterpret_cast <mnode_hw_t *> (m_worldModel->nodes), point, endPoint);
} }
return recursiveLightPoint <msurface_hw_t, mnode_hw_t> (reinterpret_cast <mnode_hw_t *> (m_worldModel->nodes), point, endPoint); return recursiveLightPoint <msurface_hw_t, mnode_hw_t> (reinterpret_cast <mnode_hw_t *> (m_worldModel->nodes), point, endPoint);
} }

View file

@ -12,9 +12,9 @@
// other platforms, and you want to run bot on it without metamod, consider enabling LINKENT_STATIC_THUNKS // 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. // when compiling the bot, to get it supported.
#if defined(LINKENT_STATIC_THUNKS) #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) { if (!addr) {
addr = game.lib ().resolve <EntityFunction> (name); addr = game.lib ().resolve <EntityProto> (name);
} }
if (!addr) { if (!addr) {
return; return;
@ -24,7 +24,7 @@ void forwardEntity_helper (EntityFunction &addr, const char *name, entvars_t *pe
#define LINK_ENTITY(entityName) \ #define LINK_ENTITY(entityName) \
CR_EXPORT void entityName (entvars_t *pev) { \ CR_EXPORT void entityName (entvars_t *pev) { \
static EntityFunction addr; \ static EntityProto addr; \
forwardEntity_helper (addr, __FUNCTION__, pev); \ forwardEntity_helper (addr, __FUNCTION__, pev); \
} }

View file

@ -412,12 +412,13 @@ void BotGraph::addPath (int addIndex, int pathIndex, float distance) {
return; return;
} }
} }
auto integerDistance = cr::abs (static_cast <int> (distance));
// check for free space in the connection indices // check for free space in the connection indices
for (auto &link : path.links) { for (auto &link : path.links) {
if (link.index == kInvalidNodeIndex) { if (link.index == kInvalidNodeIndex) {
link.index = static_cast <int16_t> (pathIndex); link.index = static_cast <int16_t> (pathIndex);
link.distance = cr::abs (static_cast <int> (distance)); link.distance = integerDistance;
msg ("Path added from %d to %d.", addIndex, pathIndex); msg ("Path added from %d to %d.", addIndex, pathIndex);
return; return;
@ -439,7 +440,7 @@ void BotGraph::addPath (int addIndex, int pathIndex, float distance) {
msg ("Path added from %d to %d.", addIndex, pathIndex); msg ("Path added from %d to %d.", addIndex, pathIndex);
path.links[slot].index = static_cast <int16_t> (pathIndex); path.links[slot].index = static_cast <int16_t> (pathIndex);
path.links[slot].distance = cr::abs (static_cast <int> (distance)); path.links[slot].distance = integerDistance;
} }
} }
@ -498,11 +499,11 @@ int BotGraph::getNearestNoBuckets (const Vector &origin, const float range, int
return index; return index;
} }
int BotGraph::getEditorNearest () { int BotGraph::getEditorNearest (const float maxRange) {
if (!hasEditFlag (GraphEdit::On)) { if (!hasEditFlag (GraphEdit::On)) {
return kInvalidNodeIndex; 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) { int BotGraph::getNearest (const Vector &origin, const float range, int flags) {
@ -625,12 +626,12 @@ void BotGraph::add (int type, const Vector &pos) {
return; return;
case NodeAddFlag::JumpStart: case NodeAddFlag::JumpStart:
index = getEditorNearest (); index = getEditorNearest (25.0f);
if (index != kInvalidNodeIndex && m_paths[index].number >= 0) { if (index != kInvalidNodeIndex && m_paths[index].number >= 0) {
const float distanceSq = m_editor->v.origin.distanceSq (m_paths[index].origin); 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; addNewNode = false;
path = &m_paths[index]; path = &m_paths[index];
@ -643,12 +644,12 @@ void BotGraph::add (int type, const Vector &pos) {
break; break;
case NodeAddFlag::JumpEnd: case NodeAddFlag::JumpEnd:
index = getEditorNearest (); index = getEditorNearest (25.0f);
if (index != kInvalidNodeIndex && m_paths[index].number >= 0) { if (index != kInvalidNodeIndex && m_paths[index].number >= 0) {
const float distanceSq = m_editor->v.origin.distanceSq (m_paths[index].origin); 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; addNewNode = false;
path = &m_paths[index]; path = &m_paths[index];
@ -1064,14 +1065,16 @@ void BotGraph::pathCreate (char dir) {
if (!isConnected (nodeFrom, nodeTo)) { if (!isConnected (nodeFrom, nodeTo)) {
addPath (nodeFrom, nodeTo, distance); addPath (nodeFrom, nodeTo, distance);
} }
for (auto &link : m_paths[nodeFrom].links) { for (auto &link : m_paths[nodeFrom].links) {
if (link.index == nodeTo && !(link.flags & PathFlag::Jump)) { if (link.index == nodeTo && !(link.flags & PathFlag::Jump)) {
link.flags |= PathFlag::Jump; link.flags |= PathFlag::Jump;
m_paths[nodeFrom].radius = 0.0f; m_paths[nodeFrom].radius = 0.0f;
msg ("Path added from %d to %d.", nodeFrom, nodeTo); msg ("Path added from %d to %d.", nodeFrom, nodeTo);
} }
else if (link.index == nodeTo && (link.flags & PathFlag::Jump)) { 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; constexpr int32_t kNarrowPlacesMinGraphVersion = 2;
// if version 2 or higher, narrow places already initialized and saved into file // 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; m_narrowChecked = true;
return; return;
} }
@ -2141,25 +2144,25 @@ void BotGraph::frame () {
} }
static int channel = 0; static int channel = 0;
auto sendHudMessage = [] (Color color, float x, float y, edict_t *to, StringRef text) { auto sendHudMessage = [&] (Color color, float x, float y, StringRef text) {
MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullptr, to) static hudtextparms_t textParams {};
.writeByte (TE_TEXTMESSAGE)
.writeByte (channel++ & 0xff) // channel textParams.channel = channel;
.writeShort (MessageWriter::fs16 (x, 13.0f)) // x textParams.x = x;
.writeShort (MessageWriter::fs16 (y, 13.0f)) // y textParams.y = y;
.writeByte (0) // effect textParams.effect = 0;
.writeByte (color.red) // r1
.writeByte (color.green) // g1 textParams.r1 = textParams.r2 = static_cast <uint8_t> (color.red);
.writeByte (color.blue) // b1 textParams.g1 = textParams.g2 = static_cast <uint8_t> (color.green);
.writeByte (1) // a1 textParams.b1 = textParams.b2 = static_cast <uint8_t> (color.blue);
.writeByte (color.red) // r2 textParams.a1 = textParams.a2 = static_cast <uint8_t> (1);
.writeByte (color.green) // g2
.writeByte (color.blue) // b2 textParams.fadeinTime = 0.0f;
.writeByte (1) // a2 textParams.fadeoutTime = 0.0f;
.writeShort (0) // fadeintime textParams.holdTime = m_pathDisplayTime;
.writeShort (0) // fadeouttime textParams.fxTime = 0.0f;
.writeShort (MessageWriter::fu16 (1.0f, 8.0f)) // holdtime
.writeString (text.chars ()); game.sendHudMessage (m_editor, textParams, text);
if (channel > 3) { if (channel > 3) {
channel = 0; channel = 0;
@ -2207,16 +2210,16 @@ void BotGraph::frame () {
}; };
// display some information // 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 // check if we need to show the cached point index
if (m_cacheNodeIndex != kInvalidNodeIndex) { 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 // check if we need to show the facing point index
if (m_facingAtIndex != kInvalidNodeIndex) { 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 ()); String timeMessage = strings.format (" Map: %s, Time: %s\n", game.getMapName (), util.getCurrentDateTime ());
@ -2230,10 +2233,10 @@ void BotGraph::frame () {
" CT: %d / %d\n" " 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); " 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 { 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 // perform DFS instead of floyd-warshall, this shit speedup this process in a bit
const auto length = cr::min (static_cast <size_t> (kMaxNodes), m_paths.length ()); const auto length = cr::min (static_cast <size_t> (kMaxNodes), m_paths.length ());
// ensure valid capacity // ensure valid capacity
assert (length > 8 && length < static_cast <size_t> (kMaxNodes)); assert (length > 8 && length < static_cast <size_t> (kMaxNodes));
PathWalk walk; PathWalk walk;
walk.init (length); walk.init (length);

179
src/hooks.cpp Normal file
View file

@ -0,0 +1,179 @@
//
// YaPB, based on PODBot by Markus Klinge ("CountFloyd").
// Copyright © YaPB Project Developers <yapb@jeefo.net>.
//
// SPDX-License-Identifier: MIT
//
#include <yapb.h>
int32_t BotSupport::sendTo (int socket, const void *message, size_t length, int flags, const sockaddr *dest, int destLength) {
const auto send = [&] (const Twin <const uint8_t *, size_t> &msg) -> int32_t {
return Socket::sendto (socket, msg.first, msg.second, flags, dest, destLength);
};
auto packet = reinterpret_cast <const uint8_t *> (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 <uint8_t> ();
for (uint8_t i = 0; i < count; ++i) {
buffer.skip <uint8_t> (); // number
auto name = buffer.readString (); // name
buffer.skip <int32_t> (); // score
auto ctime = buffer.read <float> (); // override connection time
buffer.write <float> (bots.getConnectTime (name, ctime));
}
return send (buffer.data ());
}
else if (packet[4] == 'I') {
QueryBuffer buffer { packet, length, packetLength };
buffer.skip <uint8_t> (); // protocol
// skip server name, folder, map game
for (size_t i = 0; i < 4; ++i) {
buffer.skipString ();
}
buffer.skip <short> (); // steam app id
buffer.skip <uint8_t> (); // players
buffer.skip <uint8_t> (); // maxplayers
buffer.skip <uint8_t> (); // bots
buffer.write <uint8_t> (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 <uint8_t> (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 <SendToProto *> ("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 <void *> (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 <EntityProto> (m_exports["player"]) (&ent->v);
};
if (m_exports.exists ("player")) {
callPlayer ();
return true;
}
auto playerFunction = game.lib ().resolve <EntityProto> ("player");
if (!playerFunction) {
logger.error ("Cannot resolve player() function in GameDLL.");
return false;
}
m_exports["player"] = reinterpret_cast <SharedLibrary::Func> (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 <void *> (lookupHandler), true);
if (needsBypass ()) {
m_dlclose.initialize ("kernel32.dll", "FreeLibrary", DLCLOSE_FUNCTION);
m_dlclose.install (reinterpret_cast <void *> (closeHandler), true);
}
m_self.locate (&engfuncs);
}

View file

@ -32,7 +32,58 @@ plugin_info_t Plugin_info = {
PT_ANYTIME, // when unloadable 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 // 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 // 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 // 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 <decltype (&GetEntityAPI)> (__func__); auto api_GetEntityAPI = game.lib ().resolve <decltype (&GetEntityAPI)> (__func__);
// pass other DLLs engine callbacks to function table... // 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__); logger.fatal ("Could not resolve symbol \"%s\" in the game dll.", __func__);
} }
dllfuncs.dllapi_table = &dllapi; dllfuncs.dllapi_table = &dllapi;
@ -294,7 +345,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int) {
dllapi.pfnServerDeactivate (); dllapi.pfnServerDeactivate ();
// refill export table // refill export table
ents.flush (); entlink.flush ();
}; };
table->pfnStartFrame = [] () { table->pfnStartFrame = [] () {
@ -447,12 +498,12 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
plat.bzero (table, sizeof (enginefuncs_t)); 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 *{ table->pfnCreateNamedEntity = [] (string_t classname) -> edict_t *{
if (ents.isPaused ()) { if (entlink.isPaused ()) {
ents.enable (); entlink.enable ();
ents.setPaused (false); entlink.setPaused (false);
} }
return engfuncs.pfnCreateNamedEntity (classname); return engfuncs.pfnCreateNamedEntity (classname);
}; };
@ -601,6 +652,9 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
engfuncs.pfnWriteEntity (value); 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)) { if (!game.is (GameFlags::Metamod)) {
table->pfnRegUserMsg = [] (const char *name, int size) { table->pfnRegUserMsg = [] (const char *name, int size) {
// this function registers a "user message" by the engine side. User messages are network // 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 // pass them too, else the DLL interfacing wouldn't be complete and the game possibly wouldn't
// run properly. // run properly.
plat.bzero (table, sizeof (newgamefuncs_t));
if (!(game.is (GameFlags::Metamod))) { if (!(game.is (GameFlags::Metamod))) {
auto api_GetNewDLLFunctions = game.lib ().resolve <decltype (&GetNewDLLFunctions)> (__func__); auto api_GetNewDLLFunctions = game.lib ().resolve <decltype (&GetNewDLLFunctions)> (__func__);
// pass other DLLs engine callbacks to function table... // pass other DLLs engine callbacks to function table...
if (!api_GetNewDLLFunctions || api_GetNewDLLFunctions (&newapi, interfaceVersion) == 0) { 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; dllfuncs.newapi_table = &newapi;
memcpy (table, &newapi, sizeof (newgamefuncs_t)); memcpy (table, &newapi, sizeof (newgamefuncs_t));
} }
plat.bzero (table, sizeof (newgamefuncs_t));
if (!game.is (GameFlags::Legacy)) { if (!game.is (GameFlags::Legacy)) {
table->pfnOnFreeEntPrivateData = [] (edict_t *ent) { table->pfnOnFreeEntPrivateData = [] (edict_t *ent) {
@ -847,7 +902,7 @@ CR_EXPORT int Meta_Detach (PLUG_LOADTIME now, PL_UNLOAD_REASON reason) {
practice.save (); practice.save ();
// disable hooks // disable hooks
util.disableSendTo (); fakequeries.disable ();
// make sure all stuff cleared // make sure all stuff cleared
bots.destroy (); bots.destroy ();
@ -912,7 +967,7 @@ DLL_GIVEFNPTRSTODLL GiveFnptrsToDll (enginefuncs_t *table, globalvars_t *glob) {
// initialize dynamic linkents (no memory hacking with xash3d) // initialize dynamic linkents (no memory hacking with xash3d)
if (!game.is (GameFlags::Xash3D)) { if (!game.is (GameFlags::Xash3D)) {
ents.initialize (); entlink.initialize ();
} }
// give the engine functions to the other DLL... // 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->version = SV_PHYSICS_INTERFACE_VERSION;
table->SV_CreateEntity = [] (edict_t *ent, const char *name) -> int { table->SV_CreateEntity = [] (edict_t *ent, const char *name) -> int {
auto func = game.lib ().resolve <EntityFunction> (name); // lookup symbol in game dll auto func = game.lib ().resolve <EntityProto> (name); // lookup symbol in game dll
// found one in game dll ? // found one in game dll ?
if (func) { if (func) {
@ -960,85 +1015,6 @@ CR_EXPORT int Server_GetPhysicsInterface (int version, server_physics_api_t *phy
return HLTrue; 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 <EntityFunction> ("player");
}
else {
playerFunction = reinterpret_cast <EntityFunction> (reinterpret_cast <void *> (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 <void *> (EntityLinkage::lookupHandler), true);
if (needsBypass ()) {
m_dlclose.initialize ("kernel32.dll", "FreeLibrary", DLCLOSE_FUNCTION);
m_dlclose.install (reinterpret_cast <void *> (EntityLinkage::closeHandler), true);
}
m_self.locate (&engfuncs);
}
// add linkents for android // add linkents for android
#include "entities.cpp" #include "entities.cpp"

View file

@ -8,7 +8,7 @@
#include <yapb.h> #include <yapb.h>
ConVar cv_autovacate ("autovacate", "1", "Kick bots to automatically make room for human players."); 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_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 <float> (kGameMaxPlayers)); ConVar cv_quota ("quota", "9", "Specifies the number bots to be added to the game.", true, 0.0f, static_cast <float> (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_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_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_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); 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); MUTIL_CallGameEntity (PLID, "player", &ent->v);
return; 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) { 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."); ctrl.msg ("Desired team is stacked. Unable to proceed with bot creation.");
return BotCreateResult::TeamStacked; return BotCreateResult::TeamStacked;
} }
if (difficulty < 0 || difficulty > 4) { if (difficulty < Difficulty::Noob || difficulty > Difficulty::Expert) {
difficulty = cv_difficulty.int_ (); difficulty = cv_difficulty.int_ ();
if (difficulty < 0 || difficulty > 4) { if (difficulty < Difficulty::Noob || difficulty > Difficulty::Expert) {
difficulty = rg.get (3, 4); difficulty = rg.get (3, 4);
cv_difficulty.set (difficulty); cv_difficulty.set (difficulty);
} }
} }
// try to set proffered personality
static HashMap <String, Personality> personalityMap {
{"normal", Personality::Normal },
{"careful", Personality::Careful },
{"rusher", Personality::Rusher },
};
// set personality if requested
if (personality < Personality::Normal || personality > Personality::Careful) { 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 { else {
if (rg.chance (50)) { if (rg.chance (50)) {
personality = Personality::Rusher; personality = Personality::Normal;
} }
else { 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 // first try to kick the bot that is currently dead
for (const auto &bot : m_bots) { 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 (); updateQuota ();
bot->kick (); bot->kick ();
@ -721,8 +748,9 @@ bool BotManager::kickRandom (bool decQuota, Team fromTeam) {
// worst case, just kick some random bot // worst case, just kick some random bot
for (const auto &bot : m_bots) { for (const auto &bot : m_bots) {
if (belongsTeam (bot.get ())) // is this slot used?
{ // is this slot used?
if (belongsTeam (bot.get ())) {
updateQuota (); updateQuota ();
bot->kick (); bot->kick ();
@ -824,7 +852,8 @@ void BotManager::listBots () {
}; };
for (const auto &bot : bots) { 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 <int> (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 <int> (bot->pev->frags), bot->m_isAlive ? "yes" : "no", timelimitStr);
} }
ctrl.msg ("%d bots", m_bots.length ()); 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 // set all info buffer keys for this bot
auto buffer = engfuncs.pfnGetInfoKeyBuffer (bot); auto buffer = engfuncs.pfnGetInfoKeyBuffer (bot);
engfuncs.pfnSetClientKeyValue (clientIndex, buffer, "_vgui_menus", "0"); engfuncs.pfnSetClientKeyValue (clientIndex, buffer, "_vgui_menus", "0");
engfuncs.pfnSetClientKeyValue (clientIndex, buffer, "_ah", "0");
if (!game.is (GameFlags::Legacy)) { if (!game.is (GameFlags::Legacy)) {
if (cv_show_latency.int_ () == 1) { 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 // notice nearby to victim teammates, that attacker is near
for (const auto &notify : bots) { for (const auto &notify : 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_actualReactionTime = 0.0f;
notify->m_seeEnemyTime = game.time (); notify->m_seeEnemyTime = game.time ();
notify->m_enemy = killer; notify->m_enemy = killer;

View file

@ -1842,7 +1842,6 @@ int Bot::findBombNode () {
return graph.getNearest (bomb, 512.0f); // reliability check return graph.getNearest (bomb, 512.0f); // reliability check
} }
int goal = 0, count = 0; int goal = 0, count = 0;
float lastDistanceSq = kInfiniteDistance; float lastDistanceSq = kInfiniteDistance;

View file

@ -7,10 +7,11 @@
#include <yapb.h> #include <yapb.h>
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_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_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_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) { float Heuristic::gfunctionKillsDist (int team, int currentIndex, int parentIndex) {
if (parentIndex == kInvalidNodeIndex) { if (parentIndex == kInvalidNodeIndex) {
@ -172,7 +173,7 @@ void AStarAlgo::clearRoute () {
m_routes.clear (); 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 &ag = graph[a];
const auto &bg = graph[b]; const auto &bg = graph[b];
@ -181,12 +182,14 @@ bool AStarAlgo::cantSkipNode (const int a, const int b) {
if (hasZeroRadius) { if (hasZeroRadius) {
return true; return true;
} }
const bool notVisible = !vistab.visible (ag.number, bg.number) || !vistab.visible (bg.number, ag.number);
if (notVisible) { if (!skipVisCheck) {
return true; 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; const bool tooHigh = cr::abs (ag.origin.z - bg.origin.z) > 17.0f;
if (tooHigh) { if (tooHigh) {
@ -257,6 +260,14 @@ AStarResult AStarAlgo::find (int botTeam, int srcIndex, int destIndex, NodeAdder
// always clear constructed path // always clear constructed path
m_constructedPath.clear (); 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 <float> (botTeam) * 2.0f + 5.0f);
}
while (!m_routeQue.empty ()) { while (!m_routeQue.empty ()) {
// remove the first node from the open list // remove the first node from the open list
int currentIndex = m_routeQue.pop ().index; 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]; auto childRoute = &m_routes[child.index];
// calculate the F value as F = G + H // 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 h = m_hcalc (child.index, kInvalidNodeIndex, destIndex);
const float f = g + h; const float f = g + h;

View file

@ -32,29 +32,6 @@ BotSupport::BotSupport () {
m_sentences.push ("attention, expect experimental armed hostile presence"); m_sentences.push ("attention, expect experimental armed hostile presence");
m_sentences.push ("warning, medical attention required"); 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 // register weapon aliases
m_weaponAlias[Weapon::USP] = "usp"; // HK USP .45 Tactical m_weaponAlias[Weapon::USP] = "usp"; // HK USP .45 Tactical
m_weaponAlias[Weapon::Glock18] = "glock"; // Glock18 Select Fire m_weaponAlias[Weapon::Glock18] = "glock"; // Glock18 Select Fire
@ -112,10 +89,10 @@ bool BotSupport::isVisible (const Vector &origin, edict_t *ent) {
return true; 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. // 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 entityIndex = -1, message = TE_DECAL;
int decalIndex = engfuncs.pfnDecalIndex (logo.chars ()); 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)) { if (cr::fequal (trace->flFraction, 1.0f)) {
return; return;
} }
if (!game.isNullEntity (trace->pHit)) { if (!game.isNullEntity (trace->pHit)) {
if (trace->pHit->v.solid == SOLID_BSP || trace->pHit->v.movetype == MOVETYPE_PUSHSTEP) { if (trace->pHit->v.solid == SOLID_BSP || trace->pHit->v.movetype == MOVETYPE_PUSHSTEP) {
entityIndex = game.indexOfEntity (trace->pHit); entityIndex = game.indexOfEntity (trace->pHit);
@ -208,7 +186,6 @@ bool BotSupport::isMonster (edict_t *ent) {
if (isHostageEntity (ent)) { if (isHostageEntity (ent)) {
return false; return false;
} }
return true; return true;
} }
@ -283,24 +260,30 @@ void BotSupport::checkWelcome () {
if (game.isDedicated () || !cv_display_welcome_text.bool_ () || !m_needToSendWelcome) { if (game.isDedicated () || !cv_display_welcome_text.bool_ () || !m_needToSendWelcome) {
return; return;
} }
m_welcomeReceiveTime = 0.0f;
const bool needToSendMsg = (graph.length () > 0 ? m_needToSendWelcome : true); 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) { if (isAlive (receiveEnt) && m_welcomeReceiveTime < 1.0f && needToSendMsg) {
m_welcomeReceiveTime = game.time () + 4.0f; // receive welcome message in four seconds after game has commencing 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)) { if (!game.is (GameFlags::Mobility | GameFlags::Xash3D)) {
game.serverCommand ("speak \"%s\"", m_sentences.random ()); game.serverCommand ("speak \"%s\"", m_sentences.random ());
} }
String authorStr = "Official Navigation Graph"; String authorStr = "Official Navigation Graph";
StringRef graphAuthor = graph.getAuthor (); auto graphAuthor = graph.getAuthor ();
StringRef graphModified = graph.getModifiedBy (); auto graphModified = graph.getModifiedBy ();
if (!graphAuthor.startsWith (product.name)) { if (!graphAuthor.startsWith (product.name)) {
authorStr.assignf ("Navigation Graph by: %s", graphAuthor); authorStr.assignf ("Navigation Graph by: %s", graphAuthor);
@ -309,30 +292,39 @@ void BotSupport::checkWelcome () {
authorStr.appendf (" (Modified by: %s)", graphModified); 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) .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) static hudtextparms_t textParams {};
.writeByte (TE_TEXTMESSAGE)
.writeByte (1) textParams.channel = 1;
.writeShort (MessageWriter::fs16 (-1.0f, 13.0f)) textParams.x = -1.0f;
.writeShort (MessageWriter::fs16 (-1.0f, 13.0f)) textParams.y = sendLegacyWelcome ? 0.0f : -1.0f;
.writeByte (2) textParams.effect = rg.get (1, 2);
.writeByte (rg.get (33, 255))
.writeByte (rg.get (33, 255)) textParams.r1 = static_cast <uint8_t> (sendLegacyWelcome ? 255 : rg.get (33, 255));
.writeByte (rg.get (33, 255)) textParams.g1 = static_cast <uint8_t> (sendLegacyWelcome ? 0 : rg.get (33, 255));
.writeByte (0) textParams.b1 = static_cast <uint8_t> (sendLegacyWelcome ? 0 : rg.get (33, 255));
.writeByte (rg.get (230, 255)) textParams.a1 = static_cast <uint8_t> (0);
.writeByte (rg.get (230, 255))
.writeByte (rg.get (230, 255)) textParams.r2 = static_cast <uint8_t> (sendLegacyWelcome ? 255 : rg.get (230, 255));
.writeByte (200) textParams.g2 = static_cast <uint8_t> (sendLegacyWelcome ? 255 : rg.get (230, 255));
.writeShort (MessageWriter::fu16 (0.0078125f, 8.0f)) textParams.b2 = static_cast <uint8_t> (sendLegacyWelcome ? 255 : rg.get (230, 255));
.writeShort (MessageWriter::fu16 (2.0f, 8.0f)) textParams.a2 = static_cast <uint8_t> (200);
.writeShort (MessageWriter::fu16 (6.0f, 8.0f))
.writeShort (MessageWriter::fu16 (0.1f, 8.0f)) textParams.fadeinTime = 0.0078125f;
.writeString (strings.format ("\nHello! You are playing with %s v%s\nDevised by %s\n\n%s", product.name, product.version, product.author, authorStr)); 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_welcomeReceiveTime = 0.0f;
m_needToSendWelcome = false; 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); 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); const int botLoss = rg.get (average.second / 2, average.second);
if (botPing <= 5) { if (botPing < 2) {
botPing = rg.get (10, 23); botPing = rg.get (10, 23);
} }
else if (botPing > 70) { else if (botPing > 300) {
botPing = rg.get (30, 40); botPing = rg.get (30, 40);
} }
client.ping = getPingBitmask (client.ent, botLoss, botPing); client.ping = getPingBitmask (client.ent, botLoss, botPing);
@ -522,43 +514,6 @@ void BotSupport::emitPings (edict_t *to) {
return; 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 <void *> (engfuncs.pfnPrecacheModel));
if (engineLib) {
auto address = engineLib.resolve <SendToHandle *> ("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 <void *> (BotSupport::sendTo), true);
}
}
bool BotSupport::isModel (const edict_t *ent, StringRef model) { bool BotSupport::isModel (const edict_t *ent, StringRef model) {
return model.startsWith (ent->v.model.chars (9)); return model.startsWith (ent->v.model.chars (9));
} }
@ -575,58 +530,6 @@ String BotSupport::getCurrentDateTime () {
return String (timebuf); 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 <const uint8_t *, size_t> &msg) -> int32_t {
return Socket::sendto (socket, msg.first, msg.second, flags, dest, destLength);
};
auto packet = reinterpret_cast <const uint8_t *> (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 <uint8_t> ();
for (uint8_t i = 0; i < count; ++i) {
buffer.skip <uint8_t> (); // number
auto name = buffer.readString (); // name
buffer.skip <int32_t> (); // score
auto ctime = buffer.read <float> (); // override connection time
buffer.write <float> (bots.getConnectTime (name, ctime));
}
return send (buffer.data ());
}
else if (packet[4] == 'I') {
QueryBuffer buffer { packet, length, packetLength };
buffer.skip <uint8_t> (); // protocol
// skip server name, folder, map game
for (size_t i = 0; i < 4; ++i) {
buffer.skipString ();
}
buffer.skip <short> (); // steam app id
buffer.skip <uint8_t> (); // players
buffer.skip <uint8_t> (); // maxplayers
buffer.skip <uint8_t> (); // bots
buffer.write <uint8_t> (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 <uint8_t> (0); // zero out bot count
return send (buffer.data ());
}
}
return send ({ packet, length });
}
StringRef BotSupport::weaponIdToAlias (int32_t id) { StringRef BotSupport::weaponIdToAlias (int32_t id) {
StringRef none = "none"; StringRef none = "none";
@ -636,7 +539,6 @@ StringRef BotSupport::weaponIdToAlias (int32_t id) {
return none; return none;
} }
// helper class for reading wave header // helper class for reading wave header
class WaveEndianessHelper final : public NonCopyable { class WaveEndianessHelper final : public NonCopyable {
private: private:

View file

@ -270,7 +270,7 @@ void Bot::spraypaint_ () {
game.testLine (getEyesPos (), getEyesPos () + forward * 128.0f, TraceIgnore::Monsters, ent (), &tr); game.testLine (getEyesPos (), getEyesPos () + forward * 128.0f, TraceIgnore::Monsters, ent (), &tr);
// paint the actual logo decal // 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); m_timeLogoSpray = game.time () + rg.get (60.0f, 90.0f);
} }
} }

View file

@ -55,11 +55,13 @@
<ClInclude Include="..\ext\linkage\linkage\metamod.h" /> <ClInclude Include="..\ext\linkage\linkage\metamod.h" />
<ClInclude Include="..\ext\linkage\linkage\physint.h" /> <ClInclude Include="..\ext\linkage\linkage\physint.h" />
<ClInclude Include="..\inc\analyze.h" /> <ClInclude Include="..\inc\analyze.h" />
<ClInclude Include="..\inc\chatlib.h" />
<ClInclude Include="..\inc\config.h" /> <ClInclude Include="..\inc\config.h" />
<ClInclude Include="..\inc\constant.h" /> <ClInclude Include="..\inc\constant.h" />
<ClInclude Include="..\inc\control.h" /> <ClInclude Include="..\inc\control.h" />
<ClInclude Include="..\inc\engine.h" /> <ClInclude Include="..\inc\engine.h" />
<ClInclude Include="..\inc\graph.h" /> <ClInclude Include="..\inc\graph.h" />
<ClInclude Include="..\inc\hooks.h" />
<ClInclude Include="..\inc\manager.h" /> <ClInclude Include="..\inc\manager.h" />
<ClInclude Include="..\inc\message.h" /> <ClInclude Include="..\inc\message.h" />
<ClInclude Include="..\inc\module.h" /> <ClInclude Include="..\inc\module.h" />
@ -69,26 +71,35 @@
<ClInclude Include="..\inc\sounds.h" /> <ClInclude Include="..\inc\sounds.h" />
<ClInclude Include="..\inc\storage.h" /> <ClInclude Include="..\inc\storage.h" />
<ClInclude Include="..\inc\support.h" /> <ClInclude Include="..\inc\support.h" />
<ClInclude Include="..\inc\version.h" />
<ClInclude Include="..\inc\vision.h" /> <ClInclude Include="..\inc\vision.h" />
<ClInclude Include="..\inc\vistable.h" /> <ClInclude Include="..\inc\vistable.h" />
<ClInclude Include="..\inc\yapb.h" /> <ClInclude Include="..\inc\yapb.h" />
<ClInclude Include="..\inc\version.h" /> </ItemGroup>
<ItemGroup>
<ResourceCompile Include="yapb.rc">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</ExcludedFromBuild>
</ResourceCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="..\src\analyze.cpp" /> <ClCompile Include="..\src\analyze.cpp" />
<ClCompile Include="..\src\entities.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\src\botlib.cpp" /> <ClCompile Include="..\src\botlib.cpp" />
<ClCompile Include="..\src\chatlib.cpp" /> <ClCompile Include="..\src\chatlib.cpp" />
<ClCompile Include="..\src\combat.cpp" /> <ClCompile Include="..\src\combat.cpp" />
<ClCompile Include="..\src\config.cpp" /> <ClCompile Include="..\src\config.cpp" />
<ClCompile Include="..\src\control.cpp" /> <ClCompile Include="..\src\control.cpp" />
<ClCompile Include="..\src\engine.cpp" /> <ClCompile Include="..\src\engine.cpp" />
<ClCompile Include="..\src\entities.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\src\graph.cpp" /> <ClCompile Include="..\src\graph.cpp" />
<ClCompile Include="..\src\hooks.cpp" />
<ClCompile Include="..\src\linkage.cpp" /> <ClCompile Include="..\src\linkage.cpp" />
<ClCompile Include="..\src\manager.cpp" /> <ClCompile Include="..\src\manager.cpp" />
<ClCompile Include="..\src\message.cpp" /> <ClCompile Include="..\src\message.cpp" />
@ -103,14 +114,6 @@
<ClCompile Include="..\src\vision.cpp" /> <ClCompile Include="..\src\vision.cpp" />
<ClCompile Include="..\src\vistable.cpp" /> <ClCompile Include="..\src\vistable.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ResourceCompile Include="yapb.rc">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</ExcludedFromBuild>
</ResourceCompile>
</ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="..\inc\version.h.in" /> <None Include="..\inc\version.h.in" />
</ItemGroup> </ItemGroup>

View file

@ -186,6 +186,12 @@
<ClInclude Include="..\inc\vision.h"> <ClInclude Include="..\inc\vision.h">
<Filter>inc</Filter> <Filter>inc</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\inc\chatlib.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="..\inc\hooks.h">
<Filter>inc</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="..\src\botlib.cpp"> <ClCompile Include="..\src\botlib.cpp">
@ -254,6 +260,9 @@
<ClCompile Include="..\src\tasks.cpp"> <ClCompile Include="..\src\tasks.cpp">
<Filter>src</Filter> <Filter>src</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="..\src\hooks.cpp">
<Filter>src</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ResourceCompile Include="yapb.rc"> <ResourceCompile Include="yapb.rc">