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:
parent
01046f7c9a
commit
bf91ef2831
35 changed files with 1256 additions and 734 deletions
11
.gitignore
vendored
11
.gitignore
vendored
|
|
@ -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
150
CMakeLists.txt
Normal 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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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
64
inc/chatlib.h
Normal 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);
|
||||
|
|
@ -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 "";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
201
inc/engine.h
201
inc/engine.h
|
|
@ -46,7 +46,8 @@ CR_DECLARE_SCOPED_ENUM (GameFlags,
|
|||
ReGameDLL = cr::bit (9), // server dll is a regamedll
|
||||
HasFakePings = cr::bit (10), // on that game version we can fake bots pings
|
||||
HasBotVoice = cr::bit (11), // on that game version we can use chatter
|
||||
AnniversaryHL25 = cr::bit (12) // half-life 25th anniversary engine
|
||||
AnniversaryHL25 = cr::bit (12), // half-life 25th anniversary engine
|
||||
Xash3DLegacy = cr::bit (13) // old xash3d-branch
|
||||
)
|
||||
|
||||
// defines map type
|
||||
|
|
@ -83,7 +84,7 @@ struct ConVarReg {
|
|||
};
|
||||
|
||||
// entity prototype
|
||||
using EntityFunction = void (*) (entvars_t *);
|
||||
using EntityProto = void (*) (entvars_t *);
|
||||
|
||||
// rehlds has this fixed, but original hlds doesn't allocate string space passed to precache* argument, so game will crash when unloading module using metamod
|
||||
class EngineWrap final {
|
||||
|
|
@ -128,6 +129,7 @@ private:
|
|||
Array <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);
|
||||
|
|
|
|||
|
|
@ -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
193
inc/hooks.h
Normal 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);
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
31
inc/yapb.h
31
inc/yapb.h
|
|
@ -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"
|
||||
|
|
|
|||
27
meson.build
27
meson.build
|
|
@ -6,7 +6,7 @@
|
|||
#
|
||||
|
||||
# version is now passed into the bot dll
|
||||
project (
|
||||
project(
|
||||
'yapb',
|
||||
'cpp',
|
||||
version: '4.4',
|
||||
|
|
@ -80,15 +80,15 @@ if git.found()
|
|||
cxxflags += flags_versioned
|
||||
version_data = configuration_data()
|
||||
|
||||
version_data.set('count', count)
|
||||
version_data.set('hash', hash)
|
||||
version_data.set('author', author)
|
||||
version_data.set('machine', machine)
|
||||
version_data.set('version', bot_version)
|
||||
version_data.set('winver', winver)
|
||||
version_data.set('compiler', '@0@ @1@'.format (cxx, cxx_version))
|
||||
version_data.set('BUILD_COUNT', count)
|
||||
version_data.set('BUILD_HASH', hash)
|
||||
version_data.set('BUILD_AUTHOR', author)
|
||||
version_data.set('BUILD_MACHINE', machine)
|
||||
version_data.set('BUILD_VERSION', bot_version)
|
||||
version_data.set('BUILD_WINVER', winver)
|
||||
version_data.set('BUILD_COMPILER', '@0@ @1@'.format(cxx, cxx_version))
|
||||
|
||||
configure_file (input: 'inc/version.h.in', output: 'version.build.h', configuration: version_data)
|
||||
configure_file(input: 'inc/version.h.in', output: 'version.build.h', configuration: version_data)
|
||||
endif
|
||||
|
||||
# define crlib native build
|
||||
|
|
@ -102,7 +102,7 @@ if cxx == 'clang' or cxx == 'gcc'
|
|||
'-fno-threadsafe-statics', '-pthread'
|
||||
]
|
||||
|
||||
if not opt_native and cpu != 'arm'
|
||||
if not opt_native and cpu != 'arm' and not cpu.startswith('ppc')
|
||||
cxxflags += '-mtune=generic'
|
||||
endif
|
||||
|
||||
|
|
@ -110,7 +110,7 @@ if cxx == 'clang' or cxx == 'gcc'
|
|||
cxxflags += [
|
||||
'-march=armv8-a+fp+simd',
|
||||
]
|
||||
elif cpu != 'arm'
|
||||
elif cpu != 'arm' and not cpu.startswith('ppc')
|
||||
cxxflags += [
|
||||
'-mmmx', '-msse', '-msse2', '-msse3', '-mssse3', '-mfpmath=sse'
|
||||
]
|
||||
|
|
@ -128,7 +128,7 @@ if cxx == 'clang' or cxx == 'gcc'
|
|||
'-funroll-loops', '-fomit-frame-pointer', '-fno-stack-protector', '-fvisibility=hidden', '-fvisibility-inlines-hidden'
|
||||
]
|
||||
|
||||
if os != 'darwin' and os != 'windows' and cpu != 'aarch64' and cpu != 'arm'
|
||||
if os != 'darwin' and os != 'windows' and cpu != 'aarch64' and cpu != 'arm' and not cpu.startswith('ppc')
|
||||
cxxflags += [
|
||||
'-fdata-sections',
|
||||
'-ffunction-sections',
|
||||
|
|
@ -181,7 +181,7 @@ if cxx == 'clang' or cxx == 'gcc'
|
|||
endif
|
||||
|
||||
# by default we buid 32bit binaries
|
||||
if not opt_64bit and cpu != 'aarch64' and cpu != 'arm'
|
||||
if not opt_64bit and cpu != 'aarch64' and cpu != 'arm' and not cpu.startswith('ppc')
|
||||
cxxflags += '-m32'
|
||||
ldflags += '-m32'
|
||||
|
||||
|
|
@ -261,6 +261,7 @@ sources = files(
|
|||
'src/control.cpp',
|
||||
'src/engine.cpp',
|
||||
'src/graph.cpp',
|
||||
'src/hooks.cpp',
|
||||
'src/linkage.cpp',
|
||||
'src/manager.cpp',
|
||||
'src/module.cpp',
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 ();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 ();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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); \
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
179
src/hooks.cpp
Normal 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);
|
||||
}
|
||||
160
src/linkage.cpp
160
src/linkage.cpp
|
|
@ -32,7 +32,58 @@ plugin_info_t Plugin_info = {
|
|||
PT_ANYTIME, // when unloadable
|
||||
};
|
||||
|
||||
CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int) {
|
||||
// compilers can't create lambdas with vaargs, so put this one in it's own namespace
|
||||
namespace Hooks {
|
||||
void handler_engClientCommand (edict_t *ent, char const *format, ...) {
|
||||
// this function forces the client whose player entity is ent to issue a client command.
|
||||
// How it works is that clients all have a argv global string in their client DLL that
|
||||
// stores the command string; if ever that string is filled with characters, the client DLL
|
||||
// sends it to the engine as a command to be executed. When the engine has executed that
|
||||
// command, this argv string is reset to zero. Here is somehow a curious implementation of
|
||||
// ClientCommand: the engine sets the command it wants the client to issue in his argv, then
|
||||
// the client DLL sends it back to the engine, the engine receives it then executes the
|
||||
// command therein. Don't ask me why we need all this complicated crap. Anyhow since bots have
|
||||
// no client DLL, be certain never to call this function upon a bot entity, else it will just
|
||||
// make the server crash. Since hordes of uncautious, not to say stupid, programmers don't
|
||||
// even imagine some players on their servers could be bots, this check is performed less than
|
||||
// sometimes actually by their side, that's why we strongly recommend to check it here too. In
|
||||
// case it's a bot asking for a client command, we handle it like we do for bot commands
|
||||
|
||||
if (game.isNullEntity (ent)) {
|
||||
if (game.is (GameFlags::Metamod)) {
|
||||
RETURN_META (MRES_SUPERCEDE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
va_list ap;
|
||||
auto buffer = strings.chars ();
|
||||
|
||||
va_start (ap, format);
|
||||
vsnprintf (buffer, StringBuffer::StaticBufferSize, format, ap);
|
||||
va_end (ap);
|
||||
|
||||
if (util.isFakeClient (ent) && !(ent->v.flags & FL_DORMANT)) {
|
||||
auto bot = bots[ent];
|
||||
|
||||
if (bot) {
|
||||
game.botCommand (bot->pev->pContainingEntity, buffer);
|
||||
}
|
||||
|
||||
if (game.is (GameFlags::Metamod)) {
|
||||
RETURN_META (MRES_SUPERCEDE); // prevent bots to be forced to issue client commands
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (game.is (GameFlags::Metamod)) {
|
||||
RETURN_META (MRES_IGNORED);
|
||||
}
|
||||
engfuncs.pfnClientCommand (ent, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
|
||||
// this function is called right after GiveFnptrsToDll() by the engine in the game DLL (or
|
||||
// what it BELIEVES to be the game DLL), in order to copy the list of MOD functions that can
|
||||
// be called by the engine, into a memory block pointed to by the functionTable pointer
|
||||
|
|
@ -49,7 +100,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int) {
|
|||
auto api_GetEntityAPI = game.lib ().resolve <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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ¬ify : bots) {
|
||||
if (notify->m_seeEnemyTime + 2.0f < game.time () && notify->m_isAlive && notify->m_team == victimTeam && game.isNullEntity (notify->m_enemy) && killerTeam != victimTeam && util.isVisible (killer->v.origin, notify->ent ())) {
|
||||
if (notify->m_difficulty >= Difficulty::Hard
|
||||
&& killerTeam != victimTeam
|
||||
&& notify->m_seeEnemyTime + 2.0f < game.time ()
|
||||
&& notify->m_isAlive
|
||||
&& notify->m_team == victimTeam
|
||||
&& game.isNullEntity (notify->m_enemy)
|
||||
&& game.isNullEntity (notify->m_lastEnemy)
|
||||
&& util.isVisible (killer->v.origin, notify->ent ())) {
|
||||
|
||||
// make bot look at last e nemy position
|
||||
notify->m_actualReactionTime = 0.0f;
|
||||
notify->m_seeEnemyTime = game.time ();
|
||||
notify->m_enemy = killer;
|
||||
|
|
|
|||
|
|
@ -1842,7 +1842,6 @@ int Bot::findBombNode () {
|
|||
return graph.getNearest (bomb, 512.0f); // reliability check
|
||||
}
|
||||
|
||||
|
||||
int goal = 0, count = 0;
|
||||
float lastDistanceSq = kInfiniteDistance;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
196
src/support.cpp
196
src/support.cpp
|
|
@ -32,29 +32,6 @@ BotSupport::BotSupport () {
|
|||
m_sentences.push ("attention, expect experimental armed hostile presence");
|
||||
m_sentences.push ("warning, medical attention required");
|
||||
|
||||
m_tags.emplace ("[[", "]]");
|
||||
m_tags.emplace ("-=", "=-");
|
||||
m_tags.emplace ("-[", "]-");
|
||||
m_tags.emplace ("-]", "[-");
|
||||
m_tags.emplace ("-}", "{-");
|
||||
m_tags.emplace ("-{", "}-");
|
||||
m_tags.emplace ("<[", "]>");
|
||||
m_tags.emplace ("<]", "[>");
|
||||
m_tags.emplace ("[-", "-]");
|
||||
m_tags.emplace ("]-", "-[");
|
||||
m_tags.emplace ("{-", "-}");
|
||||
m_tags.emplace ("}-", "-{");
|
||||
m_tags.emplace ("[", "]");
|
||||
m_tags.emplace ("{", "}");
|
||||
m_tags.emplace ("<", "[");
|
||||
m_tags.emplace (">", "<");
|
||||
m_tags.emplace ("-", "-");
|
||||
m_tags.emplace ("|", "|");
|
||||
m_tags.emplace ("=", "=");
|
||||
m_tags.emplace ("+", "+");
|
||||
m_tags.emplace ("(", ")");
|
||||
m_tags.emplace (")", "(");
|
||||
|
||||
// register weapon aliases
|
||||
m_weaponAlias[Weapon::USP] = "usp"; // HK USP .45 Tactical
|
||||
m_weaponAlias[Weapon::Glock18] = "glock"; // Glock18 Select Fire
|
||||
|
|
@ -112,10 +89,10 @@ bool BotSupport::isVisible (const Vector &origin, edict_t *ent) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void BotSupport::traceDecals (entvars_t *pev, TraceResult *trace, int logotypeIndex) {
|
||||
void BotSupport::decalTrace (entvars_t *pev, TraceResult *trace, int logotypeIndex) {
|
||||
// this function draw spraypaint depending on the tracing results.
|
||||
|
||||
auto logo = conf.getRandomLogoName (logotypeIndex);
|
||||
auto logo = conf.getLogoName (logotypeIndex);
|
||||
|
||||
int entityIndex = -1, message = TE_DECAL;
|
||||
int decalIndex = engfuncs.pfnDecalIndex (logo.chars ());
|
||||
|
|
@ -127,6 +104,7 @@ void BotSupport::traceDecals (entvars_t *pev, TraceResult *trace, int logotypeIn
|
|||
if (cr::fequal (trace->flFraction, 1.0f)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!game.isNullEntity (trace->pHit)) {
|
||||
if (trace->pHit->v.solid == SOLID_BSP || trace->pHit->v.movetype == MOVETYPE_PUSHSTEP) {
|
||||
entityIndex = game.indexOfEntity (trace->pHit);
|
||||
|
|
@ -208,7 +186,6 @@ bool BotSupport::isMonster (edict_t *ent) {
|
|||
if (isHostageEntity (ent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -283,24 +260,30 @@ void BotSupport::checkWelcome () {
|
|||
if (game.isDedicated () || !cv_display_welcome_text.bool_ () || !m_needToSendWelcome) {
|
||||
return;
|
||||
}
|
||||
m_welcomeReceiveTime = 0.0f;
|
||||
|
||||
|
||||
const bool needToSendMsg = (graph.length () > 0 ? m_needToSendWelcome : true);
|
||||
auto receiveEntity = game.getLocalEntity ();
|
||||
auto receiveEnt = game.getLocalEntity ();
|
||||
|
||||
if (isAlive (receiveEntity) && m_welcomeReceiveTime < 1.0f && needToSendMsg) {
|
||||
m_welcomeReceiveTime = game.time () + 4.0f; // receive welcome message in four seconds after game has commencing
|
||||
if (isAlive (receiveEnt) && m_welcomeReceiveTime < 1.0f && needToSendMsg) {
|
||||
m_welcomeReceiveTime = game.time () + 2.0f + mp_freezetime.float_ (); // receive welcome message in four seconds after game has commencing
|
||||
}
|
||||
|
||||
if (m_welcomeReceiveTime > 0.0f && needToSendMsg) {
|
||||
// legacy welcome message, to respect the original code
|
||||
constexpr StringRef legacyWelcomeMessage = "Welcome to POD-Bot V2.5 by Count Floyd\n"
|
||||
"Visit http://www.nuclearbox.com/podbot/ or\n"
|
||||
" http://www.botepidemic.com/podbot for Updates\n";
|
||||
|
||||
// it's should be send in very rare cases
|
||||
const bool sendLegacyWelcome = rg.chance (2);
|
||||
|
||||
if (m_welcomeReceiveTime > 0.0f && m_welcomeReceiveTime < game.time () && needToSendMsg) {
|
||||
if (!game.is (GameFlags::Mobility | GameFlags::Xash3D)) {
|
||||
game.serverCommand ("speak \"%s\"", m_sentences.random ());
|
||||
}
|
||||
String authorStr = "Official Navigation Graph";
|
||||
|
||||
StringRef graphAuthor = graph.getAuthor ();
|
||||
StringRef graphModified = graph.getModifiedBy ();
|
||||
auto graphAuthor = graph.getAuthor ();
|
||||
auto graphModified = graph.getModifiedBy ();
|
||||
|
||||
if (!graphAuthor.startsWith (product.name)) {
|
||||
authorStr.assignf ("Navigation Graph by: %s", graphAuthor);
|
||||
|
|
@ -309,30 +292,39 @@ void BotSupport::checkWelcome () {
|
|||
authorStr.appendf (" (Modified by: %s)", graphModified);
|
||||
}
|
||||
}
|
||||
StringRef modernWelcomeMessage = strings.format ("\nHello! You are playing with %s v%s\nDevised by %s\n\n%s", product.name, product.version, product.author, authorStr);
|
||||
StringRef modernChatWelcomeMessage = strings.format ("----- %s v%s {%s}, (c) %s, by %s (%s)-----", product.name, product.version, product.date, product.year, product.author, product.url);
|
||||
|
||||
MessageWriter (MSG_ONE, msgs.id (NetMsg::TextMsg), nullptr, receiveEntity)
|
||||
// send a chat-position message
|
||||
MessageWriter (MSG_ONE, msgs.id (NetMsg::TextMsg), nullptr, receiveEnt)
|
||||
.writeByte (HUD_PRINTTALK)
|
||||
.writeString (strings.format ("----- %s v%s {%s}, (c) %s, by %s (%s)-----", product.name, product.version, product.date, product.year, product.author, product.url));
|
||||
.writeString (modernChatWelcomeMessage.chars ());
|
||||
|
||||
MessageWriter (MSG_ONE, SVC_TEMPENTITY, nullptr, receiveEntity)
|
||||
.writeByte (TE_TEXTMESSAGE)
|
||||
.writeByte (1)
|
||||
.writeShort (MessageWriter::fs16 (-1.0f, 13.0f))
|
||||
.writeShort (MessageWriter::fs16 (-1.0f, 13.0f))
|
||||
.writeByte (2)
|
||||
.writeByte (rg.get (33, 255))
|
||||
.writeByte (rg.get (33, 255))
|
||||
.writeByte (rg.get (33, 255))
|
||||
.writeByte (0)
|
||||
.writeByte (rg.get (230, 255))
|
||||
.writeByte (rg.get (230, 255))
|
||||
.writeByte (rg.get (230, 255))
|
||||
.writeByte (200)
|
||||
.writeShort (MessageWriter::fu16 (0.0078125f, 8.0f))
|
||||
.writeShort (MessageWriter::fu16 (2.0f, 8.0f))
|
||||
.writeShort (MessageWriter::fu16 (6.0f, 8.0f))
|
||||
.writeShort (MessageWriter::fu16 (0.1f, 8.0f))
|
||||
.writeString (strings.format ("\nHello! You are playing with %s v%s\nDevised by %s\n\n%s", product.name, product.version, product.author, authorStr));
|
||||
static hudtextparms_t textParams {};
|
||||
|
||||
textParams.channel = 1;
|
||||
textParams.x = -1.0f;
|
||||
textParams.y = sendLegacyWelcome ? 0.0f : -1.0f;
|
||||
textParams.effect = rg.get (1, 2);
|
||||
|
||||
textParams.r1 = static_cast <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:
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue