From: TimePath Date: Wed, 23 Mar 2016 11:17:59 +0000 (+1100) Subject: Provisions for unit testing X-Git-Tag: xonotic-v0.8.2~1038 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=23f5cebb4f13df89b13e1478c977a0f2fd16dbb5;p=xonotic%2Fxonotic-data.pk3dir.git Provisions for unit testing --- diff --git a/qcsrc/common/state.qc b/qcsrc/common/state.qc index 1e7582d79..21e009022 100644 --- a/qcsrc/common/state.qc +++ b/qcsrc/common/state.qc @@ -20,7 +20,7 @@ void PlayerState_detach(entity this) 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); @@ -35,7 +35,7 @@ void ClientState_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); } @@ -69,7 +69,7 @@ void ClientState_detach(entity this) remove(CS(this)); this._cs = NULL; - GetCvars(-1); // free cvars + GetCvars(this, -1); // free cvars bot_clientdisconnect(this); diff --git a/qcsrc/common/state.qh b/qcsrc/common/state.qh index 4e80bfd2c..110df1c57 100644 --- a/qcsrc/common/state.qh +++ b/qcsrc/common/state.qh @@ -25,7 +25,7 @@ CLASS(PlayerState, Object) ENDCLASS(PlayerState) .PlayerState _ps; -#define PS(this) (this._ps) +#define PS(this) ((this)._ps) // TODO: renew on death void PlayerState_attach(entity this); @@ -50,7 +50,7 @@ ENDCLASS(ClientState) #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); diff --git a/qcsrc/dpdefs/doc.md b/qcsrc/dpdefs/doc.md index fa2560440..d279ced47 100644 --- a/qcsrc/dpdefs/doc.md +++ b/qcsrc/dpdefs/doc.md @@ -124,6 +124,7 @@ void SV_OnEntityPostSpawnFunction(); // parm1..n void SetNewParms(); +// Runs every frame // input: // .bool customizeentityforclient(); diff --git a/qcsrc/dpdefs/progsdefs.qh b/qcsrc/dpdefs/progsdefs.qh index 535c3b6f7..783ac036f 100644 --- a/qcsrc/dpdefs/progsdefs.qh +++ b/qcsrc/dpdefs/progsdefs.qh @@ -21,6 +21,11 @@ #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 diff --git a/qcsrc/lib/csqcmodel/cl_model.qc b/qcsrc/lib/csqcmodel/cl_model.qc index 4c7d2da72..789f115d1 100644 --- a/qcsrc/lib/csqcmodel/cl_model.qc +++ b/qcsrc/lib/csqcmodel/cl_model.qc @@ -220,14 +220,14 @@ NET_HANDLE(ENT_CLIENT_MODEL, bool isnew) 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 diff --git a/qcsrc/lib/csqcmodel/sv_model.qc b/qcsrc/lib/csqcmodel/sv_model.qc index 60ad7252f..dc2e4021f 100644 --- a/qcsrc/lib/csqcmodel/sv_model.qc +++ b/qcsrc/lib/csqcmodel/sv_model.qc @@ -34,23 +34,20 @@ bool CSQCModel_Send(entity to, int sf) { 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 @@ -68,7 +65,7 @@ bool CSQCModel_Send(entity to, int sf) 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 diff --git a/qcsrc/lib/test.qc b/qcsrc/lib/test.qc index 41e1f294a..0997e53a4 100644 --- a/qcsrc/lib/test.qc +++ b/qcsrc/lib/test.qc @@ -22,7 +22,8 @@ bool TEST_Run(string s) 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); diff --git a/qcsrc/server/autocvars.qh b/qcsrc/server/autocvars.qh index 26fa625af..893bb6478 100644 --- a/qcsrc/server/autocvars.qh +++ b/qcsrc/server/autocvars.qh @@ -263,7 +263,7 @@ bool autocvar_g_spawn_alloweffects; 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; diff --git a/qcsrc/server/campaign.qh b/qcsrc/server/campaign.qh index 8be26fef8..6feb07c45 100644 --- a/qcsrc/server/campaign.qh +++ b/qcsrc/server/campaign.qh @@ -12,6 +12,8 @@ void CampaignPostIntermission(); // must change map 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; diff --git a/qcsrc/server/cl_client.qc b/qcsrc/server/cl_client.qc index 44e1e4161..8a6eefe86 100644 --- a/qcsrc/server/cl_client.qc +++ b/qcsrc/server/cl_client.qc @@ -53,6 +53,23 @@ #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); @@ -89,14 +106,14 @@ bool ClientData_Send(entity this, entity to, int sf) 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) @@ -988,6 +1005,7 @@ void ClientConnect() SELFPARAM(); if (Ban_MaybeEnforceBanOnce(this)) return; assert(!IS_CLIENT(this), return); + this.flags |= FL_CLIENT; assert(player_count >= 0, player_count = 0); #ifdef WATERMARK @@ -1035,16 +1053,14 @@ void ClientConnect() 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 } } @@ -1759,7 +1775,7 @@ void LeaveSpectatorMode() { 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); diff --git a/qcsrc/server/cl_client.qh b/qcsrc/server/cl_client.qh index 36f33219c..337c4db83 100644 --- a/qcsrc/server/cl_client.qh +++ b/qcsrc/server/cl_client.qh @@ -28,19 +28,30 @@ CLASS(Client, Object) // 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) diff --git a/qcsrc/server/command/cmd.qc b/qcsrc/server/command/cmd.qc index 56ba45c61..4533e4f5b 100644 --- a/qcsrc/server/command/cmd.qc +++ b/qcsrc/server/command/cmd.qc @@ -177,7 +177,7 @@ void ClientCommand_join(float request) 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); @@ -471,7 +471,7 @@ void ClientCommand_sentcvar(float request, float argc, string command) tokenize_console(s); } - GetCvars(1); + GetCvars(this, 1); return; } diff --git a/qcsrc/server/defs.qh b/qcsrc/server/defs.qh index 4b1874f89..843eeb99e 100644 --- a/qcsrc/server/defs.qh +++ b/qcsrc/server/defs.qh @@ -378,7 +378,7 @@ float client_cefc_accumulatortime; string deathmessage; -.float just_joined; +.bool just_joined; .float cvar_cl_weaponimpulsemode; .float selectweapon; // last selected weapon of the player diff --git a/qcsrc/server/miscfunctions.qc b/qcsrc/server/miscfunctions.qc index 4814c4080..65d38c8aa 100644 --- a/qcsrc/server/miscfunctions.qc +++ b/qcsrc/server/miscfunctions.qc @@ -410,8 +410,8 @@ REPLICATE(cvar_g_xonoticversion, string, "g_xonoticversion"); /** * @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) diff --git a/qcsrc/server/miscfunctions.qh b/qcsrc/server/miscfunctions.qh index 4a6829a74..4d4bf4a61 100644 --- a/qcsrc/server/miscfunctions.qh +++ b/qcsrc/server/miscfunctions.qh @@ -76,7 +76,7 @@ void GameLogInit(); void GameLogClose(); -void GetCvars(float f); +void GetCvars(entity this, float f); string GetMapname(); diff --git a/qcsrc/server/mutators/mutator/gamemode_lms.qc b/qcsrc/server/mutators/mutator/gamemode_lms.qc index 7ecb290ab..4f84c5abb 100644 --- a/qcsrc/server/mutators/mutator/gamemode_lms.qc +++ b/qcsrc/server/mutators/mutator/gamemode_lms.qc @@ -215,7 +215,7 @@ MUTATOR_HOOKFUNCTION(lms, MakePlayerObserver) 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) { diff --git a/qcsrc/server/progs.inc b/qcsrc/server/progs.inc index 0665f807c..a26f56f8e 100644 --- a/qcsrc/server/progs.inc +++ b/qcsrc/server/progs.inc @@ -34,6 +34,8 @@ #include "t_quake3.qc" #include "t_quake.qc" +#include "tests.qc" + #include "bot/_all.inc" #include "command/all.qc" diff --git a/qcsrc/server/spawnpoints.qc b/qcsrc/server/spawnpoints.qc index d2c632294..344f05846 100644 --- a/qcsrc/server/spawnpoints.qc +++ b/qcsrc/server/spawnpoints.qc @@ -30,13 +30,13 @@ bool SpawnEvent_Send(entity this, entity to, int sf) 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; diff --git a/qcsrc/server/sv_main.qc b/qcsrc/server/sv_main.qc index 4aa404d75..cf090833a 100644 --- a/qcsrc/server/sv_main.qc +++ b/qcsrc/server/sv_main.qc @@ -154,8 +154,13 @@ float game_delay_last; 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); @@ -230,6 +235,7 @@ void StartFrame() 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; diff --git a/qcsrc/server/teamplay.qc b/qcsrc/server/teamplay.qc index 34ee3277a..fabeaeced 100644 --- a/qcsrc/server/teamplay.qc +++ b/qcsrc/server/teamplay.qc @@ -499,7 +499,7 @@ float FindSmallestTeam(entity pl, float ignore_pl) 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; diff --git a/qcsrc/server/teamplay.qh b/qcsrc/server/teamplay.qh index 5a2fe7b12..e24005a32 100644 --- a/qcsrc/server/teamplay.qh +++ b/qcsrc/server/teamplay.qh @@ -47,7 +47,7 @@ float TeamSmallerEqThanTeam(float ta, float tb, entity e); // 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); diff --git a/qcsrc/server/tests.qc b/qcsrc/server/tests.qc new file mode 100644 index 000000000..0b153fff1 --- /dev/null +++ b/qcsrc/server/tests.qc @@ -0,0 +1,38 @@ +#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(); +} diff --git a/qcsrc/server/tests.qh b/qcsrc/server/tests.qh new file mode 100644 index 000000000..05c8d371f --- /dev/null +++ b/qcsrc/server/tests.qh @@ -0,0 +1,10 @@ +#pragma once + +#include "autocvars.qh" +#include "cl_client.qh" +#include "command/all.qh" +#include "weapons/common.qh" +#include "weapons/selection.qh" +#include +#include +#include diff --git a/qcsrc/server/weapons/common.qc b/qcsrc/server/weapons/common.qc index 85e8820f3..2a439f3d9 100644 --- a/qcsrc/server/weapons/common.qc +++ b/qcsrc/server/weapons/common.qc @@ -8,20 +8,15 @@ #include #include -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 diff --git a/qcsrc/tools/compilationunits.sh b/qcsrc/tools/compilationunits.sh index 658fe8682..676c3f315 100755 --- a/qcsrc/tools/compilationunits.sh +++ b/qcsrc/tools/compilationunits.sh @@ -33,14 +33,21 @@ QCCFLAGS="${QCCFLAGS[@]} ${NOWARN[@]}" . 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 }