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
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
Event Radio_CoverMe = ("cover_me", "cover_me2");
// Event Radio_YouTakePoint = ("");
// Event Radio_HoldPosition = ("");
// Event Radio_RegroupTeam = ("");
Event Radio_FollowMe = ("lead_on_sir", "lead_the_way_sir", "lead_the_way", "ok_sir_lets_go", "lead_on_commander", "lead_the_way_commander", "ok_cmdr_lets_go");
Event Radio_TakingFire = ("taking_fire_need_assistance2", "i_could_use_some_help", "i_could_use_some_help_over_here", "help", "need_help", "need_help2", "im_in_trouble");
Event Radio_CoverMe = cover_me, cover_me2
// Event Radio_YouTakePoint =
// Event Radio_HoldPosition =
// Event Radio_RegroupTeam =
Event Radio_FollowMe = lead_on_sir, lead_the_way_sir, lead_the_way, ok_sir_lets_go, lead_on_commander, lead_the_way_commander, ok_cmdr_lets_go
Event Radio_TakingFire = taking_fire_need_assistance2, i_could_use_some_help, i_could_use_some_help_over_here, help, need_help, need_help2, im_in_trouble
// Event Radio_GoGoGo = ("");
// Event Radio_Fallback = ("");
// Event Radio_StickTogether = ("");
// Event Radio_GetInPosition = ("");
// Event Radio_StormTheFront = ("");
Event Radio_ReportTeam = ("report_in_team", "anyone_see_them", "anyone_see_anything", "where_are_they", "where_could_they_be");
// Event Radio_GoGoGo =
// Event Radio_Fallback =
// Event Radio_StickTogether =
// Event Radio_GetInPosition =
// Event Radio_StormTheFront =
Event Radio_ReportTeam = report_in_team, anyone_see_them, anyone_see_anything, where_are_they, where_could_they_be
Event Radio_Affirmative = ("affirmative", "roger_that", "me_too", "ill_come_with_you", "ill_go_with_you", "ill_go_too", "i_got_your_back", "i_got_your_back2", "im_with_you", "im_with_you", "sounds_like_a_plan", "good_idea");
Event Radio_EnemySpotted = ("one_guy", "two_of_them", "three", "three_of_them", "a_bunch_of_them", "theyre_all_over_the_place2", "theyre_everywhere2", "theres_too_many_of_them", "theres_too_many", "too_many2", "the_actions_hot_here", "its_a_party");
Event Radio_NeedBackup = ("taking_fire_need_assistance2", "i_could_use_some_help", "i_could_use_some_help_over_here", "help", "need_help", "need_help2", "im_in_trouble");
Event Radio_SectorClear = ("clear", "clear2", "clear3", "clear4", "area_clear", "all_clear_here", "nothing_moving_over_here", "all_quiet", "nothing_happening_over_here", "i_got_nothing", "nothing", "nothing_here", "theres_nobody_home");
Event Radio_InPosition = ("lets_wait_here", "lets_hold_up_here_for_a_minute", "im_gonna_hang_back", "im_going_to_wait_here", "im_waiting_here");
Event Radio_ReportingIn = ("reporting_in");
// Event Radio_ShesGonnaBlow = ("");
Event Radio_Negative = ("ahh_negative", "negative", "no2", "negative2", "i_dont_think_so", "naa", "no_thanks", "no", "nnno_sir", "no_sir");
Event Radio_EnemyDown = ("enemy_down", "enemy_down2");
Event Radio_Affirmative = affirmative, roger_that, me_too, ill_come_with_you, ill_go_with_you, ill_go_too, i_got_your_back, i_got_your_back2, im_with_you, im_with_you, sounds_like_a_plan, good_idea
Event Radio_EnemySpotted = one_guy, two_of_them, three, three_of_them, a_bunch_of_them, theyre_all_over_the_place2, theyre_everywhere2, theres_too_many_of_them, theres_too_many, too_many2, the_actions_hot_here, its_a_party
Event Radio_NeedBackup = taking_fire_need_assistance2, i_could_use_some_help, i_could_use_some_help_over_here, help, need_help, need_help2, im_in_trouble
Event Radio_SectorClear = clear, clear2, clear3, clear4, area_clear, all_clear_here, nothing_moving_over_here, all_quiet, nothing_happening_over_here, i_got_nothing, nothing, nothing_here, theres_nobody_home
Event Radio_InPosition = lets_wait_here, lets_hold_up_here_for_a_minute, im_gonna_hang_back, im_going_to_wait_here, im_waiting_here
Event Radio_ReportingIn = reporting_in
// Event Radio_ShesGonnaBlow =
Event Radio_Negative = ahh_negative, negative, no2, negative2, i_dont_think_so, naa, no_thanks, no, nnno_sir, no_sir
Event Radio_EnemyDown = enemy_down, enemy_down2
// end of radio, begin some voices (NOT SORTED)
Event Chatter_SpotTheBomber = ("i_see_the_bomber", "theres_the_bomber", "hes_got_the_bomb", "hes_got_the_bomb2", "hes_got_the_package", "spotted_the_delivery_boy");
Event Chatter_FriendlyFire = ("cut_it_out", "what_are_you_doing", "stop_it", "ow_its_me", "ow", "ouch", "im_on_your_side", "hold_your_fire", "hey", "hey2", "ouch", "ouch", "ouch");
Event Chatter_DiePain = ("pain2", "pain4", "pain5", "pain8", "pain9", "pain10");
Event Chatter_GotBlinded = ("ive_been_blinded", "my_eyes", "i_cant_see", "im_blind");
Event Chatter_GoingToPlantBomb = ("im_gonna_go_plant", "im_gonna_go_plant_the_bomb");
Event Chatter_RescuingHostages = ("the_hostages_are_with_me", "taking_the_hostages_to_safety", "ive_got_the_hostages", "i_have_the_hostages");
Event Chatter_GoingToCamp = ("im_going_to_camp");
Event Chatter_HearSomething = ("hang_on_i_heard_something", "i_hear_something", "i_hear_them", "i_heard_them", "i_heard_something_over_there");
Event Chatter_TeamKill = ("what_happened", "noo", "oh_my_god", "oh_man", "oh_no_sad", "what_have_you_done");
Event Chatter_ReportingIn = ("reporting_in");
Event Chatter_GuardDroppedC4 = ("bombsite", "bombsite2");
Event Chatter_Camp = ("im_waiting_here");
Event Chatter_PlantingC4 = ("planting_the_bomb", "planting");
Event Chatter_DefusingC4 = ("defusing", "defusing_bomb", "defusing_bomb_now");
Event Chatter_InCombat = ("attacking", "attacking_enemies", "engaging_enemies", "in_combat", "in_combat2", "returning_fire");
Event Chatter_SeeksEnemy = ("lets_wait_here", "lets_hold_up_here_for_a_minute", "im_gonna_hang_back", "im_going_to_wait_here", "im_waiting_here");
Event Chatter_Nothing = ("nothing_here", "nothing");
Event Chatter_EnemyDown = ("hes_dead", "hes_down", "got_him", "dropped_him", "killed_him", "ruined_his_day", "wasted_him", "made_him_cry", "took_him_down", "took_him_out2", "took_him_out", "hes_broken", "hes_done");
Event Chatter_UseHostage = ("talking_to_hostages", "rescuing_hostages");
Event Chatter_FoundC4 = ("bombs_on_the_ground", "bombs_on_the_ground_here", "the_bomb_is_down", "the_bomb_is_on_the_ground", "they_dropped_the_bomb");
Event Chatter_WonTheRound = ("good_job_team", "nice_work_team", "way_to_be_team", "well_done");
Event Chatter_QuicklyWonTheRound = ("i_am_dangerous", "do_not_mess_with_me", "we_owned_them", "they_never_knew_what_hit_them", "thats_the_way_this_is_done", "and_thats_how_its_done", "owned", "yesss", "yesss2", "yea_baby", "whoo", "whoo2", "oh_yea", "oh_yea2");
Event Chatter_ScaredEmotion = ("whoa", "uh_oh", "oh_no", "yikes", "oh", "oh_boy", "oh_boy2", "aah");
Event Chatter_HeardEnemy = ("i_hear_them", "hang_on_i_heard_something", "i_hear_something", "i_heard_them", "i_heard_something_over_there");
Event Chatter_SniperWarning = ("sniper", "sniper2", "watch_it_theres_a_sniper");
Event Chatter_SniperKilled = ("got_the_sniper", "got_the_sniper2", "sniper_down", "took_out_the_sniper", "the_sniper_is_dead");
Event Chatter_VIPSpotted = ("i_see_our_target", "target_spotted", "target_acquired");
Event Chatter_GuardingVipSafety = ("watching_the_escape_route", "im_at_the_escape_zone", "watching_the_escape_zone", "guarding_the_escape_zone", "guarding_the_escape_zone2");
Event Chatter_GoingToGuardVIPSafety = ("im_going_to_cover_the_escape_zone", "im_going_to_watch_the_escape_zone", "im_going_to_keep_an_eye_on_the_escape", "heading_to_the_escape_zone");
Event Chatter_OneEnemyLeft = ("one_guy_left", "theres_one_left");
Event Chatter_TwoEnemiesLeft = ("two_enemies_left", "two_to_go");
Event Chatter_ThreeEnemiesLeft = ("three_left", "three_to_go", "three_to_go2");
Event Chatter_NoEnemiesLeft = ("that_was_the_last_one", "that_was_it", "that_was_the_last_guy");
Event Chatter_FoundBombPlace = ("theres_the_bomb", "theres_the_bomb2");
Event Chatter_WhereIsTheBomb = ("wheres_the_bomb", "wheres_the_bomb2", "wheres_the_bomb3", "where_is_it");
Event Chatter_DefendingBombSite = ("bombsite", "bombsite2");
Event Chatter_BarelyDefused = ("i_wasnt_worried_for_a_minute", "that_was_a_close_one", "well_done", "whew_that_was_close");
Event Chatter_NiceshotCommander = ("good_one_sir", "good_one_sir2", "nice_shot_sir", "nice_one_sir");
Event Chatter_NiceshotPall = ("good_one", "good_one2", "nice_shot", "nice_shot2", "good_shot", "good_shot2", "nice", "nice2", "very_nice");
Event Chatter_GoingToGuardHostages = ("camping_hostages", "im_going_to_camp_the_hostages", "im_going_to_guard_the_hostages", "im_going_to_guard_the_hostages2");
Event Chatter_GoingToGuardDoppedBomb = ("im_going_to_guard_the_bomb", "im_going_to_guard_the_bomb2", "im_going_to_keep_an_eye_on_the_bomb", "im_going_to_watch_the_bomb");
Event Chatter_OnMyWay = ("on_my_way", "on_my_way2", "im_coming", "hang_on_im_coming", "be_right_there");
Event Chatter_LeadOnSir = ("lead_on_sir", "lead_the_way_sir", "lead_the_way", "ok_sir_lets_go", "lead_on_commander", "lead_the_way_commander", "ok_cmdr_lets_go");
Event Chatter_Pinned_Down = ("they_got_me_pinned_down_here", "im_pinned_down");
Event Chatter_GottaFindTheBomb = ("theres_the_bomb", "theres_the_bomb2");
Event Chatter_Lost_The_Commander = ("weve_lost_the_commander", "the_commander_is_down", "the_commander_is_down_repeat");
Event Chatter_CoverMe = ("cover_me", "cover_me2");
Event Chatter_BombSiteSecured = ("i_wasnt_worried_for_a_minute", "that_was_a_close_one", "well_done", "whew_that_was_close");
Event Chatter_SpotTheBomber = i_see_the_bomber, theres_the_bomber, hes_got_the_bomb, hes_got_the_bomb2, hes_got_the_package, spotted_the_delivery_boy
Event Chatter_FriendlyFire = cut_it_out, what_are_you_doing, stop_it, ow_its_me, ow, ouch, im_on_your_side, hold_your_fire, hey, hey2, ouch, ouch, ouch
Event Chatter_DiePain = pain2, pain4, pain5, pain8, pain9, pain10
Event Chatter_GotBlinded = ive_been_blinded, my_eyes, i_cant_see, im_blind
Event Chatter_GoingToPlantBomb = im_gonna_go_plant, im_gonna_go_plant_the_bomb
Event Chatter_RescuingHostages = the_hostages_are_with_me, taking_the_hostages_to_safety, ive_got_the_hostages, i_have_the_hostages
Event Chatter_GoingToCamp = im_going_to_camp
Event Chatter_HearSomething = hang_on_i_heard_something, i_hear_something, i_hear_them, i_heard_them, i_heard_something_over_there
Event Chatter_TeamKill = what_happened, noo, oh_my_god, oh_man, oh_no_sad, what_have_you_done
Event Chatter_ReportingIn = reporting_in
Event Chatter_GuardDroppedC4 = bombsite, bombsite2
Event Chatter_Camp = im_waiting_here
Event Chatter_PlantingC4 = planting_the_bomb, planting
Event Chatter_DefusingC4 = defusing, defusing_bomb, defusing_bomb_now
Event Chatter_InCombat = attacking, attacking_enemies, engaging_enemies, in_combat, in_combat2, returning_fire
Event Chatter_SeeksEnemy = lets_wait_here, lets_hold_up_here_for_a_minute, im_gonna_hang_back, im_going_to_wait_here, im_waiting_here
Event Chatter_Nothing = nothing_here, nothing
Event Chatter_EnemyDown = hes_dead, hes_down, got_him, dropped_him, killed_him, ruined_his_day, wasted_him, made_him_cry, took_him_down, took_him_out2, took_him_out, hes_broken, hes_done
Event Chatter_UseHostage = talking_to_hostages, rescuing_hostages
Event Chatter_FoundC4 = bombs_on_the_ground, bombs_on_the_ground_here, the_bomb_is_down, the_bomb_is_on_the_ground, they_dropped_the_bomb
Event Chatter_WonTheRound = good_job_team, nice_work_team, way_to_be_team, well_done
Event Chatter_QuicklyWonTheRound = i_am_dangerous, do_not_mess_with_me, we_owned_them, they_never_knew_what_hit_them, thats_the_way_this_is_done, and_thats_how_its_done, owned, yesss, yesss2, yea_baby, whoo, whoo2, oh_yea, oh_yea2
Event Chatter_ScaredEmotion = whoa, uh_oh, oh_no, yikes, oh, oh_boy, oh_boy2, aah
Event Chatter_HeardEnemy = i_hear_them, hang_on_i_heard_something, i_hear_something, i_heard_them, i_heard_something_over_there
Event Chatter_SniperWarning = sniper, sniper2, watch_it_theres_a_sniper
Event Chatter_SniperKilled = got_the_sniper, got_the_sniper2, sniper_down, took_out_the_sniper, the_sniper_is_dead
Event Chatter_VIPSpotted = i_see_our_target, target_spotted, target_acquired
Event Chatter_GuardingVipSafety = watching_the_escape_route, im_at_the_escape_zone, watching_the_escape_zone, guarding_the_escape_zone, guarding_the_escape_zone2
Event Chatter_GoingToGuardVIPSafety = im_going_to_cover_the_escape_zone, im_going_to_watch_the_escape_zone, im_going_to_keep_an_eye_on_the_escape, heading_to_the_escape_zone
Event Chatter_OneEnemyLeft = one_guy_left, theres_one_left
Event Chatter_TwoEnemiesLeft = two_enemies_left, two_to_go
Event Chatter_ThreeEnemiesLeft = three_left, three_to_go, three_to_go2
Event Chatter_NoEnemiesLeft = that_was_the_last_one, that_was_it, that_was_the_last_guy
Event Chatter_FoundBombPlace = theres_the_bomb, theres_the_bomb2
Event Chatter_WhereIsTheBomb = wheres_the_bomb, wheres_the_bomb2, wheres_the_bomb3, where_is_it
Event Chatter_DefendingBombSite = bombsite, bombsite2
Event Chatter_BarelyDefused = i_wasnt_worried_for_a_minute, that_was_a_close_one, well_done, whew_that_was_close
Event Chatter_NiceshotCommander = good_one_sir, good_one_sir2, nice_shot_sir, nice_one_sir
Event Chatter_NiceshotPall = good_one, good_one2, nice_shot, nice_shot2, good_shot, good_shot2, nice, nice2, very_nice
Event Chatter_GoingToGuardHostages = camping_hostages, im_going_to_camp_the_hostages, im_going_to_guard_the_hostages, im_going_to_guard_the_hostages2
Event Chatter_GoingToGuardDoppedBomb = im_going_to_guard_the_bomb, im_going_to_guard_the_bomb2, im_going_to_keep_an_eye_on_the_bomb, im_going_to_watch_the_bomb
Event Chatter_OnMyWay = on_my_way, on_my_way2, im_coming, hang_on_im_coming, be_right_there
Event Chatter_LeadOnSir = lead_on_sir, lead_the_way_sir, lead_the_way, ok_sir_lets_go, lead_on_commander, lead_the_way_commander, ok_cmdr_lets_go
Event Chatter_Pinned_Down = they_got_me_pinned_down_here, im_pinned_down
Event Chatter_GottaFindTheBomb = theres_the_bomb, theres_the_bomb2
Event Chatter_Lost_The_Commander = weve_lost_the_commander, the_commander_is_down, the_commander_is_down_repeat
Event Chatter_CoverMe = cover_me, cover_me2
Event Chatter_BombSiteSecured = i_wasnt_worried_for_a_minute, that_was_a_close_one, well_done, whew_that_was_close

@ -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
void cleanup ();
// show overlay message about analyzing
void displayOverlayMessage ();
public:
// 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
StringRef getRandomLogoName (int index) {
StringRef getLogoName (int index) {
return m_logos[index];
}
@ -258,7 +258,7 @@ public:
if (m_custom.exists (name)) {
return m_custom[name];
}
SimpleLogger::instance ().error ("Trying to fetch unknown custom variable: %s", name);
logger.error ("Trying to fetch unknown custom variable: %s", name);
return "";
}

View file

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

View file

@ -46,7 +46,8 @@ CR_DECLARE_SCOPED_ENUM (GameFlags,
ReGameDLL = cr::bit (9), // server dll is a regamedll
HasFakePings = cr::bit (10), // on that game version we can fake bots pings
HasBotVoice = cr::bit (11), // on that game version we can use chatter
AnniversaryHL25 = cr::bit (12) // half-life 25th anniversary engine
AnniversaryHL25 = cr::bit (12), // half-life 25th anniversary engine
Xash3DLegacy = cr::bit (13) // old xash3d-branch
)
// defines map type
@ -83,7 +84,7 @@ struct ConVarReg {
};
// entity prototype
using EntityFunction = void (*) (entvars_t *);
using EntityProto = void (*) (entvars_t *);
// rehlds has this fixed, but original hlds doesn't allocate string space passed to precache* argument, so game will crash when unloading module using metamod
class EngineWrap final {
@ -128,6 +129,7 @@ private:
Array <edict_t *> m_breakables {};
SmallArray <ConVarReg> m_cvars {};
SharedLibrary m_gameLib {};
SharedLibrary m_engineLib {};
EngineWrap m_engineWrap {};
bool m_precached {};
@ -343,6 +345,11 @@ public:
return m_gameLib;
}
// get loaded engine lib
const SharedLibrary &elib () {
return m_engineLib;
}
// get registered cvars list
const SmallArray <ConVarReg> &getCvars () {
return m_cvars;
@ -369,6 +376,9 @@ public:
// helper to sending the server message
void sendServerMessage (StringRef message);
// helper for sending hud messages to client
void sendHudMessage (edict_t *ent, const hudtextparms_t &htp, StringRef message);
// send server command
template <typename ...Args> void serverCommand (const char *fmt, Args &&...args) {
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);
}
public:
template <typename U> constexpr U get () const {
if constexpr (cr::is_same <U, float>::value) {
return ptr->value;
@ -467,25 +478,41 @@ public:
return ptr->value > 0.0f;
}
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 {
return ptr->value > 0.0f;
return get <bool> ();
}
int int_ () const {
return static_cast <int> (ptr->value);
return get <int> ();
}
float float_ () const {
return ptr->value;
return get <float> ();
}
StringRef str () const {
return ptr->string;
return get <StringRef> ();
}
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
CR_EXPOSE_GLOBAL_SINGLETON (Game, game);
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 getNearest (const Vector &origin, const float range = kInfiniteDistance, int flags = -1);
int getNearestNoBuckets (const Vector &origin, const float range = kInfiniteDistance, int flags = -1);
int getEditorNearest ();
int getEditorNearest (const float maxRange = 50.0f);
int clearConnections (int index);
int getBspSize ();
int locateBucket (const Vector &pos);
@ -302,8 +302,8 @@ public:
}
// check nodes range
bool exists (int index) const {
return index >= 0 && index < length ();
template <typename U> bool exists (U index) const {
return index >= 0 && index < static_cast <U> (length ());
}
// 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
void clearRoute ();
// can the node can be skipped?
bool cantSkipNode (const int a, const int b);
// do a post-smoothing after a* finished constructing path
void postSmooth (NodeAdderFn onAddedNode);
@ -156,6 +153,10 @@ public:
size_t getMaxLength () const {
return m_length / 2;
}
public:
// can the node can be skipped?
static bool cantSkipNode (const int a, const int b, bool skipVisCheck = false);
};
// floyd-warshall shortest path algorithm

View file

@ -17,10 +17,8 @@ private:
StringArray m_sentences {};
SmallArray <Client> m_clients {};
SmallArray <Twin <String, String>> m_tags {};
HashMap <int32_t, String> m_weaponAlias {};
Detour <decltype (sendto)> m_sendToDetour { };
public:
BotSupport ();
@ -67,23 +65,11 @@ public:
bool findNearestPlayer (void **holder, edict_t *to, float searchDistance = 4096.0, bool sameTeam = false, bool needBot = false, bool needAlive = false, bool needDrawn = false, bool needBotWithC4 = false);
// tracing decals for bots spraying logos
void traceDecals (entvars_t *pev, TraceResult *trace, int logotypeIndex);
void decalTrace (entvars_t *pev, TraceResult *trace, int logotypeIndex);
// update stats on clients
void updateClients ();
// chat helper to strip the clantags out of the string
void stripTags (String &line);
// chat helper to make player name more human-like
void humanizePlayerName (String &playerName);
// chat helper to add errors to the bot chat string
void addChatErrors (String &line);
// chat helper to find keywords for given string
bool checkKeywords (StringRef line, String &reply);
// generates ping bitmask for SVC_PINGS message
int getPingBitmask (edict_t *ent, int loss, int ping);
@ -96,9 +82,6 @@ public:
// send modified pings to all the clients
void emitPings (edict_t *to);
// installs the sendto function interception
void installSendTo ();
// checks if same model omitting the models directory
bool isModel (const edict_t *ent, StringRef model);
@ -113,6 +96,7 @@ public:
// re-show welcome after changelevel ?
void setNeedForWelcome (bool need) {
m_needToSendWelcome = need;
m_welcomeReceiveTime = -1.0f;
}
// get array of clients
@ -130,11 +114,6 @@ public:
return m_clients[index];
}
// disables send hook
bool disableSendTo () {
return m_sendToDetour.restore ();
}
// gets the shooting cone deviation
float getShootingCone (edict_t *ent, const Vector &pos) {
return ent->v.v_angle.forward () | (pos - (ent->v.origin + ent->v.view_ofs)).normalize (); // he's facing it, he meant it

View file

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

View file

@ -19,25 +19,7 @@ using namespace cr;
#include <product.h>
#include <module.h>
#include <constant.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);
}
};
#include <chatlib.h>
// tasks definition
struct BotTask {
@ -127,16 +109,6 @@ struct Client {
ClientNoise noise;
};
// define chatting collection structure
struct ChatCollection {
int chatProbability {};
float chatDelay {};
float timeNextChat {};
int entityIndex {};
String sayText {};
StringArray lastUsedSentences {};
};
// include bot graph stuff
#include <graph.h>
#include <vision.h>
@ -855,6 +827,7 @@ private:
#include "config.h"
#include "support.h"
#include "hooks.h"
#include "sounds.h"
#include "message.h"
#include "engine.h"

View file

@ -6,7 +6,7 @@
#
# version is now passed into the bot dll
project (
project(
'yapb',
'cpp',
version: '4.4',
@ -80,15 +80,15 @@ if git.found()
cxxflags += flags_versioned
version_data = configuration_data()
version_data.set('count', count)
version_data.set('hash', hash)
version_data.set('author', author)
version_data.set('machine', machine)
version_data.set('version', bot_version)
version_data.set('winver', winver)
version_data.set('compiler', '@0@ @1@'.format (cxx, cxx_version))
version_data.set('BUILD_COUNT', count)
version_data.set('BUILD_HASH', hash)
version_data.set('BUILD_AUTHOR', author)
version_data.set('BUILD_MACHINE', machine)
version_data.set('BUILD_VERSION', bot_version)
version_data.set('BUILD_WINVER', winver)
version_data.set('BUILD_COMPILER', '@0@ @1@'.format(cxx, cxx_version))
configure_file (input: 'inc/version.h.in', output: 'version.build.h', configuration: version_data)
configure_file(input: 'inc/version.h.in', output: 'version.build.h', configuration: version_data)
endif
# define crlib native build
@ -102,7 +102,7 @@ if cxx == 'clang' or cxx == 'gcc'
'-fno-threadsafe-statics', '-pthread'
]
if not opt_native and cpu != 'arm'
if not opt_native and cpu != 'arm' and not cpu.startswith('ppc')
cxxflags += '-mtune=generic'
endif
@ -110,7 +110,7 @@ if cxx == 'clang' or cxx == 'gcc'
cxxflags += [
'-march=armv8-a+fp+simd',
]
elif cpu != 'arm'
elif cpu != 'arm' and not cpu.startswith('ppc')
cxxflags += [
'-mmmx', '-msse', '-msse2', '-msse3', '-mssse3', '-mfpmath=sse'
]
@ -128,7 +128,7 @@ if cxx == 'clang' or cxx == 'gcc'
'-funroll-loops', '-fomit-frame-pointer', '-fno-stack-protector', '-fvisibility=hidden', '-fvisibility-inlines-hidden'
]
if os != 'darwin' and os != 'windows' and cpu != 'aarch64' and cpu != 'arm'
if os != 'darwin' and os != 'windows' and cpu != 'aarch64' and cpu != 'arm' and not cpu.startswith('ppc')
cxxflags += [
'-fdata-sections',
'-ffunction-sections',
@ -181,7 +181,7 @@ if cxx == 'clang' or cxx == 'gcc'
endif
# by default we buid 32bit binaries
if not opt_64bit and cpu != 'aarch64' and cpu != 'arm'
if not opt_64bit and cpu != 'aarch64' and cpu != 'arm' and not cpu.startswith('ppc')
cxxflags += '-m32'
ldflags += '-m32'
@ -261,6 +261,7 @@ sources = files(
'src/control.cpp',
'src/engine.cpp',
'src/graph.cpp',
'src/hooks.cpp',
'src/linkage.cpp',
'src/manager.cpp',
'src/module.cpp',

View file

@ -52,6 +52,9 @@ void GraphAnalyze::update () {
if (m_updateInterval >= game.time ()) {
return;
}
else {
displayOverlayMessage ();
}
// add basic nodes
if (!m_basicsCreated) {
@ -157,6 +160,8 @@ void GraphAnalyze::finish () {
return;
}
vistab.startRebuild ();
ctrl.enableDrawModels (false);
cv_quota.revert ();
}
}
@ -171,13 +176,6 @@ void GraphAnalyze::optimize () {
}
cleanup ();
// clear the useless connections
if (cv_graph_analyze_clean_paths_on_finish.bool_ ()) {
for (auto i = 0; i < graph.length (); ++i) {
graph.clearConnections (i);
}
}
auto smooth = [] (const Array <int> &nodes) {
Vector result;
@ -203,7 +201,7 @@ void GraphAnalyze::optimize () {
Array <int> indexes;
for (const auto &link : path.links) {
if (graph.exists (link.index) && !m_optimizedNodes[link.index] && cr::fequal (path.origin.z, graph[link.index].origin.z)) {
if (graph.exists (link.index) && !m_optimizedNodes[link.index] && !AStarAlgo::cantSkipNode (path.number, link.index, true)) {
indexes.emplace (link.index);
}
}
@ -218,6 +216,13 @@ void GraphAnalyze::optimize () {
graph.add (NodeAddFlag::Normal, pos);
}
}
// clear the useless connections
if (cv_graph_analyze_clean_paths_on_finish.bool_ ()) {
for (auto i = 0; i < graph.length (); ++i) {
graph.clearConnections (i);
}
}
}
void GraphAnalyze::cleanup () {
@ -262,6 +267,37 @@ void GraphAnalyze::cleanup () {
}
}
void GraphAnalyze::displayOverlayMessage () {
auto listenserverEdict = game.getLocalEntity ();
if (game.isNullEntity (listenserverEdict) || !m_isAnalyzing) {
return;
}
constexpr StringRef analyzeHudMesssage =
"+--------------------------------------------------------+\n"
" Map analysis for bots is in progress. Please Wait.. \n"
"+--------------------------------------------------------+\n";
static hudtextparms_t textParams {};
textParams.channel = 1;
textParams.x = -1.0f;
textParams.y = -1.0f;
textParams.effect = 1;
textParams.r1 = textParams.r2 = static_cast <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) {
range *= 0.75f;

View file

@ -1686,7 +1686,7 @@ void Bot::refreshEnemyPredict () {
if (distanceToLastEnemySq > cr::sqrf (128.0f) && (distanceToLastEnemySq < cr::sqrf (2048.0f) || usesSniper ())) {
m_aimFlags |= AimFlags::PredictPath;
}
const bool denyLastEnemy = pev->velocity.lengthSq2d () > 0.0f && distanceToLastEnemySq < cr::sqrf (256.0f);
const bool denyLastEnemy = pev->velocity.lengthSq2d () > 0.0f && distanceToLastEnemySq < cr::sqrf (256.0f) && m_shootTime + 2.5f > game.time ();
if (!denyLastEnemy && seesEntity (m_lastEnemyOrigin, true)) {
m_aimFlags |= AimFlags::LastEnemy;
@ -1778,19 +1778,14 @@ void Bot::setConditions () {
m_numFriendsLeft = numFriendsNear (pev->origin, kInfiniteDistance);
m_numEnemiesLeft = numEnemiesNear (pev->origin, kInfiniteDistance);
auto clearLastEnemy = [&] () {
m_lastEnemyOrigin = nullptr;
m_lastEnemy = nullptr;
};
// check if our current enemy is still valid
if (!game.isNullEntity (m_lastEnemy)) {
if (!util.isAlive (m_lastEnemy) && m_shootAtDeadTime < game.time ()) {
clearLastEnemy ();
m_lastEnemy = nullptr;
}
}
else {
clearLastEnemy ();
m_lastEnemy = nullptr;
}
// don't listen if seeing enemy, just checked for sounds or being blinded (because its inhuman)
@ -1798,8 +1793,20 @@ void Bot::setConditions () {
updateHearing ();
m_soundUpdateTime = game.time () + 0.25f;
}
else if (m_heardSoundTime < game.time ()) {
else if (m_heardSoundTime + 10.0f < game.time ()) {
m_states &= ~Sense::HearingEnemy;
// clear the last enemy pointers if time has passed or enemy far away
if (!m_lastEnemyOrigin.empty ()) {
auto distanceSq = pev->origin.distanceSq (m_lastEnemyOrigin);
if (distanceSq > cr::sqrf (2048.0f) || (game.isNullEntity (m_enemy) && m_seeEnemyTime + 10.0f < game.time ())) {
m_lastEnemyOrigin = nullptr;
m_lastEnemy = nullptr;
m_aimFlags &= ~AimFlags::LastEnemy;
}
}
}
refreshEnemyPredict ();
@ -2001,11 +2008,11 @@ void Bot::filterTasks () {
offensive = subsumeDesire (offensive, pickup); // if offensive task, don't allow picking up stuff
auto sub = maxDesire (offensive, def); // default normal & careful tasks against offensive actions
auto final = subsumeDesire (&filter[Task::Blind], maxDesire (survive, sub)); // reason about fleeing instead
auto finalTask = subsumeDesire (&filter[Task::Blind], maxDesire (survive, sub)); // reason about fleeing instead
if (!m_tasks.empty ()) {
final = maxDesire (final, getTask ());
startTask (final->id, final->desire, final->data, final->time, final->resume); // push the final behavior in our task stack to carry out
finalTask = maxDesire (finalTask, getTask ());
startTask (finalTask->id, finalTask->desire, finalTask->data, finalTask->time, finalTask->resume); // push the final behavior in our task stack to carry out
}
}
@ -2733,12 +2740,6 @@ void Bot::frame () {
kick ();
return;
}
// clear enemy far away
if (!m_lastEnemyOrigin.empty () && !game.isNullEntity (m_lastEnemy) && pev->origin.distanceSq (m_lastEnemyOrigin) >= cr::sqrf (2048.0f)) {
m_lastEnemy = nullptr;
m_lastEnemyOrigin = nullptr;
}
m_slowFrameTimestamp = game.time () + 0.5f;
}
@ -3097,7 +3098,7 @@ void Bot::showDebugOverlay () {
}
auto overlayEntity = graph.getEditor ();
if (overlayEntity->v.iuser2 == entindex ()) {
if (overlayEntity->v.iuser2 == entindex () && overlayEntity->v.origin.distanceSq (pev->origin) < cr::sqrf (256.0f)) {
displayDebugOverlay = true;
}
@ -3162,12 +3163,11 @@ void Bot::showDebugOverlay () {
if (m_tasks.empty ()) {
return;
}
const auto drawTime = globals->frametime * 500.0f;
if (tid != getCurrentTaskId () || index != m_currentNodeIndex || goal != getTask ()->data || m_timeDebugUpdateTime < game.time ()) {
tid = getCurrentTaskId ();
index = m_currentNodeIndex;
goal = getTask ()->data;
index = m_currentNodeIndex;
String enemy = "(none)";
@ -3191,31 +3191,38 @@ void Bot::showDebugOverlay () {
aimFlags.appendf (" %s", flags[static_cast <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;
debugData.assignf ("\n\n\n\n\n%s (H:%.1f/A:%.1f)- Task: %d=%s Desire:%.02f\nItem: %s Clip: %d Ammo: %d%s Money: %d AimFlags: %s\nSP=%.02f SSP=%.02f I=%d PG=%d G=%d T: %.02f MT: %d\nEnemy=%s Pickup=%s Type=%s Terrain=%s Stuck=%s\n", pev->netname.str (), m_healthValue, pev->armorvalue, tid, tasks[tid], getTask ()->desire, weapon, getAmmoInClip (), getAmmo (), m_isReloading ? " (R)" : "", m_moneyAmount, aimFlags.trim (), m_moveSpeed, m_strafeSpeed, index, m_prevGoalIndex, goal, m_navTimeset - game.time (), pev->movetype, enemy, pickup, personalities[m_personality], boolValue (m_checkTerrain), boolValue (m_isStuck));
static hudtextparms_t textParams {};
MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullptr, overlayEntity)
.writeByte (TE_TEXTMESSAGE)
.writeByte (1)
.writeShort (MessageWriter::fs16 (-1.0f, 13.0f))
.writeShort (MessageWriter::fs16 (0.0f, 13.0f))
.writeByte (0)
.writeByte (m_team == Team::CT ? 0 : 255)
.writeByte (100)
.writeByte (m_team != Team::CT ? 0 : 255)
.writeByte (0)
.writeByte (255)
.writeByte (255)
.writeByte (255)
.writeByte (0)
.writeShort (MessageWriter::fu16 (0.0f, 8.0f))
.writeShort (MessageWriter::fu16 (0.0f, 8.0f))
.writeShort (MessageWriter::fu16 (drawTime, 8.0f))
.writeString (debugData.chars ());
textParams.channel = 1;
textParams.x = -1.0f;
textParams.y = 0.0f;
textParams.effect = 0;
m_timeDebugUpdateTime = game.time () + drawTime;
textParams.r1 = textParams.r2 = static_cast <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
@ -3714,7 +3721,7 @@ void Bot::updateHearing () {
}
// didn't bot already have an enemy ? take this one...
if (m_lastEnemyOrigin.empty () || m_lastEnemy == nullptr) {
if (m_lastEnemyOrigin.empty () || game.isNullEntity (m_lastEnemy)) {
m_lastEnemy = hearedEnemy;
m_lastEnemyOrigin = hearedEnemy->v.origin;
}

View file

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

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)) {
other->m_lastEnemy = newEnemy;
other->m_lastEnemyOrigin = m_lastEnemyOrigin;
other->m_lastEnemyOrigin = newEnemy->v.origin;
other->m_seeEnemyTime = game.time ();
other->m_states |= (Sense::SuspectEnemy | Sense::HearingEnemy);
other->m_aimFlags |= AimFlags::LastEnemy;
@ -405,7 +405,7 @@ bool Bot::lookupEnemies () {
// shoot at dying players if no new enemy to give some more human-like illusion
if (m_seeEnemyTime + 0.1f > game.time ()) {
if (!usesSniper ()) {
m_shootAtDeadTime = game.time () + cr::clamp (m_agressionLevel * 1.25f, 0.25f, 0.45f);
m_shootAtDeadTime = game.time () + cr::clamp (m_agressionLevel * 1.25f, 0.15f, 0.25f);
m_actualReactionTime = 0.0f;
m_states |= Sense::SuspectEnemy;
@ -424,7 +424,11 @@ bool Bot::lookupEnemies () {
}
// if no enemy visible check if last one shoot able through wall
if (cv_shoots_thru_walls.bool_ () && rg.chance (conf.getDifficultyTweaks (m_difficulty)->seenThruPct) && m_difficulty >= Difficulty::Normal && isPenetrableObstacle (newEnemy->v.origin)) {
if (cv_shoots_thru_walls.bool_ ()
&& m_difficulty >= Difficulty::Normal
&& rg.chance (conf.getDifficultyTweaks (m_difficulty)->seenThruPct)
&& isPenetrableObstacle (newEnemy->v.origin)) {
m_seeEnemyTime = game.time ();
m_states |= Sense::SuspectEnemy;
@ -439,7 +443,15 @@ bool Bot::lookupEnemies () {
}
// check if bots should reload...
if ((m_aimFlags <= AimFlags::PredictPath && m_seeEnemyTime + 3.0f < game.time () && !(m_states & (Sense::SeeingEnemy | Sense::HearingEnemy)) && game.isNullEntity (m_lastEnemy) && game.isNullEntity (m_enemy) && getCurrentTaskId () != Task::ShootBreakable && getCurrentTaskId () != Task::PlantBomb && getCurrentTaskId () != Task::DefuseBomb) || bots.isRoundOver ()) {
if ((m_aimFlags <= AimFlags::PredictPath
&& m_seeEnemyTime + 3.0f < game.time ()
&& !(m_states & (Sense::SeeingEnemy | Sense::HearingEnemy))
&& game.isNullEntity (m_lastEnemy)
&& game.isNullEntity (m_enemy)
&& getCurrentTaskId () != Task::ShootBreakable
&& getCurrentTaskId () != Task::PlantBomb
&& getCurrentTaskId () != Task::DefuseBomb) || bots.isRoundOver ()) {
if (!m_reloadState) {
m_reloadState = Reload::Primary;
}
@ -458,20 +470,20 @@ bool Bot::lookupEnemies () {
}
Vector Bot::getBodyOffsetError (float distance) {
if (game.isNullEntity (m_enemy)) {
if (game.isNullEntity (m_enemy) || distance < kSprayDistance) {
return nullptr;
}
if (m_aimErrorTime < game.time ()) {
const float hitError = distance / (cr::clamp (static_cast <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;
m_aimLastError = Vector (rg.get (mins.x * hitError, maxs.x * hitError), rg.get (mins.y * hitError, maxs.y * hitError), rg.get (mins.z * hitError, maxs.z * hitError));
m_aimLastError = Vector (rg.get (mins.x * hitError, maxs.x * hitError), rg.get (mins.y * hitError, maxs.y * hitError), rg.get (mins.z * hitError * 0.5f, maxs.z * hitError * 0.5f));
const auto &aimError = conf.getDifficultyTweaks (m_difficulty) ->aimError;
m_aimLastError += Vector (rg.get (-aimError.x, aimError.x), rg.get (-aimError.y, aimError.y), rg.get (-aimError.z, aimError.z));
m_aimErrorTime = game.time () + rg.get (1.5f, 2.0f);
m_aimErrorTime = game.time () + rg.get (0.4f, 0.8f);
}
return m_aimLastError;
}
@ -514,7 +526,7 @@ Vector Bot::getBodyOffsetError (float distance) {
else if (util.isPlayer (m_enemy)) {
// now take in account different parts of enemy body
if (m_enemyParts & (Visibility::Head | Visibility::Body)) {
auto headshotPct = conf.getDifficultyTweaks (m_difficulty)->headshotPct;
const auto headshotPct = conf.getDifficultyTweaks (m_difficulty)->headshotPct;
// now check is our skill match to aim at head, else aim at enemy body
if (rg.chance (headshotPct)) {
@ -570,7 +582,7 @@ Vector Bot::getCustomHeight (float distance) {
{ 1.5f, -4.0f, -9.0f } // heavy
};
// only highskilled bots do that
// only high-skilled bots do that
if (m_difficulty != Difficulty::Expert || (m_enemy->v.flags & FL_DUCKING)) {
return 0.0f;
}
@ -987,12 +999,19 @@ void Bot::fireWeapons () {
// loop through all the weapons until terminator is found...
while (tab[selectIndex].id) {
const auto wid = tab[selectIndex].id;
// is the bot carrying this weapon?
if (weapons & cr::bit (tab[selectIndex].id)) {
if (weapons & cr::bit (wid)) {
// is enough ammo available to fire AND check is better to use pistol in our current situation...
if (m_ammoInClip[tab[selectIndex].id] > 0 && !isWeaponBadAtDistance (selectIndex, distance)) {
choosenWeapon = selectIndex;
if (m_ammoInClip[wid] > 0 && !isWeaponBadAtDistance (selectIndex, distance)) {
const auto &prop = conf.getWeaponProp (wid);
// skip the weapons that cannot be used underwater (regamedll addition)
if (!(pev->waterlevel == 3 && (prop.flags & ITEM_FLAG_NOFIREUNDERWATER))) {
choosenWeapon = selectIndex;
}
}
}
selectIndex++;
@ -1005,11 +1024,11 @@ void Bot::fireWeapons () {
// loop through all the weapons until terminator is found...
while (tab[selectIndex].id) {
const int id = tab[selectIndex].id;
const int wid = tab[selectIndex].id;
// is the bot carrying this weapon?
if (weapons & cr::bit (id)) {
if (getAmmo (id) >= tab[selectIndex].minPrimaryAmmo) {
if (weapons & cr::bit (wid)) {
if (getAmmo (wid) >= tab[selectIndex].minPrimaryAmmo) {
// available ammo found, reload weapon
if (m_reloadState == Reload::None || m_reloadCheckTime > game.time ()) {
@ -1195,7 +1214,7 @@ void Bot::attackMovement () {
}
}
}
else if (rg.get (0, 100) < (isInNarrowPlace () ? 25 : 75) || usesKnife ()) {
else if (usesKnife ()) {
m_fightStyle = Fight::Strafe;
}
else {
@ -1217,10 +1236,6 @@ void Bot::attackMovement () {
m_fightStyle = Fight::Strafe;
}
if (usesPistol () && distance < 768.0f) {
m_fightStyle = Fight::Strafe;
}
if (m_fightStyle == Fight::Strafe) {
auto swapStrafeCombatDir = [&] () {
m_combatStrafeDir = (m_combatStrafeDir == Dodge::Left ? Dodge::Right : Dodge::Left);

View file

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

View file

@ -195,7 +195,29 @@ int BotControl::cmdList () {
int BotControl::cmdCvars () {
enum args { alias = 1, pattern };
const auto &match = strValue (pattern);
auto match = strValue (pattern);
// revert all the cvars to their default values
if (match == "defaults") {
msg ("Bots cvars has been reverted to their default values.");
for (const auto &cvar : game.getCvars ()) {
if (!cvar.self || !cvar.self->ptr || cvar.type == Var::GameRef) {
continue;
}
// set depending on cvar type
if (cvar.bounded) {
cvar.self->set (cvar.initial);
}
else {
cvar.self->set (cvar.init.chars ());
}
}
cv_quota.revert (); // quota should be reverted instead of regval
return BotCommandResult::Handled;
}
const bool isSaveMain = match == "save";
const bool isSaveMap = match == "save_map";
@ -2162,11 +2184,9 @@ bool BotControl::handleMenuCommands (edict_t *ent) {
}
void BotControl::enableDrawModels (bool enable) {
StringArray entities;
entities.push ("info_player_start");
entities.push ("info_player_deathmatch");
entities.push ("info_vip_start");
static StringArray entities {
"info_player_start", "info_player_deathmatch", "info_vip_start"
};
if (enable) {
game.setPlayerStartDrawModels ();

View file

@ -81,7 +81,7 @@ void Game::levelInitialize (edict_t *entities, int max) {
bots.initQuota ();
// install the sendto hook to fake queries
util.installSendTo ();
fakequeries.init ();
// flush any print queue
ctrl.resetFlushTimestamp ();
@ -332,11 +332,13 @@ void Game::registerEngineCommand (const char *command, void func ()) {
// that for every "command_name" server command it receives, it should call the function
// pointed to by "function" in order to handle it.
// check for hl pre 1.1.0.4, as it's doesn't have pfnAddServerCommand
// check for hl pre 1.1.0.4, as it's doesn't have pfnAddServerCommand and many more stuff we need to work
if (!plat.isValidPtr (engfuncs.pfnAddServerCommand)) {
logger.fatal ("%s's minimum HL engine version is 1.1.0.4 and minimum Counter-Strike is Beta 6.5. Please update your engine / game version.", product.name);
}
engfuncs.pfnAddServerCommand (const_cast <char *> (command), func);
else {
engfuncs.pfnAddServerCommand (command, func);
}
}
void Game::playSound (edict_t *ent, const char *sound) {
@ -392,8 +394,8 @@ bool Game::checkVisibility (edict_t *ent, uint8_t *set) {
}
uint8_t *Game::getVisibilitySet (Bot *bot, bool pvs) {
if (is (GameFlags::Xash3D)) {
return nullptr; // TODO: bug fixed in upstream xash3d, should be removed
if (is (GameFlags::Xash3DLegacy)) {
return nullptr;
}
auto eyes = bot->getEyesPos ();
@ -460,6 +462,37 @@ void Game::sendServerMessage (StringRef message) {
engfuncs.pfnServerPrint (message.chars ());
}
void Game::sendHudMessage (edict_t *ent, const hudtextparms_t &htp, StringRef message) {
constexpr size_t maxSendLength = 512;
if (game.isNullEntity (ent)) {
return;
}
MessageWriter msg (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullptr, ent);
msg.writeByte (TE_TEXTMESSAGE);
msg.writeByte (htp.channel & 0xff);
msg.writeShort (MessageWriter::fs16 (htp.x, 13.0f));
msg.writeShort (MessageWriter::fs16 (htp.y, 13.0f));
msg.writeByte (htp.effect);
msg.writeByte (htp.r1);
msg.writeByte (htp.g1);
msg.writeByte (htp.b1);
msg.writeByte (htp.a1);
msg.writeByte (htp.r2);
msg.writeByte (htp.g2);
msg.writeByte (htp.b2);
msg.writeByte (htp.a2);
msg.writeShort (MessageWriter::fu16 (htp.fadeinTime, 8.0f));
msg.writeShort (MessageWriter::fu16 (htp.fadeoutTime, 8.0f));
msg.writeShort (MessageWriter::fu16 (htp.holdTime, 8.0f));
if (htp.effect == 2) {
msg.writeShort (MessageWriter::fu16 (htp.fxTime, 8.0f));
}
msg.writeString (message.substr (0, maxSendLength).chars ());
}
void Game::prepareBotArgs (edict_t *ent, String str) {
// the purpose of this function is to provide fakeclients (bots) with the same client
// command-scripting advantages (putting multiple commands in one line between semicolons)
@ -726,7 +759,7 @@ bool Game::loadCSBinary () {
if (!m_gameLib) {
logger.fatal ("Unable to load gamedll \"%s\". Exiting... (gamedir: %s)", dll, mod);
}
auto ent = m_gameLib.resolve <EntityFunction> ("trigger_random_unique");
auto ent = m_gameLib.resolve <EntityProto> ("trigger_random_unique");
// detect regamedll by addon entity they provide
if (ent != nullptr) {
@ -765,7 +798,12 @@ bool Game::loadCSBinary () {
}
// detect if we're running modern game
auto entity = m_gameLib.resolve <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
if (engfuncs.pfnCVarGetPointer ("host_ver") != nullptr) {
@ -855,6 +893,9 @@ bool Game::postload () {
// initialize weapons
conf.initWeapons ();
// register engine lib handle
m_engineLib.locate (reinterpret_cast <void *> (engfuncs.pfnPrecacheModel));
if (plat.android) {
m_gameFlags |= (GameFlags::Xash3D | GameFlags::Mobility | GameFlags::HasBotVoice | GameFlags::ReGameDLL);
@ -1245,7 +1286,7 @@ float LightMeasure::getLightLevel (const Vector &point) {
auto recursiveCheck = [&] () -> bool {
if (!isSoftRenderer) {
if (is25Anniversary) {
return recursiveLightPoint <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);
}

View file

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

View file

@ -412,13 +412,14 @@ void BotGraph::addPath (int addIndex, int pathIndex, float distance) {
return;
}
}
auto integerDistance = cr::abs (static_cast <int> (distance));
// check for free space in the connection indices
for (auto &link : path.links) {
if (link.index == kInvalidNodeIndex) {
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);
return;
}
@ -439,7 +440,7 @@ void BotGraph::addPath (int addIndex, int pathIndex, float distance) {
msg ("Path added from %d to %d.", addIndex, pathIndex);
path.links[slot].index = static_cast <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;
}
int BotGraph::getEditorNearest () {
int BotGraph::getEditorNearest (const float maxRange) {
if (!hasEditFlag (GraphEdit::On)) {
return kInvalidNodeIndex;
}
return getNearestNoBuckets (m_editor->v.origin, 50.0f);
return getNearestNoBuckets (m_editor->v.origin, maxRange);
}
int BotGraph::getNearest (const Vector &origin, const float range, int flags) {
@ -625,12 +626,12 @@ void BotGraph::add (int type, const Vector &pos) {
return;
case NodeAddFlag::JumpStart:
index = getEditorNearest ();
index = getEditorNearest (25.0f);
if (index != kInvalidNodeIndex && m_paths[index].number >= 0) {
const float distanceSq = m_editor->v.origin.distanceSq (m_paths[index].origin);
if (distanceSq < cr::sqrf (50.0f)) {
if (distanceSq < cr::sqrf (25.0f)) {
addNewNode = false;
path = &m_paths[index];
@ -643,12 +644,12 @@ void BotGraph::add (int type, const Vector &pos) {
break;
case NodeAddFlag::JumpEnd:
index = getEditorNearest ();
index = getEditorNearest (25.0f);
if (index != kInvalidNodeIndex && m_paths[index].number >= 0) {
const float distanceSq = m_editor->v.origin.distanceSq (m_paths[index].origin);
if (distanceSq < cr::sqrf (50.0f)) {
if (distanceSq < cr::sqrf (25.0f)) {
addNewNode = false;
path = &m_paths[index];
@ -1064,14 +1065,16 @@ void BotGraph::pathCreate (char dir) {
if (!isConnected (nodeFrom, nodeTo)) {
addPath (nodeFrom, nodeTo, distance);
}
for (auto &link : m_paths[nodeFrom].links) {
if (link.index == nodeTo && !(link.flags & PathFlag::Jump)) {
link.flags |= PathFlag::Jump;
m_paths[nodeFrom].radius = 0.0f;
msg ("Path added from %d to %d.", nodeFrom, nodeTo);
}
else if (link.index == nodeTo && (link.flags & PathFlag::Jump)) {
msg ("Denied path creation from %d to %d (path already exists).", nodeFrom, nodeTo);
msg ("Denied path creation from %d to %d (path already exists).", nodeFrom, nodeTo);
}
}
}
@ -1415,7 +1418,7 @@ void BotGraph::initNarrowPlaces () {
constexpr int32_t kNarrowPlacesMinGraphVersion = 2;
// if version 2 or higher, narrow places already initialized and saved into file
if (m_graphHeader.version >= kNarrowPlacesMinGraphVersion) {
if (m_graphHeader.version >= kNarrowPlacesMinGraphVersion && !hasEditFlag (GraphEdit::On)) {
m_narrowChecked = true;
return;
}
@ -2141,25 +2144,25 @@ void BotGraph::frame () {
}
static int channel = 0;
auto sendHudMessage = [] (Color color, float x, float y, edict_t *to, StringRef text) {
MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullptr, to)
.writeByte (TE_TEXTMESSAGE)
.writeByte (channel++ & 0xff) // channel
.writeShort (MessageWriter::fs16 (x, 13.0f)) // x
.writeShort (MessageWriter::fs16 (y, 13.0f)) // y
.writeByte (0) // effect
.writeByte (color.red) // r1
.writeByte (color.green) // g1
.writeByte (color.blue) // b1
.writeByte (1) // a1
.writeByte (color.red) // r2
.writeByte (color.green) // g2
.writeByte (color.blue) // b2
.writeByte (1) // a2
.writeShort (0) // fadeintime
.writeShort (0) // fadeouttime
.writeShort (MessageWriter::fu16 (1.0f, 8.0f)) // holdtime
.writeString (text.chars ());
auto sendHudMessage = [&] (Color color, float x, float y, StringRef text) {
static hudtextparms_t textParams {};
textParams.channel = channel;
textParams.x = x;
textParams.y = y;
textParams.effect = 0;
textParams.r1 = textParams.r2 = static_cast <uint8_t> (color.red);
textParams.g1 = textParams.g2 = static_cast <uint8_t> (color.green);
textParams.b1 = textParams.b2 = static_cast <uint8_t> (color.blue);
textParams.a1 = textParams.a2 = static_cast <uint8_t> (1);
textParams.fadeinTime = 0.0f;
textParams.fadeoutTime = 0.0f;
textParams.holdTime = m_pathDisplayTime;
textParams.fxTime = 0.0f;
game.sendHudMessage (m_editor, textParams, text);
if (channel > 3) {
channel = 0;
@ -2207,16 +2210,16 @@ void BotGraph::frame () {
};
// display some information
sendHudMessage ({ 255, 255, 255 }, 0.0f, 0.025f, m_editor, getNodeData ("Current", nearestIndex));
sendHudMessage ({ 255, 255, 255 }, 0.0f, 0.025f, getNodeData ("Current", nearestIndex));
// check if we need to show the cached point index
if (m_cacheNodeIndex != kInvalidNodeIndex) {
sendHudMessage ({ 255, 255, 255 }, 0.28f, 0.16f, m_editor, getNodeData ("Cached", m_cacheNodeIndex));
sendHudMessage ({ 255, 255, 255 }, 0.28f, 0.16f, getNodeData ("Cached", m_cacheNodeIndex));
}
// check if we need to show the facing point index
if (m_facingAtIndex != kInvalidNodeIndex) {
sendHudMessage ({ 255, 255, 255 }, 0.28f, 0.025f, m_editor, getNodeData ("Facing", m_facingAtIndex));
sendHudMessage ({ 255, 255, 255 }, 0.28f, 0.025f, getNodeData ("Facing", m_facingAtIndex));
}
String timeMessage = strings.format (" Map: %s, Time: %s\n", game.getMapName (), util.getCurrentDateTime ());
@ -2230,10 +2233,10 @@ void BotGraph::frame () {
" CT: %d / %d\n"
" T: %d / %d\n\n", dangerIndexCT, dangerIndexCT != kInvalidNodeIndex ? practice.getDamage (Team::CT, nearestIndex, dangerIndexCT) : 0, dangerIndexT, dangerIndexT != kInvalidNodeIndex ? practice.getDamage (Team::Terrorist, nearestIndex, dangerIndexT) : 0);
sendHudMessage ({ 255, 255, 255 }, 0.0f, 0.16f, m_editor, practiceText + timeMessage);
sendHudMessage ({ 255, 255, 255 }, 0.0f, 0.16f, practiceText + timeMessage);
}
else {
sendHudMessage ({ 255, 255, 255 }, 0.0f, 0.16f, m_editor, timeMessage);
sendHudMessage ({ 255, 255, 255 }, 0.0f, 0.16f, timeMessage);
}
}
}
@ -2350,10 +2353,10 @@ bool BotGraph::checkNodes (bool teleportPlayer) {
}
// perform DFS instead of floyd-warshall, this shit speedup this process in a bit
const auto length = cr::min (static_cast <size_t> (kMaxNodes), m_paths.length ());
const auto length = cr::min (static_cast <size_t> (kMaxNodes), m_paths.length ());
// ensure valid capacity
assert (length > 8 && length < static_cast <size_t> (kMaxNodes));
assert (length > 8 && length < static_cast <size_t> (kMaxNodes));
PathWalk walk;
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
};
CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int) {
// compilers can't create lambdas with vaargs, so put this one in it's own namespace
namespace Hooks {
void handler_engClientCommand (edict_t *ent, char const *format, ...) {
// this function forces the client whose player entity is ent to issue a client command.
// How it works is that clients all have a argv global string in their client DLL that
// stores the command string; if ever that string is filled with characters, the client DLL
// sends it to the engine as a command to be executed. When the engine has executed that
// command, this argv string is reset to zero. Here is somehow a curious implementation of
// ClientCommand: the engine sets the command it wants the client to issue in his argv, then
// the client DLL sends it back to the engine, the engine receives it then executes the
// command therein. Don't ask me why we need all this complicated crap. Anyhow since bots have
// no client DLL, be certain never to call this function upon a bot entity, else it will just
// make the server crash. Since hordes of uncautious, not to say stupid, programmers don't
// even imagine some players on their servers could be bots, this check is performed less than
// sometimes actually by their side, that's why we strongly recommend to check it here too. In
// case it's a bot asking for a client command, we handle it like we do for bot commands
if (game.isNullEntity (ent)) {
if (game.is (GameFlags::Metamod)) {
RETURN_META (MRES_SUPERCEDE);
}
return;
}
va_list ap;
auto buffer = strings.chars ();
va_start (ap, format);
vsnprintf (buffer, StringBuffer::StaticBufferSize, format, ap);
va_end (ap);
if (util.isFakeClient (ent) && !(ent->v.flags & FL_DORMANT)) {
auto bot = bots[ent];
if (bot) {
game.botCommand (bot->pev->pContainingEntity, buffer);
}
if (game.is (GameFlags::Metamod)) {
RETURN_META (MRES_SUPERCEDE); // prevent bots to be forced to issue client commands
}
return;
}
if (game.is (GameFlags::Metamod)) {
RETURN_META (MRES_IGNORED);
}
engfuncs.pfnClientCommand (ent, buffer);
}
}
CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
// this function is called right after GiveFnptrsToDll() by the engine in the game DLL (or
// what it BELIEVES to be the game DLL), in order to copy the list of MOD functions that can
// be called by the engine, into a memory block pointed to by the functionTable pointer
@ -49,7 +100,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int) {
auto api_GetEntityAPI = game.lib ().resolve <decltype (&GetEntityAPI)> (__func__);
// pass other DLLs engine callbacks to function table...
if (!api_GetEntityAPI || api_GetEntityAPI (&dllapi, INTERFACE_VERSION) == 0) {
if (!api_GetEntityAPI || api_GetEntityAPI (&dllapi, interfaceVersion) == 0) {
logger.fatal ("Could not resolve symbol \"%s\" in the game dll.", __func__);
}
dllfuncs.dllapi_table = &dllapi;
@ -294,7 +345,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int) {
dllapi.pfnServerDeactivate ();
// refill export table
ents.flush ();
entlink.flush ();
};
table->pfnStartFrame = [] () {
@ -447,12 +498,12 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
plat.bzero (table, sizeof (enginefuncs_t));
}
if (ents.needsBypass () && !game.is (GameFlags::Metamod)) {
if (entlink.needsBypass () && !game.is (GameFlags::Metamod)) {
table->pfnCreateNamedEntity = [] (string_t classname) -> edict_t *{
if (ents.isPaused ()) {
ents.enable ();
ents.setPaused (false);
if (entlink.isPaused ()) {
entlink.enable ();
entlink.setPaused (false);
}
return engfuncs.pfnCreateNamedEntity (classname);
};
@ -601,6 +652,9 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
engfuncs.pfnWriteEntity (value);
};
// very ancient engine versions (pre 2xxx builds) needs this to work correctly
table->pfnClientCommand = Hooks::handler_engClientCommand;
if (!game.is (GameFlags::Metamod)) {
table->pfnRegUserMsg = [] (const char *name, int size) {
// this function registers a "user message" by the engine side. User messages are network
@ -726,18 +780,19 @@ CR_EXPORT int GetNewDLLFunctions (newgamefuncs_t *table, int *interfaceVersion)
// pass them too, else the DLL interfacing wouldn't be complete and the game possibly wouldn't
// run properly.
plat.bzero (table, sizeof (newgamefuncs_t));
if (!(game.is (GameFlags::Metamod))) {
auto api_GetNewDLLFunctions = game.lib ().resolve <decltype (&GetNewDLLFunctions)> (__func__);
// pass other DLLs engine callbacks to function table...
if (!api_GetNewDLLFunctions || api_GetNewDLLFunctions (&newapi, interfaceVersion) == 0) {
logger.error ("Could not resolve symbol \"%s\" in the game dll.", __func__);
logger.error ("Could not resolve symbol \"%s\" in the game dll. Continuing...", __func__);
return HLFalse;
}
dllfuncs.newapi_table = &newapi;
memcpy (table, &newapi, sizeof (newgamefuncs_t));
}
plat.bzero (table, sizeof (newgamefuncs_t));
if (!game.is (GameFlags::Legacy)) {
table->pfnOnFreeEntPrivateData = [] (edict_t *ent) {
@ -847,7 +902,7 @@ CR_EXPORT int Meta_Detach (PLUG_LOADTIME now, PL_UNLOAD_REASON reason) {
practice.save ();
// disable hooks
util.disableSendTo ();
fakequeries.disable ();
// make sure all stuff cleared
bots.destroy ();
@ -912,7 +967,7 @@ DLL_GIVEFNPTRSTODLL GiveFnptrsToDll (enginefuncs_t *table, globalvars_t *glob) {
// initialize dynamic linkents (no memory hacking with xash3d)
if (!game.is (GameFlags::Xash3D)) {
ents.initialize ();
entlink.initialize ();
}
// give the engine functions to the other DLL...
@ -943,7 +998,7 @@ CR_EXPORT int Server_GetPhysicsInterface (int version, server_physics_api_t *phy
table->version = SV_PHYSICS_INTERFACE_VERSION;
table->SV_CreateEntity = [] (edict_t *ent, const char *name) -> int {
auto func = game.lib ().resolve <EntityFunction> (name); // lookup symbol in game dll
auto func = game.lib ().resolve <EntityProto> (name); // lookup symbol in game dll
// found one in game dll ?
if (func) {
@ -960,85 +1015,6 @@ CR_EXPORT int Server_GetPhysicsInterface (int version, server_physics_api_t *phy
return HLTrue;
}
SharedLibrary::Func EntityLinkage::lookup (SharedLibrary::Handle module, const char *function) {
static const auto &gamedll = game.lib ().handle ();
static const auto &self = m_self.handle ();
const auto resolve = [&] (SharedLibrary::Handle handle) {
return m_dlsym (handle, function);
};
if (ents.needsBypass () && !strcmp (function, "CreateInterface")) {
ents.setPaused (true);
auto ret = resolve (module);
ents.disable ();
return ret;
}
// if requested module is yapb module, put in cache the looked up symbol
if (self != module) {
return resolve (module);
}
#if defined (CR_WINDOWS)
if (HIWORD (function) == 0) {
return resolve (module);
}
#endif
if (m_exports.exists (function)) {
return m_exports[function];
}
auto botAddr = resolve (self);
if (!botAddr) {
auto gameAddr = resolve (gamedll);
if (gameAddr) {
return m_exports[function] = gameAddr;
}
}
else {
return m_exports[function] = botAddr;
}
return nullptr;
}
void EntityLinkage::callPlayerFunction (edict_t *ent) {
EntityFunction playerFunction = nullptr;
if (game.is (GameFlags::Xash3D)) {
playerFunction = game.lib ().resolve <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
#include "entities.cpp"

View file

@ -8,7 +8,7 @@
#include <yapb.h>
ConVar cv_autovacate ("autovacate", "1", "Kick bots to automatically make room for human players.");
ConVar cv_autovacate_keep_slots ("autovacate_keep_slots", "1", "How many slots autovacate feature should keep for human players", true, 1.0f, 8.0f);
ConVar cv_autovacate_keep_slots ("autovacate_keep_slots", "1", "How many slots autovacate feature should keep for human players.", true, 1.0f, 8.0f);
ConVar cv_kick_after_player_connect ("kick_after_player_connect", "1", "Kick the bot immediately when a human player joins the server (yb_autovacate must be enabled).");
ConVar cv_quota ("quota", "9", "Specifies the number bots to be added to the game.", true, 0.0f, static_cast <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_ct ("botskin_ct", "0", "Specifies the bots wanted skin for CT team.", true, 0.0f, 5.0f);
ConVar cv_preferred_personality ("preferred_personality", "none", "Sets the default personality when creating bots with quota management.\nAllowed values: 'none', 'normal', 'careful', 'rusher'.\nIf 'none' is specified personality chosen randomly.", false);
ConVar cv_ping_base_min ("ping_base_min", "7", "Lower bound for base bot ping shown in scoreboard upon creation.", true, 0.0f, 100.0f);
ConVar cv_ping_base_max ("ping_base_max", "34", "Upper bound for base bot ping shown in scoreboard upon creation.", true, 0.0f, 100.0f);
@ -149,7 +150,15 @@ void BotManager::execGameEntity (edict_t *ent) {
MUTIL_CallGameEntity (PLID, "player", &ent->v);
return;
}
ents.callPlayerFunction (ent);
if (!entlink.callPlayerFunction (ent)) {
for (const auto &bot : m_bots) {
if (bot->ent () == ent) {
bot->kick ();
break;
}
}
}
}
void BotManager::forEach (ForEachBot handler) {
@ -182,25 +191,42 @@ BotCreateResult BotManager::create (StringRef name, int difficulty, int personal
ctrl.msg ("Desired team is stacked. Unable to proceed with bot creation.");
return BotCreateResult::TeamStacked;
}
if (difficulty < 0 || difficulty > 4) {
if (difficulty < Difficulty::Noob || difficulty > Difficulty::Expert) {
difficulty = cv_difficulty.int_ ();
if (difficulty < 0 || difficulty > 4) {
if (difficulty < Difficulty::Noob || difficulty > Difficulty::Expert) {
difficulty = rg.get (3, 4);
cv_difficulty.set (difficulty);
}
}
// try to set proffered personality
static HashMap <String, Personality> personalityMap {
{"normal", Personality::Normal },
{"careful", Personality::Careful },
{"rusher", Personality::Rusher },
};
// set personality if requested
if (personality < Personality::Normal || personality > Personality::Careful) {
if (rg.chance (50)) {
personality = Personality::Normal;
// assign preferred if we're forced with cvar
if (personalityMap.exists (cv_preferred_personality.str ())) {
personality = personalityMap[cv_preferred_personality.str ()];
}
// do a holy random
else {
if (rg.chance (50)) {
personality = Personality::Rusher;
personality = Personality::Normal;
}
else {
personality = Personality::Careful;
if (rg.chance (50)) {
personality = Personality::Rusher;
}
else {
personality = Personality::Careful;
}
}
}
}
@ -685,8 +711,9 @@ bool BotManager::kickRandom (bool decQuota, Team fromTeam) {
// first try to kick the bot that is currently dead
for (const auto &bot : m_bots) {
if (!bot->m_isAlive && belongsTeam (bot.get ())) // is this slot used?
{
// is this slot used?
if (!bot->m_isAlive && belongsTeam (bot.get ())) {
updateQuota ();
bot->kick ();
@ -721,8 +748,9 @@ bool BotManager::kickRandom (bool decQuota, Team fromTeam) {
// worst case, just kick some random bot
for (const auto &bot : m_bots) {
if (belongsTeam (bot.get ())) // is this slot used?
{
// is this slot used?
if (belongsTeam (bot.get ())) {
updateQuota ();
bot->kick ();
@ -824,7 +852,8 @@ void BotManager::listBots () {
};
for (const auto &bot : bots) {
ctrl.msg ("[%-3.1d]\t%-19.16s\t%-10.12s\t%-3.4s\t%-3.1d\t%-3.1d\t%-3.4s\t%-3.0f secs", bot->index (), bot->pev->netname.chars (), bot->m_personality == Personality::Rusher ? "rusher" : bot->m_personality == Personality::Normal ? "normal" : "careful", botTeam (bot->m_team), bot->m_difficulty, static_cast <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 ());
}
@ -1007,7 +1036,9 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int skin) {
// set all info buffer keys for this bot
auto buffer = engfuncs.pfnGetInfoKeyBuffer (bot);
engfuncs.pfnSetClientKeyValue (clientIndex, buffer, "_vgui_menus", "0");
engfuncs.pfnSetClientKeyValue (clientIndex, buffer, "_ah", "0");
if (!game.is (GameFlags::Legacy)) {
if (cv_show_latency.int_ () == 1) {
@ -1260,7 +1291,16 @@ void BotManager::handleDeath (edict_t *killer, edict_t *victim) {
// notice nearby to victim teammates, that attacker is near
for (const auto &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_seeEnemyTime = game.time ();
notify->m_enemy = killer;

View file

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

View file

@ -7,10 +7,11 @@
#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_dijkstra_simple_distance ("path_dijkstra_simple_distance", "1", "Use simple distance path calculation instead of running full Dijkstra path cycle. Used only when Floyd matrices unavailable due to memory limit.");
ConVar cv_path_astar_post_smooth ("path_astar_post_smooth", "0", "Enables post-smoothing for A*. Reduces zig-zags on paths at cost of some CPU cycles.");
ConVar cv_path_randomize_on_round_start ("path_randomize_on_round_start", "1", "Randomize pathfinding on each round start.");
float Heuristic::gfunctionKillsDist (int team, int currentIndex, int parentIndex) {
if (parentIndex == kInvalidNodeIndex) {
@ -172,7 +173,7 @@ void AStarAlgo::clearRoute () {
m_routes.clear ();
}
bool AStarAlgo::cantSkipNode (const int a, const int b) {
bool AStarAlgo::cantSkipNode (const int a, const int b, bool skipVisCheck) {
const auto &ag = graph[a];
const auto &bg = graph[b];
@ -181,12 +182,14 @@ bool AStarAlgo::cantSkipNode (const int a, const int b) {
if (hasZeroRadius) {
return true;
}
const bool notVisible = !vistab.visible (ag.number, bg.number) || !vistab.visible (bg.number, ag.number);
if (notVisible) {
return true;
if (!skipVisCheck) {
const bool notVisible = !vistab.visible (ag.number, bg.number) || !vistab.visible (bg.number, ag.number);
if (notVisible) {
return true;
}
}
const bool tooHigh = cr::abs (ag.origin.z - bg.origin.z) > 17.0f;
if (tooHigh) {
@ -257,6 +260,14 @@ AStarResult AStarAlgo::find (int botTeam, int srcIndex, int destIndex, NodeAdder
// always clear constructed path
m_constructedPath.clear ();
// round start randomizer offset
auto rsRandomizer = 1.0f;
// randomize path on round start now and then
if (cv_path_randomize_on_round_start.bool_ () && bots.getRoundStartTime () + mp_freezetime.float_ () + 2.0f > game.time ()) {
rsRandomizer = rg.get (0.5f, static_cast <float> (botTeam) * 2.0f + 5.0f);
}
while (!m_routeQue.empty ()) {
// remove the first node from the open list
int currentIndex = m_routeQue.pop ().index;
@ -302,7 +313,7 @@ AStarResult AStarAlgo::find (int botTeam, int srcIndex, int destIndex, NodeAdder
auto childRoute = &m_routes[child.index];
// calculate the F value as F = G + H
const float g = curRoute->g + m_gcalc (botTeam, child.index, currentIndex);
const float g = curRoute->g + m_gcalc (botTeam, child.index, currentIndex) * rsRandomizer;
const float h = m_hcalc (child.index, kInvalidNodeIndex, destIndex);
const float f = g + h;

View file

@ -32,29 +32,6 @@ BotSupport::BotSupport () {
m_sentences.push ("attention, expect experimental armed hostile presence");
m_sentences.push ("warning, medical attention required");
m_tags.emplace ("[[", "]]");
m_tags.emplace ("-=", "=-");
m_tags.emplace ("-[", "]-");
m_tags.emplace ("-]", "[-");
m_tags.emplace ("-}", "{-");
m_tags.emplace ("-{", "}-");
m_tags.emplace ("<[", "]>");
m_tags.emplace ("<]", "[>");
m_tags.emplace ("[-", "-]");
m_tags.emplace ("]-", "-[");
m_tags.emplace ("{-", "-}");
m_tags.emplace ("}-", "-{");
m_tags.emplace ("[", "]");
m_tags.emplace ("{", "}");
m_tags.emplace ("<", "[");
m_tags.emplace (">", "<");
m_tags.emplace ("-", "-");
m_tags.emplace ("|", "|");
m_tags.emplace ("=", "=");
m_tags.emplace ("+", "+");
m_tags.emplace ("(", ")");
m_tags.emplace (")", "(");
// register weapon aliases
m_weaponAlias[Weapon::USP] = "usp"; // HK USP .45 Tactical
m_weaponAlias[Weapon::Glock18] = "glock"; // Glock18 Select Fire
@ -112,10 +89,10 @@ bool BotSupport::isVisible (const Vector &origin, edict_t *ent) {
return true;
}
void BotSupport::traceDecals (entvars_t *pev, TraceResult *trace, int logotypeIndex) {
void BotSupport::decalTrace (entvars_t *pev, TraceResult *trace, int logotypeIndex) {
// this function draw spraypaint depending on the tracing results.
auto logo = conf.getRandomLogoName (logotypeIndex);
auto logo = conf.getLogoName (logotypeIndex);
int entityIndex = -1, message = TE_DECAL;
int decalIndex = engfuncs.pfnDecalIndex (logo.chars ());
@ -127,6 +104,7 @@ void BotSupport::traceDecals (entvars_t *pev, TraceResult *trace, int logotypeIn
if (cr::fequal (trace->flFraction, 1.0f)) {
return;
}
if (!game.isNullEntity (trace->pHit)) {
if (trace->pHit->v.solid == SOLID_BSP || trace->pHit->v.movetype == MOVETYPE_PUSHSTEP) {
entityIndex = game.indexOfEntity (trace->pHit);
@ -208,7 +186,6 @@ bool BotSupport::isMonster (edict_t *ent) {
if (isHostageEntity (ent)) {
return false;
}
return true;
}
@ -283,24 +260,30 @@ void BotSupport::checkWelcome () {
if (game.isDedicated () || !cv_display_welcome_text.bool_ () || !m_needToSendWelcome) {
return;
}
m_welcomeReceiveTime = 0.0f;
const bool needToSendMsg = (graph.length () > 0 ? m_needToSendWelcome : true);
auto receiveEntity = game.getLocalEntity ();
auto receiveEnt = game.getLocalEntity ();
if (isAlive (receiveEntity) && m_welcomeReceiveTime < 1.0f && needToSendMsg) {
m_welcomeReceiveTime = game.time () + 4.0f; // receive welcome message in four seconds after game has commencing
if (isAlive (receiveEnt) && m_welcomeReceiveTime < 1.0f && needToSendMsg) {
m_welcomeReceiveTime = game.time () + 2.0f + mp_freezetime.float_ (); // receive welcome message in four seconds after game has commencing
}
if (m_welcomeReceiveTime > 0.0f && needToSendMsg) {
// legacy welcome message, to respect the original code
constexpr StringRef legacyWelcomeMessage = "Welcome to POD-Bot V2.5 by Count Floyd\n"
"Visit http://www.nuclearbox.com/podbot/ or\n"
" http://www.botepidemic.com/podbot for Updates\n";
// it's should be send in very rare cases
const bool sendLegacyWelcome = rg.chance (2);
if (m_welcomeReceiveTime > 0.0f && m_welcomeReceiveTime < game.time () && needToSendMsg) {
if (!game.is (GameFlags::Mobility | GameFlags::Xash3D)) {
game.serverCommand ("speak \"%s\"", m_sentences.random ());
}
String authorStr = "Official Navigation Graph";
StringRef graphAuthor = graph.getAuthor ();
StringRef graphModified = graph.getModifiedBy ();
auto graphAuthor = graph.getAuthor ();
auto graphModified = graph.getModifiedBy ();
if (!graphAuthor.startsWith (product.name)) {
authorStr.assignf ("Navigation Graph by: %s", graphAuthor);
@ -309,30 +292,39 @@ void BotSupport::checkWelcome () {
authorStr.appendf (" (Modified by: %s)", graphModified);
}
}
StringRef modernWelcomeMessage = strings.format ("\nHello! You are playing with %s v%s\nDevised by %s\n\n%s", product.name, product.version, product.author, authorStr);
StringRef modernChatWelcomeMessage = strings.format ("----- %s v%s {%s}, (c) %s, by %s (%s)-----", product.name, product.version, product.date, product.year, product.author, product.url);
MessageWriter (MSG_ONE, msgs.id (NetMsg::TextMsg), nullptr, receiveEntity)
// send a chat-position message
MessageWriter (MSG_ONE, msgs.id (NetMsg::TextMsg), nullptr, receiveEnt)
.writeByte (HUD_PRINTTALK)
.writeString (strings.format ("----- %s v%s {%s}, (c) %s, by %s (%s)-----", product.name, product.version, product.date, product.year, product.author, product.url));
.writeString (modernChatWelcomeMessage.chars ());
MessageWriter (MSG_ONE, SVC_TEMPENTITY, nullptr, receiveEntity)
.writeByte (TE_TEXTMESSAGE)
.writeByte (1)
.writeShort (MessageWriter::fs16 (-1.0f, 13.0f))
.writeShort (MessageWriter::fs16 (-1.0f, 13.0f))
.writeByte (2)
.writeByte (rg.get (33, 255))
.writeByte (rg.get (33, 255))
.writeByte (rg.get (33, 255))
.writeByte (0)
.writeByte (rg.get (230, 255))
.writeByte (rg.get (230, 255))
.writeByte (rg.get (230, 255))
.writeByte (200)
.writeShort (MessageWriter::fu16 (0.0078125f, 8.0f))
.writeShort (MessageWriter::fu16 (2.0f, 8.0f))
.writeShort (MessageWriter::fu16 (6.0f, 8.0f))
.writeShort (MessageWriter::fu16 (0.1f, 8.0f))
.writeString (strings.format ("\nHello! You are playing with %s v%s\nDevised by %s\n\n%s", product.name, product.version, product.author, authorStr));
static hudtextparms_t textParams {};
textParams.channel = 1;
textParams.x = -1.0f;
textParams.y = sendLegacyWelcome ? 0.0f : -1.0f;
textParams.effect = rg.get (1, 2);
textParams.r1 = static_cast <uint8_t> (sendLegacyWelcome ? 255 : rg.get (33, 255));
textParams.g1 = static_cast <uint8_t> (sendLegacyWelcome ? 0 : rg.get (33, 255));
textParams.b1 = static_cast <uint8_t> (sendLegacyWelcome ? 0 : rg.get (33, 255));
textParams.a1 = static_cast <uint8_t> (0);
textParams.r2 = static_cast <uint8_t> (sendLegacyWelcome ? 255 : rg.get (230, 255));
textParams.g2 = static_cast <uint8_t> (sendLegacyWelcome ? 255 : rg.get (230, 255));
textParams.b2 = static_cast <uint8_t> (sendLegacyWelcome ? 255 : rg.get (230, 255));
textParams.a2 = static_cast <uint8_t> (200);
textParams.fadeinTime = 0.0078125f;
textParams.fadeoutTime = 2.0f;
textParams.holdTime = 6.0f;
textParams.fxTime = 0.25f;
// send the hud message
game.sendHudMessage (receiveEnt, textParams,
sendLegacyWelcome ? legacyWelcomeMessage.chars () : modernWelcomeMessage.chars ());
m_welcomeReceiveTime = 0.0f;
m_needToSendWelcome = false;
@ -488,10 +480,10 @@ void BotSupport::syncCalculatePings () {
int botPing = bot->m_basePing + rg.get (average.first - part, average.first + part) + rg.get (bot->m_difficulty / 2, bot->m_difficulty);
const int botLoss = rg.get (average.second / 2, average.second);
if (botPing <= 5) {
if (botPing < 2) {
botPing = rg.get (10, 23);
}
else if (botPing > 70) {
else if (botPing > 300) {
botPing = rg.get (30, 40);
}
client.ping = getPingBitmask (client.ent, botLoss, botPing);
@ -522,43 +514,6 @@ void BotSupport::emitPings (edict_t *to) {
return;
}
void BotSupport::installSendTo () {
// if previously requested to disable?
if (!cv_enable_query_hook.bool_ ()) {
if (m_sendToDetour.detoured ()) {
disableSendTo ();
}
return;
}
// do not enable on not dedicated server
if (!game.isDedicated ()) {
return;
}
using SendToHandle = decltype (sendto);
SendToHandle *sendToAddress = sendto;
// linux workaround with sendto
if (!plat.win && !plat.arm) {
SharedLibrary engineLib {};
engineLib.locate (reinterpret_cast <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) {
return model.startsWith (ent->v.model.chars (9));
}
@ -575,58 +530,6 @@ String BotSupport::getCurrentDateTime () {
return String (timebuf);
}
int32_t BotSupport::sendTo (int socket, const void *message, size_t length, int flags, const sockaddr *dest, int destLength) {
const auto send = [&] (const Twin <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 none = "none";
@ -636,7 +539,6 @@ StringRef BotSupport::weaponIdToAlias (int32_t id) {
return none;
}
// helper class for reading wave header
class WaveEndianessHelper final : public NonCopyable {
private:

View file

@ -270,7 +270,7 @@ void Bot::spraypaint_ () {
game.testLine (getEyesPos (), getEyesPos () + forward * 128.0f, TraceIgnore::Monsters, ent (), &tr);
// paint the actual logo decal
util.traceDecals (pev, &tr, m_logotypeIndex);
util.decalTrace (pev, &tr, m_logotypeIndex);
m_timeLogoSpray = game.time () + rg.get (60.0f, 90.0f);
}
}

View file

@ -55,11 +55,13 @@
<ClInclude Include="..\ext\linkage\linkage\metamod.h" />
<ClInclude Include="..\ext\linkage\linkage\physint.h" />
<ClInclude Include="..\inc\analyze.h" />
<ClInclude Include="..\inc\chatlib.h" />
<ClInclude Include="..\inc\config.h" />
<ClInclude Include="..\inc\constant.h" />
<ClInclude Include="..\inc\control.h" />
<ClInclude Include="..\inc\engine.h" />
<ClInclude Include="..\inc\graph.h" />
<ClInclude Include="..\inc\hooks.h" />
<ClInclude Include="..\inc\manager.h" />
<ClInclude Include="..\inc\message.h" />
<ClInclude Include="..\inc\module.h" />
@ -69,26 +71,35 @@
<ClInclude Include="..\inc\sounds.h" />
<ClInclude Include="..\inc\storage.h" />
<ClInclude Include="..\inc\support.h" />
<ClInclude Include="..\inc\version.h" />
<ClInclude Include="..\inc\vision.h" />
<ClInclude Include="..\inc\vistable.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>
<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\chatlib.cpp" />
<ClCompile Include="..\src\combat.cpp" />
<ClCompile Include="..\src\config.cpp" />
<ClCompile Include="..\src\control.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\hooks.cpp" />
<ClCompile Include="..\src\linkage.cpp" />
<ClCompile Include="..\src\manager.cpp" />
<ClCompile Include="..\src\message.cpp" />
@ -103,14 +114,6 @@
<ClCompile Include="..\src\vision.cpp" />
<ClCompile Include="..\src\vistable.cpp" />
</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>
<None Include="..\inc\version.h.in" />
</ItemGroup>

View file

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