Inventory_delete(self);
}
-void GetCvars(int);
+void GetCvars(entity this, int);
void DecodeLevelParms(entity this);
void PlayerScore_Attach(entity this);
void ClientData_Attach(entity this);
{
this._cs = NEW(ClientState, this);
- GetCvars(0); // get other cvars from player
+ GetCvars(this, 0); // get other cvars from player
// TODO: xonstat elo.txt support, until then just 404s
if (false && IS_REAL_CLIENT(this)) { PlayerStats_PlayerBasic_CheckUpdate(this); }
remove(CS(this));
this._cs = NULL;
- GetCvars(-1); // free cvars
+ GetCvars(this, -1); // free cvars
bot_clientdisconnect(this);
ENDCLASS(PlayerState)
.PlayerState _ps;
-#define PS(this) (this._ps)
+#define PS(this) ((this)._ps)
// TODO: renew on death
void PlayerState_attach(entity this);
#if NDEBUG
#define CS(this) (this._cs)
#else
- ClientState CS(entity this) { assert(IS_CLIENT(this)); assert(this._cs); return this._cs; }
+ ClientState CS(Client this) { TC(Client, this); assert(this._cs); return this._cs; }
#endif
void ClientState_attach(entity this);
// parm1..n
void SetNewParms();
+// Runs every frame
// input:
//
.bool customizeentityforclient();
#undef spawn
#undef setmodel
+#define stuffcmd(cl, ...) MACRO_BEGIN \
+ entity _cl = (cl); \
+ if (IS_REAL_CLIENT(_cl)) stuffcmd(_cl, __VA_ARGS__); \
+MACRO_END
+
#pragma noref 0
#endif
int sf = ReadInt24_t();
// some nice flags for CSQCMODEL_IF and the hooks
- bool isplayer = (this.entnum >= 1 && this.entnum <= maxclients);
+ bool isplayer = ReadByte() || (this.entnum >= 1 && this.entnum <= maxclients);
if (isnew && isplayer)
{
CSQCModel_players[this.entnum - 1] = this;
this.entremove = CSQCModel_remove;
}
bool islocalplayer = (this.entnum == player_localnum + 1);
- noref bool isnolocalplayer = (isplayer && (this.entnum != player_localnum + 1));
+ noref bool isnolocalplayer = (isplayer && !islocalplayer);
this.classname = "csqcmodel";
this.iflags |= IFLAG_ORIGIN; // interpolate origin too
{
SELFPARAM();
// some nice flags for CSQCMODEL_IF
- float isplayer = (IS_CLIENT(self));
- float islocalplayer = (self == to);
- float isnolocalplayer = (isplayer && (self != to));
-
- unused_float = isplayer;
- unused_float = islocalplayer;
- unused_float = isnolocalplayer;
+ noref bool isplayer = IS_CLIENT(this);
+ noref bool islocalplayer = (this == to);
+ noref bool isnolocalplayer = (isplayer && (this != to));
WriteHeader(MSG_ENTITY, ENT_CLIENT_MODEL);
WriteInt24_t(MSG_ENTITY, sf);
+ WriteByte(MSG_ENTITY, isplayer);
#define CSQCMODEL_IF(cond) if(cond) {
#define CSQCMODEL_ENDIF }
#define CSQCMODEL_PROPERTY(flag,t,r,w,f) \
if(sf & flag) \
{ \
- w(MSG_ENTITY, self.csqcmodel_##f); \
+ w(MSG_ENTITY, this.csqcmodel_##f); \
}
#define CSQCMODEL_PROPERTY_SCALED(flag,t,r,w,f,s,mi,ma) CSQCMODEL_PROPERTY(flag,t,r,w,f)
ALLPROPERTIES
void CSQCModel_CheckUpdate(entity e)
{
// some nice flags for CSQCMODEL_IF
- float isplayer = (IS_CLIENT(e));
+ float isplayer = IS_CLIENT(e);
float islocalplayer = isplayer; // we set BOTH to 1 here as we need the sendflags
float isnolocalplayer = isplayer; // we set BOTH to 1 here as we need the sendflags
TEST_failed = 0;
TEST_fatal = 0;
TEST_ok = false;
- callfunction(strcat("_TEST_", s));
+ string fn = strcat("_TEST_", s);
+ if (isfunction(fn)) callfunction(fn);
if (TEST_failed > 0)
{
LOG_INFOF("%s: %d items failed.\n", s, TEST_failed);
float autocvar_g_spawn_furthest;
bool autocvar_g_spawn_useallspawns;
bool autocvar_g_spawnpoints_auto_move_out_of_solid;
-#define autocvar_g_spawnshieldtime cvar("g_spawnshieldtime")
+float autocvar_g_spawnshieldtime;
float autocvar_g_spawnshield_blockdamage;
float autocvar_g_teamdamage_resetspeed;
float autocvar_g_teamdamage_threshold;
void CampaignLevelWarp(float n);
-float campaign_bots_may_start;
-// campaign mode: bots shall spawn but wait for the player to spawn before they do anything
-// in other game modes, this is ignored
+/**
+ * campaign mode: bots shall spawn but wait for the player to spawn before they do anything
+ * in other game modes, this is ignored
+ */
+bool campaign_bots_may_start;
#include "../lib/warpzone/server.qh"
+STATIC_METHOD(Client, Add, void(Client this, int _team))
+{
+ WITH(entity, self, this, ClientConnect());
+ TRANSMUTE(Player, this);
+ this.frame = 12; // 7
+ this.team = _team;
+ WITH(entity, self, this, PutClientInServer());
+}
+
+void PutObserverInServer();
+void ClientDisconnect();
+
+STATIC_METHOD(Client, Remove, void(Client this))
+{
+ TRANSMUTE(Observer, this);
+ WITH(entity, self, this, PutClientInServer(); ClientDisconnect());
+}
void send_CSQC_teamnagger() {
WriteHeader(MSG_BROADCAST, TE_CSQC_TEAMNAGGER);
void ClientData_Attach(entity this)
{
Net_LinkEntity(this.clientdata = new_pure(clientdata), false, 0, ClientData_Send);
- self.clientdata.drawonlytoclient = this;
- self.clientdata.owner = this;
+ this.clientdata.drawonlytoclient = this;
+ this.clientdata.owner = this;
}
void ClientData_Detach(entity this)
{
remove(this.clientdata);
- self.clientdata = NULL;
+ this.clientdata = NULL;
}
void ClientData_Touch(entity e)
SELFPARAM();
if (Ban_MaybeEnforceBanOnce(this)) return;
assert(!IS_CLIENT(this), return);
+ this.flags |= FL_CLIENT;
assert(player_count >= 0, player_count = 0);
#ifdef WATERMARK
JoinBestTeam(this, false, false); // if the team number is valid, keep it
this.playerid = id;
}
+
if (autocvar_sv_spectate || autocvar_g_campaign || this.team_forced < 0) {
TRANSMUTE(Observer, this);
} else {
- if (!teamplay || autocvar_g_balance_teams)
- {
+ if (!teamplay || autocvar_g_balance_teams) {
TRANSMUTE(Player, this);
- campaign_bots_may_start = 1;
- }
- else
- {
+ campaign_bots_may_start = true;
+ } else {
TRANSMUTE(Observer, this); // do it anyway
}
}
{ JoinBestTeam(self, false, true); }
if(autocvar_g_campaign)
- { campaign_bots_may_start = 1; }
+ { campaign_bots_may_start = true; }
Kill_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER, CPID_PREVENT_JOIN);
// custom
- ATTRIB(Client, flags, int, this.flags)
ATTRIB(Client, playerid, int, this.playerid)
METHOD(Client, m_unwind, bool(Client this));
+ STATIC_METHOD(Client, Add, void(Client this, int _team));
+ STATIC_METHOD(Client, Remove, void(Client this));
+
INIT(Client) {
if (this.m_unwind(this)) return this;
+ make_impure(this);
this.classname = "player_joining";
- this.flags = FL_CLIENT;
static int playerid_last;
this.playerid = ++playerid_last;
ClientState_attach(this);
}
+ DESTRUCTOR(Client) {
+ Client_Remove(this);
+ }
+ CONSTRUCTOR(Client, string name) {
+ CONSTRUCT(Client);
+ this.netname = name;
+ this.netaddress = "local";
+ this.playermodel = "models/player/megaerebus.iqm";
+ }
ENDCLASS(Client)
CLASS(Observer, Client)
if (self.caplayer) return;
if (nJoinAllowed(self, self))
{
- if (autocvar_g_campaign) campaign_bots_may_start = 1;
+ if (autocvar_g_campaign) campaign_bots_may_start = true;
TRANSMUTE(Player, self);
PlayerScore_Clear(self);
Kill_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER, CPID_PREVENT_JOIN);
tokenize_console(s);
}
- GetCvars(1);
+ GetCvars(this, 1);
return;
}
string deathmessage;
-.float just_joined;
+.bool just_joined;
.float cvar_cl_weaponimpulsemode;
.float selectweapon; // last selected weapon of the player
/**
* @param f -1: cleanup, 0: request, 1: receive
*/
-void GetCvars(int f)
-{SELFPARAM();
+void GetCvars(entity this, int f)
+{
string s = string_null;
if (f > 0)
void GameLogClose();
-void GetCvars(float f);
+void GetCvars(entity this, float f);
string GetMapname();
MUTATOR_HOOKFUNCTION(lms, ClientConnect)
{SELFPARAM();
TRANSMUTE(Player, self);
- campaign_bots_may_start = 1;
+ campaign_bots_may_start = true;
if(PlayerScore_Add(self, SP_LMS_LIVES, LMS_NewPlayerLives()) <= 0)
{
#include "t_quake3.qc"
#include "t_quake.qc"
+#include "tests.qc"
+
#include "bot/_all.inc"
#include "command/all.qc"
if(autocvar_g_spawn_alloweffects)
{
- WriteByte(MSG_ENTITY, etof(self.owner));
- WriteCoord(MSG_ENTITY, self.owner.origin.x);
- WriteCoord(MSG_ENTITY, self.owner.origin.y);
- WriteCoord(MSG_ENTITY, self.owner.origin.z);
+ WriteByte(MSG_ENTITY, etof(this.owner));
+ WriteCoord(MSG_ENTITY, this.owner.origin.x);
+ WriteCoord(MSG_ENTITY, this.owner.origin.y);
+ WriteCoord(MSG_ENTITY, this.owner.origin.z);
send = true;
}
- else if((to == self.owner) || (IS_SPEC(to) && (to.enemy == self.owner)) )
+ else if((to == this.owner) || (IS_SPEC(to) && (to.enemy == this.owner)) )
{
WriteByte(MSG_ENTITY, 0);
send = true;
bool autocvar_sv_autopause = true;
float RedirectionThink();
+void PM_Main(Client this);
void StartFrame()
{
+ // TODO: if move is more than 50ms, split it into two moves (this matches QWSV behavior and the client prediction)
+ FOREACH_ENTITY_CLASS(STR_PLAYER, IS_NOT_A_CLIENT(it), PM_Main(it));
+ FOREACH_ENTITY_CLASS(STR_PLAYER, IS_NOT_A_CLIENT(it), WITH(entity, self, it, PlayerPreThink()));
+
execute_next_frame();
if (autocvar_sv_autopause && !server_is_dedicated) Pause_TryPause(true);
MUTATOR_CALLHOOK(SV_StartFrame);
FOREACH_CLIENT(true, LAMBDA(GlobalStats_update(it)));
+ FOREACH_ENTITY_CLASS(STR_PLAYER, IS_NOT_A_CLIENT(it), WITH(entity, self, it, PlayerPostThink()));
}
.vector originjitter;
return RandomSelection_chosen_float;
}
-float JoinBestTeam(entity pl, float only_return_best, float forcebestteam)
+int JoinBestTeam(entity pl, bool only_return_best, bool forcebestteam)
{SELFPARAM();
float smallest, selectedteam;
// NOTE: Assumes CheckAllowedTeams has already been called!
float FindSmallestTeam(entity pl, float ignore_pl);
-float JoinBestTeam(entity pl, float only_return_best, float forcebestteam);
+int JoinBestTeam(entity pl, bool only_return_best, bool forcebestteam);
//void() ctf_playerchanged;
void SV_ChangeTeam(float _color);
--- /dev/null
+#include "tests.qh"
+
+void test_weapons_hurt() {
+ SELFPARAM();
+ EXPECT_NE(100, this.health);
+ remove(this.enemy);
+ remove(this);
+}
+
+TEST(Weapons, Hurt)
+{
+ entity it;
+
+ Client a = it = NEW(Client, "A");
+ WITH(float, autocvar_g_spawnshieldtime, 0, Client_Add(it, NUM_TEAM_1));
+ it.origin = '-100 0 0';
+ it.angles = '0 0 0';
+
+ Client b = it = NEW(Client, "B");
+ WITH(float, autocvar_g_spawnshieldtime, 0, Client_Add(it, NUM_TEAM_2));
+ it.origin = '100 0 0';
+ it.angles = '0 180 0';
+
+ it = a;
+ PHYS_INPUT_BUTTON_ATCK(it) = true;
+ it.items |= IT_UNLIMITED_AMMO;
+ Weapon wep = WEP_VORTEX;
+ W_GiveWeapon(it, wep.m_id);
+ W_SwitchWeapon_Force(it, wep);
+
+ it = b;
+ PHYS_INPUT_BUTTON_JUMP(it) = true;
+ it.enemy = a;
+
+ defer(it, wep.switchdelay_raise + 0.1, test_weapons_hurt);
+
+ SUCCEED();
+}
--- /dev/null
+#pragma once
+
+#include "autocvars.qh"
+#include "cl_client.qh"
+#include "command/all.qh"
+#include "weapons/common.qh"
+#include "weapons/selection.qh"
+#include <common/items/item.qh>
+#include <common/physics/player.qh>
+#include <common/weapons/all.qh>
#include <common/weapons/all.qh>
#include <common/items/all.qc>
-void W_GiveWeapon (entity e, float wep)
-{SELFPARAM();
-
- if (!wep)
- return;
+void W_GiveWeapon(entity e, int wep)
+{
+ if (!wep) return;
e.weapons |= WepSet_FromWeapon(Weapons_from(wep));
- setself(e);
-
- if(IS_PLAYER(other))
- { Send_Notification(NOTIF_ONE, other, MSG_MULTI, ITEM_WEAPON_GOT, wep); }
-
- setself(this);
+ if (IS_PLAYER(e)) {
+ Send_Notification(NOTIF_ONE, e, MSG_MULTI, ITEM_WEAPON_GOT, wep);
+ }
}
void W_PlayStrengthSound(entity player) // void W_PlayStrengthSound
. qcc.sh
cd ..
-function check() {
+function check1() {
declare -l base="${1}"
MODE=${2}
- find ${base} -type f -name '*.qc' -print0 | sort -z | while read -r -d '' file; do
- qpp ${file} test.dat \
+ declare -l file="${3}"
+ qpp ${file} test.dat \
-include lib/_all.inc -include ${base}/_all.qh \
-I. ${QCCIDENT} ${QCCDEFS} -D${MODE} > ${WORKDIR}/${MODE}.qc
- qcc ${QCCFLAGS} -o ../${WORKDIR}/test.dat ../${WORKDIR}/${MODE}.qc >/dev/null
+ qcc ${QCCFLAGS} -o ../${WORKDIR}/test.dat ../${WORKDIR}/${MODE}.qc >/dev/null
+}
+
+function check() {
+ declare -l base="${1}"
+ MODE=${2}
+ find ${base} -type f -name '*.qc' -print0 | sort -z | while read -r -d '' file; do
+ check1 ${base} ${MODE} ${file}
done
}