From: Martin Taibr Date: Sun, 6 Nov 2016 14:47:13 +0000 (+0100) Subject: Merge branch 'master' into martin-t/msnt X-Git-Tag: xonotic-v0.8.2~456^2 X-Git-Url: https://de.git.xonotic.org/?p=xonotic%2Fxonotic-data.pk3dir.git;a=commitdiff_plain;h=7908f6be3a5bcdb29c7fc4ca663b919ed023b1f3;hp=42847fddd58877c481212d07cd914d3741434ecd Merge branch 'master' into martin-t/msnt --- diff --git a/mutators.cfg b/mutators.cfg index 2188f8007..b5e9b76df 100644 --- a/mutators.cfg +++ b/mutators.cfg @@ -125,8 +125,9 @@ seta cl_spawn_near_teammate 1 "toggle for spawning near teammates (only effectiv set g_spawn_near_teammate 0 "if set, players prefer spawns near a team mate" set g_spawn_near_teammate_distance 640 "max distance to consider a spawn to be near a team mate" set g_spawn_near_teammate_ignore_spawnpoint 0 "ignore spawnpoints and spawn right at team mates, if 2, clients can ignore this option" +set g_spawn_near_teammate_ignore_spawnpoint_max 10 "if set, test at most this many of the available teammates" set g_spawn_near_teammate_ignore_spawnpoint_delay 2.5 "how long to wait before its OK to spawn at a player after someone just spawned at this player" -set g_spawn_near_teammate_ignore_spawnpoint_delay_death 0 "how long to wait before its OK to spawn at a player after death" +set g_spawn_near_teammate_ignore_spawnpoint_delay_death 3 "how long to wait before its OK to spawn at a player after death" set g_spawn_near_teammate_ignore_spawnpoint_check_health 1 "only allow spawn at this player if their health is full" set g_spawn_near_teammate_ignore_spawnpoint_closetodeath 1 "spawn as close to death location as possible" @@ -436,4 +437,4 @@ set g_walljump 0 "Enable wall jumping mutator" set g_walljump_delay 1 "Minimum delay between wall jumps" set g_walljump_force 300 "How far to bounce/jump off the wall" set g_walljump_velocity_xy_factor 1.15 "How much to slow down along horizontal axis, higher value = higher deceleration, if factor is < 1, you accelerate by wall jumping" -set g_walljump_velocity_z_factor 0.5 "Upwards velocity factor, multiplied by normal jump velocity" \ No newline at end of file +set g_walljump_velocity_z_factor 0.5 "Upwards velocity factor, multiplied by normal jump velocity" diff --git a/qcsrc/common/mutators/mutator/spawn_near_teammate/sv_spawn_near_teammate.qc b/qcsrc/common/mutators/mutator/spawn_near_teammate/sv_spawn_near_teammate.qc index 123b8caa0..e4b784371 100644 --- a/qcsrc/common/mutators/mutator/spawn_near_teammate/sv_spawn_near_teammate.qc +++ b/qcsrc/common/mutators/mutator/spawn_near_teammate/sv_spawn_near_teammate.qc @@ -1,10 +1,13 @@ #include "sv_spawn_near_teammate.qh" +const float FLOAT_MAX = 340282346638528859811704183484516925440.0f; + float autocvar_g_spawn_near_teammate_distance; int autocvar_g_spawn_near_teammate_ignore_spawnpoint; +int autocvar_g_spawn_near_teammate_ignore_spawnpoint_max; float autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay; float autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death; -int autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health; +bool autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health; bool autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath; REGISTER_MUTATOR(spawn_near_teammate, cvar("g_spawn_near_teammate")); @@ -12,7 +15,6 @@ REGISTER_MUTATOR(spawn_near_teammate, cvar("g_spawn_near_teammate")); .entity msnt_lookat; .float msnt_timer; -.vector msnt_deathloc; .float cvar_cl_spawn_near_teammate; @@ -80,86 +82,124 @@ MUTATOR_HOOKFUNCTION(spawn_near_teammate, PlayerSpawn) player.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death; entity best_mate = NULL; - vector best_spot = '0 0 0'; - float pc = 0, best_dist = 0, dist = 0; - FOREACH_CLIENT(IS_PLAYER(it), LAMBDA( - if((autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health >= 0 && it.health >= autocvar_g_balance_health_regenstable) || autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health == 0) - if(!IS_DEAD(it)) - if(it.msnt_timer < time) - if(SAME_TEAM(player, it)) - if(time > it.spawnshieldtime) // spawn shielding - if(!forbidWeaponUse(it)) - if(STAT(FROZEN, it) == 0) - if(it != player) + vector best_pos = '0 0 0'; + float best_dist2 = FLOAT_MAX; + int tested = 0; + FOREACH_CLIENT_RANDOM(IS_PLAYER(it), LAMBDA( + if (autocvar_g_spawn_near_teammate_ignore_spawnpoint_max && tested >= autocvar_g_spawn_near_teammate_ignore_spawnpoint_max) break; + if (!SAME_TEAM(player, it)) continue; + if (autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health && it.health < autocvar_g_balance_health_regenstable) continue; + if (IS_DEAD(it)) continue; + if (time < it.msnt_timer) continue; + if (time < it.spawnshieldtime) continue; + if (forbidWeaponUse(it)) continue; + if (it == player) continue; + + tested++; // i consider a teammate to be available when he passes the checks above + + vector horiz_vel = vec2(it.velocity); + // when walking slowly sideways, we assume the player wants a clear shot ahead - spawn behind him according to where he's looking + // when running fast, spawn behind him according to his direction of movement to prevent colliding with the newly spawned player + if (vdist(horiz_vel, >, autocvar_sv_maxspeed + 50)) + fixedmakevectors(vectoangles(horiz_vel)); + else + fixedmakevectors(it.angles); // .angles is the angle of the model - usually/always 0 pitch + + // test different spots close to mate - trace upwards so it works on uneven surfaces + // don't spawn in front of player or directly behind to avoid players shooting each other + // test the potential spots in pairs (first pair is better than second and so on) but don't prefer one side + RandomSelection_Init(); + for(int i = 0; i < 6; ++i) { - tracebox(it.origin, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin - '0 0 100', MOVE_NOMONSTERS, it); - if(trace_fraction != 1.0) - if(!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)) + switch(i) { - pc = pointcontents(trace_endpos + '0 0 1'); - if(pc == CONTENT_EMPTY) + case 0: + tracebox(it.origin, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin - v_forward * 64 + v_right * 128 + v_up * 64, MOVE_NOMONSTERS, it); + break; + case 1: + tracebox(it.origin, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin - v_forward * 64 - v_right * 128 + v_up * 64, MOVE_NOMONSTERS, it); + break; + case 2: + tracebox(it.origin, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin + v_right * 192 + v_up * 64, MOVE_NOMONSTERS, it); + break; + case 3: + tracebox(it.origin, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin - v_right * 192 + v_up * 64, MOVE_NOMONSTERS, it); + break; + case 4: + tracebox(it.origin, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin - v_forward * 128 + v_right * 64 + v_up * 64, MOVE_NOMONSTERS, it); + break; + case 5: + tracebox(it.origin, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin - v_forward * 128 - v_right * 64 + v_up * 64, MOVE_NOMONSTERS, it); + break; + } + + vector horizontal_trace_endpos = trace_endpos; + //te_lightning1(NULL, it.origin, horizontal_trace_endpos); + if (trace_fraction != 1.0) goto skip; + + // 400 is about the height of a typical laser jump (in overkill) + // not traceline because we need space for the whole player, not just his origin + tracebox(horizontal_trace_endpos, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), horizontal_trace_endpos - '0 0 400', MOVE_NORMAL, it); + vector vectical_trace_endpos = trace_endpos; + //te_lightning1(NULL, horizontal_trace_endpos, vectical_trace_endpos); + if (trace_startsolid) goto skip; // inside another player + if (trace_fraction == 1.0) goto skip; // above void or too high + if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY) goto skip; + if (pointcontents(vectical_trace_endpos) != CONTENT_EMPTY) goto skip; // no lava or slime (or water which i assume would be annoying anyway) + if (tracebox_hits_trigger_hurt(horizontal_trace_endpos, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), vectical_trace_endpos)) goto skip; + + // make sure the spawned player will have floor ahead (or at least a wall - he shouldn't fall as soon as he starts moving) + vector floor_test_start = vectical_trace_endpos + v_up * STAT(PL_MAX, NULL).z + v_forward * STAT(PL_MAX, NULL).x; // top front of player's bbox - highest point we know is not inside solid + traceline(floor_test_start, floor_test_start + v_forward * 100 - v_up * 128, MOVE_NOMONSTERS, it); + //te_beam(NULL, floor_test_start, trace_endpos); + if (trace_fraction == 1.0) goto skip; + + if (autocvar_g_nades) { + bool nade_in_range = false; + IL_EACH(g_projectiles, it.classname == "nade", { - if(vdist(it.velocity, >, 5)) - fixedmakevectors(vectoangles(it.velocity)); - else - fixedmakevectors(it.angles); + if (vdist(it.origin - vectical_trace_endpos, <, autocvar_g_nades_nade_radius)) { + nade_in_range = true; + goto skip; + } + }); + if (nade_in_range) goto skip; + } + + // here, we know we found a good spot + RandomSelection_Add(it, 0, string_null, vectical_trace_endpos, 1, 1); + //te_lightning1(NULL, vectical_trace_endpos, vectical_trace_endpos + v_forward * 10); - for(pc = 0; pc < 4; ++pc) // test 4 diffrent spots close to mate +LABEL(skip) + if (i % 2 == 1 && RandomSelection_chosen_ent) + { + if (autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath) + { + float dist2 = vlen2(RandomSelection_chosen_ent.origin - player.death_origin); + if (dist2 < best_dist2) { - switch(pc) - { - case 0: - tracebox(it.origin , STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin + v_right * 128, MOVE_NOMONSTERS, it); - break; - case 1: - tracebox(it.origin , STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin - v_right * 128 , MOVE_NOMONSTERS, it); - break; - case 2: - tracebox(it.origin , STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin + v_right * 128 - v_forward * 64, MOVE_NOMONSTERS, it); - break; - case 3: - tracebox(it.origin , STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin - v_right * 128 - v_forward * 64, MOVE_NOMONSTERS, it); - break; - //case 4: - //tracebox(it.origin , STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin - v_forward * 128, MOVE_NOMONSTERS, it); - //break; - } - - if(trace_fraction == 1.0) - { - traceline(trace_endpos + '0 0 4', trace_endpos - '0 0 100', MOVE_NOMONSTERS, it); - if(trace_fraction != 1.0) - { - if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath) - { - dist = vlen(trace_endpos - player.msnt_deathloc); - if(dist < best_dist || best_dist == 0) - { - best_dist = dist; - best_spot = trace_endpos; - best_mate = it; - } - } - else - { - setorigin(player, trace_endpos); - player.angles = it.angles; - player.angles_z = 0; // never spawn tilted even if the spot says to - it.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay; - return; - } - } - } + best_dist2 = dist2; + best_pos = RandomSelection_chosen_vec; + best_mate = RandomSelection_chosen_ent; } } + else + { + setorigin(player, RandomSelection_chosen_vec); + player.angles = RandomSelection_chosen_ent.angles; + player.angles_z = 0; // never spawn tilted even if the spot says to + RandomSelection_chosen_ent.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay; + return; + } + break; // don't test the other spots near this teammate, go to the next one } } )); if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath) - if(best_dist) + if(best_mate) { - setorigin(player, best_spot); + setorigin(player, best_pos); player.angles = best_mate.angles; player.angles_z = 0; // never spawn tilted even if the spot says to best_mate.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay; @@ -178,11 +218,4 @@ MUTATOR_HOOKFUNCTION(spawn_near_teammate, PlayerSpawn) } } -MUTATOR_HOOKFUNCTION(spawn_near_teammate, PlayerDies) -{ - entity frag_target = M_ARGV(0, entity); - - frag_target.msnt_deathloc = frag_target.origin; -} - REPLICATE(cvar_cl_spawn_near_teammate, bool, "cl_spawn_near_teammate"); diff --git a/qcsrc/server/_all.qh b/qcsrc/server/_all.qh index ed3083a12..1fabe4d0f 100644 --- a/qcsrc/server/_all.qh +++ b/qcsrc/server/_all.qh @@ -43,6 +43,37 @@ const string STR_OBSERVER = "observer"; #define FOREACH_CLIENT(cond, body) FOREACH_CLIENTSLOT(IS_CLIENT(it) && (cond), body) +// using the "inside out" version of knuth-fisher-yates shuffle +// https://en.wikipedia.org/wiki/Fisher–Yates_shuffle +entity _FCR_clients[255]; +bool _FCR_entered = false; +#define FOREACH_CLIENT_RANDOM(cond, body) \ + MACRO_BEGIN { \ + if (_FCR_entered) LOG_FATAL("FOREACH_CLIENT_RANDOM must not be nested"); \ + _FCR_entered = true; \ + int _cnt = 0; \ + FOREACH_CLIENT(cond, LAMBDA( \ + int _j = floor(random() * (_cnt + 1)); \ + if (_j == _cnt) \ + { \ + _FCR_clients[_cnt] = it; \ + } \ + else \ + { \ + _FCR_clients[_cnt] = _FCR_clients[_j]; \ + _FCR_clients[_j] = it; \ + } \ + _cnt++; \ + )); \ + for (int _i = 0; _i < _cnt; ++_i) \ + { \ + const noref int i = _i; \ + ITER_CONST noref entity it = _FCR_clients[i]; \ + if (cond) { LAMBDA(body) } \ + } \ + _FCR_entered = false; \ + } MACRO_END + // NOTE: FOR_EACH_MONSTER deprecated! Use the following instead: IL_EACH(g_monsters, true, { code; }); #include