- wget -nv -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints\r
- wget -nv -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache\r
\r
- - EXPECT=f2226bf353b6ff3dd3c489a742ce4e6a\r
+ - EXPECT=20c9c4ea0364fbb611656cc0b876e02b\r
- HASH=$(${ENGINE} +exec serverbench.cfg\r
| tee /dev/stderr\r
| grep '^:'\r
set sv_vote_majority_factor 0.5 "What percentage of the PLAYERS constitute a majority? (Must be at least 0.5, recommended: 0.5)"
set sv_vote_majority_factor_of_voted 0.5 "What percentage of the VOTERS constitute a majority too? (Must be at least 0.5, recommended: 0.5)"
set sv_vote_gamestart 0 "Allow voting during map change"
+set sv_vote_debug 0 "count votes by bots too for debugging purposes (to get a bot to vote exec this command: bot_cmd 1 cc vote yes)
// when disabled, don't allow game type changes "note: set these two equal to JUST support simple majorities"
set sv_vote_override_mostrecent 0
frag_target.respawn_time = time + 1;
frag_target.respawn_flags |= RESPAWN_FORCE;
- // Cases DEATH_TEAMCHANGE and DEATH_AUTOTEAMCHANGE are needed to fix a bug whe
- // you succeed changing team through the menu: you both really die (gibbing) and get frozen
- if(ITEM_DAMAGE_NEEDKILL(frag_deathtype)
- || frag_deathtype == DEATH_TEAMCHANGE.m_id || frag_deathtype == DEATH_AUTOTEAMCHANGE.m_id)
+ // let the player die, they will be automatically frozen when they respawn
+ // it fixes a bug where you both really die (gibbing) and get frozen
+ // if you succeed changing team through the menu
+ if (frag_deathtype == DEATH_TEAMCHANGE.m_id || frag_deathtype == DEATH_AUTOTEAMCHANGE.m_id)
{
- // let the player die, they will be automatically frozen when they respawn
- if (STAT(FROZEN, frag_target) != FROZEN_NORMAL)
- {
- freezetag_Add_Score(frag_target, frag_attacker);
- freezetag_count_alive_players();
- freezetag_LastPlayerForTeam_Notify(frag_target);
- frag_target.freezetag_frozen_timeout = -2; // freeze on respawn
- }
- else
- {
- float t = frag_target.freezetag_frozen_timeout;
- float t2 = frag_target.freezetag_frozen_time;
- Unfreeze(frag_target, false); // remove ice
- // keep timeout value so it can be restored when player will be refrozen on respawn
- // NOTE this can't be exactly -2 since game starts from time 2
- frag_target.freezetag_frozen_timeout = -t;
- frag_target.freezetag_frozen_time = t2;
- }
+ freezetag_Add_Score(frag_target, frag_attacker);
+ freezetag_count_alive_players();
+ freezetag_LastPlayerForTeam_Notify(frag_target);
+ frag_target.freezetag_frozen_timeout = -2; // freeze on respawn
return true;
}
+ if(ITEM_DAMAGE_NEEDKILL(frag_deathtype))
+ {
+ // by restoring some health right after player death (soft-kill)
+ // weapons and ammo won't be reset
+ SetResourceExplicit(frag_target, RES_HEALTH, 1);
+ // restore armor as it was removed in PlayerDamage
+ SetResourceExplicit(frag_target, RES_ARMOR, frag_target.freezetag_frozen_armor);
+
+ // relocate
+ entity spot = SelectSpawnPoint(frag_target, true);
+ setorigin(frag_target, spot.origin);
+ frag_target.oldorigin = frag_target.origin;
+ frag_target.fixangle = true; // turn this way immediately
+ frag_target.angles = vec2(spot.angles);
+ frag_target.velocity = '0 0 0';
+ frag_target.oldvelocity = frag_target.velocity; // prevents fall damage, see CreatureFrame_FallDamage
+ frag_target.avelocity = '0 0 0';
+ frag_target.punchangle = '0 0 0';
+ frag_target.punchvector = '0 0 0';
+ }
+
if (STAT(FROZEN, frag_target) == FROZEN_NORMAL)
return true;
if(player.freezetag_frozen_timeout <= -2) // player was dead
{
- float t = player.freezetag_frozen_timeout;
- float t2 = player.freezetag_frozen_time;
freezetag_Freeze(player, NULL);
- if (t < -2)
- {
- player.freezetag_frozen_timeout = -t;
- player.freezetag_frozen_time = t2;
- }
return true;
}
//float frag_damage = M_ARGV(4, float);
vector frag_force = M_ARGV(6, vector);
+ frag_target.freezetag_frozen_armor = GetResource(frag_target, RES_ARMOR);
+
if (STAT(FROZEN, frag_target) == FROZEN_NORMAL && autocvar_g_freezetag_revive_auto_reducible
&& autocvar_g_freezetag_frozen_maxtime > 0 && autocvar_g_freezetag_revive_auto)
{
.float freezetag_frozen_time;
.float freezetag_frozen_timeout;
.float freezetag_frozen_force;
+.float freezetag_frozen_armor;
const float ICE_MAX_ALPHA = 1;
const float ICE_MIN_ALPHA = 0.1;
float freezetag_teams;
if (this.bot_prevaimtime == time)
return;
+ // if skill is high enough bots will not have any aim smoothing or aim errors
+ if (SUPERBOT)
+ {
+ this.v_angle = vectoangles(normalize(v));
+
+ this.v_angle.x *= -1;
+
+ makevectors(this.v_angle);
+ shotorg = this.origin + this.view_ofs;
+ shotdir = v_forward;
+
+ // bot will fire on the next tick
+ this.bot_firetimer = time + 0.001;
+ return;
+ }
+
// invalid aim dir (can happen when bot overlaps target)
if(!v) return;
if(autocvar_bot_god)
this.flags |= FL_GODMODE;
- this.bot_nextthink = max(time, this.bot_nextthink) + max(0.01, autocvar_bot_ai_thinkinterval * min(14 / (skill + this.bot_aiskill + 14), 1));
+ // if bot skill is high enough don't limit their think frequency
+ if (SUPERBOT)
+ this.bot_nextthink = max(time, this.bot_nextthink) + 0.005;
+ else
+ this.bot_nextthink = max(time, this.bot_nextthink) + max(0.01, autocvar_bot_ai_thinkinterval * min(14 / (skill + this.bot_aiskill + 14), 1));
if (!IS_PLAYER(this) || (autocvar_g_campaign && !campaign_bots_may_start))
{
this.dmg_save = 0;
this.dmg_inflictor = NULL;
- // calculate an aiming latency based on the skill setting
- // (simulated network latency + naturally delayed reflexes)
- //this.ping = 0.7 - bound(0, 0.05 * skill, 0.5); // moved the reflexes to bot_aimdir (under the name 'think')
- // minimum ping 20+10 random
- CS(this).ping = bound(0,0.07 - bound(0, (skill + this.bot_pingskill) * 0.005,0.05)+random()*0.01,0.65); // Now holds real lag to server, and higer skill players take a less laggy server
- // skill 10 = ping 0.2 (adrenaline)
- // skill 0 = ping 0.7 (slightly drunk)
+ // if bot skill is high enough don't assign latency to them
+ if (SUPERBOT)
+ CS(this).ping = 0;
+ else
+ {
+ // calculate an aiming latency based on the skill setting
+ // (simulated network latency + naturally delayed reflexes)
+ //this.ping = 0.7 - bound(0, 0.05 * skill, 0.5); // moved the reflexes to bot_aimdir (under the name 'think')
+ // minimum ping 20+10 random
+ CS(this).ping = bound(0,0.07 - bound(0, (skill + this.bot_pingskill) * 0.005,0.05)+random()*0.01,0.65); // Now holds real lag to server, and higher skill players take a less laggy server
+ // skill 10 = ping 0.2 (adrenaline)
+ // skill 0 = ping 0.7 (slightly drunk)
+ }
// clear buttons
PHYS_INPUT_BUTTON_ATCK(this) = false;
READSKILL(bot_thinkskill, 1, 0.5); // think skill
READSKILL(bot_aiskill, 2, 0); // "ai" skill
+ // if bot skill is high enough don't limit their skill
+ if (SUPERBOT)
+ {
+ // commented out means they're meaningless with this high skill
+ // no reason to set them, uncomment if this changes
+ //this.havocbot_keyboardskill = 10;
+ //this.bot_moveskill = 10; //midair modifier sets this to 0 to disable bhop
+ //this.bot_dodgeskill = 10;
+ //this.bot_pingskill = 10;
+ //this.bot_weaponskill = 10;
+ //this.bot_aggresskill = 10;
+ this.bot_rangepreference = 1; // no range preference modification
+ //this.bot_aimskill = 10;
+ //this.bot_offsetskill = 10;
+ //this.bot_mouseskill = 10;
+ //this.bot_thinkskill = 10;
+ //this.bot_aiskill = 10;
+ }
if (file >= 0 && argv(prio) != "")
LOG_INFOF("^1Warning^7: too many parameters for bot %s, please check format of %s", bot_name, autocvar_bot_config_file);
.int aistatus;
// Skill system
+#define SUPERBOT (skill > 100)
+
float autoskill_nextthink;
// havocbot_keyboardskill // keyboard movement
return selected;
}
+// Check for water/slime/lava and dangerous edges
+// (only when the bot is on the ground or jumping intentionally)
+// returns true for danger
+bool havocbot_checkdanger(entity this, vector dst_ahead)
+{
+ vector dst_down = dst_ahead - '0 0 3000';
+ traceline(this.origin + this.view_ofs, dst_ahead, true, NULL);
+
+ float s = CONTENT_SOLID;
+ if (trace_fraction == 1 && !this.jumppadcount
+ && !waypoint_is_hardwiredlink(this.goalcurrent_prev, this.goalcurrent)
+ && !(this.goalcurrent_prev && (this.goalcurrent_prev.wpflags & WAYPOINTFLAG_JUMP)))
+ if ((IS_ONGROUND(this)) || (this.aistatus & AI_STATUS_RUNNING) || (this.aistatus & AI_STATUS_ROAMING) || PHYS_INPUT_BUTTON_JUMP(this))
+ {
+ // Look downwards
+ traceline(dst_ahead , dst_down, true, NULL);
+ //te_lightning2(NULL, this.origin + this.view_ofs, dst_ahead); // Draw "ahead" look
+ //te_lightning2(NULL, dst_ahead, trace_endpos); // Draw "downwards" look
+ if (trace_endpos.z < this.origin.z + this.mins.z)
+ {
+ if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
+ return true;
+ else if (trace_endpos.z < min(this.origin.z + this.mins.z, this.goalcurrent.origin.z) - 100)
+ return true;
+ else
+ {
+ s = pointcontents(trace_endpos + '0 0 1');
+ if (s != CONTENT_SOLID)
+ {
+ if (s == CONTENT_LAVA || s == CONTENT_SLIME)
+ return true;
+ else if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
+ {
+ // 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))
+ {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+}
+
void havocbot_movetogoal(entity this)
{
vector diff;
vector dir;
vector flatdir;
float dodge_enemy_factor = 1;
- float maxspeed;
+ float maxspeed = autocvar_sv_maxspeed;
//float dist;
vector dodge;
//if (this.goalentity)
// te_lightning2(this, this.origin, (this.goalentity.absmin + this.goalentity.absmax) * 0.5);
CS(this).movement = '0 0 0';
- maxspeed = autocvar_sv_maxspeed;
PHYS_INPUT_BUTTON_CROUCH(this) = boolean(this.goalcurrent.wpflags & WAYPOINTFLAG_CROUCH);
}
}
vector gco = (this.goalcurrent.absmin + this.goalcurrent.absmax) * 0.5;
- if (this.origin.z > gco.z && vdist(vec2(this.velocity), <, autocvar_sv_maxspeed))
+ if (this.origin.z > gco.z && vdist(vec2(this.velocity), <, maxspeed))
{
if (this.velocity.z < 0)
this.aistatus &= ~AI_STATUS_OUT_JUMPPAD;
if(this.velocity.z > 0 && this.origin.z - this.lastteleport_origin.z > (this.maxs.z - this.mins.z) * 0.5)
{
vector velxy = this.velocity; velxy_z = 0;
- if(vdist(velxy, <, autocvar_sv_maxspeed * 0.2))
+ if(vdist(velxy, <, maxspeed * 0.2))
{
LOG_TRACE("Warning: ", this.netname, " got stuck on a jumppad (velocity in xy is ", vtos(velxy), "), trying to get out of it now");
this.aistatus |= AI_STATUS_OUT_JUMPPAD;
}
else
{
- float s;
+ float s = 0;
vector offset;
if(this.aistatus & AI_STATUS_OUT_WATER)
this.aistatus &= ~AI_STATUS_OUT_WATER;
// 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';
- traceline(this.origin + this.view_ofs, dst_ahead, true, NULL);
-
bool unreachable = false;
- s = CONTENT_SOLID;
- if (trace_fraction == 1 && !this.jumppadcount
- && !waypoint_is_hardwiredlink(this.goalcurrent_prev, this.goalcurrent)
- && !(this.goalcurrent_prev && (this.goalcurrent_prev.wpflags & WAYPOINTFLAG_JUMP)))
- if((IS_ONGROUND(this)) || (this.aistatus & AI_STATUS_RUNNING) || (this.aistatus & AI_STATUS_ROAMING) || PHYS_INPUT_BUTTON_JUMP(this))
+ if (havocbot_checkdanger(this, dst_ahead))
{
- // Look downwards
- traceline(dst_ahead , dst_down, true, NULL);
- //te_lightning2(NULL, this.origin + this.view_ofs, dst_ahead); // Draw "ahead" look
- //te_lightning2(NULL, dst_ahead, trace_endpos); // Draw "downwards" look
- if(trace_endpos.z < this.origin.z + this.mins.z)
+ if (destorg.z > this.origin.z + jumpstepheightvec.z)
{
- 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
- {
- s = pointcontents(trace_endpos + '0 0 1');
- if (s != CONTENT_SOLID)
- {
- 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 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;
- }
- }
- }
- }
+ // the goal is probably on an upper platform, assume bot can't get there
+ unreachable = true;
}
+ else
+ danger_detected = true;
}
dir = flatdir;
dodge = havocbot_dodge(this);
if (dodge)
dodge *= bound(0, 0.5 + (skill + this.bot_dodgeskill) * 0.1, 1);
+ // midair sets moveskill to 0 so avoid jumping when dodging in midair mutator
+ if (dodge.z > 0 && this.bot_moveskill == 0)
+ dodge.z = 0;
if (this.enemy)
{
traceline(this.origin, (this.enemy.absmin + this.enemy.absmax) * 0.5, true, NULL);
if (IS_PLAYER(trace_ent))
dodge_enemy_factor = bound(0, (skill + this.bot_dodgeskill) / 7, 1);
}
- // this.bot_dodgevector = dir;
- // this.bot_dodgevector_jumpbutton = PHYS_INPUT_BUTTON_JUMP(this);
+ //this.bot_dodgevector = dir;
+ //this.bot_dodgevector_jumpbutton = PHYS_INPUT_BUTTON_JUMP(this);
+
+ // don't dodge to danger
+ if (havocbot_checkdanger(this, this.origin + this.view_ofs + dodge * 32))
+ {
+ dodge = '0 0 0';
+ }
}
float ladder_zdir = 0;
CS(this).movement_y = dir * v_right * maxspeed;
CS(this).movement_z = dir * v_up * maxspeed;
+ // when high enough skill bots engage in combat they move randomly
+ if (SUPERBOT && this.aistatus == AI_STATUS_ATTACKING && !dodge)
+ {
+ if (!this.randomdirectiontime || this.randomdirectiontime + 0.35 < time)
+ {
+ // 75% chance to generate a random direction to follow for
+ // 0.3 seconds, there's a 15% chance to fail the generation
+ // and only generation attempt one every 0.35s so bots move
+ // towards their goal slightly
+ if (random() < 0.15)
+ this.randomdirection = '0 0 0';
+ else
+ {
+ // random values from -1 to 1
+ this.randomdirection.x = crandom() * maxspeed;
+ this.randomdirection.y = crandom() * maxspeed;
+ //this.randomdirection.z = crandom() * maxspeed;
+ }
+
+ this.randomdirectiontime = time;
+ }
+ if (this.randomdirectiontime + 0.3 >= time && this.randomdirection)
+ {
+ CS(this).movement_x = this.randomdirection.x;
+ CS(this).movement_y = this.randomdirection.y;
+ // no random vertical direction
+ }
+ }
+
+
// Emulate keyboard interface
if (skill < 10)
havocbot_keyboard_movement(this, destorg);
if (dodge * v_up > 0 && random() * frametime >= 0.2 * bound(0, (10 - skill - this.bot_dodgeskill) * 0.1, 1))
PHYS_INPUT_BUTTON_JUMP(this) = true;
if (dodge * v_up < 0 && random() * frametime >= 0.5 * bound(0, (10 - skill - this.bot_dodgeskill) * 0.1, 1))
+ {
+ if(IS_ONGROUND(this))
+ PHYS_INPUT_BUTTON_JUMP(this) = false;
this.havocbot_ducktime = time + 0.3 / bound(0.1, skill + this.bot_dodgeskill, 10);
+ PHYS_INPUT_BUTTON_CROUCH(this) = true;
+ }
}
}
}
if (time < this.havocbot_chooseenemy_finished)
return;
- this.havocbot_chooseenemy_finished = time + autocvar_bot_ai_enemydetectioninterval;
+ // don't limit the detection interval to several seconds for bots with enough skill
+ if (SUPERBOT)
+ this.havocbot_chooseenemy_finished = time + 0.1;
+ else
+ this.havocbot_chooseenemy_finished = time + autocvar_bot_ai_enemydetectioninterval;
+
vector eye = this.origin + this.view_ofs;
entity best = NULL;
float bestrating = autocvar_bot_ai_enemydetectionradius ** 2;
continue;
vector v = (it.absmin + it.absmax) * 0.5;
- float rating = vlen2(v - eye);
- if (rating < bestrating && bot_shouldattack(this, it))
+ float distance = vlen2(v - eye);
+
+ if (SUPERBOT)
+ {
+ if (bot_shouldattack(this, it))
+ {
+ // skilled enough bots take account target health and distance
+ float health = GetResource(it, RES_HEALTH);
+ float armor = GetResource(it, RES_ARMOR);
+ float rating = bound(50, health + armor, 250) * distance;
+ if (!best || (rating < bestrating))
+ {
+ traceline(eye, v, true, this);
+ if (trace_ent == it || trace_fraction >= 1)
+ {
+ best = it;
+ bestrating = rating;
+ }
+ }
+ }
+ }
+ else
{
- traceline(eye, v, true, this);
- if (trace_ent == it || trace_fraction >= 1)
+ if (distance < bestrating && bot_shouldattack(this, it))
{
- best = it;
- bestrating = rating;
+ traceline(eye, v, true, this);
+ if (trace_ent == it || trace_fraction >= 1)
+ {
+ best = it;
+ bestrating = distance;
+ }
}
}
});
scan_secondary_targets = true;
// restart the loop
bestrating = autocvar_bot_ai_enemydetectionradius ** 2;
+
goto scan_targets;
}
void havocbot_chooseweapon(entity this, .entity weaponentity)
{
int i;
+ float w;
// ;)
if(g_weaponarena_weapons == WEPSET(TUBA))
}
// TODO: clean this up by moving it to weapon code
- if(this.enemy==NULL)
+ if (this.enemy == NULL)
{
- // If no weapon was chosen get the first available weapon
- if(this.(weaponentity).m_weapon==WEP_Null)
- FOREACH(Weapons, it != WEP_Null, {
- if(client_hasweapon(this, it, weaponentity, true, false))
+ // Choose the first available weapon from medium range weaponlist
+ // TODO: don't do this but don't make bots hold out a blaster out either
+ for (i = 0; i < REGISTRY_COUNT(Weapons) && bot_weapons_mid[i] != -1 ; ++i){
+ w = bot_weapons_mid[i];
+ if (bot_custom_weapon)
{
- this.(weaponentity).m_switchweapon = it;
- return;
+ if (client_hasweapon(this, REGISTRY_GET(Weapons, w), weaponentity, true, false))
+ {
+ if ((this.(weaponentity).m_weapon == WEP_Null) || havocbot_chooseweapon_checkreload(this, weaponentity, w))
+ continue;
+ this.(weaponentity).m_switchweapon = REGISTRY_GET(Weapons, w);
+ return;
+ }
}
- });
+ }
+
+ // If no weapon was chosen get the first available weapon
+ if (this.(weaponentity).m_weapon == WEP_Null)
+ FOREACH(Weapons, it != WEP_Null, {
+ if (client_hasweapon(this, it, weaponentity, true, false))
+ {
+ this.(weaponentity).m_switchweapon = it;
+ return;
+ }
+ });
return;
}
if(f < 1)
return;
- float w;
float distance; distance=bound(10,vlen(this.origin-this.enemy.origin)-200,10000);
// Should it do a weapon combo?
vector havocbot_dodge(entity this)
{
// LordHavoc: disabled because this is too expensive
- return '0 0 0';
-#if 0
+ // Dr. Jaska: re-enable this but only for bots with high enough skill
+ if (!SUPERBOT)
+ return '0 0 0';
+
+#if 1
entity head;
vector dodge, v, n;
float danger, bestdanger, vl, d;
if (d > (0 - head.bot_dodgerating))
if (d < (vl * 0.2 + head.bot_dodgerating))
{
- // calculate direction and distance from the flight path, by removing the forward axis
+ // calculate direction and distance from the
+ // flight path by removing the forward axis
v = v - (n * (v * n));
danger = head.bot_dodgerating - vlen(v);
if (bestdanger < danger)
head = head.chain;
}
return dodge;
+#else
+ return '0 0 0';
#endif
}
.vector havocbot_keyboard;
+.float randomdirectiontime;
+.vector randomdirection;
+
/*
* Functions
*/
// Game logic for voting
// =======================
-void VoteReset()
+void VoteStop(entity stopper, bool show_name);
+void VoteReset(bool verbose)
{
+ if (verbose && vote_called)
+ {
+ VoteStop(NULL, true);
+ return;
+ }
+
FOREACH_CLIENT(true, { it.vote_selection = 0; });
if (vote_called)
Nagger_VoteChanged();
}
-void VoteStop(entity stopper)
+void VoteStop(entity stopper, bool canceled)
{
- bprint("\{1}^2* ^3", GetCallerName(stopper), "^2 stopped ^3", OriginalCallerName(), "^2's vote\n");
+ if (canceled)
+ bprint("\{1}^2* ^3", OriginalCallerName(), "^2's vote was canceled\n");
+ else
+ bprint("\{1}^2* ^3", GetCallerName(stopper), "^2 stopped ^3", OriginalCallerName(), "^2's vote\n");
if (autocvar_sv_eventlog) GameLogEcho(strcat(":vote:vstop:", ftos(stopper.playerid)));
// Don't force them to wait for next vote, this way they can e.g. correct their vote.
if ((vote_caller) && (stopper == vote_caller)) vote_caller.vote_waittime = time + autocvar_sv_vote_stop;
- VoteReset();
+ VoteReset(false);
}
void VoteAccept()
if (vote_caller) vote_caller.vote_waittime = 0; // people like your votes, you don't need to wait to vote again
- VoteReset();
+ VoteReset(false);
Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_ACCEPT);
}
void VoteReject()
{
bprint("\{1}^2* ^3", OriginalCallerName(), "^2's vote for ", vote_called_display, "^2 was rejected\n");
- VoteReset();
+ VoteReset(false);
Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_FAIL);
}
void VoteTimeout()
{
bprint("\{1}^2* ^3", OriginalCallerName(), "^2's vote for ", vote_called_display, "^2 timed out\n");
- VoteReset();
+ VoteReset(false);
Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_FAIL);
}
void VoteCount(float first_count)
{
- // declarations
vote_accept_count = vote_reject_count = vote_abstain_count = 0;
float vote_player_count = 0, notvoters = 0;
Nagger_VoteCountChanged();
// add up all the votes from each connected client
- FOREACH_CLIENT(IS_REAL_CLIENT(it), {
+ FOREACH_CLIENT(IS_REAL_CLIENT(it) || autocvar_sv_vote_debug, {
++vote_player_count;
if (IS_PLAYER(it) || INGAME(it)) ++vote_real_player_count;
switch (it.vote_selection)
{
if (vote_caller) vote_caller.vote_waittime = 0;
print_to(vote_caller, "^1There are not enough players on this server to allow you to become vote master.");
- VoteReset();
+ VoteReset(false);
return;
}
if (!is_fake_round_start && !autocvar_g_campaign)
Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_COUNTDOWN_RESTART);
- VoteReset();
+ VoteReset(true);
// clear overtime, we have to decrease timelimit to its original value again.
if (checkrules_overtimesadded > 0 && g_race_qualifying != 2)
FOREACH_CLIENT(IS_REAL_CLIENT(it), { ++tmp_playercount; });
bprint("\{1}^2* ^3", OriginalCallerName(), "^2 calls a vote for ", vote_called_display, "\n");
+ if (autocvar_sv_vote_debug)
+ bprint("\{1}^2* ^3", "^6DEBUG MODE ACTIVE: bots can vote too\n"); // so servers don't forget it on
if (autocvar_sv_eventlog)
GameLogEcho(strcat(":vote:vcall:", ftos(vote_caller.playerid), ":", vote_called_display));
Nagger_VoteChanged();
}
else if (((caller == vote_caller) || caller.vote_master) && autocvar_sv_vote_no_stops_vote)
{
- VoteStop(caller);
+ VoteStop(caller, true);
}
else // everything went okay, continue changing vote
case CMD_REQUEST_COMMAND:
{
if (!vote_called) print_to(caller, "^1No vote called.");
- else if ((caller == vote_caller) || !caller || caller.vote_master) VoteStop(caller);
+ else if ((caller == vote_caller) || !caller || caller.vote_master) VoteStop(caller, false);
else print_to(caller, "^1You are not allowed to stop that vote.");
return;
}
bool autocvar_sv_vote_call;
bool autocvar_sv_vote_change;
string autocvar_sv_vote_commands;
+bool autocvar_sv_vote_debug;
+bool autocvar_sv_vote_gamestart;
int autocvar_sv_vote_limit;
float autocvar_sv_vote_majority_factor;
float autocvar_sv_vote_majority_factor_of_voted;
float autocvar_sv_vote_stop;
float autocvar_sv_vote_timeout;
float autocvar_sv_vote_wait;
-bool autocvar_sv_vote_gamestart;
// definitions for command selection between progs
const float VC_ASGNMNT_BOTH = 1;
// allow functions to be used in other code like world.qc and teamplay.qc
void VoteThink();
-void VoteReset();
+void VoteReset(bool verbose);
void VoteCommand(int request, entity caller, int argc, string vote_command);
// warmup and nagger stuff
void InitGameplayMode()
{
- VoteReset();
+ VoteReset(false);
// find out good world mins/maxs bounds, either the static bounds found by looking for solid, or the mapinfo specified bounds
get_mi_min_max(1);
//pos = FindIntermission ();
- VoteReset();
+ VoteReset(true);
DumpStats(true);
set sv_itemstime 1 "enable networking of time left until respawn for items such as mega health/armor and powerups"
+// bans
set g_ban_default_bantime 5400 "90 minutes"
set g_ban_default_masksize 3 "masksize 0 means banning by UID only, 1 means banning by /8 (IPv6: /32) network, 2 means banning by /16 (IPv6: /48) network, 3 means banning by /24 (IPv6: /56) network, 4 means banning by single IP (IPv6: /64 network)"
+set g_ban_sync_uri "" "sync using this ban list provider (empty string to disable)"
+set g_ban_sync_interval 5 "sync every 5 minutes"
+set g_ban_sync_trusted_servers "" "request ban lists from these xonotic servers (do not include your own server there, or unbanning may fail)"
+set g_ban_sync_timeout 45 "time out in seconds for the ban sync requests"
+set g_ban_sync_trusted_servers_verify 0 "when set to 1, additional bans sent by the servers are ignored, and only bans for the requested IP are used"
set g_ban_telluser 1 "notify the banned player about it when they try to join"
set g_banned_list "" "format: IP remainingtime IP remainingtime ..."
set g_banned_list_idmode "1" "when set, the IP banning system always uses the ID over the IP address (so a user in a banned IP range can connect if they have a valid signed ID)"
+set g_muteban_list "" "list of banned players from chat, format: IP playerkey ..."
+set g_playban_list "" "list of banned players from playing (forced to spectate), format: IP playerkey ..."
+set g_voteban_list "" "list of banned players from voting, format: IP playerkey ..."
// useful vote aliases
set timelimit_increment 5 "number of minutes added to the timer when voting for extendmatchtime"
set sv_clones 0 "number of clones a player may make (reset by the \"kill\" command)"
-set g_ban_sync_uri "" "sync using this ban list provider (empty string to disable)"
-set g_ban_sync_interval 5 "sync every 5 minutes"
-set g_ban_sync_trusted_servers "" "request ban lists from these xonotic servers (do not include your own server there, or unbanning may fail)"
-set g_ban_sync_timeout 45 "time out in seconds for the ban sync requests"
-set g_ban_sync_trusted_servers_verify 0 "when set to 1, additional bans sent by the servers are ignored, and only bans for the requested IP are used"
-
set g_showweaponspawns 1 "1: display waypoints for weapon spawns found on the map when a weapon key is pressed and the weapon is not owned; 2: for dropped weapons too; 3: for all the weapons sharing the same impulse"
set g_ballistics_mindistance 2 "when shooting through walls thinner than this, treat them as this thick (useful because patches (curved surfaces) have no thickness)"