From: terencehill Date: Sun, 5 Apr 2020 12:28:53 +0000 (+0200) Subject: Merge branch 'martin-t/limit' X-Git-Tag: xonotic-v0.8.5~1133 X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fxonotic-data.pk3dir.git;a=commitdiff_plain;h=caa42d15f2b2cd3ed4bc177a9aa70903e67d5142;hp=472c73cc4e56077b075157325f7d05a7f4af165b Merge branch 'martin-t/limit' --- diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 15ea0ec539..4906ab601a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,7 +29,7 @@ test_sv_game: - wget -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints - wget -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache - make - - EXPECT=6535600492e1cb63af8e449570bffe4a + - EXPECT=a98e5e5ee0cc3d2e80ee0ad812786703 - HASH=$(${ENGINE} -noconfig -nohome +exec serverbench.cfg | tee /dev/stderr | grep '^:' diff --git a/.tx/merge-base b/.tx/merge-base index 803036b6a9..a43f5bb0f2 100644 --- a/.tx/merge-base +++ b/.tx/merge-base @@ -1 +1 @@ -Fri Mar 6 07:24:27 CET 2020 +Mon Mar 30 07:24:48 CEST 2020 diff --git a/common.ast.po b/common.ast.po index 68a14f48f6..2e397ca68a 100644 --- a/common.ast.po +++ b/common.ast.po @@ -4,8 +4,9 @@ # # Translators: # Ximielga , 2014-2015 -# Ḷḷumex03 , 2014 -# Ḷḷumex03 , 2014-2015 +# Ḷḷumex03, 2014 +# Ḷḷumex03, 2014 +# Ḷḷumex03, 2014-2015 # Tornes Ḷḷume , 2015-2017 # Ximielga , 2014 msgid "" diff --git a/effectinfo.txt b/effectinfo.txt index 129dba36e2..f7ee697520 100644 --- a/effectinfo.txt +++ b/effectinfo.txt @@ -3418,6 +3418,30 @@ effect TE_TEI_G3PINK_HIT sizeincrease -6 size 10 10 trailspacing 40 +effect TE_TEI_G3_HIT + type beam + alpha 128 128 256 + color 0xFFFFFF 0xFFFFFF + countabsolute 1 + size 8 8 + tex 200 200 +effect TE_TEI_G3_HIT + type smoke + airfriction -4 + alpha 256 256 512 + color 0xFFFFFF 0xFFFFFF + sizeincrease -2 + size 2 2 + trailspacing 20 + velocityjitter 2 2 2 +effect TE_TEI_G3_HIT + type smoke + airfriction -4 + alpha 256 256 512 + color 0xFFFFFF 0xFFFFFF + sizeincrease -6 + size 10 10 + trailspacing 40 effect particlegibs_damage_hit type blood airfriction 3 diff --git a/mutators.cfg b/mutators.cfg index f3ed5e0769..2d52893786 100644 --- a/mutators.cfg +++ b/mutators.cfg @@ -9,6 +9,7 @@ set g_dodging 0 "set to 1 to enable dodging (quick acceleration in a given direction)" seta cl_dodging_timeout 0.2 "determines how long apart (in seconds) two taps on the same direction key are considered a dodge. use 0 to disable" +seta cl_dodging 0 "enable dodging, requires sv_dodging_clientselect to be enabled on the server" set sv_dodging_air_dodging 0 set sv_dodging_wall_dodging 0 "allow dodging off walls" @@ -27,6 +28,7 @@ set sv_dodging_frozen 0 "allow dodging while frozen" set sv_dodging_frozen_doubletap 0 set sv_dodging_maxspeed 350 "maximum speed a player can be moving at to use the standard dodging from an (almost) standstill" set sv_dodging_air_maxspeed 450 "maximum speed a player can be moving at before they dodge again when air dodging is enabled" +set sv_dodging_clientselect 0 "allow clients to opt-in to dodging movement with the cl_dodging cvar, dodging will be off for clients by default with this enabled" // =========== @@ -444,8 +446,9 @@ set g_breakablehook_owner 0 "allow owner to break their own hook" // =========== // multijump // =========== -seta cl_multijump 1 "allow multijump mutator" +seta cl_multijump -1 "allow multijump mutator, set to -1 for the server to decide whether it's enabled" set g_multijump 0 "Number of multiple jumps to allow (jumping again in the air), -1 allows for infinite jumps" +set g_multijump_client 1 "If the client sets cl_multijump to -1, this setting is used for them as a default" set g_multijump_add 0 "0 = make the current z velocity equal to jumpvelocity, 1 = add jumpvelocity to the current z velocity" set g_multijump_speed -999999 "Minimum vertical speed a player must have in order to jump again" set g_multijump_maxspeed 0 diff --git a/qcsrc/client/autocvars.qh b/qcsrc/client/autocvars.qh index 6759c80782..234f9b0a9a 100644 --- a/qcsrc/client/autocvars.qh +++ b/qcsrc/client/autocvars.qh @@ -416,7 +416,7 @@ float autocvar_cl_hitsound_nom_damage = 25; float autocvar_cl_hitsound_antispam_time; bool autocvar_cl_eventchase_spectated_change = false; float autocvar_cl_eventchase_spectated_change_time = 1; -int autocvar_cl_eventchase_death = 1; +int autocvar_cl_eventchase_death = 2; float autocvar_cl_eventchase_distance = 140; bool autocvar_cl_eventchase_frozen = false; float autocvar_cl_eventchase_speed = 1.3; diff --git a/qcsrc/client/main.qc b/qcsrc/client/main.qc index b7b7e53037..72ee803fa4 100644 --- a/qcsrc/client/main.qc +++ b/qcsrc/client/main.qc @@ -136,7 +136,7 @@ void CSQC_Init() registercvar("cl_shootfromfixedorigin", ""); - registercvar("cl_multijump", "1"); + registercvar("cl_multijump", "-1"); registercvar("cl_spawn_near_teammate", "1"); diff --git a/qcsrc/common/effects/effectinfo.inc b/qcsrc/common/effects/effectinfo.inc index d45bdf36d4..7c2dde1ed7 100644 --- a/qcsrc/common/effects/effectinfo.inc +++ b/qcsrc/common/effects/effectinfo.inc @@ -5154,6 +5154,49 @@ TE_TEI_G3(YELLOW, "0xffff00", "0xffff11", "0x202000", "0x404000") TE_TEI_G3(PINK, "0xFF00FF", "0xFF11FF", "0x200020", "0x400040") #undef TE_TEI_G3 +// Vaporizer hit effect +DEF(TE_TEI_G3_HIT); +SUB(TE_TEI_G3_HIT) { + MY(alpha_min) = 128; + MY(alpha_max) = 128; + MY(alpha_fade) = 256; + MY(color_min) = "0xFFFFFF"; + MY(color_max) = "0xFFFFFF"; + MY(countabsolute) = 1; + MY(size_min) = 8; + MY(size_max) = 8; + MY(tex_min) = 200; + MY(tex_max) = 200; + MY(type) = "beam"; +} +SUB(TE_TEI_G3_HIT) /* rings */ { + MY(airfriction) = -4; + MY(alpha_min) = 256; + MY(alpha_max) = 256; + MY(alpha_fade) = 512; + MY(color_min) = "0xFFFFFF"; + MY(color_max) = "0xFFFFFF"; + MY(sizeincrease) = -2; + MY(size_min) = 2; + MY(size_max) = 2; + MY(trailspacing) = 20; + MY(type) = "smoke"; + MY(velocityjitter) = '2.0 2.0 2.0'; +} +SUB(TE_TEI_G3_HIT) { + MY(airfriction) = -4; + MY(alpha_min) = 256; + MY(alpha_max) = 256; + MY(alpha_fade) = 512; + MY(color_min) = "0xFFFFFF"; + MY(color_max) = "0xFFFFFF"; + MY(sizeincrease) = -6; + MY(size_min) = 10; + MY(size_max) = 10; + MY(trailspacing) = 40; + MY(type) = "smoke"; +} + #include "effectinfo_gentle_particlegibs.inc" #include "effectinfo_onslaught.inc" diff --git a/qcsrc/common/gamemodes/gamemode/ctf/sv_ctf.qc b/qcsrc/common/gamemodes/gamemode/ctf/sv_ctf.qc index 11bbaea6fb..cfe8d4058d 100644 --- a/qcsrc/common/gamemodes/gamemode/ctf/sv_ctf.qc +++ b/qcsrc/common/gamemodes/gamemode/ctf/sv_ctf.qc @@ -438,7 +438,19 @@ void ctf_Handle_Throw(entity player, entity receiver, int droptype) if(!flag) { return; } if((droptype == DROP_PASS) && !receiver) { return; } - if(flag.speedrunning) { ctf_RespawnFlag(flag); return; } + if(flag.speedrunning) + { + // ensure old waypoints are removed before resetting the flag + WaypointSprite_Kill(player.wps_flagcarrier); + + if(player.wps_enemyflagcarrier) + WaypointSprite_Kill(player.wps_enemyflagcarrier); + + if(player.wps_flagreturn) + WaypointSprite_Kill(player.wps_flagreturn); + ctf_RespawnFlag(flag); + return; + } // reset the flag setattachment(flag, NULL, ""); diff --git a/qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qc b/qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qc index 89060694a6..eaa60f8271 100644 --- a/qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qc +++ b/qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qc @@ -130,7 +130,7 @@ bool freezetag_CheckWinner() { Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN)); Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN)); - TeamScore_AddToTeam(winner_team, ST_SCORE, +1); + TeamScore_AddToTeam(winner_team, ST_FT_ROUNDS, +1); } else if(winner_team == -1) { @@ -620,6 +620,12 @@ MUTATOR_HOOKFUNCTION(ft, SV_ParseServerCommand) return false; } +MUTATOR_HOOKFUNCTION(ft, Scores_CountFragsRemaining) +{ + // announce remaining frags + return true; +} + void freezetag_Initialize() { freezetag_teams = autocvar_g_freezetag_teams_override; @@ -628,6 +634,7 @@ void freezetag_Initialize() freezetag_teams = BITS(bound(2, freezetag_teams, 4)); GameRules_scoring(freezetag_teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, { + field_team(ST_FT_ROUNDS, "rounds", SFL_SORT_PRIO_PRIMARY); field(SP_FREEZETAG_REVIVALS, "revivals", 0); }); diff --git a/qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qh b/qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qh index 3eb753020b..b77318ca2e 100644 --- a/qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qh +++ b/qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qh @@ -8,6 +8,8 @@ int autocvar_g_freezetag_point_leadlimit; bool autocvar_g_freezetag_team_spawns; string autocvar_g_freezetag_weaponarena = "most_available"; +const int ST_FT_ROUNDS = 1; + void freezetag_Initialize(); REGISTER_MUTATOR(ft, false) diff --git a/qcsrc/common/gamemodes/gamemode/keyhunt/sv_keyhunt.qc b/qcsrc/common/gamemodes/gamemode/keyhunt/sv_keyhunt.qc index 1240ee4975..5d52ed7cb0 100644 --- a/qcsrc/common/gamemodes/gamemode/keyhunt/sv_keyhunt.qc +++ b/qcsrc/common/gamemodes/gamemode/keyhunt/sv_keyhunt.qc @@ -541,7 +541,12 @@ void kh_WinnerTeam(int winner_team) // runs when a team wins midpoint += thisorigin; if(!first) - te_lightning2(NULL, lastorigin, thisorigin); + { + // TODO: this effect has been replaced due to a possible crash it causes + // see https://gitlab.com/xonotic/darkplaces/issues/123 + //te_lightning2(NULL, lastorigin, thisorigin); + Send_Effect(EFFECT_TR_NEXUIZPLASMA, lastorigin, thisorigin, 1); + } lastorigin = thisorigin; if(first) firstorigin = thisorigin; @@ -549,7 +554,8 @@ void kh_WinnerTeam(int winner_team) // runs when a team wins } if(NumTeams(kh_teams) > 2) { - te_lightning2(NULL, lastorigin, firstorigin); + //te_lightning2(NULL, lastorigin, firstorigin); // TODO see above + Send_Effect(EFFECT_TR_NEXUIZPLASMA, lastorigin, firstorigin, 1); } midpoint = midpoint * (1 / NumTeams(kh_teams)); te_customflash(midpoint, 1000, 1, Team_ColorRGB(winner_team) * 0.5 + '0.5 0.5 0.5'); // make the color >=0.5 in each component diff --git a/qcsrc/common/mutators/mutator/dodging/cl_dodging.qh b/qcsrc/common/mutators/mutator/dodging/cl_dodging.qh index 84f77314ed..696a2d9e58 100644 --- a/qcsrc/common/mutators/mutator/dodging/cl_dodging.qh +++ b/qcsrc/common/mutators/mutator/dodging/cl_dodging.qh @@ -2,3 +2,6 @@ float cvar_cl_dodging_timeout; REPLICATE(cvar_cl_dodging_timeout, float, "cl_dodging_timeout"); + +float cvar_cl_dodging; +REPLICATE(cvar_cl_dodging, float, "cl_dodging"); diff --git a/qcsrc/common/mutators/mutator/dodging/sv_dodging.qc b/qcsrc/common/mutators/mutator/dodging/sv_dodging.qc index 66d18ac671..3651e9aa07 100644 --- a/qcsrc/common/mutators/mutator/dodging/sv_dodging.qc +++ b/qcsrc/common/mutators/mutator/dodging/sv_dodging.qc @@ -18,6 +18,7 @@ #define PHYS_DODGING_AIR autocvar_sv_dodging_air_dodging #define PHYS_DODGING_MAXSPEED autocvar_sv_dodging_maxspeed #define PHYS_DODGING_AIR_MAXSPEED autocvar_sv_dodging_air_maxspeed +#define PHYS_DODGING_CLIENTSELECT autocvar_sv_dodging_clientselect // we ran out of stats slots! TODO: re-enable this when prediction is available for dodging #if 0 @@ -37,18 +38,24 @@ #define PHYS_DODGING_AIR STAT(DODGING_AIR, this) #define PHYS_DODGING_MAXSPEED STAT(DODGING_MAXSPEED, this) #define PHYS_DODGING_AIR_MAXSPEED STAT(DODGING_AIR_MAXSPEED, this) +#define PHYS_DODGING_CLIENTSELECT STAT(DODGING_CLIENTSELECT, this) #endif #ifdef CSQC float cvar_cl_dodging_timeout; + bool cvar_cl_dodging; + bool autocvar_cl_dodging; #define PHYS_DODGING_FRAMETIME (1 / (frametime <= 0 ? 60 : frametime)) #define PHYS_DODGING_TIMEOUT(s) STAT(DODGING_TIMEOUT) #define PHYS_DODGING_PRESSED_KEYS(s) (s).pressedkeys + #define PHYS_DODGING_ENABLED(s) autocvar_cl_dodging #elif defined(SVQC) .float cvar_cl_dodging_timeout; + .bool cvar_cl_dodging; #define PHYS_DODGING_FRAMETIME sys_frametime #define PHYS_DODGING_TIMEOUT(s) CS(s).cvar_cl_dodging_timeout #define PHYS_DODGING_PRESSED_KEYS(s) CS(s).pressedkeys + #define PHYS_DODGING_ENABLED(s) CS(s).cvar_cl_dodging #endif #ifdef SVQC @@ -214,7 +221,7 @@ void PM_dodging(entity this) if (!this.dodging_action) return; // when swimming or dead, no dodging allowed.. - if (this.waterlevel >= WATERLEVEL_SWIMMING || IS_DEAD(this)) + if (this.waterlevel >= WATERLEVEL_SWIMMING || IS_DEAD(this) || (PHYS_DODGING_CLIENTSELECT && !PHYS_DODGING_ENABLED(this))) { this.dodging_action = 0; this.dodging_direction.x = 0; @@ -299,6 +306,7 @@ MUTATOR_HOOKFUNCTION(dodging, PlayerPhysics) #ifdef SVQC REPLICATE(cvar_cl_dodging_timeout, float, "cl_dodging_timeout"); +REPLICATE(cvar_cl_dodging, bool, "cl_dodging"); MUTATOR_HOOKFUNCTION(dodging, GetPressedKeys) { diff --git a/qcsrc/common/mutators/mutator/multijump/multijump.qc b/qcsrc/common/mutators/mutator/multijump/multijump.qc index 0efd85635c..40bc9a33e9 100644 --- a/qcsrc/common/mutators/mutator/multijump/multijump.qc +++ b/qcsrc/common/mutators/mutator/multijump/multijump.qc @@ -20,16 +20,17 @@ REGISTER_MUTATOR(multijump, true); #define PHYS_MULTIJUMP_MAXSPEED(s) STAT(MULTIJUMP_MAXSPEED, s) #define PHYS_MULTIJUMP_DODGING(s) STAT(MULTIJUMP_DODGING, s) #define PHYS_MULTIJUMP_COUNT(s) STAT(MULTIJUMP_COUNT, s) +#define PHYS_MULTIJUMP_CLIENTDEFAULT(s) STAT(MULTIJUMP_CLIENT, s) .bool multijump_ready; #ifdef CSQC -bool cvar_cl_multijump; -bool autocvar_cl_multijump = true; +int cvar_cl_multijump; +int autocvar_cl_multijump = -1; #define PHYS_MULTIJUMP_CLIENT(s) autocvar_cl_multijump #elif defined(SVQC) -.bool cvar_cl_multijump; +.int cvar_cl_multijump; #define PHYS_MULTIJUMP_CLIENT(s) CS(s).cvar_cl_multijump #endif @@ -54,6 +55,8 @@ MUTATOR_HOOKFUNCTION(multijump, PlayerJump) if(!PHYS_MULTIJUMP(player)) { return; } int client_multijump = PHYS_MULTIJUMP_CLIENT(player); + if(client_multijump == -1) + client_multijump = PHYS_MULTIJUMP_CLIENTDEFAULT(player); if(client_multijump > 1) return; // nope @@ -115,7 +118,7 @@ MUTATOR_HOOKFUNCTION(multijump, PlayerJump) } } -REPLICATE(cvar_cl_multijump, bool, "cl_multijump"); +REPLICATE(cvar_cl_multijump, int, "cl_multijump"); #ifdef SVQC diff --git a/qcsrc/common/mutators/mutator/overkill/okhmg.qc b/qcsrc/common/mutators/mutator/overkill/okhmg.qc index a0a8962a7d..68d69ab2f0 100644 --- a/qcsrc/common/mutators/mutator/overkill/okhmg.qc +++ b/qcsrc/common/mutators/mutator/overkill/okhmg.qc @@ -57,10 +57,8 @@ void W_OverkillHeavyMachineGun_Attack_Auto(Weapon thiswep, entity actor, .entity METHOD(OverkillHeavyMachineGun, wr_aim, void(entity thiswep, entity actor, .entity weaponentity)) { - if(vdist(actor.origin - actor.enemy.origin, <, 3000 - bound(0, skill, 10) * 200)) - PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false); - else - PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false); + if(vdist(actor.origin - actor.enemy.origin, <, 3000 - bound(0, skill, 10) * 200)) + PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false); } METHOD(OverkillHeavyMachineGun, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire)) diff --git a/qcsrc/common/mutators/mutator/overkill/okmachinegun.qc b/qcsrc/common/mutators/mutator/overkill/okmachinegun.qc index 17e71f06f8..817e369816 100644 --- a/qcsrc/common/mutators/mutator/overkill/okmachinegun.qc +++ b/qcsrc/common/mutators/mutator/overkill/okmachinegun.qc @@ -53,8 +53,6 @@ METHOD(OverkillMachineGun, wr_aim, void(entity thiswep, entity actor, .entity we { if(vdist(actor.origin - actor.enemy.origin, <, 3000 - bound(0, skill, 10) * 200)) PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false); - else - PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false); } METHOD(OverkillMachineGun, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire)) diff --git a/qcsrc/common/stats.qh b/qcsrc/common/stats.qh index 245d92604e..c5e45e866a 100644 --- a/qcsrc/common/stats.qh +++ b/qcsrc/common/stats.qh @@ -145,11 +145,13 @@ float autocvar_g_multijump_add; float autocvar_g_multijump_speed; float autocvar_g_multijump_maxspeed; float autocvar_g_multijump_dodging = 1; +bool autocvar_g_multijump_client = true; #endif REGISTER_STAT(MULTIJUMP_DODGING, int, autocvar_g_multijump_dodging) REGISTER_STAT(MULTIJUMP_MAXSPEED, float, autocvar_g_multijump_maxspeed) REGISTER_STAT(MULTIJUMP_ADD, int, autocvar_g_multijump_add) REGISTER_STAT(MULTIJUMP_SPEED, float, autocvar_g_multijump_speed) +REGISTER_STAT(MULTIJUMP_CLIENT, bool, autocvar_g_multijump_client) .int multijump_count; REGISTER_STAT(MULTIJUMP_COUNT, int, this.multijump_count) REGISTER_STAT(MULTIJUMP, int, autocvar_g_multijump) @@ -244,6 +246,7 @@ bool autocvar_sv_dodging_wall_dodging; bool autocvar_sv_dodging_air_dodging; float autocvar_sv_dodging_maxspeed; float autocvar_sv_dodging_air_maxspeed; +bool autocvar_sv_dodging_clientselect; #endif #if 0 @@ -263,6 +266,7 @@ REGISTER_STAT(DODGING_WALL, bool, autocvar_sv_dodging_wall_dodging) REGISTER_STAT(DODGING_AIR, bool, autocvar_sv_dodging_air_dodging) REGISTER_STAT(DODGING_MAXSPEED, float, autocvar_sv_dodging_maxspeed) REGISTER_STAT(DODGING_AIR_MAXSPEED, float, autocvar_sv_dodging_air_maxspeed) +REGISTER_STAT(DODGING_CLIENTSELECT, bool, autocvar_sv_dodging_clientselect) #endif /** cvar loopback */ REGISTER_STAT(DODGING_FROZEN, int, autocvar_sv_dodging_frozen) diff --git a/qcsrc/common/weapons/all.qc b/qcsrc/common/weapons/all.qc index b8d2428f3f..a8d1376d1d 100644 --- a/qcsrc/common/weapons/all.qc +++ b/qcsrc/common/weapons/all.qc @@ -577,10 +577,10 @@ NET_HANDLE(wframe, bool isNew) vector a = '0 0 0'; switch(fr) { + default: case WFRAME_IDLE: a = wepent.anim_idle; break; case WFRAME_FIRE1: a = wepent.anim_fire1; break; case WFRAME_FIRE2: a = wepent.anim_fire2; break; - default: case WFRAME_RELOAD: a = wepent.anim_reload; break; } a.z *= t; diff --git a/qcsrc/ecs/systems/physics.qc b/qcsrc/ecs/systems/physics.qc index b9eca8ca8e..cd59c516c9 100644 --- a/qcsrc/ecs/systems/physics.qc +++ b/qcsrc/ecs/systems/physics.qc @@ -33,7 +33,12 @@ void sys_phys_update(entity this, float dt) if (IS_SVQC) { if (this.move_movetype == MOVETYPE_NONE) { return; } // when we get here, disableclientprediction cannot be 2 - this.disableclientprediction = (this.move_qcphysics) ? -1 : 0; + if(this.move_movetype == MOVETYPE_FOLLOW) // not compatible with prediction + this.disableclientprediction = 1; + else if(this.move_qcphysics) + this.disableclientprediction = -1; + else + this.disableclientprediction = 0; } viewloc_PlayerPhysics(this); diff --git a/qcsrc/lib/vector.qh b/qcsrc/lib/vector.qh index ca0e84f67a..6f419954cc 100644 --- a/qcsrc/lib/vector.qh +++ b/qcsrc/lib/vector.qh @@ -69,18 +69,6 @@ float vlen_minnorm2d(vector v) return min(max(v.x, -v.x), max(v.y, -v.y)); } -ERASEABLE -float dist_point_line(vector p, vector l0, vector ldir) -{ - ldir = normalize(ldir); - - // remove the component in line direction - p = p - (p * ldir) * ldir; - - // vlen of the remaining vector - return vlen(p); -} - /** requires that m2>m1 in all coordinates, and that m4>m3 */ ERASEABLE float boxesoverlap(vector m1, vector m2, vector m3, vector m4) { return m2_x >= m3_x && m1_x <= m4_x && m2_y >= m3_y && m1_y <= m4_y && m2_z >= m3_z && m1_z <= m4_z; } @@ -122,6 +110,19 @@ vector Rotate(vector v, float a) noref vector _yinvert; #define yinvert(v) (_yinvert = (v), _yinvert.y = 1 - _yinvert.y, _yinvert) +/// \param[in] p point +/// \param[in] l0 starting point of ldir +/// \param[in] ldir line +/// \return Vector starting from p perpendicular to ldir +ERASEABLE +vector point_line_vec(vector p, vector l0, vector ldir) +{ + ldir = normalize(ldir); + p = l0 - p; + // remove the component in line direction from p + return p - ((p * ldir) * ldir); +} + /** * @param dir the directional vector * @param norm the normalized normal diff --git a/qcsrc/lib/warpzone/mathlib.qc b/qcsrc/lib/warpzone/mathlib.qc index cf86d97f9c..2b1d00c2cc 100644 --- a/qcsrc/lib/warpzone/mathlib.qc +++ b/qcsrc/lib/warpzone/mathlib.qc @@ -24,11 +24,12 @@ bool isinf(float e) } bool isnan(float e) { - // the sane way to detect NaN is broken because of a compiler bug - // (works with constants but breaks when assigned to variables) - // use conversion to string instead - //float f = e; - //return (e != f); + // The sane way to detect NaN is this: + // float f = e; + // return (e != f); + // but darkplaces used to be compiled with -ffinite-math-only which broke it. + // DP is fixed now but until all clients update (so after 0.8.3) we have to use the following workaround + // or they'd have issues when connecting to newer servers. // Negative NaN ("-nan") is much more common but plain "nan" can be created by negating *some* -nans so we need to check both. // DP's QCVM and GMQCC's QCVM behave differently - one needs ftos(-(0.0 / 0.0)), the other ftos(-sqrt(-1)). @@ -225,7 +226,7 @@ float copysign(float e, float f) { return fabs(e) * ((f>0) ? 1 : -1); } -/// Always use `isnan` function to compare because `float x = nan(); x == x;` gives true + float nan(string tag) { return sqrt(-1); diff --git a/qcsrc/menu/xonotic/dialog_settings_game_view.qc b/qcsrc/menu/xonotic/dialog_settings_game_view.qc index ed21eeae0b..b4c170280c 100644 --- a/qcsrc/menu/xonotic/dialog_settings_game_view.qc +++ b/qcsrc/menu/xonotic/dialog_settings_game_view.qc @@ -26,7 +26,7 @@ void XonoticGameViewSettingsTab_fill(entity me) me.TD(me, 1, 3, e = makeXonoticRadioButton(1, "chase_active", "0", _("1st person perspective"))); me.TR(me); me.TDempty(me, 0.2); - me.TD(me, 1, 2.8, e = makeXonoticCheckBox(0, "cl_eventchase_death", _("Slide to third person upon death"))); + me.TD(me, 1, 2.8, e = makeXonoticCheckBoxEx(2, 0, "cl_eventchase_death", _("Slide to third person upon death"))); setDependent(e, "chase_active", -1, 0); me.TR(me); me.TDempty(me, 0.2); diff --git a/qcsrc/server/bot/default/bot.qc b/qcsrc/server/bot/default/bot.qc index 4d0d4cdff2..7b30b62dfa 100644 --- a/qcsrc/server/bot/default/bot.qc +++ b/qcsrc/server/bot/default/bot.qc @@ -104,7 +104,8 @@ void bot_think(entity this) // clear buttons PHYS_INPUT_BUTTON_ATCK(this) = false; - PHYS_INPUT_BUTTON_JUMP(this) = false; + // keep jump button pressed for a short while, useful with ramp jumps + PHYS_INPUT_BUTTON_JUMP(this) = (!IS_DEAD(this) && time < this.bot_jump_time + 0.2); PHYS_INPUT_BUTTON_ATCK2(this) = false; PHYS_INPUT_BUTTON_ZOOM(this) = false; PHYS_INPUT_BUTTON_CROUCH(this) = false; @@ -309,6 +310,10 @@ void bot_setnameandstuff(entity this) void bot_custom_weapon_priority_setup() { + static string bot_priority_far_prev; + static string bot_priority_mid_prev; + static string bot_priority_close_prev; + static string bot_priority_distances_prev; float tokens, i, w; bot_custom_weapon = false; @@ -316,70 +321,50 @@ void bot_custom_weapon_priority_setup() if( autocvar_bot_ai_custom_weapon_priority_far == "" || autocvar_bot_ai_custom_weapon_priority_mid == "" || autocvar_bot_ai_custom_weapon_priority_close == "" || - autocvar_bot_ai_custom_weapon_priority_distances == "" + autocvar_bot_ai_custom_weapon_priority_distances == "" ) return; - // Parse distances - tokens = tokenizebyseparator(autocvar_bot_ai_custom_weapon_priority_distances," "); - - if (tokens!=2) - return; - - bot_distance_far = stof(argv(0)); - bot_distance_close = stof(argv(1)); - - if(bot_distance_far < bot_distance_close){ - bot_distance_far = stof(argv(1)); - bot_distance_close = stof(argv(0)); - } + if (bot_priority_distances_prev != autocvar_bot_ai_custom_weapon_priority_distances) + { + strcpy(bot_priority_distances_prev, autocvar_bot_ai_custom_weapon_priority_distances); + tokens = tokenizebyseparator(autocvar_bot_ai_custom_weapon_priority_distances," "); - // Initialize list of weapons - bot_weapons_far[0] = -1; - bot_weapons_mid[0] = -1; - bot_weapons_close[0] = -1; + if (tokens!=2) + return; - // Parse far distance weapon priorities - tokens = tokenizebyseparator(W_NumberWeaponOrder(autocvar_bot_ai_custom_weapon_priority_far)," "); + bot_distance_far = stof(argv(0)); + bot_distance_close = stof(argv(1)); - int c = 0; - for(i=0; i < tokens && c < Weapons_COUNT; ++i){ - w = stof(argv(i)); - if ( w >= WEP_FIRST && w <= WEP_LAST) { - bot_weapons_far[c] = w; - ++c; + if(bot_distance_far < bot_distance_close){ + bot_distance_far = stof(argv(1)); + bot_distance_close = stof(argv(0)); } } - if(c < Weapons_COUNT) - bot_weapons_far[c] = -1; - // Parse mid distance weapon priorities - tokens = tokenizebyseparator(W_NumberWeaponOrder(autocvar_bot_ai_custom_weapon_priority_mid)," "); - - c = 0; - for(i=0; i < tokens && c < Weapons_COUNT; ++i){ - w = stof(argv(i)); - if ( w >= WEP_FIRST && w <= WEP_LAST) { - bot_weapons_mid[c] = w; - ++c; - } - } - if(c < Weapons_COUNT) - bot_weapons_mid[c] = -1; + int c; + + #define PARSE_WEAPON_PRIORITIES(dist) MACRO_BEGIN \ + if (bot_priority_##dist##_prev != autocvar_bot_ai_custom_weapon_priority_##dist) { \ + strcpy(bot_priority_##dist##_prev, autocvar_bot_ai_custom_weapon_priority_##dist); \ + tokens = tokenizebyseparator(W_NumberWeaponOrder(autocvar_bot_ai_custom_weapon_priority_##dist)," "); \ + bot_weapons_##dist[0] = -1; \ + c = 0; \ + for(i = 0; i < tokens && c < Weapons_COUNT; ++i) { \ + w = stof(argv(i)); \ + if (w >= WEP_FIRST && w <= WEP_LAST) { \ + bot_weapons_##dist[c] = w; \ + ++c; \ + } \ + } \ + if (c < Weapons_COUNT) \ + bot_weapons_##dist[c] = -1; \ + } \ + MACRO_END - // Parse close distance weapon priorities - tokens = tokenizebyseparator(W_NumberWeaponOrder(autocvar_bot_ai_custom_weapon_priority_close)," "); - - c = 0; - for(i=0; i < tokens && i < Weapons_COUNT; ++i){ - w = stof(argv(i)); - if ( w >= WEP_FIRST && w <= WEP_LAST) { - bot_weapons_close[c] = w; - ++c; - } - } - if(c < Weapons_COUNT) - bot_weapons_close[c] = -1; + PARSE_WEAPON_PRIORITIES(far); + PARSE_WEAPON_PRIORITIES(mid); + PARSE_WEAPON_PRIORITIES(close); bot_custom_weapon = true; } @@ -846,10 +831,6 @@ void bot_serverframe() if (autocvar_g_waypointeditor_auto) botframe_autowaypoints(); - if(time > bot_cvar_nextthink) - { - if(currentbots>0) - bot_custom_weapon_priority_setup(); - bot_cvar_nextthink = time + 5; - } + if (currentbots > 0) + bot_custom_weapon_priority_setup(); } diff --git a/qcsrc/server/bot/default/bot.qh b/qcsrc/server/bot/default/bot.qh index 98b2ae3df7..88dba449d5 100644 --- a/qcsrc/server/bot/default/bot.qh +++ b/qcsrc/server/bot/default/bot.qh @@ -65,6 +65,7 @@ entity bot_list; .bool bot_pickup_respawning; .float bot_canfire; .float bot_strategytime; +.float bot_jump_time; .float bot_forced_team; .float bot_config_loaded; @@ -75,7 +76,6 @@ entity bot_strategytoken; float botframe_spawnedwaypoints; float botframe_nextthink; float botframe_nextdangertime; -float bot_cvar_nextthink; int _content_type; #define IN_LAVA(pos) (_content_type = pointcontents(pos), (_content_type == CONTENT_LAVA || _content_type == CONTENT_SLIME)) diff --git a/qcsrc/server/bot/default/cvars.qh b/qcsrc/server/bot/default/cvars.qh index 55bc9c8f3f..269cd68044 100644 --- a/qcsrc/server/bot/default/cvars.qh +++ b/qcsrc/server/bot/default/cvars.qh @@ -19,10 +19,12 @@ float autocvar_bot_ai_aimskill_order_mix_3th; float autocvar_bot_ai_aimskill_order_mix_4th; float autocvar_bot_ai_aimskill_order_mix_5th; float autocvar_bot_ai_aimskill_think; -float autocvar_bot_ai_bunnyhop_firstjumpdelay; float autocvar_bot_ai_bunnyhop_skilloffset; -float autocvar_bot_ai_bunnyhop_startdistance; -float autocvar_bot_ai_bunnyhop_stopdistance; +float autocvar_bot_ai_bunnyhop_dir_deviation_max; +float autocvar_bot_ai_bunnyhop_downward_pitch_max; +float autocvar_bot_ai_bunnyhop_turn_angle_min; +float autocvar_bot_ai_bunnyhop_turn_angle_max; +float autocvar_bot_ai_bunnyhop_turn_angle_reduction; float autocvar_bot_ai_chooseweaponinterval; string autocvar_bot_ai_custom_weapon_priority_close; string autocvar_bot_ai_custom_weapon_priority_distances; @@ -31,6 +33,7 @@ string autocvar_bot_ai_custom_weapon_priority_mid; float autocvar_bot_ai_dangerdetectioninterval; float autocvar_bot_ai_dangerdetectionupdates; float autocvar_bot_ai_enemydetectioninterval; +float autocvar_bot_ai_enemydetectioninterval_stickingtoenemy; float autocvar_bot_ai_enemydetectionradius; float autocvar_bot_ai_friends_aware_pickup_radius; float autocvar_bot_ai_ignoregoal_timeout; diff --git a/qcsrc/server/bot/default/havocbot/havocbot.qc b/qcsrc/server/bot/default/havocbot/havocbot.qc index 477d1ec464..619d08dc5c 100644 --- a/qcsrc/server/bot/default/havocbot/havocbot.qc +++ b/qcsrc/server/bot/default/havocbot/havocbot.qc @@ -47,8 +47,12 @@ void havocbot_ai(entity this) } else { - if (!this.jumppadcount && !STAT(FROZEN, this)) + if (!this.jumppadcount && !STAT(FROZEN, this) + && !(this.goalcurrent_prev && (this.goalcurrent_prev.wpflags & WAYPOINTFLAG_JUMP))) + { + // find a new goal this.havocbot_role(this); // little too far down the rabbit hole + } } // if we don't have a goal and we're under water look for a waypoint near the "shore" and push it @@ -100,6 +104,8 @@ void havocbot_ai(entity this) { if (this.goalcurrent) navigation_clearroute(this); + this.enemy = NULL; + this.bot_aimtarg = NULL; return; } @@ -198,6 +204,63 @@ void havocbot_ai(entity this) } } +void havocbot_bunnyhop(entity this, vector dir) +{ + bool can_run = false; + if (!(this.aistatus & AI_STATUS_ATTACKING) && this.goalcurrent && !IS_PLAYER(this.goalcurrent) + && vdist(vec2(this.velocity), >=, autocvar_sv_maxspeed) && !(this.aistatus & AI_STATUS_DANGER_AHEAD) + && this.waterlevel <= WATERLEVEL_WETFEET && !IS_DUCKED(this) + && IS_ONGROUND(this) && !(this.goalcurrent_prev && (this.goalcurrent_prev.wpflags & WAYPOINTFLAG_JUMP))) + { + vector vel_angles = vectoangles(this.velocity); + vector deviation = vel_angles - vectoangles(dir); + while (deviation.y < -180) deviation.y = deviation.y + 360; + while (deviation.y > 180) deviation.y = deviation.y - 360; + if (fabs(deviation.y) < autocvar_bot_ai_bunnyhop_dir_deviation_max) + { + vector gco = get_closer_dest(this.goalcurrent, this.origin); + float vel = vlen(vec2(this.velocity)); + + // with the current physics, jump distance grows linearly with the speed + float jump_distance = 52.661 + 0.606 * vel; + jump_distance += this.origin.z - gco.z; // roughly take into account vertical distance too + if (vdist(vec2(gco - this.origin), >, max(0, jump_distance))) + can_run = true; + else if (!(this.goalcurrent.wpflags & WAYPOINTFLAG_JUMP) + && !(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT) + && this.goalstack01 && !wasfreed(this.goalstack01) && !(this.goalstack01.wpflags & WAYPOINTFLAG_JUMP) + && vdist(vec2(gco - this.goalstack01.origin), >, 70)) + { + vector gno = (this.goalstack01.absmin + this.goalstack01.absmax) * 0.5; + vector ang = vectoangles(gco - this.origin); + deviation = vectoangles(gno - gco) - vel_angles; + while (deviation.y < -180) deviation.y = deviation.y + 360; + while (deviation.y > 180) deviation.y = deviation.y - 360; + + float max_turn_angle = autocvar_bot_ai_bunnyhop_turn_angle_max; + max_turn_angle -= autocvar_bot_ai_bunnyhop_turn_angle_reduction * ((vel - autocvar_sv_maxspeed) / autocvar_sv_maxspeed); + if ((ang.x < 90 || ang.x > 360 - autocvar_bot_ai_bunnyhop_downward_pitch_max) + && fabs(deviation.y) < max(autocvar_bot_ai_bunnyhop_turn_angle_min, max_turn_angle)) + { + can_run = true; + } + } + } + } + + if (can_run) + { + PHYS_INPUT_BUTTON_JUMP(this) = true; + this.bot_jump_time = time; + this.aistatus |= AI_STATUS_RUNNING; + } + else + { + if (IS_ONGROUND(this) || this.waterlevel > WATERLEVEL_WETFEET) + this.aistatus &= ~AI_STATUS_RUNNING; + } +} + void havocbot_keyboard_movement(entity this, vector destorg) { if(time <= this.havocbot_keyboardtime) @@ -268,114 +331,6 @@ void havocbot_keyboard_movement(entity this, vector destorg) CS(this).movement = CS(this).movement + (keyboard - CS(this).movement) * blend; } -void havocbot_bunnyhop(entity this, vector dir) -{ - // Don't jump when attacking - if(this.aistatus & AI_STATUS_ATTACKING) - return; - - if (!this.goalcurrent || IS_PLAYER(this.goalcurrent)) - return; - - if((this.aistatus & AI_STATUS_RUNNING) && vdist(this.velocity, <, autocvar_sv_maxspeed * 0.75) - || (this.aistatus & AI_STATUS_DANGER_AHEAD)) - { - this.aistatus &= ~AI_STATUS_RUNNING; - PHYS_INPUT_BUTTON_JUMP(this) = false; - this.bot_canruntogoal = 0; - this.bot_timelastseengoal = 0; - return; - } - - if(this.waterlevel > WATERLEVEL_WETFEET || IS_DUCKED(this)) - { - this.aistatus &= ~AI_STATUS_RUNNING; - return; - } - - if(this.bot_lastseengoal != this.goalcurrent && !(this.aistatus & AI_STATUS_RUNNING)) - { - this.bot_canruntogoal = 0; - this.bot_timelastseengoal = 0; - } - - // Run only to visible goals - if(IS_ONGROUND(this)) - if(vdist(vec2(this.velocity), >=, autocvar_sv_maxspeed)) - if(checkpvs(this.origin + this.view_ofs, this.goalcurrent)) - { - this.bot_lastseengoal = this.goalcurrent; - - // seen it before - if(this.bot_timelastseengoal) - { - vector gco = get_closer_dest(this.goalcurrent, this.origin); - // for a period of time - if(time - this.bot_timelastseengoal > autocvar_bot_ai_bunnyhop_firstjumpdelay) - { - bool checkdistance = true; - - // don't run if it is too close - if(this.bot_canruntogoal==0) - { - if(vdist(this.origin - gco, >, autocvar_bot_ai_bunnyhop_startdistance)) - this.bot_canruntogoal = 1; - else - this.bot_canruntogoal = -1; - } - - if(this.bot_canruntogoal != 1) - return; - - if(this.aistatus & AI_STATUS_ROAMING) - if(this.goalcurrent.classname == "waypoint") - if (!(this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL)) - if(fabs(gco.z - this.origin.z) < this.maxs.z - this.mins.z) - if (this.goalstack01 && !wasfreed(this.goalstack01)) - if (!(this.goalstack01.wpflags & WAYPOINTFLAG_JUMP)) - { - vector gno = (this.goalstack01.absmin + this.goalstack01.absmax) * 0.5; - vector deviation = vectoangles(gno - this.origin) - vectoangles(gco - this.origin); - while (deviation.y < -180) deviation.y = deviation.y + 360; - while (deviation.y > 180) deviation.y = deviation.y - 360; - - if(fabs(deviation.y) < 20) - if(vlen2(this.origin - gco) < vlen2(this.origin - gno)) - if(fabs(gno.z - gco.z) < this.maxs.z - this.mins.z) - { - if(vdist(gco - gno, >, autocvar_bot_ai_bunnyhop_startdistance)) - if(checkpvs(this.origin + this.view_ofs, this.goalstack01)) - { - checkdistance = false; - } - } - } - - if(checkdistance) - { - this.aistatus &= ~AI_STATUS_RUNNING; - // increase stop distance in case the goal is on a slope or a lower platform - if(vdist(this.origin - gco, >, autocvar_bot_ai_bunnyhop_stopdistance + (this.origin.z - gco.z))) - PHYS_INPUT_BUTTON_JUMP(this) = true; - } - else - { - this.aistatus |= AI_STATUS_RUNNING; - PHYS_INPUT_BUTTON_JUMP(this) = true; - } - } - } - else - { - this.bot_timelastseengoal = time; - } - } - else - { - this.bot_timelastseengoal = 0; - } -} - // return true when bot isn't getting closer to the current goal bool havocbot_checkgoaldistance(entity this, vector gco) { @@ -436,8 +391,6 @@ void havocbot_movetogoal(entity this) vector diff; vector dir; vector flatdir; - vector evadeobstacle; - vector evadelava; float dodge_enemy_factor = 1; float maxspeed; //float dist; @@ -611,9 +564,28 @@ void havocbot_movetogoal(entity this) * ((this.strength_finished > time) ? autocvar_g_balance_powerup_strength_selfdamage : 1) \ * ((this.invincible_finished > time) ? autocvar_g_balance_powerup_invincible_takedamage : 1) - tracebox(this.origin, this.mins, this.maxs, this.origin + '0 0 -65536', MOVE_NOMONSTERS, this); - if(tracebox_hits_trigger_hurt(this.origin, this.mins, this.maxs, trace_endpos )) - if(this.items & IT_JETPACK) + // save some CPU cycles by checking trigger_hurt after checking + // that something can be done to evade it (cheaper checks) + int action_for_trigger_hurt = 0; + if (this.items & IT_JETPACK) + action_for_trigger_hurt = 1; + else if (!this.jumppadcount && !waypoint_is_hardwiredlink(this.goalcurrent_prev, this.goalcurrent) + && !(this.goalcurrent_prev && this.goalcurrent_prev.wpflags & WAYPOINTFLAG_JUMP) + && GetResource(this, RES_HEALTH) + GetResource(this, RES_ARMOR) > ROCKETJUMP_DAMAGE()) + { + action_for_trigger_hurt = 2; + } + else if (!this.goalcurrent) + action_for_trigger_hurt = 3; + + if (action_for_trigger_hurt) + { + tracebox(this.origin, this.mins, this.maxs, this.origin + '0 0 -65536', MOVE_NOMONSTERS, this); + if(!tracebox_hits_trigger_hurt(this.origin, this.mins, this.maxs, trace_endpos)) + action_for_trigger_hurt = 0; + } + + if(action_for_trigger_hurt == 1) // jetpack { tracebox(this.origin, this.mins, this.maxs, this.origin + '0 0 65536', MOVE_NOMONSTERS, this); if(tracebox_hits_trigger_hurt(this.origin, this.mins, this.maxs, trace_endpos + '0 0 1' )) @@ -651,9 +623,7 @@ void havocbot_movetogoal(entity this) return; } - else if(!this.jumppadcount && !waypoint_is_hardwiredlink(this.goalcurrent_prev, this.goalcurrent) - && !(this.goalcurrent_prev && this.goalcurrent_prev.wpflags & WAYPOINTFLAG_JUMP) - && GetResource(this, RES_HEALTH) + GetResource(this, RES_ARMOR) > ROCKETJUMP_DAMAGE()) + else if(action_for_trigger_hurt == 2) // rocketjump { if(this.velocity.z < 0) { @@ -687,11 +657,10 @@ void havocbot_movetogoal(entity this) } } } - else + else if(action_for_trigger_hurt == 3) // no goal { // If there is no goal try to move forward - if(this.goalcurrent==NULL) - CS(this).movement_x = maxspeed; + CS(this).movement_x = maxspeed; } } @@ -703,8 +672,6 @@ void havocbot_movetogoal(entity this) dir.z = 1; else if(this.velocity.z >= 0 && !(this.waterlevel == WATERLEVEL_WETFEET && this.watertype == CONTENT_WATER)) PHYS_INPUT_BUTTON_JUMP(this) = true; - else - PHYS_INPUT_BUTTON_JUMP(this) = false; makevectors(this.v_angle.y * '0 1 0'); vector v = dir * maxspeed; CS(this).movement.x = v * v_forward; @@ -910,12 +877,13 @@ void havocbot_movetogoal(entity this) dir = normalize(diff); flatdir = (diff.z == 0) ? dir : normalize(vec2(diff)); + bool danger_detected = false; + vector do_break = '0 0 0'; + //if (this.bot_dodgevector_time < time) { //this.bot_dodgevector_time = time + cvar("bot_ai_dodgeupdateinterval"); //this.bot_dodgevector_jumpbutton = 1; - evadeobstacle = '0 0 0'; - evadelava = '0 0 0'; this.aistatus &= ~AI_STATUS_DANGER_AHEAD; makevectors(this.v_angle.y * '0 1 0'); @@ -938,7 +906,6 @@ void havocbot_movetogoal(entity this) } else { - PHYS_INPUT_BUTTON_JUMP(this) = false; if (destorg.z > this.origin.z) dir = flatdir; } @@ -982,8 +949,7 @@ void havocbot_movetogoal(entity this) ) { PHYS_INPUT_BUTTON_JUMP(this) = true; - // avoid changing route while bot is jumping a gap - navigation_goalrating_timeout_extend_if_needed(this, 1.5); + this.bot_jump_time = time; } } else if (!this.goalstack01 || (this.goalcurrent.wpflags & (WAYPOINTFLAG_TELEPORT | WAYPOINTFLAG_LADDER))) @@ -1059,13 +1025,19 @@ void havocbot_movetogoal(entity this) vector jump_height = (IS_ONGROUND(this)) ? stepheightvec + jumpheight_vec : jumpstepheightvec; tracebox(this.origin + jump_height, this.mins, this.maxs, actual_destorg + jump_height, false, this); if (trace_fraction > s) + { PHYS_INPUT_BUTTON_JUMP(this) = true; + this.bot_jump_time = time; + } else { jump_height = stepheightvec + jumpheight_vec / 2; tracebox(this.origin + jump_height, this.mins, this.maxs, actual_destorg + jump_height, false, this); if (trace_fraction > s) + { PHYS_INPUT_BUTTON_JUMP(this) = true; + this.bot_jump_time = time; + } } } } @@ -1099,15 +1071,15 @@ void havocbot_movetogoal(entity this) this.goalcurrent_distance_time = -time; // mark second try } - // Check for water/slime/lava and dangerous edges - // (only when the bot is on the ground or jumping intentionally) - if (skill + this.bot_moveskill <= 3 && time > this.bot_stop_moving_timeout && current_speed > maxspeed * 0.9 && fabs(deviation.y) > 70) { this.bot_stop_moving_timeout = time + 0.4 + random() * 0.2; } + // Check for water/slime/lava and dangerous edges + // (only when the bot is on the ground or jumping intentionally) + offset = (vdist(this.velocity, >, 32) ? this.velocity * 0.2 : flatdir * 32); vector dst_ahead = this.origin + this.view_ofs + offset; vector dst_down = dst_ahead - '0 0 3000'; @@ -1126,37 +1098,42 @@ void havocbot_movetogoal(entity this) //te_lightning2(NULL, dst_ahead, trace_endpos); // Draw "downwards" look if(trace_endpos.z < this.origin.z + this.mins.z) { - s = pointcontents(trace_endpos + '0 0 1'); - if (s != CONTENT_SOLID) - if (s == CONTENT_LAVA || s == CONTENT_SLIME) - evadelava = normalize(this.velocity) * -1; - else if (s == CONTENT_SKY) - evadeobstacle = normalize(this.velocity) * -1; - else if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos)) + if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY) + danger_detected = true; + else if (trace_endpos.z < min(this.origin.z + this.mins.z, this.goalcurrent.origin.z) - 100) + danger_detected = true; + else { - // the traceline check isn't enough but is good as optimization, - // when not true (most of the time) this tracebox call is avoided - tracebox(dst_ahead, this.mins, this.maxs, dst_down, true, this); - if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos)) + s = pointcontents(trace_endpos + '0 0 1'); + if (s != CONTENT_SOLID) { - if (destorg.z > this.origin.z + jumpstepheightvec.z) + if (s == CONTENT_LAVA || s == CONTENT_SLIME) + danger_detected = true; + else if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos)) { - // the goal is probably on an upper platform, assume bot can't get there - unreachable = true; + // the traceline check isn't enough but is good as optimization, + // when not true (most of the time) this tracebox call is avoided + tracebox(dst_ahead, this.mins, this.maxs, dst_down, true, this); + if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos)) + { + if (destorg.z > this.origin.z + jumpstepheightvec.z) + { + // the goal is probably on an upper platform, assume bot can't get there + unreachable = true; + } + else + danger_detected = true; + } } - else - evadelava = normalize(this.velocity) * -1; } } } } dir = flatdir; - evadeobstacle.z = 0; - evadelava.z = 0; makevectors(this.v_angle.y * '0 1 0'); - if(evadeobstacle || evadelava || (s == CONTENT_WATER)) + if (danger_detected || (s == CONTENT_WATER)) { this.aistatus |= AI_STATUS_DANGER_AHEAD; if(IS_PLAYER(this.goalcurrent)) @@ -1171,7 +1148,7 @@ void havocbot_movetogoal(entity this) // tracebox wouldn't work when bot is still on the ledge traceline(this.origin, this.origin - '0 0 200', true, this); if (this.origin.z - trace_endpos.z > 120) - evadeobstacle = normalize(this.velocity) * -1; + do_break = normalize(this.velocity) * -1; } if(unreachable) @@ -1186,8 +1163,6 @@ void havocbot_movetogoal(entity this) dodge = havocbot_dodge(this); if (dodge) dodge *= bound(0, 0.5 + (skill + this.bot_dodgeskill) * 0.1, 1); - dodge += evadeobstacle + evadelava; - evadelava = evadelava * bound(1,3-(skill+this.bot_dodgeskill),3); //Noobs fear lava a lot and take more distance from it if (this.enemy) { traceline(this.origin, (this.enemy.absmin + this.enemy.absmax) * 0.5, true, NULL); @@ -1240,10 +1215,27 @@ void havocbot_movetogoal(entity this) bot_aimdir(this, dir, 0); } + vector evadedanger = '0 0 0'; if (!ladder_zdir) { dir *= dodge_enemy_factor; - dir = normalize(dir + dodge); + if (danger_detected && vdist(this.velocity, >, maxspeed * 0.8) && this.goalcurrent_prev + && this.goalcurrent.classname == "waypoint") + { + vector p = this.origin + this.velocity * 0.2; + vector evadedanger = point_line_vec(p, vec2(this.goalcurrent_prev.origin) + eZ * p.z, + vec2(destorg - this.goalcurrent_prev.origin)); + if (vdist(evadedanger, >, 20)) + { + if (vdist(evadedanger, >, 40)) + do_break = normalize(this.velocity) * -1; + evadedanger = normalize(evadedanger); + evadedanger *= bound(1, 3 - (skill + this.bot_dodgeskill), 3); // Noobs fear dangers a lot and take more distance from them + } + else + evadedanger = '0 0 0'; + } + dir = normalize(dir + dodge + do_break + evadedanger); } makevectors(this.v_angle); @@ -1259,7 +1251,7 @@ void havocbot_movetogoal(entity this) havocbot_keyboard_movement(this, destorg); // Bunnyhop! - if (!bunnyhop_forbidden && skill + this.bot_moveskill >= autocvar_bot_ai_bunnyhop_skilloffset) + if (!bunnyhop_forbidden && !evadedanger && !do_break && skill + this.bot_moveskill >= autocvar_bot_ai_bunnyhop_skilloffset) havocbot_bunnyhop(this, dir); if (dir * v_up >= autocvar_sv_jumpvelocity * 0.5 && IS_ONGROUND(this)) @@ -1300,6 +1292,7 @@ void havocbot_chooseenemy(entity this) this.enemy = NULL; return; } + if (this.enemy) { if (!bot_shouldattack(this, this.enemy)) @@ -1308,26 +1301,21 @@ void havocbot_chooseenemy(entity this) this.enemy = NULL; this.havocbot_chooseenemy_finished = time; } - else if (this.havocbot_stickenemy) + else if (this.havocbot_stickenemy_time && time < this.havocbot_stickenemy_time) { // tracking last chosen enemy - // if enemy is visible - // and not really really far away - // and we're not severely injured - // then keep tracking for a half second into the future - traceline(this.origin+this.view_ofs, ( this.enemy.absmin + this.enemy.absmax ) * 0.5,false,NULL); + vector targ_pos = (this.enemy.absmin + this.enemy.absmax) * 0.5; + traceline(this.origin + this.view_ofs, targ_pos, false, NULL); if (trace_ent == this.enemy || trace_fraction == 1) - if (vdist(((this.enemy.absmin + this.enemy.absmax) * 0.5) - this.origin, <, 1000)) - if (GetResource(this, RES_HEALTH) > 30) + if (vdist(targ_pos - this.origin, <, 1000)) { // remain tracking him for a shot while (case he went after a small corner or pilar this.havocbot_chooseenemy_finished = time + 0.5; return; } - // enemy isn't visible, or is far away, or we're injured severely - // so stop preferring this enemy - // (it will still take a half second until a new one is chosen) - this.havocbot_stickenemy = 0; + + // stop preferring this enemy + this.havocbot_stickenemy_time = 0; } } if (time < this.havocbot_chooseenemy_finished) @@ -1335,7 +1323,7 @@ void havocbot_chooseenemy(entity this) this.havocbot_chooseenemy_finished = time + autocvar_bot_ai_enemydetectioninterval; vector eye = this.origin + this.view_ofs; entity best = NULL; - float bestrating = 100000000; + float bestrating = autocvar_bot_ai_enemydetectionradius ** 2; // Backup hit flags int hf = this.dphitcontentsmask; @@ -1366,9 +1354,7 @@ LABEL(scan_targets) vector v = (it.absmin + it.absmax) * 0.5; float rating = vlen2(v - eye); - if (vdist(v - eye, <, autocvar_bot_ai_enemydetectionradius)) - if (bestrating > rating) - if (bot_shouldattack(this, it)) + if (rating < bestrating && bot_shouldattack(this, it)) { traceline(eye, v, true, this); if (trace_ent == it || trace_fraction >= 1) @@ -1383,7 +1369,7 @@ LABEL(scan_targets) { scan_secondary_targets = true; // restart the loop - bestrating = 100000000; + bestrating = autocvar_bot_ai_enemydetectionradius ** 2; goto scan_targets; } @@ -1404,9 +1390,9 @@ LABEL(scan_targets) this.dphitcontentsmask = hf; this.enemy = best; - this.havocbot_stickenemy = true; + this.havocbot_stickenemy_time = time + autocvar_bot_ai_enemydetectioninterval_stickingtoenemy; if(best && best.classname == "misc_breakablemodel") - this.havocbot_stickenemy = false; + this.havocbot_stickenemy_time = 0; } float havocbot_chooseweapon_checkreload(entity this, .entity weaponentity, int new_weapon) diff --git a/qcsrc/server/bot/default/havocbot/havocbot.qh b/qcsrc/server/bot/default/havocbot/havocbot.qh index d1a36a1172..02c0f3e7d7 100644 --- a/qcsrc/server/bot/default/havocbot/havocbot.qh +++ b/qcsrc/server/bot/default/havocbot/havocbot.qh @@ -12,15 +12,13 @@ .float havocbot_keyboardtime; .float havocbot_ducktime; -.float bot_timelastseengoal; -.float bot_canruntogoal; .float bot_chooseweapontime; .float rocketjumptime; .float nextaim; .float havocbot_personal_waypoint_searchtime; .float havocbot_personal_waypoint_failcounter; .float havocbot_chooseenemy_finished; -.float havocbot_stickenemy; +.float havocbot_stickenemy_time; .float havocbot_role_timeout; .float bot_stop_moving_timeout; diff --git a/qcsrc/server/bot/default/navigation.qc b/qcsrc/server/bot/default/navigation.qc index 5457211d84..1f5377bb38 100644 --- a/qcsrc/server/bot/default/navigation.qc +++ b/qcsrc/server/bot/default/navigation.qc @@ -47,6 +47,7 @@ bool navigation_goalrating_timeout(entity this) return this.bot_strategytime < time; } +ERASEABLE void navigation_goalrating_timeout_extend_if_needed(entity this, float seconds) { this.bot_strategytime = max(this.bot_strategytime, time + seconds); @@ -943,10 +944,7 @@ entity navigation_findnearestwaypoint_withdist_except(entity ent, float walkfrom if(boxesoverlap(pm1, pm2, it.absmin, it.absmax)) { if(walkfromwp && !ent.navigation_dynamicgoal) - { waypoint_clearlinks(ent); // initialize wpXXmincost fields - navigation_item_addlink(it, ent); - } return it; } }); @@ -965,24 +963,6 @@ entity navigation_findnearestwaypoint_withdist_except(entity ent, float walkfrom org.z = ent.origin.z + ent.mins.z - PL_MIN_CONST.z; // player height } - if(!autocvar_g_waypointeditor && walkfromwp && !ent.navigation_dynamicgoal) - { - waypoint_clearlinks(ent); // initialize wpXXmincost fields - IL_EACH(g_waypoints, it != ent, - { - if (walkfromwp && (it.wpflags & WPFLAGMASK_NORELINK)) - continue; - - set_tracewalk_dest(ent, it.origin, false); - if (vdist(tracewalk_dest - it.origin, <, 1050) - && tracewalk(ent, it.origin, PL_MIN_CONST, PL_MAX_CONST, - tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode)) - { - navigation_item_addlink(it, ent); - } - }); - } - // box check failed, try walk IL_EACH(g_waypoints, it != ent, { @@ -1507,7 +1487,7 @@ bool navigation_routetogoal(entity this, entity e, vector startposition) if(e == NULL) return false; - if(nearest_wp && nearest_wp.enemy) + if(nearest_wp && nearest_wp.enemy && !(nearest_wp.enemy.wpflags & WPFLAGMASK_NORELINK)) { // often path can be optimized by not adding the nearest waypoint if (this.goalentity.navigation_dynamicgoal || autocvar_g_waypointeditor) @@ -1529,8 +1509,35 @@ bool navigation_routetogoal(entity this, entity e, vector startposition) } } } - else if(navigation_item_islinked(nearest_wp.enemy, this.goalentity)) - e = nearest_wp.enemy; + else + { + // NOTE unlike waypoints, items hold incoming links + navigation_item_initlinks_ifneeded(this.goalentity); + int link_num = navigation_item_getlinknum(this.goalentity, nearest_wp.enemy); + if (link_num >= 0) + { + if (navigation_item_iswalkablelink(this.goalentity, link_num)) + e = nearest_wp.enemy; + } + else // untested link + { + entity wp = nearest_wp.enemy; + entity goal = this.goalentity; + bool walkable = false; + if (checkpvs(wp.origin, goal)) + { + set_tracewalk_dest(goal, wp.origin, false); + if (vdist(tracewalk_dest - wp.origin, <, 1050) + && tracewalk(goal, wp.origin, PL_MIN_CONST, PL_MAX_CONST, + tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode)) + { + walkable = true; + e = nearest_wp.enemy; + } + } + navigation_item_add_link(wp, goal, walkable); + } + } } for (;;) @@ -1710,11 +1717,16 @@ int navigation_poptouchedgoals(entity this) } // Loose goal touching check when running - if(this.aistatus & AI_STATUS_RUNNING) - if(this.goalcurrent.classname=="waypoint") - if(vdist(vec2(this.velocity), >=, autocvar_sv_maxspeed)) // if -really- running - { - if(vdist(this.origin - this.goalcurrent.origin, <, 150)) + // check goalstack01 to make sure waypoint isn't the final goal + if(this.aistatus & AI_STATUS_RUNNING && this.goalcurrent.classname == "waypoint" && !(this.goalcurrent.wpflags & WAYPOINTFLAG_JUMP) + && this.goalstack01 && !wasfreed(this.goalstack01) && vdist(vec2(this.velocity), >=, autocvar_sv_maxspeed)) + { + vector gco = this.goalcurrent.origin; + float min_dist = BOT_BUNNYHOP_WP_DETECTION_RANGE; + // also detect waypoints when bot is way above them but with a narrower horizontal range + // so to increase chances bot ends up in the standard range (optimizes nearest waypoint finding) + if(vdist(this.origin - gco, <, min_dist) + || (vdist(vec2(this.origin - gco), <, min_dist * 0.5) && vdist(this.origin - eZ * 1.5 * min_dist - gco, <, min_dist))) { traceline(this.origin + this.view_ofs , this.goalcurrent.origin, true, NULL); if(trace_fraction==1) @@ -1742,7 +1754,7 @@ int navigation_poptouchedgoals(entity this) if(this.goalcurrent.classname == "waypoint" && !this.goalcurrent.wpisbox) { gc_min = this.goalcurrent.origin - '1 1 1' * 12; - gc_max = this.goalcurrent.origin + '1 1 1' * 12; + gc_max = this.goalcurrent.origin + '1 1 1' * 12 + eZ * (jumpheight_vec.z + STAT(PL_MIN, this).z); } if (time < this.ladder_time) { @@ -1778,7 +1790,8 @@ entity navigation_get_really_close_waypoint(entity this) wp = this.goalcurrent_prev; if(!wp) return NULL; - if(wp != this.goalcurrent_prev && vdist(wp.origin - this.origin, >, 50)) + float min_dist = ((this.aistatus & AI_STATUS_RUNNING) ? BOT_BUNNYHOP_WP_DETECTION_RANGE : 50); + if(wp != this.goalcurrent_prev && vdist(wp.origin - this.origin, >, min_dist)) { wp = this.goalcurrent_prev; if(!wp) @@ -1790,12 +1803,12 @@ entity navigation_get_really_close_waypoint(entity this) if(!wp) return NULL; } - if(vdist(wp.origin - this.origin, >, 50)) + if(vdist(wp.origin - this.origin, >, min_dist)) { wp = NULL; IL_EACH(g_waypoints, !(it.wpflags & (WAYPOINTFLAG_TELEPORT | WAYPOINTFLAG_JUMP)), { - if(vdist(it.origin - this.origin, <, 50)) + if(vdist(it.origin - this.origin, <, min_dist)) { wp = it; break; diff --git a/qcsrc/server/bot/default/navigation.qh b/qcsrc/server/bot/default/navigation.qh index 026d326b9e..07eacc1860 100644 --- a/qcsrc/server/bot/default/navigation.qh +++ b/qcsrc/server/bot/default/navigation.qh @@ -41,6 +41,9 @@ entity navigation_bestgoal; /* // item it is linked from waypoint it.wpXX (INCOMING link) // links are sorted by their cost (wpXXmincost) +// one of these links is added in game every time a bot heads to an item +// even links that are not walkable are added (marked with a high cost) +// so that bots next time know if they can walk it or not saving a tracewalk call .entity wp00, wp01, wp02, wp03, wp04, wp05, wp06, wp07, wp08, wp09, wp10, wp11, wp12, wp13, wp14, wp15; .entity wp16, wp17, wp18, wp19, wp20, wp21, wp22, wp23, wp24, wp25, wp26, wp27, wp28, wp29, wp30, wp31; @@ -50,9 +53,12 @@ entity navigation_bestgoal; .float wp24mincost, wp25mincost, wp26mincost, wp27mincost, wp28mincost, wp29mincost, wp30mincost, wp31mincost; */ -#define navigation_item_islinked(from_wp, to_item) waypoint_islinked(to_item, from_wp) -#define navigation_item_addlink(from_wp, to_item) \ - waypoint_addlink_customcost(to_item, from_wp, waypoint_getlinkcost(from_wp, to_item)) +#define navigation_item_initlinks_ifneeded(e) MACRO_BEGIN if (!e.wp00) waypoint_clearlinks(e); MACRO_END // initialize wpXXmincost fields +#define navigation_item_getlinknum(to_item, from_wp) waypoint_getlinknum(to_item, from_wp) +#define navigation_item_iswalkablelink(to_item, from_wp) (waypoint_get_assigned_link_cost(to_item, from_wp) < 999) + +#define navigation_item_add_link(from_wp, to_item, walkable) \ + waypoint_addlink_customcost(to_item, from_wp, (walkable ? waypoint_getlinkcost(from_wp, to_item) : 999)) #define TELEPORT_USED(pl, tele_wp) \ boxesoverlap(tele_wp.absmin, tele_wp.absmax, pl.lastteleport_origin + STAT(PL_MIN, pl), pl.lastteleport_origin + STAT(PL_MAX, pl)) @@ -82,6 +88,8 @@ entity bot_waypoint_queue_goal; // Head of the temporary list of goals entity bot_waypoint_queue_bestgoal; float bot_waypoint_queue_bestgoalrating; +const float BOT_BUNNYHOP_WP_DETECTION_RANGE = 100; + .entity bot_basewaypoint; .bool navigation_dynamicgoal; void navigation_dynamicgoal_init(entity this, bool initially_static); diff --git a/qcsrc/server/bot/default/waypoints.qc b/qcsrc/server/bot/default/waypoints.qc index c09d8f81d4..da407cbbc8 100644 --- a/qcsrc/server/bot/default/waypoints.qc +++ b/qcsrc/server/bot/default/waypoints.qc @@ -1572,6 +1572,46 @@ void waypoint_load_hardwiredlinks() LOG_TRACE("loaded ", ftos(c), " waypoint links from maps/", mapname, ".waypoints.hardwired"); } +float waypoint_get_assigned_link_cost(entity w, float i) +{ + switch(i) + { + case 0: return w.wp00mincost; + case 1: return w.wp01mincost; + case 2: return w.wp02mincost; + case 3: return w.wp03mincost; + case 4: return w.wp04mincost; + case 5: return w.wp05mincost; + case 6: return w.wp06mincost; + case 7: return w.wp07mincost; + case 8: return w.wp08mincost; + case 9: return w.wp09mincost; + case 10: return w.wp10mincost; + case 11: return w.wp11mincost; + case 12: return w.wp12mincost; + case 13: return w.wp13mincost; + case 14: return w.wp14mincost; + case 15: return w.wp15mincost; + case 16: return w.wp16mincost; + case 17: return w.wp17mincost; + case 18: return w.wp18mincost; + case 19: return w.wp19mincost; + case 20: return w.wp20mincost; + case 21: return w.wp21mincost; + case 22: return w.wp22mincost; + case 23: return w.wp23mincost; + case 24: return w.wp24mincost; + case 25: return w.wp25mincost; + case 26: return w.wp26mincost; + case 27: return w.wp27mincost; + case 28: return w.wp28mincost; + case 29: return w.wp29mincost; + case 30: return w.wp30mincost; + case 31: return w.wp31mincost; + default: return -1; + } +} + entity waypoint_get_link(entity w, float i) { switch(i) diff --git a/qcsrc/server/bot/default/waypoints.qh b/qcsrc/server/bot/default/waypoints.qh index 25356446a4..e9aa1ee12a 100644 --- a/qcsrc/server/bot/default/waypoints.qh +++ b/qcsrc/server/bot/default/waypoints.qh @@ -67,6 +67,8 @@ void waypoint_think(entity this); void waypoint_clearlinks(entity wp); void waypoint_schedulerelink(entity wp); +float waypoint_get_assigned_link_cost(entity w, float i); + float waypoint_getlinkcost(entity from, entity to); float waypoint_gettravelcost(vector from, vector to, entity from_ent, entity to_ent); float waypoint_getlinearcost(float dist); diff --git a/qcsrc/server/client.qc b/qcsrc/server/client.qc index dcddced84f..9b6b6c4c1f 100644 --- a/qcsrc/server/client.qc +++ b/qcsrc/server/client.qc @@ -1537,8 +1537,10 @@ float CalcRot(float current, float stable, float rotfactor, float rotframetime) return max(stable, current + (stable - current) * rotfactor * rotframetime); } -float CalcRotRegen(float current, float regenstable, float regenfactor, float regenlinear, float regenframetime, float rotstable, float rotfactor, float rotlinear, float rotframetime, float limit) +void RotRegen(entity this, int res, float regenstable, float regenfactor, float regenlinear, float regenframetime, float rotstable, float rotfactor, float rotlinear, float rotframetime, float limit_mod) { + float old = GetResource(this, res); + float current = old; if(current > rotstable) { if(rotframetime > 0) @@ -1556,10 +1558,12 @@ float CalcRotRegen(float current, float regenstable, float regenfactor, float re } } + float limit = GetResourceLimit(this, res) * limit_mod; if(current > limit) current = limit; - return current; + if (current != old) + SetResource(this, res, current); } void player_regen(entity this) @@ -1589,23 +1593,16 @@ void player_regen(entity this) if(!mutator_returnvalue) if(!STAT(FROZEN, this)) { - float mina, maxa, limith, limita; - maxa = autocvar_g_balance_armor_rotstable; - mina = autocvar_g_balance_armor_regenstable; - limith = GetResourceLimit(this, RES_HEALTH); - limita = GetResourceLimit(this, RES_ARMOR); + float maxa = autocvar_g_balance_armor_rotstable; + float mina = autocvar_g_balance_armor_regenstable; - regen_health_rotstable = regen_health_rotstable * max_mod; - regen_health_stable = regen_health_stable * max_mod; - limith = limith * limit_mod; - limita = limita * limit_mod; + RotRegen(this, RES_ARMOR, mina, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, + regen_mod * frametime * (time > this.pauseregen_finished), maxa, autocvar_g_balance_armor_rot, autocvar_g_balance_armor_rotlinear, + rot_mod * frametime * (time > this.pauserotarmor_finished), limit_mod); - SetResource(this, RES_ARMOR, CalcRotRegen(GetResource(this, RES_ARMOR), mina, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, - regen_mod * frametime * (time > this.pauseregen_finished), maxa, autocvar_g_balance_armor_rot, autocvar_g_balance_armor_rotlinear, - rot_mod * frametime * (time > this.pauserotarmor_finished), limita)); - SetResource(this, RES_HEALTH, CalcRotRegen(GetResource(this, RES_HEALTH), regen_health_stable, regen_health, regen_health_linear, - regen_mod * frametime * (time > this.pauseregen_finished), regen_health_rotstable, regen_health_rot, regen_health_rotlinear, - rot_mod * frametime * (time > this.pauserothealth_finished), limith)); + RotRegen(this, RES_HEALTH, regen_health_stable * max_mod, regen_health, regen_health_linear, + regen_mod * frametime * (time > this.pauseregen_finished), regen_health_rotstable * max_mod, regen_health_rot, regen_health_rotlinear, + rot_mod * frametime * (time > this.pauserothealth_finished), limit_mod); } // if player rotted to death... die! @@ -1620,15 +1617,12 @@ void player_regen(entity this) if (!(this.items & IT_UNLIMITED_AMMO)) { - float minf, maxf, limitf; + float maxf = autocvar_g_balance_fuel_rotstable; + float minf = autocvar_g_balance_fuel_regenstable; - maxf = autocvar_g_balance_fuel_rotstable; - minf = autocvar_g_balance_fuel_regenstable; - limitf = GetResourceLimit(this, RES_FUEL); - - SetResource(this, RES_FUEL, CalcRotRegen(GetResource(this, RES_FUEL), minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, - frametime * (time > this.pauseregen_finished) * ((this.items & ITEM_JetpackRegen.m_itemid) != 0), - maxf, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, frametime * (time > this.pauserotfuel_finished), limitf)); + RotRegen(this, RES_FUEL, minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, + frametime * (time > this.pauseregen_finished) * ((this.items & ITEM_JetpackRegen.m_itemid) != 0), + maxf, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, frametime * (time > this.pauserotfuel_finished), 1); } } diff --git a/qcsrc/server/client.qh b/qcsrc/server/client.qh index b20a8af93c..20148933cb 100644 --- a/qcsrc/server/client.qh +++ b/qcsrc/server/client.qh @@ -141,6 +141,7 @@ CLASS(Client, Object) ATTRIB(Client, autoswitch, bool, this.autoswitch); ATTRIB(Client, cvar_cl_casings, bool, this.cvar_cl_casings); ATTRIB(Client, cvar_cl_dodging_timeout, float, this.cvar_cl_dodging_timeout); + ATTRIB(Client, cvar_cl_dodging, float, this.cvar_cl_dodging); ATTRIB(Client, cvar_cl_multijump, bool, this.cvar_cl_multijump); ATTRIB(Client, cvar_cl_accuracy_data_share, bool, this.cvar_cl_accuracy_data_share); ATTRIB(Client, cvar_cl_accuracy_data_receive, bool, this.cvar_cl_accuracy_data_receive); @@ -265,7 +266,7 @@ void DebugPrintToChatTeam(int team_num, string text); void play_countdown(entity this, float finished, Sound samp); -float CalcRotRegen(float current, float regenstable, float regenfactor, float regenlinear, float regenframetime, float rotstable, float rotfactor, float rotlinear, float rotframetime, float limit); +void RotRegen(entity this, float current, float regenstable, float regenfactor, float regenlinear, float regenframetime, float rotstable, float rotfactor, float rotlinear, float rotframetime, float limit_mod); bool Spectate(entity this, entity pl); diff --git a/qcsrc/server/weapons/weaponsystem.qc b/qcsrc/server/weapons/weaponsystem.qc index 59812425d6..5f4f6f8332 100644 --- a/qcsrc/server/weapons/weaponsystem.qc +++ b/qcsrc/server/weapons/weaponsystem.qc @@ -366,6 +366,9 @@ void weapon_thinkf(entity actor, .entity weaponentity, WFRAME fr, float t, void( bool restartanim; if (fr == WFRAME_DONTCHANGE) { + // this can happen when the weapon entity is newly spawned, since it has a clear state and no previous weapon frame + if (this.wframe == WFRAME_DONTCHANGE) + this.wframe = WFRAME_IDLE; fr = this.wframe; restartanim = false; } diff --git a/xonotic-client.cfg b/xonotic-client.cfg index 29a49329a8..f1335c9d29 100644 --- a/xonotic-client.cfg +++ b/xonotic-client.cfg @@ -202,7 +202,7 @@ seta cl_hitsound_nom_damage 25 "damage amount at which hitsound bases pitch off" seta cl_eventchase_spectated_change 0 "camera goes into 3rd person mode for a moment when changing spectated player" seta cl_eventchase_spectated_change_time 1 "how much time the effect lasts when changing spectated player" -seta cl_eventchase_death 1 "camera goes into 3rd person mode when the player is dead; set to 2 to active the effect only when the corpse doesn't move anymore" +seta cl_eventchase_death 2 "camera goes into 3rd person mode when the player is dead; set to 2 to active the effect only when the corpse doesn't move anymore" seta cl_eventchase_frozen 0 "camera goes into 3rd person mode when the player is frozen" seta cl_eventchase_nexball 1 "camera goes into 3rd person mode when in nexball game-mode" seta cl_eventchase_distance 140 "final camera distance" diff --git a/xonotic-server.cfg b/xonotic-server.cfg index 843a83d3d6..0edaf7ea4c 100644 --- a/xonotic-server.cfg +++ b/xonotic-server.cfg @@ -126,7 +126,8 @@ set bot_typefrag 0 "Allow bots to shoot players while they're typing" set bot_ai_thinkinterval 0.05 "Frame rate at which bots update their navigation and aiming, scales by skill" set bot_ai_strategyinterval 7 "How often a new objective is chosen" set bot_ai_strategyinterval_movingtarget 5.5 "How often a new objective is chosen when current objective can move" -set bot_ai_enemydetectioninterval 2 "How often bots pick a new target" +set bot_ai_enemydetectioninterval 2 "How often bots try to pick a new target if no suitable target is found" +set bot_ai_enemydetectioninterval_stickingtoenemy 4 "How often bots try to pick a new target while targetting an enemy" set bot_ai_enemydetectionradius 10000 "How far bots can see enemies" set bot_ai_dodgeupdateinterval 0.2 "How often scan for items to dodge. Currently not in use." set bot_ai_chooseweaponinterval 0.5 "How often the best weapon according to the situation will be chosen" @@ -152,9 +153,11 @@ set bot_ai_weapon_combo_threshold 0.4 "Try to make a combo N seconds after the l set bot_ai_friends_aware_pickup_radius "500" "Bots will not pickup items if a team mate is this distance near the item" set bot_ai_ignoregoal_timeout 3 "Ignore goals making bots to get stuck in front of a wall for N seconds" set bot_ai_bunnyhop_skilloffset 7 "Bots with skill equal or greater than this value will perform the \"bunnyhop\" technique" -set bot_ai_bunnyhop_startdistance 200 "Run to goals located further than this distance" -set bot_ai_bunnyhop_stopdistance 300 "Stop jumping after reaching this distance to the goal" -set bot_ai_bunnyhop_firstjumpdelay 0.2 "Start running to the goal only if it was seen for more than N seconds" +set bot_ai_bunnyhop_dir_deviation_max 20 "bunnyhop if speed - direction deviation is <= this amount" +set bot_ai_bunnyhop_downward_pitch_max 30 "bunnyhop if downard pitch towards the next waypoint is <= this amount" +set bot_ai_bunnyhop_turn_angle_max 80 "bunnyhop if next turn angle is <= this amount at walk speed (sv_maxspeed)" +set bot_ai_bunnyhop_turn_angle_min 4 "bunnyhop regardless of speed if next turn angle is <= this amount" +set bot_ai_bunnyhop_turn_angle_reduction 40 "linearly reduce max turn angle by this amount when speed increases by sv_maxspeed" set bot_god 0 "god mode for bots" set bot_ai_navigation_jetpack 0 "Enable bots to navigate maps using the jetpack" set bot_ai_navigation_jetpack_mindistance 3500 "Bots will try fly to objects located farther than this distance"