-- -- PAYDAY: The Heist user config by https://steamcommunity.com/id/neonsynthosc/ -- Snippets added from zneixs, dussels and Cloaker Haters config -- https://mods.neonsynth.de/user.lua -- -- Save this file to your /mods folder and rename it to "user.lua" -- local is_first_load = ... local always_reload = true -- if true, always use values defined in this file and thus ignoring any dynamic changes; default false local prioritize_gui = false -- if true, config values defined in this file won't overwrite any existing settings set with the gui; default false -- helper functions -- escape left square bracket in s so that they aren't parsed as colours local function clear_string(s) return (string.gsub(s, "%[", "%[%[")) end -- remove leading and trailing spaces from s local function trim(s) return (string.gsub(s, "^%s*(.-)%s*$", "%1")) end -- localize an integer value to make it look prettier to a user local function format_integer(integer) -- returns string local reversed_integer = string.reverse(tostring(integer)) -- insert commas every three digits, remove trailing commas local formatted_integer = reversed_integer:gsub("(%d%d%d)", "%1,"):gsub(",$", "") -- reverse the formatted number back to its original order. keep in mind that returned type is a string return formatted_integer:reverse() end -- iso for the win (to be used as a format for os.date()) local iso8601 = "%Y-%m-%d %H:%M:%S" -- more colours -- [name] sets the color by name, but there's only a handful defined by default (add new with e.g. Color.orange = Color("FF9900")) -- [#aarrggbb] sets the color by hex value (I think some places actually use [#rrggbbaa], e.g. menu_waiting_is_{,-not}ready) -- [] sets the color to the previous value -- [[ escapes the left bracket Color.white = Color("FFFFFF") Color.nice_blue = Color("7DEEFF") Color.orange = Color("FF9900") Color.yellow = Color("FFFF00") Color.grey = Color(.7, .7, .7) Color.blueish = Color("B3FCE5") Color.red = Color("FF0000") Color.purple = Color("CA00FF") Color.golden = Color("FFCF7D") Color.silver = Color("CECECE") Color.rainbow = function(message, colors, vars) local saturation = vars and vars.saturation or 1 local lightness = vars and vars.lightness or 0.5 local function f(h, s, l) if s == 0 then h, s = l, l else local a = .5 > l and l * (1 + s) or l + s - l * s local b = 2 * l - a local c = function(x, y, z) z = z < 0 and z + 1 or z > 1 and z - 1 or z if z < 0.166 then return x + 6 * (y - x) * z elseif .5 > z then return y elseif z < 0.666 then return x + (y - x) * (0.666 - z) * 6 else return x end end l = c(b, a, h + 0.333) s = c(b, a, h) h = c(b, a, h - 0.333) end return l, s, h end local result = {} local a, b = 1 / #message, 0 local alpha = 1 for i = 1, #message do local c = message:sub(i, i) if c ~= " " then b = b + a table.insert(result, { i = i, j = i, c = Color(alpha, f(b, saturation, lightness)) }) end end return result end Color.assault = Color(1, .9, .2, .2) Color.assault_break = Color(.9, 0, 1, 0) Color.cheat = Color("FF8E7C") Color.team_ai = Color(1, 0.2, 0.8, 1) Color.meds = Color("00EFC3") Color.ammo = Color("A85600") Color.regen = Color("6B00EF") Color.down = Color("FC1B6E") Colors = Color -- declare an alias to prevent very rare crashes in some places -- helper variables for localization overrides local loc_peer = "[peer_color]$PLAYER_NAME;[] " local loc_self = "[peer_color]You[] " local loc_meds = "[meds]took MEDS[] [DOWNS;#FC5D5D;#FFFFFF]$REGEN_PERCENT;%[], [meds]($DOWNS_DIFF;/$TOTAL_DOWNS; downs)" local loc_ammo = "[ammo]took AMMO [AMOUNT;#FC5D5D;#FFFFFF]$AMOUNT_PERCENT%.2f;%" local loc_down = "[down]went DOWN [DOWNS;#FFFFFF;#FC5D5D]$DOWNS;[]/$TOTAL_DOWNS;" local localization = { -- requires "zneixs_fixes" v1.0.2 or newer (https://mods.neonsynth.de/my_used_public_mods/zneixs_fixes.zip) menu_waiting_is_not_ready = "[#e53333ff]NOT READY", menu_waiting_is_ready = "[#00ff00e5]READY", mask_check_cheater = "Player [peer_color]$PLAYER_NAME;[] [[$PLAYER_LEVEL;] [cheat]DOES NOT[] have the $MASK; mask set!", -- also show amount of unacknowledged packets on the extras panel hud_player_ping = "$PING;ms $UNACKNOWLEDGED;", -- taking meds hud_chat_used_med_bag = loc_peer .. loc_meds, hud_chat_player_used_med_bag = loc_self .. loc_meds, -- regenerating, most likely due leveling up (same result as if you'd take meds + take ammo) hud_chat_downs_restore = loc_peer .. "[regen]has regenerated", hud_chat_player_downs_restore = loc_self .. "[regen]have regenerated", -- taking ammo hud_chat_ammo_taken = loc_peer .. loc_ammo, hud_chat_player_ammo_taken = loc_self .. loc_ammo, -- getting downed hud_chat_downs = loc_peer .. loc_down, hud_chat_player_downs = loc_self .. loc_down, mutated_game_join_msg = "[#FF8E7C]This game has multiple active mutators to modify the gameplay. Type !mutators to get more info.", } -- used by /ammo chat command as a fallback for names if no weapon name is defined in localization table -- this is added seperately outside of 'localization' table to not change weapon names elsewhere in the game local weapon_names = { -- handguns debug_glock = "WATERGUN", debug_raging_bull = "BRONCO", debug_c45 = "CROSSKILL", debug_beretta92 = "B9", -- primary weapons debug_hk21 = "BRENNER", debug_r870_shotgun = "REINBECK", debug_m14 = "M308", debug_ak47 = "AK", debug_m4 = "AMCAR", -- used by managers.player (which is the context we use within "/ammo" chat command) -- secondary weapons debug_mossberg = "LOCO", debug_m79 = "GL", debug_mp5 = "COMPACT 5", debug_mac11 = "MARK 11", } local welcome_text = "\n Welcome!\n\n My PDTH stuff: neonsynth.de\n DAHM v" .. D:version() -- calm_down_bro override to make people shouting at escorts more bearable local escort_frequency = { 90, 120 } local follow_frequency = { 3, 5, 7 } local no_exempt_conditions = {} local friend_exempt_condition = { is_friend = true } -- local config = { synced_sound_names_ptf = { -- calm_down_bro override ["f21a_sin"] = { frequencies = follow_frequency, exempt_conditions = friend_exempt_condition }, -- dallas, follow me ["f21b_sin"] = { frequencies = follow_frequency, exempt_conditions = friend_exempt_condition }, -- chains, follow me ["f21c_sin"] = { frequencies = follow_frequency, exempt_conditions = friend_exempt_condition }, -- wolf, follow me ["f21d_sin"] = { frequencies = follow_frequency, exempt_conditions = friend_exempt_condition }, -- hoxton, follow me ["e01x_sin"] = { frequencies = escort_frequency, exempt_conditions = no_exempt_conditions }, -- escort 1 ["e02x_sin"] = { frequencies = escort_frequency, exempt_conditions = no_exempt_conditions }, -- escort 2 ["e03x_sin"] = { frequencies = escort_frequency, exempt_conditions = no_exempt_conditions }, -- escort 3 ["e04x_sin"] = { frequencies = escort_frequency, exempt_conditions = no_exempt_conditions }, -- escort 4 ["e05x_sin"] = { frequencies = escort_frequency, exempt_conditions = no_exempt_conditions }, -- escort 5 ["any"] = { frequencies = { 1.5 } }, -- standard interaction delay }, ai_mask_set_override = { -- override AI masks... all mask values here: https://gist.github.com/neonsynthosc/9d5337f22f5ba0ffab94752242abc3ce russian = { "troll", "hockey_com", "developer" }, -- dallas american = { "troll", "hockey_com", "developer" }, -- hoxton german = { "troll", "hockey_com", "developer" }, -- wolf spanish = { "troll", "hockey_com", "developer" }, -- chains }, -- Dorentuz: most (if not all) xxx_str options can be disabled by setting their value to false override_user_config_with_gui_settings = false, -- prioritize this file instead of the gui config if prioritize_gui is set to false -- messages shown to other players (and me) whenever they join my lobby welcome_messages = { { text = welcome_text, new_peers_only = true, host_only = true, as_lobby = true, options = { pre_show_callback = function(peer, is_new) if Util:is_in_state("any_ingame_playing") then return welcome_text .. "\n\n The heist has already started.", nil -- text override, options object override end end }, }, -- inform peers if we're playing unpatched UC { text = "WARNING: this lobby has disabled DAHM's Undercover fix! Expect bugged specials.", new_peers_only = false, -- show this to everyone regardless of whether they've already been in the lobby (similar to ovk_193/mutator motds) host_only = true, as_lobby = true, options = { priority = 10, -- better if it's sent later, I think... visibility_fn = function(peer) return D:conf("disable_mission_fixes_undercover") and (Global.level_data and Global.level_data.level_id == "secret_stash") end, }, }, }, banlist_path = "mods/banlist.csv", -- custom path to the banlist -- chat_log_to_file = "logs/chat.log", -- logs ALL chat output to this file (uncomment this) cml_always_show_chat = true, -- always shows chat while in loadout menu, requires "character_n_mask_in_loadout" mod chat_enable_singleplayer_input = true, -- enable chat (while in-game) in singleplayer chat_alt_clipboard_mode = true, -- enables Ctrl+C, Ctrl+V, Ctrl+X, Ctrl+A in chat, requires DAHM 1.15.2.0 or newer chat_input_history_limit = 80, chat_height_multiplier = 3.4, -- general chat height multiplier chat_height_multiplier_ingame = 4.5, -- chat height multiplier (ingame chat only) chat_input_width_multiplier = 2.7, -- chat width multiplier (ingame chat only) chat_split_uc_sender = true, -- Upper-case sender name invalid_chat_command_notify = true, -- don't send invalid commands to chat chat_dedupe_max_age = 20, -- only dedupe messages older than x seconds chat_agressive_dedupe = true, -- dedupe the whole chat or only the latest message hud_intro_show_game_info = true, debug_show_raw_text_strings = false, -- set to true if you want to get the string IDs which then you can use to change the localization force_load_controller_settings = true, -- ANTICHEAT STUFF -- perform_profile_check = true, -- perform check on profiles (non-public profile, name mismatch, bans, etc.) perform_profile_state_check = true, -- perform check on profiles states perform_check_ignore_host_publish = true, -- perform a check even when the host publishes results perform_profile_ban_check = true, -- check whether the profile is in your ban list perform_playtime_check = true, -- check total players' playtime when we first see them perform_mask_check = true, -- perform check on masks profile_game_stats_flagged_limit = 9, profile_game_stats_combine_stats = true, -- show stats on one line constructed of the "combined" strings host_check_mask_publish = true, -- publish results for mask check (host only) host_check_profile_publish = true, -- publish results for profile check (host only) check_result_always_visible = false, -- spammy if true; show all profile check results, even the valid ones anticheat_extra_desync_time = 0, -- some events happen in the wrong order and desync can mess up the detection anticheat_notify_sender = false, -- don't send a message to a cheater if we detect something, they don't need to know anticheat_verify_bullet_damage = true, -- verify that bullet damage does not exceed weapon limits anticheat_verify_damage_against_loadout = true, -- use equipped weapons for damage calculations anticheat_invalid_bullet_damage_publish = true, -- publish incorrect bullet damage anticheat_spawn_prevention = true, -- prevent the spawn of invalid equipment when being host publish_results_as_lobby = true, -- publish the results as the 'lobby' user instead of your own name (does not change color) profile_check_name_mismatch = "both", -- check for name mismatch using the steam client instead of what's on the user's online profile profile_check_retry = true, -- try fetching the profile again after getting an invalid result (with increasing delay) profile_checker_ban_days_to_report = nil, -- by default, VAC bans older than 1111 days are ignored, but I don't want to ignore these anticheat_check_sentrygun_health = true, anticheat_check_sentrygun_ammo = true, client_check_steam_blocked = true, server_check_steam_blocked = true, profile_game_stats_check_times = true, bugfixes_show_time_correction_in_chat = true, -- at the end of the heist show corrected endgame time --disable_mission_fixes_undercover = true, -- Setting this to true will enable double the amount of specials in UC, like it does in vanilla ovk193_mutators_state_prefs = { roof_door_closed = true }, ovk_193_ammo_respawn = false, -- controls the amount of ammo peers respawn with, default: "full" (on 193+ clients respawn with full ammo by default) lobby_show_recreate_button = true, -- shows 'RE-REGISTER LOBBY' button replace_ingame_invite = true, -- collapses 2 vanilla menu entries into 1 debug_add_reputation_permission = "146", -- adds an extra reputation requirement(s) which can be used while hosting ---- MOUSE SENSITIVITY ---- mouse_sensitivity_slider_step = 0.1, -- value added to the sensitivity in each step for the slider mouse_sensitivity_slider_min_value = 0.1, -- min value for the slider control (0 is no mouse movement) mouse_sensitivity_slider_max_value = 1.7, -- max value for the slider control kick_with_reason = { { msg = "Kicking '%s' for cheating.", menu = "Kick for cheating", menu_to_upper = true, kick_type = "permanent", kick_reason = "cheat"}, { msg = "Kicking '%s' for using a cheat mod.", menu = "Kick for using cheat mod", menu_to_upper = true, kick_type = "permanent", kick_reason = "cheat mod"}, { msg = "Kicking '%s' for having a non-public profile.", menu = "Kick for having a private profile", menu_to_upper = true, kick_type = "false"}, { msg = "Kicking '%s' for having a suspicious profile (e.g. cheated times or money).", menu = "Kick for having a suspicious profile", menu_to_upper = true, kick_reason = "cheat"}, { msg = "Kicking '%s' for using cheated masks.", menu = "Kick for using cheated masks", kick_type = "permanent", kick_reason = "cheated masks" }, { msg = "Kicking '%s' for not following orders.", menu = "Kick for not listening",kick_type = "session", menu_to_upper = true }, { msg = "Kicking '%s': Read chat/learn english!", menu = "Kick for eng", menu_to_upper = true, kick_type = "permanent", kick_reason = "chat" }, { msg = "Kicking '%s': AFK, rejoin!", menu = "Kick for afk", menu_to_upper = true, kick_type = "false", kick_reason = "afk - rejoin!" }, { msg = "Kicking '%s' for being a retard.", menu = "Kick for being a retard", menu_to_upper = true, kick_type = "permanent", kick_reason = "retard" }, { msg = "Kicking '%s': Check your equipment, especially your BAG!", menu = "Kick for bag", menu_to_upper = true, kick_type = "false", kick_reason = "bag" }, { msg = "Kicking '%s': Please play successfully on overkill first.", menu = "Kick for ovk", menu_to_upper = true, kick_type = "permanent", kick_reason = "low" }, { msg = "Kicking '%s': Read some guides before playing this difficulty.", menu = "Read some guides", menu_to_upper = true, kick_type = "session", kick_reason = "low"}, }, -- FPS INDICATOR -- --hud_fps_indicator = true, -- I use the Steam performance/fps meter now so feel free to uncomment hud_fps_offset_x = 1855, hud_fps_offset_y = 1, -- HUD STUFF -- hud_prefer_virtual_reps = true, hud_present_virtual_level_up = true, -- show own virtual levelups hud_xp_panel_style = "virtual_with_progress", hud_show_loadout_deployable = true, hud_show_loadout_weapons = true, hud_show_mugshot_timers = true, hud_bleedout_time_threshold = 5, -- a down is considered critical if at most this many seconds hud_hide_name_labels_in_steelsight = false, hud_hide_waypoints_in_steelsight = true, hud_show_all_completed_challenges = true, hud_show_collected_money_at_max_level = true, --hud_show_all_completed_challenges_inclusion_list = "take_money kill_thugs windowlicker quick_hands drop_armored_car dozen_angry ready_yet chavez_can_run the_darkness ninja federal_crime crack_bang blood_in_blood_out bomb_man tester pacifist blow_out take_sapphires lay_on_hands crowd_control saviour one_shot_one_kill stand_together kill_cameras dodge_this quick_gold cheney wrong_door afraid_of_the_dark hot_lava det_gadget cant_touch noob_herder showoff ", hud_show_all_completed_challenges_inclusion_list = false, hud_show_all_completed_challenges_exclusion_list = "civil_disobedience eagle_eyes bullet_to_bleed_out intimidating revived diplomatic arrested deploy_.+ tiedown_%w+ fall_to_bleed_out .+_no_civilians_hard .+_no_deaths_hard .+_no_bleedouts_hard .+_success_overkill .+_overkill_no_trade .+_success_overkill_%d+ christmas_present.* duck_hunting det_gadget saviour five_five_five citys_finest", -- counter stuff is ignored anyway hud_show_multikills = 3, -- minimal required amount of cops killed with 1 shot to trigger multikill text (e.g. "x5") for a brief moment hud_hide_deployable_when_used = "transparent", -- accepts: "transparent", "darken" hud_equipment_weapon_mark_enabled = true, hud_equipment_disabled_alpha = .3, hud_equipment_disabled_rgb = .7, hud_show_peer_downs = "always", -- shows others' downs on mugshot hud_show_player_downs = "always", -- shown own downs on mugshot -- show teammates' (and own) actions in chat hud_show_downs_restore_in_chat = "all", -- show meds taken / levelup regenerations in chat (everyone) hud_show_ammo_taken_in_chat = "all", -- shows ammo taken in chat (everyone) hud_show_downs_in_chat = "all", -- shows downs in chat (others); can also be true hud_show_player_downs_in_chat = "all", -- shows downs in chat (own) hud_chat_critical_downs_only = false, -- don't ignore any downs hud_hide_player_downs = false, -- we don't want to ignore our own downs hud_stats_screen_permissions = true, -- show the lobby permissions on the stats screen --HUD TIMERS-- hud_show_session_timer = true, hud_show_assault_timer = true, hud_sl_time_precision = 0, -- precision for the game timer in the stats line hud_st_time_precision = 2, -- precision for the game timer hud_endgame_time_precision = 2, -- precision for endgame screen hud_color_overrides = { mugshot = { weapon_outline = Color.orange, weapon = Color.white:with_alpha(.7), deployable = {}, -- seems like nested values are needed to be reposted or else tablex.merge will unset/delete dor pls fix (or not) }, }, hints_blacklist = { "take_hostages", -- bad hint in general, it's blacklisted by default "cant_stand_up", -- most annoying and useless hint "civilian_escaped", -- brings more confusion (e.g. making you think a teammate was downed) than benefit }, -- update_spammer config - Notify people still using an ancient version of DAHM (below v.1.16.0) update_spammer_notify_outdated = true, update_spammer_notify_self = true, -- always publish all sorts of information about the currently played heist to both Discord and Steam (because why not, I have nothing to hide) discord_rpc_set_character = true, discord_rpc_set_time = true, steam_publish_game_status = true, steam_publish_party_details = true, steam_publish_player_names_in_private_lobbies = true, steam_publish_player_names = true, steam_publish_game_override = true, steam_publish_singleplayer = true, steam_publish_details_in_singleplayer_mode = true, ---- MATCHMAKING FILTERS ---- filter_default_distance = 3, -- integer, -1 - 3 (-1 = close, 2 = far, 3 = worldwide, other stuff not used by the game in between) -- hud_str vars: -- 1: session timer -- 2: "hud_kills_str" variable -- 3: assault timer -- 4: difficulty -- 5: heist name -- hud_kills_str vars: -- 1: total kills -- 2: total specials kills -- 3: civs killed (str, conf:hud_kills_civs_str) -- 4: total head shots -- 5: accuracy (float) -- 6: shield kills -- 7: spooc kills -- 8: taser kills -- 9: tank kills -- 10: total head shots ratio (float) -- 11: kills per minute (float) -- 12: total shots -- 13: total hits -- 14: weapon accuracy (float) -- 15: weapon shots -- 16: weapon hits -- new variables introduced in 1.15.2.0-beta5 (on earlier versions using these will crash!) -- 17: weapon damage -- 18: weapon damage per minute -- 19: weapon damage per shot -- 20: damage -- 21: damage per minute -- 22: damage per shot -- 23: weapon total kills -- xx: weapon total specials kills -- unused(?) -- 24: weapon head shots -- 25: weapon head shot ratio -- 26: weapon kills per minute -- advanced HUD and stats line setup --hud_offset_x = 62, -- integer pixel offset from the left --hud_offset_y = 30, -- integer pixel offset from the bottom -- STATS LINE -- hud_font = "font_univers_latin_530_bold", -- one of 'font_univers_530_medium, font_univers_530_bold, font_univers_latin_530_bold, font_univers_530_bold_no_outline, font_fortress_22' (default is 'font_univers_530_medium') hud_font_size = 21, -- font size, defaults to 14 hud_str = "[white]%5$s[] %4$s %2$s", hud_kills_str = "[blueish]kills[] [white]%1$i[] [[[orange]kpm %11$.1f[] [#00ffff]hs %10$.1f%%[]] [blueish]specs[] [white]%2$i[] [[[#adcad9]S %6$i[] [#ffff00]T %8$i[] [#00ff00]C %7$i[] [#E67300]D %9$i[]] [#80A0FF]civ %3$s[] [blueish]accuracy[] [[%16$i/%15$i [white]%14$.1f%%[] %13$i/%12$i [white]%5$.1f%%[]]", hud_kills_civs_relevant_str = "%2$i/%1$i", -- relevant/total (opposite of what's by default) -- short abbreviations for map names (used by stats line) hud_level_names = { bank = "FWB", heat_street = "HS", bridge = "GB", apartment = "PR", diamond_heist = "DH", slaughter_house = "SH", suburbia = "CF", secret_stash = "UC", hospital = "NM", }, -- short abbreviations of difficulty names (used by stats line), colours taken from Steam statistics page (and https://pdthlbs.com/stats_hsr.php) hud_difficulty_names = { easy = "[#0099cc]EASY[]", normal = "[#99cc00]NORM[]", hard = "[#ffcc00]HARD[]", overkill = "[#ff6600]OVKL[]", overkill_145 = "[#cc0000]145+[]", overkill_193 = "[#dccc00]193+[]", }, -- below options require "zneixs_fixes" mod hud_show_peer_ids = false, -- show ID in front of peer names hud_show_host_mark = ">> ", -- add an indicator to the host's name (suppressed if "hud_show_peer_ids" is set to true) hud_name_label_format = "[peer_color]$NAME;[] [[$LEVEL;]", -- add colors to the name (above player) label hud_mugshot_name_format = "$NAME; [[[peer_color]$LEVEL;[]]", -- add colors to the mugshot (on hud) name hud_ai_mugshot_name_format = "[team_ai]$NAME;[]", hud_name_marker_obj_override = { use_chat_colors = true, }, player_verification_mugshot_local_peer_color = "orange", -- sets outline of my own mugshot to a nice orange colour player_verification_hardcoded_verified_users = { -- players from my friends list that have been impersonated ["76561199226680671"] = "Insider Trading Bo Andersson" }, } -- Chat commands which just print text to chat and do nothing else local commands = { dz = "Watch out! Bulldozer!", escort = "When you shout at an escort once and stay close to him you don't have to shout again (as long as there's no cops near him)", bags = "Rule #1, always double check your load out before going on a heist. One of you idiots must have switched the bag. How can you guys fuck this up?!?!?! You play this everyday.", dussel = "Each team needs a leader. Listen to him. Do what he says. If he's an idiot you still can choose not to listen, but because you've checked his profile you know if he succeeded this heist in the past or not.", userconfig = "link.neonsynth.de/cfg", } -- Explicitly disable some mods without having to remove/delete them: local blacklisted_modules = { "reset_stats_achievements", } -- CONFIG COMMANDS -- -- framerate D:register_command_alias("fps144", "/set framerate_limit 144") D:register_command_alias("fps130", "/set framerate_limit 130") -- recommended D:register_command_alias("fps90", "/set framerate_limit 90") D:register_command_alias("fps60", "/set framerate_limit 60") D:register_command_alias("fps30", "/set framerate_limit 30") -- clients_are_idiots D:register_command_alias("AIme", "/set disable_ai_follow_clients true") D:register_command_alias("AIfriends", "/set disable_ai_follow_clients friends") D:register_command_alias("AIall", "/set disable_ai_follow_clients false") -- Show in chat how much ammo I have left D:register_chat_command("ammo", "GAME", function(args) local player_unit = managers.player:player_unit() if not alive(player_unit) then return true end local inventory = player_unit:inventory() if not inventory then return true end local chat_msg = "" for _, weapon in ipairs(inventory._available_selections) do local weapon_base = weapon.unit:base() chat_msg = chat_msg .. string.format("%s %s/%s ", localization["debug_" .. weapon_base.name_id] or weapon_names["debug_" .. weapon_base.name_id] or weapon_base.name_id, weapon_base._ammo_total, weapon_base._ammo_max) end Util:chat_message(trim(chat_msg), true, false) -- change second argument to false to send the message only locally (e.g. for debugging purposes) return true end) -- Sometimes the "hud" module does not send a request to some peers for dispatching interaction progress information to our client -- This command sends such request manually, helpful if you can't see interaction progress of some players even if they have DAHM installed D:register_chat_command("rqi", "GAME", function(args) if not managers.network or not managers.network:session() then return end local first_arg = args:match("%S+") local peer_id = tonumber(first_arg) if not peer_id then return end local peer = managers.network:session():peer(peer_id) if peer ~= nil and not peer:is_local_user() then DNet:send_to_peer(peer, "ModEvent", { module = "hud", event = "EnableInteractionEvents" }, false, false) end end) D:register_chat_command("calc_rep", function(args) if not managers.experience._calculate_vrep_from_money then -- just to not make people's game crash in some places, maybe... not gonna keep this check for a long while anyway Util:chat_message("update your DAHM, silly. this piece of code needs version 1.16.1.3 or above", false, "LOCAL") return end local cmd_prefix = D:conf('chat_command_prefix') or '/' local value = tonumber(args and args:match("^(%d+)")) if value == nil then Util:chat_message("bad arguments, syntax " .. cmd_prefix .. "calc_rep ", false, "LOCAL") return end -- assume we're giving a level and want cash if it's 5000 or below (since that's maximum vrep anyway) -- or otherwise, giving cash and seeing which level it gets up otherwise if value > 5000 then local rep = managers.experience:_calculate_vrep_from_money(value) Util:chat_message("Reputation achieved with [#00ff00]" .. format_integer(value) .. "[] cash: [#ff0000]" .. rep, false, "LOCAL") elseif value > 0 then local cash = managers.experience:_get_required_money_for_level(value) Util:chat_message("Cash needed to reach rep [#ff0000]" .. tostring(value) .. "[]: [#00ff00]" .. format_integer(cash), false, "LOCAL") else -- silly goose, invalid value (maybe give some feedback in chat? prob not needed) end end) -- /r command for reloading config D:register_command("r", function() local userfile, err = D:root_path() .. 'user.lua' if io.is_readable(userfile) then local userfile_f userfile_f, err = loadfile(userfile) if userfile_f then local success, ret = xpcall(function() return userfile_f(true) end, debug.traceback) if success then -- update some stuff to make use of newly loaded config if managers.hud then managers.hud:update_hud_settings() end Util:say_to_chat("Config has been reloaded.", false, "DEBUG") else err = ret end end else err = "user.lua file is not readable" end if err then Util:say_to_chat("Config cannot be reloaded: " .. tostring(err), false, "DEBUG") end end) D:register_command("reload", function() -- requires https://modworkshop.net/mod/53474 DB:reload_override_mods() Util:say_to_chat("mod_overrides folder has been reloaded. Alt-tab and open the game for it to take effect.", false, "DEBUG") end) -- Override existing keybinds and add new ones local keybind_overrides = { hud_scroll_chat_up = "left alt + k", hud_scroll_chat_down = "left alt + j", } local visual_upgrades = { ["hk21"] = "hk21_recoil2", -- brenner scope ["m4"] = "m4_spread4", -- amcar scope ["m14"] = "m14_spread2", -- m308 scope ["ak47"] = "ak47_spread1", -- ak scope ["m79"] = "m79_expl_range2", -- gl40 range finder (thing on the left) } -- toggle currently held weapon's sight with a keybind D:register_keybind("toggle_visual_weapon_upgrade", "num 4", "GAME", function() if not MenuCallbackHandler then return end local player_unit = managers.player:player_unit() if not alive(player_unit) then return end local inventory = player_unit:inventory() if not inventory then return end local weapon = inventory._available_selections[inventory._equipped_selection] if not weapon then return end local weapon_base = weapon.unit:base() local upgrade = visual_upgrades[weapon_base.name_id] if not upgrade then dlog_ll(3, "UserLua", "no visual upgrade for", weapon_base.name_id) return end player_unit:sound():play("c45_dryfire") dlog_ll(0, "UserLua", "toggling visual upgrade", upgrade) D._menu_cb_handler = D._menu_cb_handler or MenuCallbackHandler:new() D._menu_cb_handler:toggle_visual_upgrade({ parameters = function() return { upgrade_id = upgrade } end }) end) D:register_keybind("toggle_sfx_mute", "f3", function() if MenuCallbackHandler then local item = { value = function() if D._sfx_volume_level then local lvl = D._sfx_volume_level D._sfx_volume_level = nil return lvl else D._sfx_volume_level = 50 return 0 end end } D._menu_cb_handler = D._menu_cb_handler or MenuCallbackHandler:new() D._menu_cb_handler:set_sfx_volume(item) end end) -- quick reregister lobby D:register_keybind("reregister_lobby", "num 1", function() local cmd_prefix = D:conf('chat_command_prefix') or '/' D:process_chat_input(cmd_prefix .. "recreate_lobby") end) -- list of some game-altering mods which are not suspicious and considered normal/ok to use local GAMods_whitelist = { ["interact_toggle"] = true, ["restart_end_job"] = true, ["restore_deployables"] = true, ["corpse_despawn"] = true, ["crybaby"] = true, ["overdrill7200"] = true, } -- my hooks -- locally show whenever a lobby is re-registered local function handle_UpdatedLobbyID(peer, data) Util:say_to_chat("[golden]lobby has been re-registered to [nice_blue]steam://joinlobby/24240/" .. tostring(data), false, "DEBUG") end -- report whenever someone has some Gameplay Altering mods loaded (it doesn't mean they're using them at the moment, but they have them loaded through DAHM) local function handle_GAMods(peer, data) if type(data) ~= "table" then return -- received malformed data end local ga_mods = "" -- string containing list of Gameplay Altering mod names for k, _ in pairs(data) do if not GAMods_whitelist[k] then ga_mods = string.format("%s %q", ga_mods, k) end end if ga_mods ~= "" then dlog_ll(2, "UserLua", "> ga mods: ", trim(ga_mods)) Util:say_to_chat(string.format("[golden][peer_color]%s[] has following Gameplay Altering mod(s) active: [nice_blue]%s", clear_string(peer:name()), trim(ga_mods)), false, "ANTICHEAT", { subject_peer = peer }) end end -- use one hook for all DAHM's events to be more efficient than hooking each function seperately D:hook("OnNetworkDataRecv", "OnNetworkDataRecv_UserLuaHooks", { "UpdatedLobbyID", "GAMods", }, function(peer, data_type, data) if data_type == "UpdatedLobbyID" then handle_UpdatedLobbyID(peer, data) elseif data_type == "GAMods" then handle_GAMods(peer, data) end end) do -- voip checking (requires mod on the other end) D:register_command("v", function(args) if managers.network:game() then local peers_to_check local peer_id = type(args) == "string" and args:match("%d+") if peer_id then local peer = managers.network:session():peer(tonumber(args)) if peer then peers_to_check = { peer } end elseif string.is_nil_or_empty(args) then peers_to_check = managers.network:session():peers() end if peers_to_check then for _, peer in pairs(peers_to_check) do local dh_version = peer:preference("version") if dh_version and Util:version_compare(dh_version, "1.1.0") >= 0 then DNet:send_to_peer(peer, "VOIP?", {}, true) else Util:say_to_chat("Peer '" .. peer:name() .. "' does not support command." .. Util:version_compare(dh_version, "1.1.0"), false, true) end end end end end) D:hook("OnNetworkDataRecv", "OnNetworkDataRecv_CheckVoipReply", "VOIP!", function(peer, data_type, data) if type(data) == "table" then Util:say_to_chat("VOIP settings for '" .. peer:name() .. "': " .. string.format("enabled: %s, muted: %s, volume: { music:%i%%, sfx:%i%%, voice:%i%% }", data.enabled and "yes" or "NO", data.muted and "YES" or "no", type(data.music) == "number" and data.music or -1, type(data.sfx) == "number" and data.sfx or -1, type(data.volume) == "number" and data.volume * 100 or -1 ), false, true) end end) end do -- stats logging local statsHeaderPrinted = false local charWidths = { [" "] = 4, ["!"] = 4, ["%"] = 12, ["("] = 5, [")"] = 5, ["+"] = 8, [","] = 4, ["-"] = 5, ["."] = 4, ["/"] = 5, ["0"] = 9, ["1"] = 9, ["2"] = 9, ["3"] = 9, ["4"] = 9, ["5"] = 9, ["6"] = 9, ["7"] = 9, ["8"] = 9, ["9"] = 9, [":"] = 4, [";"] = 4, ["="] = 8, ["?"] = 8, ["A"] = 9, ["B"] = 9, ["C"] = 9, ["D"] = 9, ["E"] = 9, ["F"] = 9, ["G"] = 9, ["H"] = 9, ["I"] = 5, ["J"] = 9, ["K"] = 9, ["L"] = 9, ["M"] = 11, ["N"] = 9, ["O"] = 9, ["P"] = 9, ["Q"] = 9, ["R"] = 9, ["S"] = 9, ["T"] = 9, ["U"] = 9, ["V"] = 9, ["W"] = 12, ["X"] = 9, ["Y"] = 9, ["Z"] = 9, ["_"] = 8, ["a"] = 8, ["b"] = 8, ["c"] = 8, ["d"] = 8, ["e"] = 8, ["f"] = 5, ["g"] = 8, ["h"] = 8, ["i"] = 4, ["j"] = 4, ["k"] = 8, ["l"] = 4, ["m"] = 12, ["n"] = 8, ["o"] = 8, ["p"] = 8, ["q"] = 8, ["r"] = 5, ["s"] = 8, ["t"] = 5, ["u"] = 8, ["v"] = 8, ["w"] = 11, ["x"] = 8, ["y"] = 8, ["z"] = 8, ["|"] = 4, ["Ä"] = 9, ["Ö"] = 9, ["Ü"] = 9, ["ß"] = 8, ["ä"] = 8, ["ö"] = 8, ["ü"] = 8, } local defaultCharWidth = 8 local function getStringWidth(str) local width = 0 for i = 1, #str do local c = str:sub(i, i) width = width + (charWidths[c] or defaultCharWidth) end return width end local function padToWidth(str, targetWidth) local currentWidth = getStringWidth(str) local spaceWidth = charWidths[" "] local spacesNeeded = math.floor((targetWidth - currentWidth) / spaceWidth) if spacesNeeded > 0 then return str .. string.rep(" ", spacesNeeded) end return str end local function truncateToWidth(str, maxWidth) local width = 0 local result = "" for i = 1, #str do local c = str:sub(i, i) local cw = charWidths[c] or defaultCharWidth if width + cw > maxWidth then break end width = width + cw result = result .. c end return result end -- entfernt "Clan" Tags, also alles zwischen [ und ] local function sanitizeName(name) return name:gsub("%[.-%]", ""):gsub("^%s+", ""):gsub("%s+$", "") end -- Spaltenbreiten in Pixeln: ID, Name, K, SpK, HS, Acc, D, Time local colWidths = { 25, 140, 45, 45, 45, 65, 35, 55 } local function stats_logger(peer, result) if type(peer) == "table" and result.time_played then -- Header einmalig if not statsHeaderPrinted then local header = padToWidth("ID", colWidths[1]) .. padToWidth("Name", colWidths[2]) .. padToWidth("K", colWidths[3]) .. padToWidth("SpK", colWidths[4]) .. padToWidth("HS", colWidths[5]) .. padToWidth("Acc", colWidths[6]) .. padToWidth("D", colWidths[7]) .. "Time" Util:say_to_chat(header, false, true) statsHeaderPrinted = true end -- Spielerdaten local acc = result.accuracy_f or result.accuracy or 0 if type(acc) == "number" then acc = string.format("%.1f%%", acc) end local cleanName = truncateToWidth(sanitizeName(peer:name()), colWidths[2] - 8) local row = padToWidth(tostring(peer:id()), colWidths[1]) .. padToWidth(cleanName, colWidths[2]) .. padToWidth(tostring(result.total_kills or "-"), colWidths[3]) .. padToWidth(tostring(result.total_specials_kills or "-"), colWidths[4]) .. padToWidth(tostring(result.total_head_shots or "-"), colWidths[5]) .. padToWidth(acc, colWidths[6]) .. padToWidth(tostring(result.downs or "-"), colWidths[7]) .. tostring(result.time_played or "-") Util:say_to_chat(row, false, true) peer:remove_listener("OnGameStatsEvent_LogStats") end end local function peer_level_verifier(peer, level) if level == 194 and peer.progress then local total = 0 for tree, level in pairs(peer:progress()) do total = total + level end if total < 194 then Util:say_to_chat(DLocalizer:convert_locale_obj_to_string({ text_id = "profile_level_out_of_bounds", macros = { PLAYER_ID = peer:user_id(), PLAYER_NAME = peer:name(), PLAYER_LEVEL = peer:level(1) or "?", }, subject_peer = peer, destination = "chat", type = "cheat" }), false, true) D:log(1, "[peer_level_verifier]", string.format( "player %s | %s has level %s, which is greater than the sum of the trees!", peer:name(), peer:user_id(), peer:level(), total )) end end end D:hook("OnNetworkDataRecv", "OnNetworkDataRecv_ShowGameStats", "GameStats", function(peer, data_type, data) if data_type ~= "GameStats" or not data or not data.peer_id then return end if peer:id() == data.peer_id or peer:is_server() then local target_peer = peer if peer:id() ~= data.peer_id then target_peer = managers.network:session() and managers.network:session():peer(data.peer_id) end if target_peer and (not target_peer:has_statistics() or not target_peer:statistics().time_played) then if target_peer.set_statistics2 and target_peer._stats_translation_map then target_peer:set_statistics2(table.remap_keys(data, target_peer._stats_translation_map)) else target_peer:set_statistics(data.kills, data.sp_kills, data.headshots, data.accuracy, data.downs, data.time, data.is_dropin) end end end end) D:hook("OnEnteredLobby", "OnEnteredLobby_LogLocalPeerStats", function(state, level) statsHeaderPrinted = false managers.network:session():local_peer():add_listener("OnGameStatsEvent_LogStats", "game_stats", stats_logger) end) D:hook("OnPeerAdded", "OnPeerAdded_LogPeerStats", function(peer, is_new) peer:add_listener("OnGameStatsEvent_LogStats", "game_stats", stats_logger) peer:add_listener("OnGameStatsEvent_VerifyLevelValidity", "level", peer_level_verifier) if is_new and managers.menu and D:has_module("enhanced_chat") then managers.menu:_log_message_to_file(string.format( "%s -- Player '%s' has joined. Steam ID: %s, IP: %s\n", os.date("%H:%M:%S"), tostring(peer:name()), tostring(peer:user_id()), tostring(Network:get_ip_address_from_user_id(peer:user_id())) )) end end) end -- end stats logging D:hook("OnModuleInit", "OnModuleLoaed_UserLuaOverwriteChatLogFormat", function(module, params) if module:id() == "enhanced_chat" then module.chat_log_format = function(name, message, peer, state) return string.format("%s -- \"%s\" ", os.date(iso8601), state or "?") .. "%n%s: %s\n" end end end) --- apply table values -- to set a value to nil, explicitly call "D:set_config_option(, nil)" However, it's usually sufficient to set it to false or an empty string local overwrite = not prioritize_gui and (is_first_load or always_reload) for key, value in pairs(config) do D:set_config_option(key, value, overwrite) end for key, value in pairs(localization) do D:add_localization_override(key, value) end for id, key in pairs(keybind_overrides) do D:update_keybind(id, key) end for key, text in pairs(commands) do D:register_chat_command(key, function() Util:say_to_chat(text) return true end) end for _, module in ipairs(blacklisted_modules) do D:blacklist_module(module) end local f, err = loadfile("mods/user-secret.lua") -- Loads other configs that are private for example the steam web API key and debugging stuff if f ~= nil then f(...) end local steam_id = Steam and Steam:userid() if steam_id then local f,err = loadfile(string.format("mods/%s.user.lua", steam_id)) if f ~= nil then f(...) end end