-*.q[ch] linguist-language=C
-*.inc linguist-language=C
-
* -crlf
*.0 -crlf
*.1 crlf=input
*.ico -crlf
*.idl crlf=input
*.idsoftware crlf=input
-*.inc crlf=input
*.in crlf=input
+*.inc crlf=input linguist-language=C gitlab-language=C
*.info-1 -crlf
*.info-2 -crlf
*.info -crlf
*.psd -crlf
*.py crlf=input
*.q3map1 crlf=input
-*.qc crlf=input
+*.q[ch] crlf=input linguist-language=C gitlab-language=C
*.qdt crlf=input
-*.qh crlf=input
*.rar -crlf
*.rb crlf=input
*.rc2 crlf=input
test_sv_game:
stage: test
script:
- - export EXPECT=210a5126bffa3cd2acdb8d62bcec9e11
+ - export EXPECT=ded5d54d7a8326069f5f3d015cee1bf2
- qcsrc/tools/sv_game-hashtest.sh
- exit $?
-Sat Jan 20 07:23:48 AM CET 2024
+Fri Jan 26 07:22:59 AM CET 2024
# Antoni Das <Antonidas159@gmail.com>, 2017
# Antonio <piuntn@gmail.com>, 2023
# LegendGuard, 2020
-# LegendGuard, 2020-2023
+# LegendGuard, 2020-2024
# Lento <securemailfor28-xonotic@yahoo.co.jp>, 2015
# RYU N. <ryusho2523@yahoo.co.jp>, 2021
# z 411 <winrar.hurr@gmail.com>, 2021-2022
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-11 07:22+0200\n"
"PO-Revision-Date: 2013-09-12 16:53+0000\n"
-"Last-Translator: RYU N. <ryusho2523@yahoo.co.jp>, 2021\n"
+"Last-Translator: LegendGuard, 2020-2024\n"
"Language-Team: Japanese (Japan) (http://app.transifex.com/team-xonotic/"
"xonotic/language/ja_JP/)\n"
"Language: ja_JP\n"
#: qcsrc/client/hud/panel/quickmenu.qc:838
msgid "QMCMD^defending (l:%l^7) (h:%h^7 a:%a^7 w:%w^7)"
-msgstr "QMCMD^守り中 (l:%l^7) (h:%h^7 a:%a^7 w:%w^7)"
+msgstr "QMCMD^防衛中 (l:%l^7) (h:%h^7 a:%a^7 w:%w^7)"
#: qcsrc/client/hud/panel/quickmenu.qc:838
msgid "QMCMD^defending, icon"
set g_ca_teams_override 0
set g_ca_team_spawns 0 "when 1, players spawn from the team spawnpoints of the map, if any"
set g_ca_teams 0
-set g_ca_prevent_stalemate 0 "when round time ends instead of instant stalemate give round win to the team with most survivors or with the most total health"
+set g_ca_prevent_stalemate 0 "When round time ends instead of instant stalemate give round win to the team with 1: most survivors. 2: most total health. 3: most survivors or if equal then most total health"
set g_ca_weaponarena "most" "starting weapons - takes the same options as g_weaponarena"
sv_jumpspeedcap_max ""
// CPMA has ramp jumping
sv_jumpspeedcap_max_disable_on_ramps 1
-// FIXME: Q3 teleporters _set_ speed to 400, not cap it
g_teleport_maxspeed 400
+g_teleport_minspeed 400
sv_track_canjump 1
sv_gameplayfix_stepdown_maxspeed 0
g_movement_highspeed_q3_compat 1
sv_jumpspeedcap_max 0.35
sv_jumpspeedcap_max_disable_on_ramps 1
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 0
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 0
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 0
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 1
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 0
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 1
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 0
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max 0.38
sv_jumpspeedcap_max_disable_on_ramps 0
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 0
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 0
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 0
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 0
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 0
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 0
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 0
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 0
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 0
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 0
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 0
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 0
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 0
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 0
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 0
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 0
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 0
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 0
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 0
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 1
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 0
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 1
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 400
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 0
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 0
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 0
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 0
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 0
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 0
g_movement_highspeed_q3_compat 0
// VQ3 has no ramp jumping
sv_jumpspeedcap_max 270
sv_jumpspeedcap_max_disable_on_ramps 0
-// FIXME: Q3 teleporters _set_ speed to 400, not cap it
g_teleport_maxspeed 400
+g_teleport_minspeed 400
sv_track_canjump 1
sv_gameplayfix_stepdown_maxspeed 0
g_movement_highspeed_q3_compat 1
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 0
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 0
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 0
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 0
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 0
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 0
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 0
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 0
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 0
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 0
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 0
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 0
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 1
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 400
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 1
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 400
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 1
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 400
g_movement_highspeed_q3_compat 0
sv_jumpspeedcap_max 0.5
sv_jumpspeedcap_max_disable_on_ramps 1
g_teleport_maxspeed 600
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 400
// needed for correct q3 haste simulation
sv_jumpspeedcap_max ""
sv_jumpspeedcap_max_disable_on_ramps 1
g_teleport_maxspeed 0
+g_teleport_minspeed 0
sv_track_canjump 0
sv_gameplayfix_stepdown_maxspeed 400
g_movement_highspeed_q3_compat 0
this.colormap = 1024 + autocvar_cl_forcemyplayercolors;
else if (autocvar_cl_forceuniqueplayercolors && !islocalplayer && !gametype.m_1v1)
{
- // Assign each enemy unique colors
+ // Assign each enemy an unique color combination
// pick colors from 0 to 14 since 15 is the rainbow color
// pl01 0 1, pl02 1 2, ..., pl14 13 14, pl15 14 0
// pl16 0 2, pl17 1 3, ..., pl29 13 0, pl30 14 1
- int num = this.entnum - 1;
+ int num;
+ if (this.isplayermodel & ISPLAYER_CLIENT)
+ num = this.entnum - 1;
+ else
+ num = this.sv_entnum - 1;
int c1 = num % 15;
int q = floor(num / 15);
int c2 = (c1 + 1 + q) % 15;
.float anim_start_time; // reusing for bob waveform synchronisation
.vector angles_held; // reusing for (re)storing original angles
.float wait, delay, pointtime; // reusing for despawn effects
+.vector m_mins, m_maxs; // reusing for storing standard bbox (same purpose as in SVQC itemdef)
HashMap ENT_CLIENT_ITEM_simple;
STATIC_INIT(ENT_CLIENT_ITEM_simple)
LOG_WARNF("this.model is unset for item %s", this.classname);
precache_model(this.model);
_setmodel(this, this.model);
- setsize(this, '-16 -16 0', '16 16 48');
- // bones_was_here TODO: network proper box size for sv_legacy_bbox_expand 0
+ setsize(this, this.m_mins, this.m_maxs);
}
void ItemDraw(entity this)
if (bobheight != this.origin_z - this.oldorigin_z)
{
this.origin_z = this.oldorigin_z + bobheight;
- this.mins_z = 0 - bobheight; // don't want the absmin and absmax to bob
- this.maxs_z = 48 - bobheight;
- // bones_was_here TODO: network proper box size for sv_legacy_bbox_expand 0
+ this.mins_z = this.m_mins.z - bobheight; // don't want the absmin and absmax to bob
+ this.maxs_z = this.m_maxs.z - bobheight;
}
// set alpha based on distance
this.angles = this.angles_held = ReadAngleVector();
}
- /* bones_was_here TODO: network proper box size for sv_legacy_bbox_expand 0
- if(sf & ISF_SIZE)
- {
- setsize(this, '-16 -16 0', '16 16 48');
- }
- */
-
if(sf & ISF_STATUS) // need to read/write status first so model can handle simple, fb etc.
{
int prevItemStatus = this.ItemStatus;
}
}
- if(sf & ISF_MODEL)
+ if(sf & ISF_SIZE || sf & ISF_SIZE2) // always true when it's spawned (in CSQC's perspective)
{
if(isnew)
{
this.entremove = ItemRemove;
}
+ if(sf & ISF_SIZE && !(sf & ISF_SIZE2)) // Small
+ {
+ this.m_mins = ITEM_S_MINS;
+ this.m_maxs = ITEM_S_MAXS;
+ }
+ else if(!(sf & ISF_SIZE) && sf & ISF_SIZE2) // Large
+ {
+ this.m_mins = ITEM_D_MINS;
+ this.m_maxs = ITEM_L_MAXS;
+ }
+ else // Default
+ {
+ this.m_mins = ITEM_D_MINS;
+ this.m_maxs = ITEM_D_MAXS;
+ }
+
this.fade_end = ReadShort();
strcpy(this.mdl, ReadString());
SET_ONGROUND(this); // extra overkill
}
- if(sf & ISF_REMOVEFX && !(sf & ISF_SIZE) && !(sf & ISF_MODEL)) // TODO !isnew isn't reliable for this... are we double sending initialisations?
+ if(sf & ISF_REMOVEFX && !(sf & ISF_SIZE) && !(sf & ISF_SIZE2)) // TODO !isnew isn't reliable for this... are we double sending initialisations?
{
// no longer available to pick up, about to be removed
if (this.drawmask) // this.alpha > 0
#include <client/shownames.qh>
#include <client/view.qh>
#include <client/weapons/projectile.qh>
+#include <common/checkextension.qh>
#include <common/deathtypes/all.qh>
#include <common/effects/all.inc>
#include <common/effects/all.qh>
LOG_TRACEF("^4CSQC Build information: ^1%s", WATERMARK);
#endif
+ CheckEngineExtensions();
+
{
int i = 0;
for ( ; i < 255; ++i)
noref float autocvar_net_connecttimeout = 30;
+#include "checkextension.qc"
+
#ifdef GAMEQC
#include "anim.qc"
#include "animdecide.qc"
--- /dev/null
+#include "checkextension.qh"
+
+#ifdef GAMEQC
+entity findbox_tofield_Fallback(vector mins, vector maxs, .entity tofield)
+{
+ // 0.03125 minimum radius because findradius returns no results if radius is zero
+ // but findbox for a zero-sized box returns entities touching the specified point
+ entity chain = findradius_tofield(0.5 * (mins + maxs), max(0.03125, 0.5 * vlen(maxs - mins)), tofield);
+ entity prev = NULL;
+ for (entity e = chain; e; e = e.tofield)
+ {
+ if (boxesoverlap(e.absmin, e.absmax, mins, maxs))
+ prev = e;
+ else // not in box so remove from chain
+ {
+ if (prev)
+ prev.tofield = e.tofield;
+ else
+ chain = chain.tofield;
+ }
+ }
+ return chain;
+}
+entity findbox_Fallback(vector mins, vector maxs)
+{
+ return findbox_tofield_Fallback(mins, maxs, chain);
+}
+#endif // GAMEQC
+
+void CheckEngineExtensions(void)
+{
+ if (!cvar("pr_checkextension"))
+ LOG_FATAL("Engine lacks the QC extension system.");
+
+ if (!checkextension("DP_QC_URI_GET") || !checkextension("DP_QC_URI_POST"))
+ LOG_WARN("Engine lacks HTTP support, XonStat and map downloads are unavailable.");
+
+ if (!checkextension("DP_CRYPTO"))
+ LOG_WARN("Engine lacks DP_CRYPTO, Player IDs (required for XonStat and CTS/CTF records) are unavailable.");
+
+#ifdef SVQC // change to GAMEQC if/when we use nudgeoutofsolid in CSQC
+ if (!checkextension("DP_QC_NUDGEOUTOFSOLID"))
+ {
+ LOG_WARN("Engine lacks DP_QC_NUDGEOUTOFSOLID, falling back to WarpZoneLib_MoveOutOfSolid().");
+ // DP_QC_NUDGEOUTOFSOLID fixes many cases WarpZoneLib_MoveOutOfSolid() can't, usually in less CPU time
+ nudgeoutofsolid = WarpZoneLib_MoveOutOfSolid;
+ }
+#endif
+
+#ifdef GAMEQC
+ if (!checkextension("DP_QC_FINDBOX"))
+ {
+ LOG_WARN("Engine lacks DP_QC_FINDBOX, performance will be suboptimal.");
+ findbox = findbox_Fallback;
+ findbox_tofield = findbox_tofield_Fallback;
+ }
+#endif
+
+ // TODO: add proper warns/errors for other extensions we depend on
+}
--- /dev/null
+#pragma once
+
+void CheckEngineExtensions(void);
const int FL_MONSTER = 32; /* BIT(4) */
const int FL_GODMODE = 64; /* BIT(5) */ // player cheat
const int FL_NOTARGET = 128; /* BIT(6) */ // player cheat
-const int FL_ITEM = 256; /* BIT(7) */ // extra wide size for bonus items
+const int FL_ITEM = 256; /* BIT(7) */ // extra wide size for bonus items IF sv_legacy_bbox_expand is 1
const int FL_ONGROUND = 512; /* BIT(8) */ // standing on something
const int FL_PARTIALGROUND = 1024; /* BIT(9) */ // not all corners are valid
const int FL_WATERJUMP = 2048; /* BIT(10) */ // player jumping out of water
// bits above 14 are defined in lib/csqcmodel/common.qh
#define CSQCMODEL_EXTRAPROPERTIES \
CSQCMODEL_PROPERTY(BIT(0), int, ReadShort, WriteShort, colormap) \
+ CSQCMODEL_IF(!isplayer) \
+ CSQCMODEL_PROPERTY(BIT(0), int, ReadByte, WriteByte, sv_entnum) \
+ CSQCMODEL_ENDIF \
CSQCMODEL_PROPERTY(BIT(1), int, ReadInt24_t, WriteInt24_t, effects) \
CSQCMODEL_PROPERTY(BIT(2), int, ReadByte, WriteByte, modelflags) \
CSQCMODEL_PROPERTY(BIT(2), int, ReadByte, WriteByte, skin) \
#include "sv_clanarena.qh"
float autocvar_g_ca_damage2score = 100;
-bool autocvar_g_ca_prevent_stalemate;
+int autocvar_g_ca_prevent_stalemate;
float autocvar_g_ca_start_health = 200;
float autocvar_g_ca_start_armor = 200;
int CA_PreventStalemate()
{
- //LOG_INFO("PreventStalemate running");
- int winnerTeam = 0;
- int secondTeam = 0;
+ //bprint("PreventStalemate running\n");
- for(int i = 1; i <= AVAILABLE_TEAMS; i++)
+ // g_ca_prevent_stalemate:
+ // Run survivor count check with 1 aka bit 0b0001
+ // Run total health check with 2 aka bit 0b0010
+ // With a value like 3 which has both bits both are ran
+
+ bool prevent_stalemate_by_survivors = (autocvar_g_ca_prevent_stalemate & BIT(0));
+ bool prevent_stalemate_by_health = (autocvar_g_ca_prevent_stalemate & BIT(1));
+
+ // Check which team has more alive players
+ if (prevent_stalemate_by_survivors)
{
- if(!winnerTeam || Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) > Team_GetNumberOfAlivePlayers(Team_GetTeam(winnerTeam)))
+ int winnerTeam = 0;
+ int secondTeam = 0;
+
+ for(int i = 1; i <= AVAILABLE_TEAMS; ++i)
{
- secondTeam = winnerTeam;
- winnerTeam = Team_IndexToTeam(i);
+ if(!winnerTeam || Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) > Team_GetNumberOfAlivePlayers(Team_GetTeam(winnerTeam)))
+ {
+ secondTeam = winnerTeam;
+ winnerTeam = Team_IndexToTeam(i);
+ }
+ else
+ {
+ if(!secondTeam || Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) > Team_GetNumberOfAlivePlayers(Team_GetTeam(secondTeam)))
+ secondTeam = Team_IndexToTeam(i);
+ }
}
- else
+
+ if(Team_GetNumberOfAlivePlayers(Team_GetTeam(winnerTeam)) != Team_GetNumberOfAlivePlayers(Team_GetTeam(secondTeam)))
{
- if(!secondTeam || Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) > Team_GetNumberOfAlivePlayers(Team_GetTeam(secondTeam)))
- secondTeam = Team_IndexToTeam(i);
+ bprint(sprintf("Stalemate broken by alive players. Best team: %s%s (%d)^7 - Trailing team: %s%s (%d)\n",
+ Team_ColorCode(winnerTeam), Team_ColorName(winnerTeam), Team_GetNumberOfAlivePlayers(Team_GetTeam(winnerTeam)),
+ Team_ColorCode(secondTeam), Team_ColorName(secondTeam), Team_GetNumberOfAlivePlayers(Team_GetTeam(secondTeam))));
+ return winnerTeam;
}
}
- if(Team_GetNumberOfAlivePlayers(Team_GetTeam(winnerTeam)) != Team_GetNumberOfAlivePlayers(Team_GetTeam(secondTeam)))
+ // Check which team has more health
+ if (prevent_stalemate_by_health)
{
- LOG_INFOF("Stalemate broken by alive players. Best team: %s%s (%d)^7 - Trailing team: %s%s (%d)",
- Team_ColorCode(winnerTeam), Team_ColorName(winnerTeam), Team_GetNumberOfAlivePlayers(Team_GetTeam(winnerTeam)),
- Team_ColorCode(secondTeam), Team_ColorName(secondTeam), Team_GetNumberOfAlivePlayers(Team_GetTeam(secondTeam)));
- return winnerTeam;
- }
-
- // Equality. Let's check which team has more health now
- //LOG_INFO("Equality. Checking health now.");
- winnerTeam = 0;
- secondTeam = 0;
- int winnerTeamHealth = 0;
- int secondTeamHealth = 0;
- int teamIndex, teamHealth;
+ int winnerTeam = 0;
+ int secondTeam = 0;
+ int winnerTeamHealth = 0;
+ int secondTeamHealth = 0;
+ int teamIndex, teamHealth;
- for(int i = 1; i <= AVAILABLE_TEAMS; i++)
- {
- teamIndex = i;
- teamHealth = 0;
-
- // Add up health for the players in this team
- FOREACH_CLIENT(IS_PLAYER(it) && Entity_HasValidTeam(it) && it.team == Team_IndexToTeam(teamIndex),
+ for(int i = 1; i <= AVAILABLE_TEAMS; ++i)
{
- if (IS_DEAD(it))
- continue;
- teamHealth += GetResource(it, RES_HEALTH) + GetResource(it, RES_ARMOR);
- });
+ teamIndex = i;
+ teamHealth = 0;
- // Set the winner teams
- if(!winnerTeam || teamHealth > winnerTeamHealth)
- {
- secondTeam = winnerTeam;
- secondTeamHealth = winnerTeamHealth;
- winnerTeam = Team_IndexToTeam(i);
- winnerTeamHealth = teamHealth;
- }
- else
- {
- if(!secondTeam || teamHealth > secondTeamHealth)
+ // Add up health for the players in this team
+ FOREACH_CLIENT(IS_PLAYER(it) && Entity_HasValidTeam(it) && it.team == Team_IndexToTeam(teamIndex),
+ {
+ if (IS_DEAD(it))
+ continue;
+ teamHealth += GetResource(it, RES_HEALTH) + GetResource(it, RES_ARMOR);
+ });
+
+ // Set the winner teams
+ if(!winnerTeam || teamHealth > winnerTeamHealth)
+ {
+ secondTeam = winnerTeam;
+ secondTeamHealth = winnerTeamHealth;
+ winnerTeam = Team_IndexToTeam(i);
+ winnerTeamHealth = teamHealth;
+ }
+ else
{
- secondTeam = Team_IndexToTeam(i);
- secondTeamHealth = teamHealth;
+ if(!secondTeam || teamHealth > secondTeamHealth)
+ {
+ secondTeam = Team_IndexToTeam(i);
+ secondTeamHealth = teamHealth;
+ }
}
}
- }
- if(winnerTeamHealth != secondTeamHealth)
- {
- LOG_INFOF("Stalemate broken by team health. Best team: %s%s (%d)^7 - Trailing team: %s%s (%d)",
- Team_ColorCode(winnerTeam), Team_ColorName(winnerTeam), winnerTeamHealth,
- Team_ColorCode(secondTeam), Team_ColorName(secondTeam), secondTeamHealth);
- return winnerTeam;
+ if(winnerTeamHealth != secondTeamHealth)
+ {
+ bprint(sprintf("Stalemate broken by team health. Best team: %s%s (%d)^7 - Trailing team: %s%s (%d)\n",
+ Team_ColorCode(winnerTeam), Team_ColorName(winnerTeam), winnerTeamHealth,
+ Team_ColorCode(secondTeam), Team_ColorName(secondTeam), secondTeamHealth));
+ return winnerTeam;
+ }
}
- else
- return -2; // Equality. Can't avoid the stalemate.
+
+ return -2; // Equality. Can't avoid the stalemate.
}
float CA_CheckWinner()
if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
{
- if(autocvar_g_ca_prevent_stalemate)
+ // attempt to prevent stalemate by survivor count AND/OR total team health?
+ bool prevent_stalemate_by_survivors = (autocvar_g_ca_prevent_stalemate & BIT(0));
+ bool prevent_stalemate_by_health = (autocvar_g_ca_prevent_stalemate & BIT(1));
+
+ if(prevent_stalemate_by_survivors || prevent_stalemate_by_health)
winner_team = CA_PreventStalemate();
else
winner_team = -2;
tracebox(player.origin - FLAG_DROP_OFFSET, flag.m_mins, flag.m_maxs, player.origin + FLAG_DROP_OFFSET, MOVE_NOMONSTERS, flag);
flag.solid = SOLID_TRIGGER; // before setorigin to ensure area grid linking
setorigin(flag, trace_endpos);
+ if (trace_startsolid && !nudgeoutofsolid(flag)) // TODO: trace_allsolid would perform better but isn't 100% reliable yet
+ {
+ // the flag's bbox doesn't fit but we can assume the player's current bbox does
+ tracebox(player.origin - FLAG_DROP_OFFSET, player.mins, player.maxs, player.origin + FLAG_DROP_OFFSET, MOVE_NOMONSTERS, flag);
+ flag.origin = trace_endpos;
+ setsize(flag, player.mins, player.maxs); // this allows physics to move the flag somewhere its think func can resize it
+ }
flag.owner.flagcarried = NULL;
GameRules_scoring_vip(flag.owner, false);
flag.owner = NULL;
// sanity checks
if(this.mins != this.m_mins || this.maxs != this.m_maxs) { // reset the flag boundaries in case it got squished
- LOG_TRACE("wtf the flag got squashed?");
tracebox(this.origin, this.m_mins, this.m_maxs, this.origin, MOVE_NOMONSTERS, this);
if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
setsize(this, this.m_mins, this.m_maxs);
// appearence
_setmodel(flag, flag.model); // precision set below
- setsize(flag, CTF_FLAG.m_mins * flag.scale, CTF_FLAG.m_maxs * flag.scale);
+ // 0.8.6 with sv_legacy_bbox_expand 1 did this FL_ITEM expansion in DP
+ setsize(flag, CTF_FLAG.m_mins * flag.scale - '15 15 1', CTF_FLAG.m_maxs * flag.scale + '15 15 1');
flag.m_mins = flag.mins; // store these for squash checks
flag.m_maxs = flag.maxs;
setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
if(!this.flags & FL_ITEM)
IL_PUSH(g_items, this);
this.flags = FL_ITEM;
- setsize(this, '-32 -32 -32', '32 32 32');
+ setsize(this, '-48 -48 -32', '48 48 32'); // 0.8.6 used '-32 -32 -32', '32 32 32' with sv_legacy_bbox_expand 1 and FL_ITEM
setorigin(this, this.origin + '0 0 20');
droptofloor(this);
IL_PUSH(g_damagedbycontents, ball);
ball.effects &= ~EF_NODRAW;
setorigin(ball, player.origin + '0 0 10');
+ nudgeoutofsolid(ball); // a ball has a horizontally bigger bbox than a player
ball.velocity = '0 0 200' + '0 100 0'*crandom() + '100 0 0'*crandom();
ball.owner = NULL;
navigation_dynamicgoal_set(ball, player);
entity e = new(keepawayball);
setmodel(e, MDL_KA_BALL);
e.solid = SOLID_TRIGGER; // before setsize to ensure area grid linking
- setsize(e, '-16 -16 -20', '16 16 20'); // 20 20 20 was too big, player is only 16 16 24... gotta cheat with the Z (20) axis so that the particle isn't cut off
+ // 20 20 20 was too big, player is only 16 16 24... gotta cheat with the Z (20) axis so that the particle isn't cut off
+ // bones_was_here: that was WITH sv_legacy_bbox_expand 1 and FL_ITEM (mins -= '15 15 1'; maxs += '15 15 1')
+ // it's round so should have a symmetrical bbox, same height as pickup items so it can't be jumped over in any physics
+ setsize(e, '-24 -24 -24', '24 24 24');
e.damageforcescale = autocvar_g_keepawayball_damageforcescale;
e.takedamage = DAMAGE_YES;
e.event_damage = ka_DamageEvent;
#endif
const float KH_KEY_WP_ZSHIFT = 20;
-const vector KH_KEY_MIN = '-10 -10 -46';
-const vector KH_KEY_MAX = '10 10 3';
+const vector KH_KEY_MIN = '-25 -25 -46'; // 0.8.6 used '-10 -10 -46' with sv_legacy_bbox_expand 1 and FL_ITEM
+const vector KH_KEY_MAX = '25 25 4'; // 0.8.6 used '10 10 3' with sv_legacy_bbox_expand 1 and FL_ITEM
const float KH_KEY_BRIGHTNESS = 2;
bool kh_no_radar_circles;
if(!IL_CONTAINS(g_items, key))
IL_PUSH(g_items, key);
set_movetype(key, MOVETYPE_TOSS);
+ nudgeoutofsolid(key); // a key has a bigger bbox than a player
key.pain_finished = time + autocvar_g_balance_keyhunt_delay_return;
key.damageforcescale = autocvar_g_balance_keyhunt_damageforcescale;
key.takedamage = DAMAGE_YES;
#include "survival.qh"
+
+#ifdef GAMEQC
REGISTER_NET_LINKED(ENT_CLIENT_SURVIVALSTATUSES)
+#endif
ball.takedamage = DAMAGE_YES;
ball.effects &= ~EF_NODRAW;
setorigin(ball, player.origin + '0 0 10');
+ nudgeoutofsolid(ball); // a ball has a horizontally bigger bbox than a player
ball.velocity = '0 0 200' + '0 100 0'*crandom() + '100 0 0'*crandom();
ball.owner = NULL;
navigation_dynamicgoal_set(ball, player);
{
entity e = new(keepawayball);
setmodel(e, MDL_TKA_BALL);
- setsize(e, '-16 -16 -20', '16 16 20'); // 20 20 20 was too big, player is only 16 16 24... gotta cheat with the Z (20) axis so that the particle isn't cut off
+ // 20 20 20 was too big, player is only 16 16 24... gotta cheat with the Z (20) axis so that the particle isn't cut off
+ // bones_was_here: that was WITH sv_legacy_bbox_expand 1 and FL_ITEM (mins -= '15 15 1'; maxs += '15 15 1')
+ // it's round so should have a symmetrical bbox, same height as pickup items so it can't be jumped over in any physics
+ setsize(e, '-24 -24 -24', '24 24 24');
e.damageforcescale = autocvar_g_tkaball_damageforcescale;
e.takedamage = DAMAGE_YES;
e.solid = SOLID_TRIGGER;
// item networking
const int ISF_REMOVEFX = BIT(0); // technically unnecessary (after the kludge in Item_Think() is reverted), but cheaper and cleaner than using ITS_AVAILABLE
const int ISF_LOCATION = BIT(1);
-const int ISF_MODEL = BIT(2);
+const int ISF_SIZE2 = BIT(2);
const int ISF_STATUS = BIT(3);
const int ISF_COLORMAP = BIT(4);
const int ISF_DROP = BIT(5);
// FIXME but updating faster applies the kludge in Item_Think() sooner so it's less noticeable
const float IT_UPDATE_INTERVAL = 0.0625;
+// item bboxes for sv_legacy_bbox_expand 0
+// Small, Default and Large (large maxs are used with default mins)
+const vector ITEM_S_MINS = '-24 -24 0';
+const vector ITEM_S_MAXS = '24 24 48';
+const vector ITEM_D_MINS = '-30 -30 0'; // 0.8.6 set '-16 -16 0' then DP subtracted '15 15 1' but NetRadiant used '-30 -30 0'
+const vector ITEM_D_MAXS = '30 30 48'; // 0.8.6 set '16 16 48' then DP added '15 15 1' but NetRadiant used '30 30 32'
+const vector ITEM_L_MAXS = '30 30 70'; // 0.8.6 set '16 16 80' for powerups, '16 16 70' for megas, '16 16 60' for buffs
+
.float fade_start;
.float fade_end;
#include "pickup.qh"
CLASS(Armor, Pickup)
#ifdef SVQC
- ATTRIB(Armor, m_mins, vector, '-16 -16 0');
- ATTRIB(Armor, m_maxs, vector, '16 16 48');
ATTRIB(Armor, m_pickupevalfunc, float(entity player, entity item), healtharmor_pickupevalfunc);
ATTRIB(Armor, m_botvalue, int, 5000);
#endif
this.m_icon = "armor"; // compatible with Xonotic v0.8.2 or lower
#endif
#ifdef SVQC
+ this.m_mins = ITEM_S_MINS;
+ this.m_maxs = ITEM_S_MAXS;
this.m_itemid = IT_RESOURCE;
this.m_respawntime = GET(g_pickup_respawntime_armor_small);
this.m_respawntimejitter = GET(g_pickup_respawntimejitter_armor_small);
this.m_icon = "armor"; // compatible with Xonotic v0.8.2 or lower
#endif
#ifdef SVQC
+ this.m_mins = ITEM_S_MINS;
+ this.m_maxs = ITEM_S_MAXS;
this.m_itemid = IT_RESOURCE;
this.m_respawntime = GET(g_pickup_respawntime_armor_medium);
this.m_respawntimejitter = GET(g_pickup_respawntimejitter_armor_medium);
this.m_waypoint = _("Mega armor");
this.m_waypointblink = 2;
#ifdef SVQC
- this.m_maxs = '16 16 70';
+ this.m_maxs = ITEM_L_MAXS;
this.m_itemid = IT_RESOURCE;
this.m_respawntime = GET(g_pickup_respawntime_armor_mega);
this.m_respawntimejitter = GET(g_pickup_respawntimejitter_armor_mega);
#include "pickup.qh"
CLASS(Health, Pickup)
#ifdef SVQC
- ATTRIB(Health, m_mins, vector, '-16 -16 0');
- ATTRIB(Health, m_maxs, vector, '16 16 48');
ATTRIB(Health, m_pickupevalfunc, float(entity player, entity item), healtharmor_pickupevalfunc);
ATTRIB(Health, m_botvalue, int, 5000);
#endif
this.m_icon = "health"; // compatible with Xonotic v0.8.2 or lower
#endif
#ifdef SVQC
+ this.m_mins = ITEM_S_MINS;
+ this.m_maxs = ITEM_S_MAXS;
this.m_itemid = IT_RESOURCE;
this.m_respawntime = GET(g_pickup_respawntime_health_small);
this.m_respawntimejitter = GET(g_pickup_respawntimejitter_health_small);
this.m_icon = "health"; // compatible with Xonotic v0.8.2 or lower
#endif
#ifdef SVQC
+ this.m_mins = ITEM_S_MINS;
+ this.m_maxs = ITEM_S_MAXS;
this.m_itemid = IT_RESOURCE;
this.m_respawntime = GET(g_pickup_respawntime_health_medium);
this.m_respawntimejitter = GET(g_pickup_respawntimejitter_health_medium);
this.m_waypoint = _("Mega health");
this.m_waypointblink = 2;
#ifdef SVQC
- this.m_maxs = '16 16 70';
+ this.m_maxs = ITEM_L_MAXS;
this.m_itemid = IT_RESOURCE;
this.m_respawntime = GET(g_pickup_respawntime_health_mega);
this.m_respawntimejitter = GET(g_pickup_respawntimejitter_health_mega);
}
ATTRIB(Pickup, m_itemid, int, 0);
#ifdef SVQC
- ATTRIB(Pickup, m_mins, vector, '-16 -16 0');
- ATTRIB(Pickup, m_maxs, vector, '16 16 48');
+ ATTRIB(Pickup, m_mins, vector, ITEM_D_MINS);
+ ATTRIB(Pickup, m_maxs, vector, ITEM_D_MAXS);
ATTRIB(Pickup, m_botvalue, int, 0);
ATTRIB(Pickup, m_itemflags, int, 0);
float generic_pickupevalfunc(entity player, entity item);
{
MapInfo_Map_flags |= MAPINFO_FLAG_FRUSTRATING;
}
- else if(t == "noautomaplist")
+ else if(t == "donotwant" || t == "noautomaplist")
{
- MapInfo_Map_flags |= MAPINFO_FLAG_NOAUTOMAPLIST;
+ MapInfo_Map_flags |= MAPINFO_FLAG_DONOTWANT;
}
else if(t == "gameversion_min")
{
if (cvar("gameversion") < stof(s))
- MapInfo_Map_flags |= MAPINFO_FLAG_NOAUTOMAPLIST;
+ MapInfo_Map_flags |= MAPINFO_FLAG_DONOTWANT;
}
else if(t == "type")
{
REGISTRY(Gametypes, 32)
REGISTER_REGISTRY(Gametypes)
-REGISTRY_SORT(Gametypes);
+REGISTRY_SORT(Gametypes)
REGISTRY_CHECK(Gametypes)
REGISTRY_DEFINE_GET(Gametypes, NULL)
const int MAPINFO_FLAG_HIDDEN = 1; // not in lsmaps/menu/vcall/etc., can just be changed to manually
const int MAPINFO_FLAG_FORBIDDEN = 2; // don't even allow the map by a cvar setting that allows hidden maps
const int MAPINFO_FLAG_FRUSTRATING = 4; // this map is near impossible to play, enable at your own risk
-const int MAPINFO_FLAG_NOAUTOMAPLIST = 8; // do not include when automatically building maplist (counts as hidden for maplist building purposes)
+const int MAPINFO_FLAG_DONOTWANT = 8; // do not include in GUI voting screen or select in GotoNextMap()/GetNextMap(), unless added with `suggestmap` or required as a fallback
float MapInfo_count;
{
FOREACH_ENTITY_RADIUS((this.absmin + this.absmax) * 0.5, vlen(this.absmax - this.absmin) * 0.5 + 1, it.conveyor.active == ACTIVE_NOT && isPushable(it),
{
- vector emin = it.absmin;
- vector emax = it.absmax;
- if(this.solid == SOLID_BSP)
+ if (WarpZoneLib_ExactTrigger_Touch(this, it, false))
{
- emin -= '1 1 1';
- emax += '1 1 1';
+ if(!it.conveyor)
+ IL_PUSH(g_conveyed, it);
+ it.conveyor = this;
}
- if(boxesoverlap(emin, emax, this.absmin, this.absmax)) // quick
- if(WarpZoneLib_BoxTouchesBrush(emin, emax, this, it)) // accurate
- {
- if(!it.conveyor)
- IL_PUSH(g_conveyed, it);
- it.conveyor = this;
- }
});
IL_EACH(g_conveyed, it.conveyor == this,
{
bool reverse = false;
if((this.spawnflags & DOOR_CRUSH)
+ && !Q3COMPAT_COMMON
#ifdef SVQC
&& (blocker.takedamage != DAMAGE_NO)
#elif defined(CSQC)
else
{
#ifdef SVQC
- if((this.dmg) && (blocker.takedamage == DAMAGE_YES)) // Shall we bite?
+ if (this.dmg && blocker.takedamage != DAMAGE_NO) // Shall we bite?
Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
#endif
- // don't change direction for dead or dying stuff
- if(IS_DEAD(blocker)
+ // don't change direction for dead or dying stuff
+ if (!IS_DEAD(blocker)
#ifdef SVQC
- && (blocker.takedamage == DAMAGE_NO)
+ && blocker.takedamage != DAMAGE_NO
#endif
+ && this.wait >= 0
+ && !(Q3COMPAT_COMMON && (this.spawnflags & Q3_DOOR_CRUSHER))
)
{
- if (this.wait >= 0)
+ if (this.state == STATE_DOWN)
{
- if (this.state == STATE_DOWN)
- {
- if (this.classname == "door")
- door_go_up(this, NULL, NULL);
- else
- door_rotating_go_up(this, blocker);
- }
+ if (this.classname == "door")
+ door_go_up(this, NULL, NULL);
else
- {
- if (this.classname == "door")
- door_go_down(this);
- else
- door_rotating_go_down(this);
- }
- reverse = true;
+ door_rotating_go_up(this, blocker);
}
+ else
+ {
+ if (this.classname == "door")
+ door_go_down(this);
+ else
+ door_rotating_go_down(this);
+ }
+ reverse = true;
}
#ifdef SVQC
else
{
//gib dying stuff just to make sure
- if((this.dmg) && (blocker.takedamage != DAMAGE_NO)) // Shall we bite?
+ if (this.dmg && blocker.takedamage != DAMAGE_NO && IS_DEAD(blocker)) // Shall we bite?
Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
}
#endif
}
- if (!reverse && this.classname == "door")
+ // if we didn't change direction and are using a non-linear movement controller, we must pause it
+ if (!reverse && this.classname == "door" && this.move_controller)
SUB_CalcMovePause(this);
}
return false;
}
-void door_fire(entity this, entity actor, entity trigger)
+void door_use(entity this, entity actor, entity trigger)
{
- if (this.owner != this)
- objerror (this, "door_fire: this.owner != this");
+ //dprint("door_use (model: ");dprint(this.model);dprint(")\n");
+
+ if (!this.owner)
+ return;
+ this = this.owner;
if (this.spawnflags & DOOR_TOGGLE)
{
} while ((e != this) && (e != NULL));
}
-void door_use(entity this, entity actor, entity trigger)
-{
- //dprint("door_use (model: ");dprint(this.model);dprint(")\n");
-
- if (this.owner)
- door_fire(this.owner, actor, trigger);
-}
-
void door_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
{
if(this.spawnflags & NOSPLASH)
#endif
return;
- if (time < this.door_finished)
+ if (this.owner.state == STATE_UP)
return;
// check if door is locked
if (!door_check_keys(this, toucher))
return;
- this.door_finished = time + 1;
+ if (this.owner.state == STATE_TOP)
+ {
+ if (this.owner.nextthink < this.owner.ltime + this.owner.wait)
+ {
+ entity e = this.owner;
+ do {
+ e.nextthink = e.ltime + e.wait;
+ e = e.enemy;
+ } while (e != this.owner);
+ }
+ return;
+ }
door_use(this.owner, toucher, NULL);
}
entity LinkDoors_nextent(entity cur, entity near, entity pass)
{
- while((cur = find(cur, classname, pass.classname)) && ((cur.spawnflags & DOOR_DONT_LINK) || cur.enemy))
+ while ((cur = find(cur, classname, pass.classname))
+ && ((!Q3COMPAT_COMMON && (cur.spawnflags & DOOR_DONT_LINK)) || cur.enemy))
{
}
return cur;
bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
{
+ if(Q3COMPAT_COMMON)
+ return e1.team == e2.team;
+
float DELTA = 4;
if((e1.absmin_x > e2.absmax_x + DELTA)
|| (e1.absmin_y > e2.absmax_y + DELTA)
if (this.enemy)
return; // already linked by another door
- if (this.spawnflags & DOOR_DONT_LINK)
+
+ // Q3 door linking is done for teamed doors only and is not affected by spawnflags or bmodel proximity
+ if ((!Q3COMPAT_COMMON && (this.spawnflags & DOOR_DONT_LINK)) || (Q3COMPAT_COMMON && !this.team))
{
this.owner = this.enemy = this;
"speed" movement speed (100 default)
"wait" wait before returning (3 default, -1 = never return)
"lip" lip remaining at end of move (8 default)
-"dmg" damage to inflict when blocked (2 default)
+"dmg" damage to inflict when blocked (0 default)
"sounds"
0) no sound
1) stone
if (q3compat)
{
- // CPMA adds these fields for overriding the engine sounds
+ // CPMA adds these fields for overriding the Q3 default sounds
string s = GetField_fullspawndata(this, "sound_start", true);
string e = GetField_fullspawndata(this, "sound_end", true);
if (s)
this.noise2 = strzone(s);
+ else
+ {
+ // PK3s supporting Q3A sometimes include custom sounds at Q3 default paths
+ s = "sound/movers/doors/dr1_strt.wav";
+ if (FindFileInMapPack(s))
+ this.noise2 = s;
+ }
+
if (e)
this.noise1 = strzone(e);
+ else
+ {
+ e = "sound/movers/doors/dr1_end.wav";
+ if (FindFileInMapPack(e))
+ this.noise1 = e;
+ }
}
// sound when door stops moving
}
else if (!this.wait)
{
- this.wait = 3;
+ this.wait = q3compat ? 2 : 3;
}
if (!this.lip)
if(this.spawnflags & DOOR_NONSOLID)
this.solid = SOLID_NOT;
-// DOOR_START_OPEN is to allow an entity to be lighted in the closed position
-// but spawn in the open position
+ // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
+ // but spawn in the open position
+ // the tuba door on xoylent requires the delayed init
if (this.spawnflags & DOOR_START_OPEN)
InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION);
door_init_shared(this);
this.pos1 = this.origin;
- this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip);
+ vector absmovedir;
+ absmovedir.x = fabs(this.movedir.x);
+ absmovedir.y = fabs(this.movedir.y);
+ absmovedir.z = fabs(this.movedir.z);
+ this.pos2 = this.pos1 + this.movedir * (absmovedir * this.size - this.lip);
if(autocvar_sv_doors_always_open)
{
this.speed = 100;
}
+ if (q3compat)
+ {
+ if (!this.dmg)
+ this.dmg = 2;
+
+ if (!this.team)
+ {
+ string t = GetField_fullspawndata(this, "team");
+ // bones_was_here: same hack as used to support teamed items on Q3 maps
+ if (t) this.team = crc16(false, t);
+ }
+ }
+
settouch(this, door_touch);
-// LinkDoors can't be done until all of the doors have been spawned, so
-// the sizes can be detected properly.
+ // LinkDoors can't be done until all of the doors have been spawned, so
+ // the sizes can be detected properly.
InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS);
this.reset = door_reset;
bool autocvar_sv_doors_always_open;
#endif
-const int DOOR_START_OPEN = BIT(0);
+const int DOOR_START_OPEN = BIT(0); // has same meaning in Q3: reverse position 1 and 2
const int DOOR_DONT_LINK = BIT(2);
const int SPAWNFLAGS_GOLD_KEY = BIT(3); // Quake 1 compat, can only be used with func_door!
const int SPAWNFLAGS_SILVER_KEY = BIT(4); // Quake 1 compat, can only be used with func_door!
const int DOOR_NONSOLID = BIT(10);
const int DOOR_CRUSH = BIT(11); // can't use CRUSH cause that is the same as DOOR_DONT_LINK
+#define Q3_DOOR_CRUSHER BIT(2) // in Q3 this disables the auto reverse so the blocking player takes damage every frame
#ifdef CSQC
// stuff for preload
FOREACH_ENTITY_RADIUS((this.absmin + this.absmax) * 0.5, vlen(this.absmax - this.absmin) * 0.5 + 1, !it.ladder_entity && IS_PLAYER(it) && it.move_movetype != MOVETYPE_NOCLIP && !IS_DEAD(it),
{
- vector emin = it.absmin;
- vector emax = it.absmax;
- if(this.solid == SOLID_BSP || (IS_CSQC && this.solid == SOLID_TRIGGER)) // CSQC doesn't expand properly
+ if (WarpZoneLib_ExactTrigger_Touch(this, it, false))
{
- emin -= '1 1 1';
- emax += '1 1 1';
- }
- if(boxesoverlap(emin, emax, this.absmin, this.absmax)) // quick
- {
- if(WarpZoneLib_BoxTouchesBrush(emin, emax, this, it)) // accurate
- {
- if(!it.ladder_entity)
- IL_PUSH(g_ladderents, it);
- it.ladder_entity = this;
- }
+ if(!it.ladder_entity)
+ IL_PUSH(g_ladderents, it);
+ it.ladder_entity = this;
}
});
this.move_time = time;
this.entremove = func_ladder_remove;
- // NOTE: CSQC's version of setorigin doesn't expand
- this.absmin -= '1 1 1';
- this.absmax += '1 1 1';
-
return true;
}
#endif
void plat_delayedinit(entity this)
{
plat_link(this);
- plat_spawn_inside_trigger(this); // the "start moving" trigger
+ // Q3 uses only a truth check of .targetname to decide whether to spawn a trigger
+ if (!Q3COMPAT_COMMON || this.targetname == "")
+ plat_spawn_inside_trigger(this); // the "start moving" trigger
}
float plat_send(entity this, entity to, float sf)
spawnfunc(func_plat)
{
- if (this.spawnflags & CRUSH)
+ if (q3compat)
+ {
+ this.spawnflags = 0; // Q3 plats have no spawnflags
+ if (!this.dmg) this.dmg = 2;
+ }
+ else if (this.spawnflags & CRUSH)
{
this.dmg = 10000;
}
if (q3compat)
{
- // CPMA adds these fields for overriding the engine sounds
+ // CPMA adds these fields for overriding the Q3 default sounds
string s = GetField_fullspawndata(this, "sound_start", true);
string e = GetField_fullspawndata(this, "sound_end", true);
if (s)
this.noise = strzone(s);
+ else
+ {
+ // PK3s supporting Q3A sometimes include custom sounds at Q3 default paths
+ s = "sound/movers/plats/pt1_strt.wav";
+ if (FindFileInMapPack(s))
+ this.noise = s;
+ }
+
if (e)
this.noise1 = strzone(e);
+ else
+ {
+ e = "sound/movers/plats/pt1_end.wav";
+ if (FindFileInMapPack(e))
+ this.noise1 = e;
+ }
}
if(this.noise && this.noise != "")
setblocked(this, plat_crush);
- if (!this.speed) this.speed = 150;
- if (!this.lip) this.lip = 16;
+ if (!this.speed) this.speed = q3compat ? 200 : 150;
+ if (!this.lip) this.lip = q3compat ? 8 : 16;
if (!this.height) this.height = this.size.z - this.lip;
this.pos1 = this.origin;
set_movetype(this, MOVETYPE_PUSH);
this.move_time = time;
- plat_spawn_inside_trigger(this);
+ if (!Q3COMPAT_COMMON || this.targetname == "")
+ plat_spawn_inside_trigger(this);
}
if(sf & SF_TRIGGER_RESET)
plat_go_down(this);
}
+void plat_target_use(entity this, entity actor, entity trigger)
+{
+ if (this.state == STATE_TOP)
+ this.nextthink = this.ltime + 1;
+ else if (this.state != STATE_UP)
+ plat_go_up(this);
+}
+
// WARNING: backwards compatibility because people don't use already existing fields :(
// TODO: Check if any maps use these fields and remove these fields if it doesn't break maps
.string sound1, sound2;
void plat_reset(entity this)
{
- if(this.targetname && this.targetname != "")
+ if (this.targetname && this.targetname != "" && !Q3COMPAT_COMMON)
{
setorigin(this, this.pos1);
this.state = STATE_UP;
{
setorigin(this, this.pos2);
this.state = STATE_BOTTOM;
- this.use = plat_trigger_use;
+ this.use = (this.targetname != "" && Q3COMPAT_COMMON) ? plat_target_use : plat_trigger_use;
}
#ifdef SVQC
break;
}
+ // Q3 implements this fallback for all movers at the end of its InitMover()
+ // If .speed is negative this applies, instead of the mover-specific default speed.
+ if (traveltime <= 0)
+ traveltime = 0.001;
+
// Very short animations don't really show off the effect
// of controlled animation, so let's just use linear movement.
// Alternatively entities can choose to specify non-controlled movement.
if(!(teleporter.classname == "trigger_teleport" && (teleporter.spawnflags & TELEPORT_KEEP_SPEED)) &&
!(teleporter.classname == "target_teleporter" && (teleporter.spawnflags & TARGET_TELEPORTER_KEEP_SPEED)))
+ {
+ // test if player is slower than min
+ if(STAT(TELEPORT_MINSPEED, player))
+ if(vdist(player.velocity, <, STAT(TELEPORT_MINSPEED, player)))
+ player.velocity = normalize(player.velocity) * max(0, STAT(TELEPORT_MINSPEED, player));
+ // test if player is faster than max (or if min is above max)
if(STAT(TELEPORT_MAXSPEED, player))
if(vdist(player.velocity, >, STAT(TELEPORT_MAXSPEED, player)))
player.velocity = normalize(player.velocity) * max(0, STAT(TELEPORT_MAXSPEED, player));
+ }
locout = e.origin + '0 0 1' * (1 - player.mins.z - 24);
{
FOREACH_ENTITY_RADIUS((this.absmin + this.absmax) * 0.5, vlen(this.absmax - this.absmin) * 0.5 + 1, it.swampslug.active == ACTIVE_NOT && IS_PLAYER(it) && !IS_DEAD(it),
{
- vector emin = it.absmin;
- vector emax = it.absmax;
- if(this.solid == SOLID_BSP)
+ if (WarpZoneLib_ExactTrigger_Touch(this, it, false))
{
- emin -= '1 1 1';
- emax += '1 1 1';
+ if(!it.swampslug)
+ IL_PUSH(g_swamped, it);
+ it.swampslug = this;
}
- if(boxesoverlap(emin, emax, this.absmin, this.absmax)) // quick
- if(WarpZoneLib_BoxTouchesBrush(emin, emax, this, it)) // accurate
- {
- if(!it.swampslug)
- IL_PUSH(g_swamped, it);
- it.swampslug = this;
- }
});
IL_EACH(g_swamped, it.swampslug == this,
#if 1
FOREACH_CLIENT(!it.viewloc && IS_PLAYER(it),
{
- vector emin = it.absmin;
- vector emax = it.absmax;
- if(this.solid == SOLID_BSP)
- {
- emin -= '1 1 1';
- emax += '1 1 1';
- }
- if(boxesoverlap(emin, emax, this.absmin, this.absmax)) // quick
- {
- if(WarpZoneLib_BoxTouchesBrush(emin, emax, this, it)) // accurate
- it.viewloc = this;
- }
+ if (WarpZoneLib_ExactTrigger_Touch(this, it, false))
+ it.viewloc = this;
});
#else
-
- for(e = findradius((this.absmin + this.absmax) * 0.5, vlen(this.absmax - this.absmin) * 0.5 + 1); e; e = e.chain)
- if(!e.viewloc)
- if(IS_PLAYER(e)) // should we support non-player entities with this?
- //if(!IS_DEAD(e)) // death view is handled separately, we can't override this just yet
- {
- vector emin = e.absmin;
- vector emax = e.absmax;
- if(this.solid == SOLID_BSP)
- {
- emin -= '1 1 1';
- emax += '1 1 1';
- }
- if(boxesoverlap(emin, emax, this.absmin, this.absmax)) // quick
- if(WarpZoneLib_BoxTouchesBrush(emin, emax, this, e)) // accurate
- e.viewloc = this;
- }
+ for(e = findradius((this.absmin + this.absmax) * 0.5, vlen(this.absmax - this.absmin) * 0.5 + 1); e; e = e.chain)
+ if(!e.viewloc)
+ if(IS_PLAYER(e)) // should we support non-player entities with this?
+ //if(!IS_DEAD(e)) // death view is handled separately, we can't override this just yet
+ if (WarpZoneLib_ExactTrigger_Touch(this, it, false))
+ e.viewloc = this;
#endif
this.nextthink = time;
#include <common/mutators/mutator/waypoints/all.qh>
#endif
-#ifdef GAMEQC
-REGISTER_WAYPOINT(Buff, _("Buff"), "", '1 0.5 0', 1);
-REGISTER_RADARICON(Buff, 1);
-#endif
-
#define REGISTER_BUFF(id, inst) \
REGISTER(StatusEffect, BUFF_##id, m_id, inst)
this.skin = buff.m_skin;
setmodel(this, MDL_BUFF);
- setsize(this, BUFF_MIN, BUFF_MAX);
+ setsize(this, ITEM_D_MINS, ITEM_L_MAXS);
if(this.buff_waypoint)
{
setthink(this, buff_Think);
settouch(this, buff_Touch);
setmodel(this, MDL_BUFF);
- setsize(this, BUFF_MIN, BUFF_MAX);
+ setsize(this, ITEM_D_MINS, ITEM_L_MAXS);
this.reset = buff_Reset;
this.nextthink = time + 0.1;
this.gravity = 1;
.float buff_shield; // delay for players to keep them from spamming buff pickups
.entity buff_model; // controls effects (TODO: make csqc)
-const vector BUFF_MIN = ('-16 -16 0');
-const vector BUFF_MAX = ('16 16 60');
-
float buff_Available(entity buff);
void buff_RemoveAll(entity actor, int removal_type);
#include "damagetext.qh"
+#ifdef GAMEQC
REGISTER_NET_LINKED(damagetext)
+#endif
REGISTER_MUTATOR(itemstime, true);
+#ifdef GAMEQC
REGISTER_NET_TEMP(itemstime)
+#endif
#ifdef SVQC
void IT_Write(entity e, int i, float f) {
return EFFECT_Null;
}
-#endif
REGISTER_NET_TEMP(TE_CSQC_DARKBLINKING);
+
+#endif
+
#ifdef CSQC
#include <client/draw.qh>
#include <client/hud/hud.qh>
}
SPAWNFUNC_ITEM(item_invisibility, ITEM_Invisibility)
+SPAWNFUNC_ITEM(item_buff_invisibility, ITEM_Invisibility)
CLASS(Invisibility, Powerups)
ATTRIB(Invisibility, netname, string, "invisibility");
}
SPAWNFUNC_ITEM(item_speed, ITEM_Speed)
+SPAWNFUNC_ITEM(item_buff_speed, ITEM_Speed)
CLASS(Speed, Powerups)
ATTRIB(Speed, netname, string, "speed");
#include <common/items/item/pickup.qh>
CLASS(Powerup, Pickup)
#ifdef SVQC
- ATTRIB(Powerup, m_mins, vector, '-16 -16 0');
- ATTRIB(Powerup, m_maxs, vector, '16 16 80');
+ ATTRIB(Powerup, m_maxs, vector, ITEM_L_MAXS);
ATTRIB(Powerup, m_botvalue, int, 11000);
ATTRIB(Powerup, m_itemflags, int, FL_POWERUP);
ATTRIB(Powerup, m_respawntime, float(), GET(g_pickup_respawntime_powerup));
REGISTER_WAYPOINT(VehicleIntruder, _("Intruder!"), "", '1 1 1', 1);
REGISTER_WAYPOINT(Seeker, _("Tagged"), "", '0.5 1 0', 2);
+
+REGISTER_WAYPOINT(Buff, _("Buff"), "", '1 0.5 0', 1);
REGISTER_RADARICON(Item, 1);
REGISTER_RADARICON(Vehicle, 1);
REGISTER_RADARICON(Weapon, 1);
+REGISTER_RADARICON(Buff, 1);
#include "all.inc"
REGISTER_MUTATOR(waypointsprites, true);
+#ifdef GAMEQC
REGISTER_NET_LINKED(waypointsprites)
+#endif
#ifdef SVQC
bool WaypointSprite_SendEntity(entity this, entity to, float sendflags)
REGISTRY(Notifications, BITS(11))
REGISTER_REGISTRY(Notifications)
-REGISTRY_SORT(Notifications);
+REGISTRY_SORT(Notifications)
REGISTRY_DEFINE_GET(Notifications, NULL)
STATIC_INIT(Notifications) { FOREACH(Notifications, true, it.m_id = i); }
int save_trace_dphitq3surfaceflags = trace_dphitq3surfaceflags;
string save_trace_dphittexturename = trace_dphittexturename;
- FOREACH_ENTITY_RADIUS_ORDERED(0.5 * (this.absmin + this.absmax), 0.5 * vlen(this.absmax - this.absmin), true, {
+ vector emin = this.absmin, emax = this.absmax;
+ // Xonotic and Nexuiz maps assume triggers will be activated by adjacent players
+ // prior to sv_legacy_bbox_expand 0 DP always did this for SVQC and never for CSQC
+ // we also need this for zero-size bboxes because radius == 0 returns nothing
+ // see also: WarpZoneLib_ExactTrigger_Touch()
+ emin -= '1 1 1';
+ emax += '1 1 1';
+
+ FOREACH_ENTITY_RADIUS_ORDERED(0.5 * (this.absmin + this.absmax), 0.5 * vlen(emin - emax), true, {
if (it.solid == SOLID_TRIGGER && it != this)
if (it.move_nomonsters != MOVE_NOMONSTERS && it.move_nomonsters != MOVE_WORLDONLY)
- if (gettouch(it) && boxesoverlap(it.absmin, it.absmax, this.absmin, this.absmax))
+ if (gettouch(it) && boxesoverlap(it.absmin, it.absmax, emin, emax))
{
trace_allsolid = false;
trace_startsolid = false;
gettouch(it)(it, this);
}
- });
+ });
trace_allsolid = save_trace_allsolid;
trace_startsolid = save_trace_startsolid;
{
if(autocvar__movetype_debug)
{
- vector mi, ma;
- if(this.solid == SOLID_BSP)
- {
- // TODO set the absolute bbox
- mi = this.mins;
- ma = this.maxs;
- }
- else
- {
- mi = this.mins;
- ma = this.maxs;
- }
- mi += this.origin;
- ma += this.origin;
-
- if(this.flags & FL_ITEM)
- {
- mi -= '15 15 1';
- ma += '15 15 1';
- }
- else
- {
- mi -= '1 1 1';
- ma += '1 1 1';
- }
-
- this.absmin = mi;
- this.absmax = ma;
+ this.absmin = this.origin + this.mins;
+ this.absmax = this.origin + this.maxs;
}
else
- {
setorigin(this, this.origin); // calls SV_LinkEdict
- #ifdef CSQC
- // NOTE: CSQC's version of setorigin doesn't expand
- this.absmin -= '1 1 1';
- this.absmax += '1 1 1';
- #endif
- }
if(touch_triggers)
_Movetype_LinkEdict_TouchAreaGrid(this);
_Movetype_PushEntityTrace(this, push);
this.move_nomonsters = oldtype;
if(trace_startsolid)
+ {
+ trace_fraction = 0;
return true;
+ }
}
this.origin = trace_endpos;
float movetime = dt;
for (int bump = 0; bump < MAX_CLIP_PLANES && movetime > 0; bump++)
{
+ if(this.velocity == '0 0 0')
+ break;
+
vector move = this.velocity * movetime;
if(!_Movetype_PushEntity(this, move, true))
- return;
+ return; // teleported
if (wasfreed(this))
return;
// NOTE: this is bmodelstartsolid in the engine
- if (trace_startsolid && trace_ent.solid == SOLID_BSP)
+ if (trace_allsolid && trace_fraction == 0 && trace_ent.solid == SOLID_BSP)
{
// QC lacks pointers so we must save the old trace values
float oldtrace_fraction = trace_fraction;
trace_plane_normal = oldtrace_plane_normal;
trace_ent = oldtrace_ent;
if(!_Movetype_PushEntity(this, move, true))
- return;
+ return; // teleported
if (wasfreed(this))
return;
+ if (trace_allsolid && trace_fraction == 0)
+ {
+ // immovably stuck, don't waste CPU trying to move again
+ this.velocity = '0 0 0';
+ SET_ONGROUND(this);
+ return;
+ }
}
if (trace_fraction == 1)
{
PlayerStats_GameReport_DelayMapVote = true;
- serverflags |= SERVERFLAG_PLAYERSTATS;
if(autocvar_g_playerstats_gamereport_uri != cvar_defstring("g_playerstats_gamereport_uri"))
- {
- serverflags |= SERVERFLAG_PLAYERSTATS_CUSTOM;
- }
+ serverflags |= SERVERFLAG_PLAYERSTATS | SERVERFLAG_PLAYERSTATS_CUSTOM;
+ else if(checkextension("DP_CRYPTO") && checkextension("DP_QC_URI_POST"))
+ // XonStat submission requires player and server IDs, and HTTPS POST
+ serverflags |= SERVERFLAG_PLAYERSTATS;
PlayerStats_GameReport_AddEvent(PLAYERSTATS_ALIVETIME);
PlayerStats_GameReport_AddEvent(PLAYERSTATS_AVGLATENCY);
}
#endif
-REGISTRY_SORT(Resources);
-REGISTRY_CHECK(Resources);
+REGISTRY_SORT(Resources)
+REGISTRY_CHECK(Resources)
REGISTRY_DEFINE_GET(Resources, NULL)
STATIC_INIT(Resources_renumber) { FOREACH(Resources, true, it.m_id = i); }
#define MAX_SCORE 64
#define REGISTER_SP(id) REGISTER(Scores, SP, id, m_id, new_pure(PlayerScoreField))
-REGISTRY(Scores, MAX_SCORE);
+REGISTRY(Scores, MAX_SCORE)
REGISTER_REGISTRY(Scores)
// do not sort alphabetically, player sort priority is based on score registration order
-//REGISTRY_SORT(Scores);
-REGISTRY_CHECK(Scores);
+//REGISTRY_SORT(Scores)
+REGISTRY_CHECK(Scores)
REGISTRY_DEFINE_GET(Scores, NULL)
STATIC_INIT(Scores_renumber) { FOREACH(Scores, true, it.m_id = i); }
#ifdef SVQC
float autocvar_g_teleport_maxspeed;
+float autocvar_g_teleport_minspeed;
#endif
REGISTER_STAT(TELEPORT_MAXSPEED, float, autocvar_g_teleport_maxspeed)
+REGISTER_STAT(TELEPORT_MINSPEED, float, autocvar_g_teleport_minspeed)
REGISTER_STAT(TELEPORT_TELEFRAG_AVOID, int, autocvar_g_telefrags_avoid)
REGISTER_STAT(CAMERA_SPECTATOR, int)
if(this.cnt < 0 && !failhard && this.realowner.playerid == this.playerid && !IS_DEAD(this.realowner) && !(STAT(WEAPONS, this.realowner) & WEPSET(PORTO)))
{
- setsize(this, '-16 -16 0', '16 16 48');
+ // FIXME: item properties should be obtained from the registry
+ setsize(this, ITEM_D_MINS, ITEM_D_MAXS);
setorigin(this, this.origin + trace_plane_normal);
- if(move_out_of_solid(this))
+ if(nudgeoutofsolid(this))
{
this.flags = FL_ITEM;
IL_PUSH(g_items, this);
#include "shockwave.qh"
+#ifdef GAMEQC
REGISTER_NET_TEMP(TE_CSQC_SHOCKWAVEPARTICLE)
+#endif
#ifdef SVQC
#include "vaporizer.qh"
+#ifdef GAMEQC
REGISTER_NET_TEMP(TE_CSQC_VAPORBEAMPARTICLE)
+#endif
#if defined(SVQC)
void SendCSQCVaporizerBeamParticle(entity player, int hit) {
}
#endif
+#ifdef GAMEQC
REGISTER_NET_TEMP(TE_CSQC_VORTEXBEAMPARTICLE)
+#endif
#if defined(SVQC)
void SendCSQCVortexBeamParticle(float charge) {
#define ASSERT_LESS(name, var, const) noref int name[(const - var + 1)];
-#if defined(MENUQC)
-string(string, string...) strcat1n = #53;
-#else
-string(string, string...) strcat1n = #115;
-#endif
-
// would be nice if __FUNC__ could be concatenated at compile time
#if 0
// less work, bigger binary
- #define __SOURCELOC__ (sprintf("^7%s^9(^9"__FILE__"^7:^9"STR(__LINE__)"^7)", __FUNC__))
+ #define __SOURCELOC__ (sprintf("^7%s^9(^9"__FILE__"^7:^9"STR(__LINE__)"^7)\n", __FUNC__))
#else
- #define __SOURCELOC__ (sprintf("^7%s^9(^9%s^7:^9%s^7)", __FUNC__, __FILE__, STR(__LINE__)))
+ #define __SOURCELOC__ (sprintf("^7%s^9(^9%s^7:^9%s^7)\n", __FUNC__, __FILE__, STR(__LINE__)))
#endif
-#define _LOG_HEADER(level) "^9[::^7"PROGNAME"^9::"level"^9] ", __SOURCELOC__
-#define _LOG(f, level, s) \
+#define _LOG_HEADER(level, full) strcat("^9[::^7", PROGNAME, "^9::", level, "^9] ", ((full) ? __SOURCELOC__ : ""))
+
+#define _LOG(func_header, level, func_msg, s) \
MACRO_BEGIN \
- if (autocvar_developer > 0) f(strcat1n(_LOG_HEADER(level), "\n")); \
- f(strcat1n("^7", s, "\n")); \
+ func_header(_LOG_HEADER(level, autocvar_developer > 0)); \
+ func_msg(strcat("^7", s, "\n")); \
MACRO_END
-#define LOG_FATAL(...) _LOG_FATAL(strcat1n(__VA_ARGS__))
+#define LOG_FATAL(...) _LOG_FATAL(strcat(__VA_ARGS__))
#define LOG_FATALF(...) _LOG_FATAL(sprintf(__VA_ARGS__))
-#define _LOG_FATAL(s) _LOG(error, "^1FATAL", s)
+#define _LOG_FATAL(s) _LOG(print, "^1FATAL", error, s)
-#define LOG_SEVERE(...) _LOG_SEVERE(strcat1n(__VA_ARGS__))
+#define LOG_SEVERE(...) _LOG_SEVERE(strcat(__VA_ARGS__))
#define LOG_SEVEREF(...) _LOG_SEVERE(sprintf(__VA_ARGS__))
-#define _LOG_SEVERE(s) _LOG(backtrace, "^1SEVERE", s)
+#define _LOG_SEVERE(s) _LOG(print, "^1SEVERE", backtrace, s)
-#define LOG_WARN(...) _LOG_WARN(strcat1n(__VA_ARGS__))
+#define LOG_WARN(...) _LOG_WARN(strcat(__VA_ARGS__))
#define LOG_WARNF(...) _LOG_WARN(sprintf(__VA_ARGS__))
-#define _LOG_WARN(s) _LOG(print, "^3WARNING", s)
+#define _LOG_WARN(s) _LOG(print, "^3WARNING", print, s)
-#define LOG_INFO(...) _LOG_INFO(strcat1n(__VA_ARGS__))
+#define LOG_INFO(...) _LOG_INFO(strcat(__VA_ARGS__))
#define LOG_INFOF(...) _LOG_INFO(sprintf(__VA_ARGS__))
#define _LOG_INFO(s) \
MACRO_BEGIN \
- if (autocvar_developer > 1) dprint(strcat1n(_LOG_HEADER("^5INFO"), "\n")); \
+ if (autocvar_developer > 0) print(_LOG_HEADER("^5INFO", autocvar_developer > 1)); \
string __s = s; \
print("^7", __s); \
/* TODO: unconditionally add a newline when possible */ \
if (str2chr(__s, strlen(__s) - 1) != '\n') { print("\n"); } \
MACRO_END
-#define LOG_TRACE(...) _LOG_TRACE(strcat1n(__VA_ARGS__))
+#define LOG_TRACE(...) _LOG_TRACE(strcat(__VA_ARGS__))
#define LOG_TRACEF(...) _LOG_TRACE(sprintf(__VA_ARGS__))
-#define _LOG_TRACE(s) _LOG(dprint, "^6TRACE", s)
+#define _LOG_TRACE(s) _LOG(dprint, "^6TRACE", dprint, s)
-#define LOG_DEBUG(...) _LOG_DEBUG(strcat1n(__VA_ARGS__))
+#define LOG_DEBUG(...) _LOG_DEBUG(strcat(__VA_ARGS__))
#define LOG_DEBUGF(...) _LOG_DEBUG(sprintf(__VA_ARGS__))
-#define _LOG_DEBUG(s) _LOG(dprint2, "^2DEBUG", s)
-
-#define dprint2(msg) \
- MACRO_BEGIN \
- if (autocvar_developer > 1) dprint(msg); \
- MACRO_END
+#define _LOG_DEBUG(s) if (autocvar_developer > 1) _LOG(dprint, "^2DEBUG", dprint, s)
// same as LOG_INFO but without any debug information that bloats console output and compiled program files
-#define LOG_HELP(...) _LOG_HELP(strcat1n(__VA_ARGS__))
+#define LOG_HELP(...) _LOG_HELP(strcat(__VA_ARGS__))
#define LOG_HELPF(...) _LOG_HELP(sprintf(__VA_ARGS__))
#define _LOG_HELP(s) \
MACRO_BEGIN \
// netcode mismatch and not sure what caused it? developer_csqcentities 1
.string netname;
+
+#ifdef GAMEQC
+
.int m_id;
.bool(entity this, entity sender, bool isNew) m_read;
#define NET_HANDLE(id, param) bool Net_Handle_##id(entity this, entity sender, param)
this.netname = #id; \
}
#endif
-#define REGISTER_NET_S2C(id) REGISTER_NET_TEMP(id)
REGISTRY(TempEntities, BITS(8) - 80)
REGISTER_REGISTRY(TempEntities)
REGISTRY_DEFINE_GET(C2S_Protocol, NULL)
STATIC_INIT(C2S_Protocol_renumber) { FOREACH(C2S_Protocol, true, it.m_id = i); }
+#endif // GAMEQC
+
#ifdef SVQC
const int MSG_ENTITY = 5;
STATIC_INIT(Registry_check_##id) \
{ \
/* Note: SHA256 isn't always available, use MD4 instead */ \
- string s = ""; \
- FOREACH(id, true, s = strcat(s, ":", it.registered_id)); \
+ string s = "", group = ""; \
+ int str_len = 0, digests_len = 0, group_idx = 0; \
+ FOREACH(id, true, { \
+ group = strcat(group, ":", it.registered_id); \
+ if (++group_idx < 50) /* this is to reduce strlen calls */ \
+ continue; \
+ int group_len = strlen(group); \
+ if (str_len + 1 + group_len >= VM_TEMPSTRING_MAXSIZE) \
+ { \
+ /* keep previous digests and replace current string with its digest */ \
+ s = strcat(substring(s, 0, digests_len), ":", digest_hex("MD4", s)); \
+ digests_len = str_len = strlen(s); \
+ } \
+ s = strcat(s, group); \
+ str_len += group_len; \
+ group = ""; \
+ group_idx = 0; \
+ }); \
+ s = strcat(s, group); \
s = substring(s, 1, -1); /* remove initial ":" */ \
string h = REGISTRY_HASH(id) = strzone(digest_hex("MD4", s)); \
LOG_DEBUGF(#id ": %s\n[%s]", h, s); \
} \
- void Registry_check(string r, string sv) \
+ void Registry_check(string r, string sv) /* called by CSQC */ \
{ \
if (r == #id) \
{ \
} \
} \
} \
- void Registry_send_all() { Registry_send(#id, REGISTRY_HASH(id)); } \
+ void Registry_send_all() { Registry_send(#id, REGISTRY_HASH(id)); } /* called by SVQC */ \
#define _REGISTER_REGISTRY(id, str) \
ACCUMULATE_FUNCTION(__static_init_1, Register##id) \
#include "net.qh"
+#ifdef GAMEQC
REGISTER_NET_TEMP(registry)
+#endif
#ifdef CSQC
NET_HANDLE(registry, bool isnew)
#include "sort.qh"
#include "oo.qh"
+// this is not exactly 16KiB (16384 bytes) because one byte is reserved for the \0 terminator
+#define VM_TEMPSTRING_MAXSIZE 16383
+
// string logic
//
// true: is truthy
bool WarpZoneLib_ExactTrigger_Touch(entity this, entity toucher, bool touchfunc)
{
vector emin = toucher.absmin, emax = toucher.absmax;
- if(STAT(Q3COMPAT))
+ if (!Q3COMPAT_COMMON)
{
- // DP's tracebox enlarges absolute bounding boxes by a single quake unit
- // we must undo that here to allow accurate touching
- emin += '1 1 1';
- emax -= '1 1 1';
+ // Xonotic and Nexuiz maps assume triggers will be activated by adjacent players
+ // prior to sv_legacy_bbox_expand 0 DP always did this for SVQC and never for CSQC
+ emin -= '1 1 1';
+ emax += '1 1 1';
}
// if called from a touch func, we can assume the boxes do overlap
}
}
-bool WarpZoneLib_MoveOutOfSolid(entity e)
+int WarpZoneLib_MoveOutOfSolid(entity e)
{
vector o = e.origin;
traceline(o, o, MOVE_WORLDONLY, e);
if (trace_startsolid)
- return false;
+ return 0; // too stuck, giving up
tracebox(o, e.mins, e.maxs, o, MOVE_WORLDONLY, e);
if (!trace_startsolid)
- return true;
+ return -1; // wasn't stuck
vector m0 = e.mins;
vector m1 = e.maxs;
if (trace_startsolid)
{
setorigin(e, o);
- return false;
+ return 0; // can't fix
}
- return true;
+ return 1; // was stuck but is fixed now
}
#ifndef BITXOR_ASSIGN
# define BITXOR_ASSIGN(a,b) ((a) = ((a) | (b)) - ((a) & (b)))
#endif
-bool WarpZoneLib_MoveOutOfSolid(entity e);
+int WarpZoneLib_MoveOutOfSolid(entity e);
#define move_out_of_solid(e) WarpZoneLib_MoveOutOfSolid(e)
bool WarpZoneLib_ExactTrigger_Touch(entity this, entity toucher, bool touchfunc);
#pragma once
-bool WarpZoneLib_MoveOutOfSolid(entity e);
#ifdef SVQC
void WarpZoneLib_ExactTrigger_Init(entity this, bool unsetmodel);
#endif
#include "xonotic/util.qh"
+#include <common/checkextension.qh>
#include <common/items/_mod.qh>
#include <common/weapons/_all.qh>
#include <common/mapinfo.qh>
LOG_TRACEF("^4MQC Build information: ^1%s", WATERMARK);
#endif
+ CheckEngineExtensions();
+
// list all game dirs (TEST)
if (cvar("developer") > 0)
{
if (Menu_Active)
{
if (mode == 1) return;
+ // when togglemenu is called without arguments (mode is -1)
+ // the menu is closed only when connected
+ if (mode == -1 && !(gamestatus & GAME_CONNECTED)) return;
+ // togglemenu 0 always closes the menu
m_hide();
}
else
IL_EACH(g_ladders, true,
{
if(it.bot_pickup)
- if(boxesoverlap(org + m1 + '-1 -1 -1', org + m2 + '1 1 1', it.absmin, it.absmax))
- if(boxesoverlap(end, end2, it.absmin + vec2(m1) + '-1 -1 0', it.absmax + vec2(m2) + '1 1 0'))
+ if(boxesoverlap(org + m1, org + m2, it.absmin, it.absmax))
+ if(boxesoverlap(end, end2, it.absmin + vec2(m1), it.absmax + vec2(m2)))
{
vector top = org;
top.z = it.absmax.z + (PL_MAX_CONST.z - PL_MIN_CONST.z);
e = it; break;
});
if (!e)
- e = waypoint_spawn(jp.absmin - PL_MAX_CONST + '1 1 1', jp.absmax - PL_MIN_CONST + '-1 -1 -1', WAYPOINTFLAG_TELEPORT);
+ e = waypoint_spawn(jp.absmin - PL_MAX_CONST, jp.absmax - PL_MIN_CONST, WAYPOINTFLAG_TELEPORT);
if (!pl.wp_locked)
pl.wp_locked = e;
}
void waypoint_spawnforteleporter(entity e, vector destination, float timetaken, entity tracetest_ent)
{
destination = waypoint_fixorigin(destination, tracetest_ent);
- waypoint_spawnforteleporter_boxes(e, WAYPOINTFLAG_TELEPORT, e.absmin - PL_MAX_CONST + '1 1 1', e.absmax - PL_MIN_CONST + '-1 -1 -1', destination, destination, timetaken);
+ waypoint_spawnforteleporter_boxes(e, WAYPOINTFLAG_TELEPORT, e.absmin - PL_MAX_CONST, e.absmax - PL_MIN_CONST, destination, destination, timetaken);
}
entity waypoint_spawnpersonal(entity this, vector position)
=============
PlayerPreThink
-Called every frame for each client before the physics are run
+Called every frame for each real client by DP (and for each bot by StartFrame()),
+and when executing every asynchronous move, so only include things that MUST be done then.
+Use PlayerFrame() instead for code that only needs to run once per server frame.
+frametime == 0 in the asynchronous code path.
+
+TODO: move more stuff from here and PlayerThink() and ObserverOrSpectatorThink() to PlayerFrame() (frametime is always set there)
=============
*/
.float last_vehiclecheck;
void PlayerPreThink (entity this)
{
- STAT(GUNALIGN, this) = CS_CVAR(this).cvar_cl_gunalign; // TODO
- STAT(MOVEVARS_CL_TRACK_CANJUMP, this) = CS_CVAR(this).cvar_cl_movement_track_canjump;
-
WarpZone_PlayerPhysics_FixVAngle(this);
- if (frametime) {
- // physics frames: update anticheat stuff
- anticheat_prethink(this);
-
- // WORKAROUND: only use dropclient in server frames (frametime set).
- // Never use it in cl_movement frames (frametime zero).
- if (blockSpectators && IS_REAL_CLIENT(this)
- && (IS_SPEC(this) || IS_OBSERVER(this)) && !INGAME(this)
- && time > (CS(this).spectatortime + autocvar_g_maxplayers_spectator_blocktime))
- {
- if (dropclient_schedule(this))
- Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_QUIT_KICK_SPECTATING);
- }
- }
-
zoomstate_set = false;
- // Check for nameless players
- if (this.netname == "" || this.netname != CS(this).netname_previous)
- {
- bool assume_unchanged = (CS(this).netname_previous == "");
- if (autocvar_sv_name_maxlength > 0 && strlennocol(this.netname) > autocvar_sv_name_maxlength)
- {
- int new_length = textLengthUpToLength(this.netname, autocvar_sv_name_maxlength, strlennocol);
- this.netname = strzone(strcat(substring(this.netname, 0, new_length), "^7"));
- sprint(this, sprintf("Warning: your name is longer than %d characters, it has been truncated.\n", autocvar_sv_name_maxlength));
- assume_unchanged = false;
- // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe?
- }
- if (isInvisibleString(this.netname))
- {
- this.netname = strzone(sprintf("Player#%d", this.playerid));
- sprint(this, "Warning: invisible names are not allowed.\n");
- assume_unchanged = false;
- // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe?
- }
- if (!assume_unchanged && autocvar_sv_eventlog)
- GameLogEcho(strcat(":name:", ftos(this.playerid), ":", playername(this.netname, this.team, false)));
- strcpy(CS(this).netname_previous, this.netname);
- }
-
- // version nagging
- if (CS(this).version_nagtime && CS_CVAR(this).cvar_g_xonoticversion && time > CS(this).version_nagtime) {
- CS(this).version_nagtime = 0;
- if (strstrofs(CS_CVAR(this).cvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(CS_CVAR(this).cvar_g_xonoticversion, "autobuild", 0) >= 0) {
- // git client
- } else if (strstrofs(autocvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(autocvar_g_xonoticversion, "autobuild", 0) >= 0) {
- // git server
- Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_BETA, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
- } else {
- int r = vercmp(CS_CVAR(this).cvar_g_xonoticversion, autocvar_g_xonoticversion);
- if (r < 0) { // old client
- Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OUTDATED, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
- } else if (r > 0) { // old server
- Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OLD, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
- }
- }
- }
-
- // GOD MODE info
- if (!(this.flags & FL_GODMODE) && this.max_armorvalue)
- {
- Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_GODMODE_OFF, this.max_armorvalue);
- this.max_armorvalue = 0;
- }
-
- if (frametime && IS_PLAYER(this) && time >= game_starttime)
- {
- if (STAT(FROZEN, this) == FROZEN_TEMP_REVIVING)
- {
- STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) + frametime * this.revive_speed, 1);
- SetResourceExplicit(this, RES_HEALTH, max(1, STAT(REVIVE_PROGRESS, this) * start_health));
- if (this.iceblock)
- this.iceblock.alpha = bound(0.2, 1 - STAT(REVIVE_PROGRESS, this), 1);
-
- if (STAT(REVIVE_PROGRESS, this) >= 1)
- Unfreeze(this, false);
- }
- else if (STAT(FROZEN, this) == FROZEN_TEMP_DYING)
- {
- STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) - frametime * this.revive_speed, 1);
- SetResourceExplicit(this, RES_HEALTH, max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this)));
-
- if (GetResource(this, RES_HEALTH) < 1)
- {
- if (this.vehicle)
- vehicles_exit(this.vehicle, VHEF_RELEASE);
- if(this.event_damage)
- this.event_damage(this, this, this.frozen_by, 1, DEATH_NADE_ICE_FREEZE.m_id, DMG_NOWEP, this.origin, '0 0 0');
- }
- else if (STAT(REVIVE_PROGRESS, this) <= 0)
- Unfreeze(this, false);
- }
- }
-
MUTATOR_CALLHOOK(PlayerPreThink, this);
- if(autocvar_g_vehicles_enter && (time > this.last_vehiclecheck) && !game_stopped && !this.vehicle)
- if(IS_PLAYER(this) && !STAT(FROZEN, this) && !IS_DEAD(this) && !IS_INDEPENDENT_PLAYER(this))
- {
- FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_vehicles_enter_radius, IS_VEHICLE(it) && !IS_DEAD(it) && it.takedamage != DAMAGE_NO,
- {
- if(!it.owner)
- {
- if(!it.team || SAME_TEAM(this, it))
- Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER);
- else if(autocvar_g_vehicles_steal)
- Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER_STEAL);
- }
- else if((it.vehicle_flags & VHF_MULTISLOT) && SAME_TEAM(it.owner, this))
- {
- Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER_GUNNER);
- }
- });
-
- this.last_vehiclecheck = time + 1;
- }
-
if(PHYS_INPUT_BUTTON_USE(this) && !CS(this).usekeypressed)
PlayerUseKey(this);
CS(this).usekeypressed = PHYS_INPUT_BUTTON_USE(this);
SetZoomState(this, PHYS_INPUT_BUTTON_ZOOM(this) || PHYS_INPUT_BUTTON_ZOOMSCRIPT(this) || wep_zoomed);
}
+ // Voice sound effects
if (CS(this).teamkill_soundtime && time > CS(this).teamkill_soundtime)
{
CS(this).teamkill_soundtime = 0;
=============
PlayerPostThink
-Called every frame for each client after the physics are run
+Called every frame for each real client by DP (and for each bot by StartFrame()),
+and when executing every asynchronous move, so only include things that MUST be done then.
+Use PlayerFrame() instead for code that only needs to run once per server frame.
+frametime == 0 in the asynchronous code path.
=============
*/
void PlayerPostThink (entity this)
{
Player_Physics(this);
+ if (IS_PLAYER(this)) {
+ if(this.death_time == time && IS_DEAD(this))
+ {
+ // player's bbox gets resized now, instead of in the damage event that killed the player,
+ // once all the damage events of this frame have been processed with normal size
+ this.maxs.z = 5;
+ setsize(this, this.mins, this.maxs);
+ }
+ DrownPlayer(this);
+ UpdateChatBubble(this);
+ if (CS(this).impulse) ImpulseCommands(this);
+ GetPressedKeys(this);
+ if (game_stopped)
+ {
+ CSQCMODEL_AUTOUPDATE(this);
+ return;
+ }
+ }
+ else if (IS_OBSERVER(this) && STAT(PRESSED_KEYS, this))
+ {
+ CS(this).pressedkeys = 0;
+ STAT(PRESSED_KEYS, this) = 0;
+ }
+
+ CSQCMODEL_AUTOUPDATE(this);
+}
+
+/*
+=============
+PlayerFrame
+
+Called every frame for each client by StartFrame().
+Use this for code that only needs to run once per server frame.
+frametime is always set here.
+=============
+*/
+void PlayerFrame (entity this)
+{
+// formerly PreThink code
+ STAT(GUNALIGN, this) = CS_CVAR(this).cvar_cl_gunalign; // TODO
+ STAT(MOVEVARS_CL_TRACK_CANJUMP, this) = CS_CVAR(this).cvar_cl_movement_track_canjump;
+
+ // physics frames: update anticheat stuff
+ anticheat_prethink(this);
+
+ // Check if spectating is allowed
+ if (blockSpectators && IS_REAL_CLIENT(this)
+ && (IS_SPEC(this) || IS_OBSERVER(this)) && !INGAME(this)
+ && time > (CS(this).spectatortime + autocvar_g_maxplayers_spectator_blocktime))
+ {
+ if (dropclient_schedule(this))
+ Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_QUIT_KICK_SPECTATING);
+ }
+
+ // Check for nameless players
+ if (this.netname == "" || this.netname != CS(this).netname_previous)
+ {
+ bool assume_unchanged = (CS(this).netname_previous == "");
+ if (autocvar_sv_name_maxlength > 0 && strlennocol(this.netname) > autocvar_sv_name_maxlength)
+ {
+ int new_length = textLengthUpToLength(this.netname, autocvar_sv_name_maxlength, strlennocol);
+ this.netname = strzone(strcat(substring(this.netname, 0, new_length), "^7"));
+ sprint(this, sprintf("Warning: your name is longer than %d characters, it has been truncated.\n", autocvar_sv_name_maxlength));
+ assume_unchanged = false;
+ // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe?
+ }
+ if (isInvisibleString(this.netname))
+ {
+ this.netname = strzone(sprintf("Player#%d", this.playerid));
+ sprint(this, "Warning: invisible names are not allowed.\n");
+ assume_unchanged = false;
+ // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe?
+ }
+ if (!assume_unchanged && autocvar_sv_eventlog)
+ GameLogEcho(strcat(":name:", ftos(this.playerid), ":", playername(this.netname, this.team, false)));
+ strcpy(CS(this).netname_previous, this.netname);
+ }
+
+ // version nagging
+ if (CS(this).version_nagtime && CS_CVAR(this).cvar_g_xonoticversion && time > CS(this).version_nagtime)
+ {
+ CS(this).version_nagtime = 0;
+ if (strstrofs(CS_CVAR(this).cvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(CS_CVAR(this).cvar_g_xonoticversion, "autobuild", 0) >= 0)
+ {
+ // git client
+ }
+ else if (strstrofs(autocvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(autocvar_g_xonoticversion, "autobuild", 0) >= 0)
+ {
+ // git server
+ Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_BETA, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
+ }
+ else
+ {
+ int r = vercmp(CS_CVAR(this).cvar_g_xonoticversion, autocvar_g_xonoticversion);
+ if (r < 0) // old client
+ Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OUTDATED, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
+ else if (r > 0) // old server
+ Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OLD, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
+ }
+ }
+
+ // GOD MODE info
+ if (!(this.flags & FL_GODMODE) && this.max_armorvalue)
+ {
+ Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_GODMODE_OFF, this.max_armorvalue);
+ this.max_armorvalue = 0;
+ }
+
+ // FreezeTag
+ if (IS_PLAYER(this) && time >= game_starttime)
+ {
+ if (STAT(FROZEN, this) == FROZEN_TEMP_REVIVING)
+ {
+ STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) + frametime * this.revive_speed, 1);
+ SetResourceExplicit(this, RES_HEALTH, max(1, STAT(REVIVE_PROGRESS, this) * start_health));
+ if (this.iceblock)
+ this.iceblock.alpha = bound(0.2, 1 - STAT(REVIVE_PROGRESS, this), 1);
+
+ if (STAT(REVIVE_PROGRESS, this) >= 1)
+ Unfreeze(this, false);
+ }
+ else if (STAT(FROZEN, this) == FROZEN_TEMP_DYING)
+ {
+ STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) - frametime * this.revive_speed, 1);
+ SetResourceExplicit(this, RES_HEALTH, max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this)));
+
+ if (GetResource(this, RES_HEALTH) < 1)
+ {
+ if (this.vehicle)
+ vehicles_exit(this.vehicle, VHEF_RELEASE);
+ if(this.event_damage)
+ this.event_damage(this, this, this.frozen_by, 1, DEATH_NADE_ICE_FREEZE.m_id, DMG_NOWEP, this.origin, '0 0 0');
+ }
+ else if (STAT(REVIVE_PROGRESS, this) <= 0)
+ Unfreeze(this, false);
+ }
+ }
+
+ // Vehicles
+ if(autocvar_g_vehicles_enter && (time > this.last_vehiclecheck) && !game_stopped && !this.vehicle)
+ if(IS_PLAYER(this) && !STAT(FROZEN, this) && !IS_DEAD(this) && !IS_INDEPENDENT_PLAYER(this))
+ {
+ FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_vehicles_enter_radius, IS_VEHICLE(it) && !IS_DEAD(it) && it.takedamage != DAMAGE_NO,
+ {
+ if(!it.owner)
+ {
+ if(!it.team || SAME_TEAM(this, it))
+ Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER);
+ else if(autocvar_g_vehicles_steal)
+ Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER_STEAL);
+ }
+ else if((it.vehicle_flags & VHF_MULTISLOT) && SAME_TEAM(it.owner, this))
+ {
+ Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER_GUNNER);
+ }
+ });
+
+ this.last_vehiclecheck = time + 1;
+ }
+
+
+
+// formerly PostThink code
if (autocvar_sv_maxidle > 0 || (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0))
- if (frametime) // WORKAROUND: only use dropclient in server frames (frametime set). Never use it in cl_movement frames (frametime zero).
if (IS_REAL_CLIENT(this))
if (IS_PLAYER(this) || autocvar_sv_maxidle_alsokickspectators)
if (!intermission_running) // NextLevel() kills all centerprints after setting this true
CS(this).teamkill_soundsource = NULL;
}
- if (IS_PLAYER(this)) {
- if(this.death_time == time && IS_DEAD(this))
- {
- // player's bbox gets resized now, instead of in the damage event that killed the player,
- // once all the damage events of this frame have been processed with normal size
- this.maxs.z = 5;
- setsize(this, this.mins, this.maxs);
- }
- DrownPlayer(this);
- UpdateChatBubble(this);
- if (CS(this).impulse) ImpulseCommands(this);
- GetPressedKeys(this);
- if (game_stopped)
- {
- CSQCMODEL_AUTOUPDATE(this);
- return;
- }
- }
- else if (IS_OBSERVER(this) && STAT(PRESSED_KEYS, this))
- {
- CS(this).pressedkeys = 0;
- STAT(PRESSED_KEYS, this) = 0;
- }
-
if (this.waypointsprite_attachedforcarrier) {
float hp = healtharmor_maxdamage(GetResource(this, RES_HEALTH), GetResource(this, RES_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id).x;
WaypointSprite_UpdateHealth(this.waypointsprite_attachedforcarrier, hp);
}
-
- CSQCMODEL_AUTOUPDATE(this);
}
// hack to copy the button fields from the client entity to the Client State
void ClientState_attach(entity this);
-IntrusiveList g_players;
-STATIC_INIT(g_players) { g_players = IL_NEW(); }
-
CLASS(Client, Object)
/** Client name */
ATTRIB(Client, netname, string, this.netname);
INIT(Player) {
this.classname = STR_PLAYER;
- IL_PUSH(g_players, this);
}
DESTRUCTOR(Player) {
- IL_REMOVE(g_players, this);
}
ENDCLASS(Player)
bool joinAllowed(entity this);
void Join(entity this);
+void PlayerFrame (entity this);
+
#define SPECTATE_COPY() ACCUMULATE void SpectateCopy(entity this, entity spectatee)
#define SPECTATE_COPYFIELD(fld) SPECTATE_COPY() { this.(fld) = spectatee.(fld); }
#include <server/command/radarmap.qh>
#include <server/intermission.qh>
#include <server/ipban.qh>
+#include <server/mapvoting.qh>
#include <server/mutators/_mod.qh>
#include <server/player.qh>
#include <server/scores_rules.qh>
if (argv(1) != "")
{
string s = argv(1);
- Gametype t = MapInfo_Type_FromString(s, false, false), tsave = MapInfo_CurrentGametype();
+ Gametype t = MapInfo_Type_FromString(s, false, false);
if (t)
- {
- MapInfo_SwitchGameType(t);
- MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 0);
- if (MapInfo_count > 0)
- {
- // update lsmaps in case the gametype changed, this way people can easily list maps for it
- if (lsmaps_reply != "") strunzone(lsmaps_reply);
- lsmaps_reply = strzone(getlsmaps());
- bprint("Game type successfully switched to ", s, "\n");
- }
- else
- {
- bprint("Cannot use this game type: no map for it found\n");
- MapInfo_SwitchGameType(tsave);
- MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 0);
- }
- }
+ GameTypeVote_SetGametype(t);
else
- {
bprint("Failed to switch to ", s, ": this game type does not exist!\n");
- }
return;
}
return mapname;
}
-float Map_Count, Map_Current;
-string Map_Current_Name;
+int Map_Count, Map_Current;
// NOTE: this now expects the map list to be already tokenized and the count in Map_Count
int GetMaplistPosition()
{
if(pass == 2)
return true;
+ // MapInfo_Map_flags was set by MapInfo_CheckMap()
+ if (MapInfo_Map_flags & MAPINFO_FLAG_DONOTWANT)
+ return false;
if(MapHasRightSize(map_next))
return true;
return false;
getmapname_stored = strzone(nextmapname);
}
-void Map_Goto_SetFloat(float position)
+void Map_Goto_SetIndex(int position)
{
+ Map_Current = position;
cvar_set("g_maplist_index", ftos(position));
Map_Goto_SetStr(argv(position));
}
// return codes of map selectors:
// -1 = temporary failure (that is, try some method that is guaranteed to succeed)
// -2 = permanent failure
-float MaplistMethod_Iterate() // usual method
+int MaplistMethod_Iterate(void) // usual method
{
- float pass, i;
+ int pass, i;
LOG_TRACE("Trying MaplistMethod_Iterate");
{
for(i = 1; i < Map_Count; ++i)
{
- float mapindex;
+ int mapindex;
mapindex = (i + Map_Current) % Map_Count;
if(Map_Check(mapindex, pass))
return mapindex;
return -1;
}
-float MaplistMethod_Repeat() // fallback method
+int MaplistMethod_Repeat(void) // fallback method
{
LOG_TRACE("Trying MaplistMethod_Repeat");
return -2;
}
-float MaplistMethod_Random() // random map selection
+int MaplistMethod_Random(void) // random map selection
{
- float i, imax;
+ int i, imax;
LOG_TRACE("Trying MaplistMethod_Random");
for(i = 0; i <= imax; ++i)
{
- float mapindex;
+ int mapindex;
mapindex = (Map_Current + floor(random() * (Map_Count - 1) + 1)) % Map_Count; // any OTHER map
if(Map_Check(mapindex, 1))
return mapindex;
// the exponent sets a bias on the map selection:
// the higher the exponent, the less likely "shortly repeated" same maps are
-float MaplistMethod_Shuffle(float exponent) // more clever shuffling
+int MaplistMethod_Shuffle(float exponent) // more clever shuffling
{
float i, j, imax, insertpos;
Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
// NOTE: the selected map has just been inserted at (insertpos-1)th position
- Map_Current = insertpos - 1; // this is not really valid, but this way the fallback has a chance of working
- if(Map_Check(Map_Current, 1))
- return Map_Current;
+ if (Map_Check(insertpos - 1, 1))
+ return insertpos - 1;
}
return -1;
}
-void Maplist_Init()
+int Maplist_Init(void)
{
- float i = Map_Count = 0;
+ int i, available_maps = 0;
+ Map_Count = 0;
if(autocvar_g_maplist != "")
{
Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
for (i = 0; i < Map_Count; ++i)
- {
if (Map_Check(i, 2))
- break;
- }
+ ++available_maps;
}
- if (i == Map_Count)
+ if (!available_maps)
{
bprint( "Maplist contains no usable maps! Resetting it to default map list.\n" );
- cvar_set("g_maplist", MapInfo_ListAllAllowedMaps(MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags() | MAPINFO_FLAG_NOAUTOMAPLIST));
- if(autocvar_g_maplist_shuffle)
- ShuffleMaplist();
+ cvar_set("g_maplist", MapInfo_ListAllowedMaps(MapInfo_CurrentGametype(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags()));
if(!server_is_dedicated)
localcmd("\nmenu_cmd sync\n");
Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
+ for (i = 0; i < Map_Count; ++i)
+ if (Map_Check(i, 2))
+ ++available_maps;
}
+
if(Map_Count == 0)
error("empty maplist, cannot select a new map");
+
Map_Current = bound(0, GetMaplistPosition(), Map_Count - 1);
- strcpy(Map_Current_Name, argv(Map_Current)); // will be automatically freed on exit thanks to DP
- // this may or may not be correct, but who cares, in the worst case a map
- // isn't chosen in the first pass that should have been
+ if(autocvar_g_maplist_shuffle)
+ cvar_set("g_maplist", shufflewords(autocvar_g_maplist));
+
+ return available_maps;
}
-string GetNextMap()
+// NOTE: call Maplist_Init() before making GetNextMap() call(s)
+string GetNextMap(void)
{
- Maplist_Init();
- float nextMap = -1;
+ int nextMap = -1;
- if(nextMap == -1)
- if(autocvar_g_maplist_shuffle > 0)
- nextMap = MaplistMethod_Shuffle(autocvar_g_maplist_shuffle + 1);
+ if(nextMap == -1 && autocvar_g_maplist_shuffle > 0)
+ nextMap = MaplistMethod_Shuffle(autocvar_g_maplist_shuffle + 1);
- if(nextMap == -1)
- if(autocvar_g_maplist_selectrandom)
- nextMap = MaplistMethod_Random();
+ if(nextMap == -1 && autocvar_g_maplist_selectrandom)
+ nextMap = MaplistMethod_Random();
if(nextMap == -1)
nextMap = MaplistMethod_Iterate();
if(nextMap >= 0)
{
- Map_Goto_SetFloat(nextMap);
+ Map_Goto_SetIndex(nextMap);
return getmapname_stored;
}
return;
alreadychangedlevel = true;
+ Maplist_Init();
string nextMap = GetNextMap();
if(nextMap == "")
error("Everything is broken - cannot find a next map. Please report this to the developers.");
Map_Goto(reinit);
}
-void ShuffleMaplist()
-{
- cvar_set("g_maplist", shufflewords(autocvar_g_maplist));
-}
-
string GotoMap(string m)
{
m = GameTypeVote_MapInfo_FixName(m);
bool Map_Check(int position, float pass);
-string GetNextMap();
-
-void ShuffleMaplist();
+int Maplist_Init(void);
+string GetNextMap(void);
void Map_Goto_SetStr(string nextmapname);
else
sf &= ~ISF_DROP;
+ // if this item is being spawned (in CSQC's perspective)
+ // reuse ISF_SIZE and ISF_SIZE2 to also tell CSQC its bbox size
+ if(sf & ISF_SIZE)
+ {
+ if(this.maxs == ITEM_S_MAXS) // Small
+ {
+ sf |= ISF_SIZE;
+ sf &= ~ISF_SIZE2;
+ }
+ else if(this.maxs == ITEM_L_MAXS) // Large
+ {
+ sf &= ~ISF_SIZE;
+ sf |= ISF_SIZE2;
+ }
+ else // Default
+ sf |= ISF_SIZE | ISF_SIZE2;
+ }
+
WriteHeader(MSG_ENTITY, ENT_CLIENT_ITEM);
WriteByte(MSG_ENTITY, sf);
WriteAngleVector(MSG_ENTITY, this.angles);
}
- // sets size on the client, unused on server
- //if(sf & ISF_SIZE)
-
if(sf & ISF_STATUS)
WriteByte(MSG_ENTITY, this.ItemStatus);
- if(sf & ISF_MODEL)
+ if(sf & ISF_SIZE || sf & ISF_SIZE2) // always true when it's spawned (in CSQC's perspective)
{
WriteShort(MSG_ENTITY, bound(0, this.fade_end, 32767));
this.nextthink = max(this.strength_finished, this.invincible_finished, this.superweapons_finished);
}
+ // most loot items have a bigger horizontal size than a player
+ nudgeoutofsolid(this);
+
// don't drop if in a NODROP zone (such as lava)
traceline(this.origin, this.origin, MOVE_NORMAL, this);
if (trace_dpstartcontents & DPCONTENTS_NODROP)
if(this.angles != '0 0 0')
this.SendFlags |= ISF_ANGLES;
- if(q3compat && !this.team)
+ if(q3compat)
{
- string t = GetField_fullspawndata(this, "team");
- // bones_was_here: this hack is cheaper than changing to a .string strcmp()
- if(t) this.team = crc16(false, t);
+ if (!this.team)
+ {
+ string t = GetField_fullspawndata(this, "team");
+ // bones_was_here: this hack is cheaper than changing to a .string strcmp()
+ if(t) this.team = crc16(false, t);
+ }
+
+ // In Q3 the origin is in the middle of the bbox ("radius" 15), in Xon it's at the bottom
+ // so we need to offset vertically (only for items placed by the mapper).
+ this.origin.z += -15 - this.mins.z;
+ setorigin(this, this.origin);
}
// it's a level item
// do item filtering according to game mode and other things
if (this.noalign <= 0)
{
- // first nudge it off the floor a little bit to avoid math errors
- setorigin(this, this.origin + '0 0 1');
// note droptofloor returns false if stuck/or would fall too far
if (!this.noalign)
droptofloor(this);
void sys_phys_update(entity this, float dt);
void StartFrame()
{
- // TODO: if move is more than 50ms, split it into two moves (this matches QWSV behavior and the client prediction)
- IL_EACH(g_players, IS_FAKE_CLIENT(it), sys_phys_update(it, frametime));
- IL_EACH(g_players, IS_FAKE_CLIENT(it), PlayerPreThink(it));
+ FOREACH_CLIENT(IS_FAKE_CLIENT(it),
+ {
+ // DP calls these for real clients only
+ sys_phys_update(it, frametime); // called by SV_PlayerPhysics for players
+ PlayerPreThink(it);
+ });
execute_next_frame();
MUTATOR_CALLHOOK(SV_StartFrame);
GlobalStats_updateglobal();
- FOREACH_CLIENT(true, GlobalStats_update(it));
- IL_EACH(g_players, IS_FAKE_CLIENT(it), PlayerPostThink(it));
+ FOREACH_CLIENT(true,
+ {
+ GlobalStats_update(it);
+ if (IS_FAKE_CLIENT(it))
+ PlayerPostThink(it); // DP calls this for real clients only
+ PlayerFrame(it);
+ });
}
.vector originjitter;
return v;
}
+/*
+=============
+FindFileInMapPack
+
+Returns the first matching VFS file path that exists in the current map's pack.
+Returns string_null if no files match or the map isn't packaged.
+=============
+*/
+string FindFileInMapPack(string pattern)
+{
+ if (!checkextension("DP_QC_FS_SEARCH_PACKFILE"))
+ return string_null;
+
+ string base_pack = whichpack(strcat("maps/", mapname, ".bsp"));
+ if (base_pack == "" || !base_pack) // this map isn't packaged or there was an error
+ return string_null;
+
+ int glob = search_packfile_begin(pattern, true, true, base_pack);
+ if (glob < 0)
+ return string_null;
+
+ string file = search_getfilename(glob, 0);
+ search_end(glob);
+ return file;
+}
+
void WarpZone_PostInitialize_Callback()
{
// create waypoint links for warpzones
#include <server/command/cmd.qh>
#include <server/command/getreplies.qh>
#include <server/gamelog.qh>
-#include <server/intermission.qh>
#include <server/world.qh>
// definitions
void MapVote_AddVotableMaps(int nmax, int smax)
{
- int available_maps = 0;
- if (autocvar_g_maplist != "")
- {
- int c = tokenizebyseparator(autocvar_g_maplist, " ");
- for (int i = 0; i < c; ++i)
- {
- if (Map_Check(i, 1) || Map_Check(i, 2))
- ++available_maps;
- }
- }
+ int available_maps = Maplist_Init();
int max_attempts = available_maps;
if (available_maps >= 2)
max_attempts = min(available_maps * 5, 100);
MapVote_AddVotable(GetNextMap(), false);
}
+bool GameTypeVote_SetGametype(Gametype type);
+void GameTypeVote_ApplyGameType(Gametype type)
+{
+ localcmd("sv_vote_gametype_hook_all\n");
+ localcmd("sv_vote_gametype_hook_", MapInfo_Type_ToString(type), "\n");
+
+ if (!GameTypeVote_SetGametype(type))
+ LOG_TRACE("Selected gametype is not supported by any map");
+}
+
+Gametype voted_gametype;
+Gametype match_gametype;
void MapVote_Init()
{
int nmax, smax;
MapVote_AddVotableMaps(nmax, smax);
- if(mapvote_count == 0)
- {
- bprint( "Maplist contains no single playable map! Resetting it to default map list.\n" );
- cvar_set("g_maplist", MapInfo_ListAllowedMaps(MapInfo_CurrentGametype(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags()));
- if(autocvar_g_maplist_shuffle)
- ShuffleMaplist();
- localcmd("\nmenu_cmd sync\n");
- MapVote_AddVotableMaps(nmax, 0);
- }
-
mapvote_count_real = mapvote_count;
if(mapvote_abstain)
MapVote_AddVotable("don't care", false);
mapvote_keeptwotime = 0;
MapVote_Spawn();
+
+ // If match_gametype is set it means voted_gametype has just been applied (on game type vote end).
+ // In this case apply back match_gametype here so that the "restart" command, if called,
+ // properly restarts the map applying the current game type.
+ // Applying voted_gametype before map vote start is needed to properly initialize map vote.
+ if (match_gametype)
+ GameTypeVote_ApplyGameType(match_gametype);
}
void MapVote_SendPicture(entity to, int id)
{
// map vote but gametype has been chosen via voting screen
WriteByte(MSG_ENTITY, BIT(1)); // gametypevote_flags
- WriteString(MSG_ENTITY, MapInfo_Type_ToText(MapInfo_CurrentGametype()));
+ WriteString(MSG_ENTITY, MapInfo_Type_ToText(voted_gametype));
}
else
WriteByte(MSG_ENTITY, 0); // map vote
{
if (time > mapvote_winner_time + 1)
{
+ if (voted_gametype)
+ {
+ // clear match_gametype so that GameTypeVote_ApplyGameType
+ // prints the game type switch message
+ match_gametype = NULL;
+ GameTypeVote_ApplyGameType(voted_gametype);
+ }
+
Map_Goto_SetStr(mapvote_maps[mapvote_winner]);
Map_Goto(0);
}
// update lsmaps in case the gametype changed, this way people can easily list maps for it
if(lsmaps_reply != "") { strunzone(lsmaps_reply); }
lsmaps_reply = strzone(getlsmaps());
- bprint("Game type successfully switched to ", MapInfo_Type_ToString(type), "\n");
+
+ if (!match_gametype) // don't show this msg if we are temporarily switching game type
+ bprint("Game type successfully switched to ", MapInfo_Type_ToString(type), "\n");
}
else
{
return false;
}
- //localcmd("gametype ", MapInfo_Type_ToString(type), "\n");
-
cvar_set("g_maplist", MapInfo_ListAllowedMaps(type, MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags()) );
- if(autocvar_g_maplist_shuffle)
- ShuffleMaplist();
return true;
}
if(!gametypevote || gametypevote_finished)
return false;
- localcmd("sv_vote_gametype_hook_all\n");
- localcmd("sv_vote_gametype_hook_", mapvote_maps[pos], "\n");
+ match_gametype = MapInfo_CurrentGametype();
+ voted_gametype = MapInfo_Type_FromString(mapvote_maps[pos], false, false);
- if ( !GameTypeVote_SetGametype(GameTypeVote_Type_FromString(mapvote_maps[pos])) )
- {
- LOG_TRACE("Selected gametype is not supported by any map");
- }
+ GameTypeVote_ApplyGameType(voted_gametype);
gametypevote_finished = true;
void MapVote_Spawn();
void MapVote_Think();
void MapVote_SendPicture(entity to, int id);
+bool GameTypeVote_SetGametype(entity type);
float GameTypeVote_Start();
-float GameTypeVote_Finished(float pos);
+float GameTypeVote_Finished(int pos);
string GameTypeVote_MapInfo_FixName(string m);
bool gametypevote;
if(clone.colormap <= maxclients && clone.colormap > 0)
clone.colormap = 1024 + this.clientcolors;
+ clone.sv_entnum = etof(this); // sent to CSQC for color mapping purposes
CSQCMODEL_AUTOINIT(clone);
clone.CopyBody_nextthink = this.nextthink;
#include "world.qh"
+#include <common/checkextension.qh>
#include <common/constants.qh>
#include <common/deathtypes/all.qh>
#include <common/gamemodes/_mod.qh>
{
// cvar_set("_sv_init", "0");
// we do NOT set this to 0 any more, so someone "accidentally" changing
- // to this "init" map on a dedicated server will cause no permanent
- // harm
- if(autocvar_g_maplist_shuffle)
- ShuffleMaplist();
- n = tokenizebyseparator(autocvar_g_maplist, " ");
- cvar_set("g_maplist_index", ftos(n - 1)); // jump to map 0 in GotoNextMap
+ // to this "init" map on a dedicated server will cause no permanent harm
MapInfo_Enumerate();
MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 0);
if(adding)
{
+ if (cvar_changes == "")
+ cvar_changes = "// this server runs at modified server settings:\n";
+
cvar_changes = strcat(cvar_changes, k, " \"", v, "\" // \"", d, "\"\n");
- if(strlen(cvar_changes) > 16384)
+ if(strlen(cvar_changes) >= VM_TEMPSTRING_MAXSIZE)
{
cvar_changes = "// too many settings have been changed to show them here\n";
adding = 0;
if(pureadding)
{
+ if (cvar_purechanges == "")
+ cvar_purechanges = "// this server runs at modified gameplay settings:\n";
+
cvar_purechanges = strcat(cvar_purechanges, k, " \"", v, "\" // \"", d, "\"\n");
- if(strlen(cvar_purechanges) > 16384)
+ if(strlen(cvar_purechanges) >= VM_TEMPSTRING_MAXSIZE)
{
cvar_purechanges = "// too many settings have been changed to show them here\n";
pureadding = 0;
// though.
}
buf_del(h);
+
if(cvar_changes == "")
cvar_changes = "// this server runs at default server settings\n";
- else
- cvar_changes = strcat("// this server runs at modified server settings:\n", cvar_changes);
cvar_changes = strzone(cvar_changes);
+
if(cvar_purechanges == "")
cvar_purechanges = "// this server runs at default gameplay settings\n";
- else
- cvar_purechanges = strcat("// this server runs at modified gameplay settings:\n", cvar_purechanges);
cvar_purechanges = strzone(cvar_purechanges);
}
bool world_already_spawned;
spawnfunc(worldspawn)
{
+ CheckEngineExtensions();
+
cvar_set("_endmatch", "0");
server_is_dedicated = boolean(stof(cvar_defstring("is_dedicated")));
delete_fn = remove_unsafely;
}
-// deferred dropping
-// ported from VM_SV_droptofloor TODO: make a common function for the client-side?
-void DropToFloor_Handler(entity this)
+// originally ported from DP's droptofloor() builtin
+// TODO: make a common function for the client-side?
+// bones_was_here: when we have a use case for it, yes
+void DropToFloor_QC(entity this)
{
+ int nudgeresult;
+
if(!this || wasfreed(this))
{
- // no modifying free entities
+ LOG_WARN("DropToFloor_QC: can not modify free entity");
return;
}
+ /* Prior to sv_legacy_bbox_expand 0, both droptofloor and nudgeoutofsolid were done for items
+ * using box '-16 -16 0' '16 16 48' (without the FL_ITEM expansion applied),
+ * which often resulted in bboxes partially in solids once expansion was applied.
+ * We don't want bboxes in solids (bad for gameplay and culling),
+ * but we also don't want items to land on a "skirting board" or the base of a sloping wall.
+ * For initial nudgeoutofsolid and droptofloor stages we use a small box
+ * so they fall as far and in the same place as they traditionally would,
+ * then we nudge the full size box out of solid, in a direction appropriate for the plane(s).
+ */
+ vector saved_mins = this.mins; // gmqcc's used-uninitialized check doesn't handle
+ vector saved_maxs = this.maxs; // making these assignments FL_ITEM conditional.
+ if (this.flags & FL_ITEM)
+ {
+ // Using the Q3 bbox for best compatibility with all maps, except...
+ this.mins.x = -15;
+ this.mins.y = -15;
+ this.maxs.x = 15;
+ this.maxs.y = 15;
+ this.maxs.z = this.mins.z + 30; // ...Nex, Xon and Quake use a different vertical offset, see also: StartItem()
+ }
+
+ /* NOTE: sv_gameplayfix_droptofloorstartsolid_nudgetocorrect isn't checked, so it won't need to be networked to CSQC.
+ * It was enabled by default in all Xonotic releases and in Nexuiz, so now certain maps depend on it.
+ * Example: on erbium 0.8.6 the shards @ crylink are too low (in collision with the floor),
+ * so without this those fall through the floor.
+ * Q3, Q2 and Quake don't try to move items out of solid.
+ */
+ if(!Q3COMPAT_COMMON && autocvar_sv_mapformat_is_quake3) // Xonotic, Nexuiz
+ {
+ nudgeresult = nudgeoutofsolid(this);
+ if (!nudgeresult)
+ LOG_WARNF("DropToFloor_QC at \"%v\": COULD NOT FIX badly placed entity \"%s\" before drop", this.origin, this.classname);
+ else if (nudgeresult > 0)
+ LOG_WARNF("DropToFloor_QC at \"%v\": FIXED badly placed entity \"%s\" before drop", this.origin, this.classname);
+ }
+
vector end = this.origin;
if (autocvar_sv_mapformat_is_quake3)
end.z -= 4096;
end.z -= 128;
else
end.z -= 256; // Quake, QuakeWorld
+ tracebox(this.origin, this.mins, this.maxs, end, MOVE_NOMONSTERS, this);
- // NOTE: SV_NudgeOutOfSolid is used in the engine here
- if(autocvar_sv_gameplayfix_droptofloorstartsolid_nudgetocorrect)
+ if (!autocvar_sv_mapformat_is_quake3 && !autocvar_sv_mapformat_is_quake2 && (trace_allsolid || trace_fraction == 1)) // Quake
+ {
+ // Quake games just delete badly placed entities...
+ LOG_WARNF("DropToFloor_QC at \"%v\" (Quake compat): DELETING badly placed entity \"%s\"", this.origin, this.classname);
+ delete(this);
+ return;
+ }
+ else if ((Q3COMPAT_COMMON || autocvar_sv_mapformat_is_quake2) && trace_startsolid) // Q3, Q2
{
- _Movetype_UnstickEntity(this);
- move_out_of_solid(this);
+ // ...but we can't do that on Q3 maps like jamdm1
+ // because our tracebox hits things Q3's trace doesn't (patches?).
+ LOG_WARNF("DropToFloor_QC at \"%v\" (Quake 3 compat): badly placed entity \"%s\"", this.origin, this.classname);
}
- tracebox(this.origin, this.mins, this.maxs, end, MOVE_NORMAL, this);
+ /* NOTE: sv_gameplayfix_droptofloorstartsolid (fallback from tracebox to traceline) isn't implemented.
+ * It was disabled by default in all Xonotic releases and in Nexuiz.
+ * Q3 doesn't support it (always uses its '-15 -15 -15' '15 15 15' box when dropping items), neither does Quake or Q2.
+ */
- if(trace_startsolid && autocvar_sv_gameplayfix_droptofloorstartsolid)
+ if (!autocvar_sv_mapformat_is_quake2) // Quake, Q3, Nexuiz, Xonotic
+ // allow to ride movers (or unset if in freefall)
+ this.groundentity = trace_ent;
+
+ if (!autocvar_sv_mapformat_is_quake3)
+ // if support is destroyed, keep suspended (gross hack for floating items in various maps)
+ // bones_was_here: is this for Q1BSP only? Which maps use it? Do we need it at all? Intentions unclear in DP...
+ this.move_suspendedinair = true;
+
+ if (trace_fraction)
+ this.origin = trace_endpos;
+
+ if (this.flags & FL_ITEM)
{
- vector offset, org;
- offset = 0.5 * (this.mins + this.maxs);
- offset.z = this.mins.z;
- org = this.origin + offset;
- traceline(org, end, MOVE_NORMAL, this);
- trace_endpos = trace_endpos - offset;
- if(trace_startsolid)
- {
- LOG_DEBUGF("DropToFloor_Handler: %v could not fix badly placed entity", this.origin);
- _Movetype_LinkEdict(this, false);
- SET_ONGROUND(this);
- this.groundentity = NULL;
- }
- else if(trace_fraction < 1)
- {
- LOG_DEBUGF("DropToFloor_Handler: %v fixed badly placed entity", this.origin);
- setorigin(this, trace_endpos);
- if(autocvar_sv_gameplayfix_droptofloorstartsolid_nudgetocorrect)
- {
- _Movetype_UnstickEntity(this);
- move_out_of_solid(this);
- }
- SET_ONGROUND(this);
- this.groundentity = trace_ent;
- // if support is destroyed, keep suspended (gross hack for floating items in various maps)
- this.move_suspendedinair = true;
- }
+ this.mins = saved_mins;
+ this.maxs = saved_maxs;
+
+ // A side effect of using a small box to drop items (and do the initial nudge) is
+ // the full size box can end up in collision with a sloping floor or terrain model.
+ nudgeresult = nudgeoutofsolid(this);
+ // No warns for successful nudge because it would spam about items on slopes/terrain.
}
- else
+ else if (trace_allsolid && trace_fraction) // dropped using "proper" bbox but never left solid
{
- if(!trace_allsolid && trace_fraction < 1)
- {
- setorigin(this, trace_endpos);
- SET_ONGROUND(this);
- this.groundentity = trace_ent;
- // if support is destroyed, keep suspended (gross hack for floating items in various maps)
- this.move_suspendedinair = true;
- }
- else
- {
- // if we can't get the entity out of solid, mark it as on ground so physics doesn't attempt to drop it
- // hacky workaround for #2774
- SET_ONGROUND(this);
- }
+ nudgeresult = nudgeoutofsolid(this);
+ if (nudgeresult > 0)
+ LOG_WARNF("DropToFloor_QC at \"%v\": FIXED badly placed entity \"%s\" after drop", this.origin, this.classname);
}
- this.dropped_origin = this.origin;
+ else
+ nudgeresult = -1;
+
+ if (!nudgeresult)
+ if (!Q3COMPAT_COMMON) // to be expected on Q3 maps like gu3-pewter because we use bigger final bboxes
+ LOG_WARNF("DropToFloor_QC at \"%v\": COULD NOT FIX stuck entity \"%s\" after drop", this.origin, this.classname);
+
+ setorigin(this, this.dropped_origin = this.origin);
}
void droptofloor(entity this)
{
- InitializeEntity(this, DropToFloor_Handler, INITPRIO_DROPTOFLOOR);
+ InitializeEntity(this, DropToFloor_QC, INITPRIO_DROPTOFLOOR);
}
bool autocvar_sv_gameplayfix_multiplethinksperframe = true;
float autocvar_timelimit_overtime;
int autocvar_timelimit_overtimes;
float autocvar_timelimit_suddendeath;
-bool autocvar_sv_gameplayfix_droptofloorstartsolid;
-bool autocvar_sv_gameplayfix_droptofloorstartsolid_nudgetocorrect;
bool autocvar_sv_mapformat_is_quake3;
bool autocvar_sv_mapformat_is_quake2;
createdtoday "data/maps/_init.bsp" \
|| wget -nv -O data/maps/_init.bsp https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/_init/_init.bsp
+PASS=0
while read -r LINE
do
printf "%s\n" "$LINE"
- [ "$LINE" = "All tests OK" ] && PASS=1
+ printf "%s\n" "$LINE" | grep -q ".*All tests OK$" && PASS=1
done < <(${ENGINE} +developer 1 +map _init +sv_cmd runtest +wait +quit)
-test "$PASS" = "1" || { printf 'sv_cmd runtest failed!'; exit 1; }
+test "$PASS" = "1" || { printf "\033[1;31m%s\033[0m\n" "sv_cmd runtest failed!"; exit 1; }
${ENGINE} +map _init +sv_cmd dumpnotifs +wait +quit
diff notifications.cfg data/data/notifications_dump.cfg ||
printf "\033[32m%s\033[0m\n" "hashes match"
exit 0
else # red error print
- printf "\033[32m%s\033[0m\n" "expected: $EXPECT"
- printf "\033[32m%s\033[0m\n" " actual: $HASH"
- printf "\033[31m%s\033[0m\n" "!!! ERROR: HASHES DO NOT MATCH !!!"
+ printf "\033[31m%s\033[0m\n" "expected: $EXPECT"
+ printf "\033[31m%s\033[0m\n" " actual: $HASH"
+ printf "\033[1;31m%s\033[0m\n" "!!! ERROR: HASHES DO NOT MATCH !!!"
exit 1
fi
set cl_deathglow 2 "number of seconds during which dead bodies glow out"
set cl_deathglow_min 0.5 "glow out up to this glow factor"
-set cl_respawn_ghosts_keepcolors 0 "if enabled respawn ghosts keep body colors"
+set cl_respawn_ghosts_keepcolors 1 "if enabled respawn ghosts keep body colors"
set _teams_available 0 "internal cvar, value is synced from the server to reflect currently available teams to join"
set g_telefrags_teamplay 1 "never telefrag team mates"
set g_telefrags_avoid 1 "when teleporters have a random destination, avoid teleporting to locations where a telefrag would happen"
set g_teleport_maxspeed 0 "maximum speed that a player can keep when going through a teleporter (if a misc_teleporter_dest also has a cap the smallest one of these will be used), 0 = don't limit, -1 = keep no speed"
+set g_teleport_minspeed 0 "minimum speed that a player can keep when going through a teleporter which affects speed"
set g_respawn_ghosts 1 "if 1 dead bodies become ghosts and float away when the player respawns"
set g_respawn_ghosts_speed 5 "the speed with which respawn ghosts float and rotate"
set g_maplist_mostrecent_count 3 "number of most recent maps that are blocked from being played again"
set g_maplist_index 0 "this is used internally for saving position in maplist cycle"
set g_maplist_selectrandom 0 "if 1, a random map will be chosen as next map - DEPRECATED in favor of g_maplist_shuffle"
-set g_maplist_shuffle 1 "new randomization method: like selectrandom, but avoid playing the same maps in short succession. This works by taking out the first element and inserting it into g_maplist with a bias to the end of the list"
+set g_maplist_shuffle 1 "1: shuffling method which avoids playing the same maps in short succession by taking out the first element and inserting it into g_maplist with a bias to the end of the list. -1: a simpler shuffling method which should be adequate if g_maplist_mostrecent_count is large enough."
set g_maplist_check_waypoints 0 "when 1, maps are skipped if there currently are bots, but the map has no waypoints"
set g_maplist_ignore_sizes 0 "when 1, all maps are shown in the map list regardless of player count"
set g_maplist_sizes_count_maxplayers 1 "check the player limit when getting the player count so forced spectators don't affect the size restrictions"
set g_player_damageforcescale 2 "push multiplier of attacks against players"
-set g_player_damageplayercenter 0 "0: always calculate knockback force direction from player's eyes instead of bbox center. 1: use bbox center point for others, shot origin for attacker's self-damage"
+set g_player_damageplayercenter 1 "0: always calculate knockback force direction from player's eyes instead of bbox center. 1: use bbox center point for others, shot origin for attacker's self-damage"
set g_playerclip_collisions 1 "0 = disable collision testing against playerclips, might be useful on some defrag maps"
set g_botclip_collisions 1 "0 = disable collision testing against botclips, might be useful on some defrag maps"
set timelimit_min 5 "shortest match time achieveable with reducematchtime and timelimit votes"
set timelimit_max 60 "maximum match time achieveable with extendmatchtime and timelimit votes"
-sv_gameplayfix_delayprojectiles 0
-sv_gameplayfix_q2airaccelerate 1
-sv_gameplayfix_stepmultipletimes 1
-sv_gameplayfix_stepdown 2
-// only available in qc physics
-set sv_gameplayfix_stepdown_maxspeed 0 "maximum speed walking entities can be moving for stepping down to apply"
-
// delay for "kill" to prevent abuse
set g_balance_kill_delay 2 "timer before death when using the kill command"
set g_balance_kill_antispam 5 "additional time added to the kill delay if used repeatedly"
-// this feature is currently buggy in the engine (it appears to PREVENT any dropping in lots of maps, leading to weirdly aligned entities, and in some cases even CAUSES them to drop through solid, like in facing worlds nex)
-sv_gameplayfix_droptofloorstartsolid 0
-
set sv_foginterval 1 "force enable fog in regular intervals"
set sv_maxidle 0 "kick players idle for more than this amount of time in seconds"
alias pm_gw "prvm_globalwatchpoint menu ${* ?}"
///////// qc debugger shortcuts END /////////
+sv_gameplayfix_delayprojectiles 0
+sv_gameplayfix_q2airaccelerate 1
+sv_gameplayfix_stepmultipletimes 1
+sv_gameplayfix_stepdown 2
+// only available in qc physics
+set sv_gameplayfix_stepdown_maxspeed 0 "maximum speed walking entities can be moving for stepping down to apply"
+
+// this feature is currently buggy in the engine (it appears to PREVENT any dropping in lots of maps, leading to weirdly aligned entities, and in some cases even CAUSES them to drop through solid, like in facing worlds nex)
+// the bugs should be fixed in https://gitlab.com/xonotic/darkplaces/-/merge_requests/144 but we don't need this, didn't enable it in the past, and it's not implemented in DropToFloor_QC().
+sv_gameplayfix_droptofloorstartsolid 0
+
// otherwise, antilag breaks
sv_gameplayfix_consistentplayerprethink 1
sv_gameplayfix_gravityunaffectedbyticrate 1
sv_gameplayfix_nogravityonground 1
+// avoid needing to work around Quake bbox expansion in many places
+sv_legacy_bbox_expand 0
+
set sv_q3compat_changehitbox 0 "use Q3 player hitbox dimensions and camera height on Q3 maps (maps with an entry in a .arena or .defi file)"
set g_movement_highspeed 1 "multiplier scale for movement speed (applies to sv_maxspeed and sv_maxairspeed, also applies to air acceleration when g_movement_highspeed_q3_compat is set to 0)"