From: terencehill Date: Wed, 6 Sep 2017 16:36:50 +0000 (+0200) Subject: Merge branch 'master' into terencehill/bot_waypoints X-Git-Tag: xonotic-v0.8.5~2378^2~73 X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fxonotic-data.pk3dir.git;a=commitdiff_plain;h=678127044857fb9b89f06faf7a3313bdeb43205c;hp=64e7b6ddf13d965e8a91746540dc06de3e4c16fe Merge branch 'master' into terencehill/bot_waypoints --- diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9305f527cd..596f61ad1d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,7 +29,7 @@ test_sv_game: - wget -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints - wget -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache - make - - EXPECT=ed9be8d1b1a544f89bcdd7d36876fede + - EXPECT=0a08daa9132d147f533776deda07643e - HASH=$(${ENGINE} -noconfig -nohome +exec serverbench.cfg | tee /dev/stderr | grep '^:' diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e5fc791f8..9a66f9fd2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,9 +8,18 @@ add_custom_target(${all}) set(checks qc-checks) add_custom_target(${checks}) -# depend on qcc -if (TARGET gmqcc) - add_dependencies(${checks} gmqcc) +if (gmqcc_BINARY_DIR) + set(compilerinfo "${gmqcc_BINARY_DIR}/gmqcc.h") + add_custom_command( + OUTPUT "${compilerinfo}" + DEPENDS "${gmqcc_BINARY_DIR}/gmqcc" + VERBATIM + COMMAND ${CMAKE_COMMAND} -E + md5sum "${gmqcc_BINARY_DIR}/gmqcc" > "${compilerinfo}" + ) + add_custom_target(qcc ALL + DEPENDS "${compilerinfo}" + ) endif () add_dependencies(${checks} data-check-cvars) @@ -45,6 +54,25 @@ add_custom_target(qc-whitespace VERBATIM COMMAND ./tools/whitespace.sh ) +function(prog name dir) + add_executable(${name} qcsrc/${dir}/progs.inc) + add_dependencies(${all} ${name}) + add_dependencies(${name} ${checks}) + add_dependencies(${name} qcc) + set_source_files_properties(qcsrc/${dir}/progs.inc PROPERTIES OBJECT_DEPENDS "${compilerinfo}") +endfunction() + +function(set_prelude target prelude) + get_target_property(MY_PROJECT_SOURCES ${target} SOURCES) + foreach (source IN LISTS MY_PROJECT_SOURCES) + set_property( + SOURCE ${source} + APPEND PROPERTY COMPILE_FLAGS + "-include ${prelude}" + ) + endforeach () +endfunction() + include_directories(qcsrc) add_definitions(-DXONOTIC=1) @@ -75,33 +103,16 @@ set_source_files_properties( HEADER_FILE_ONLY FALSE ) -add_executable(csprogs qcsrc/client/progs.inc) -add_dependencies(${all} csprogs) -add_dependencies(csprogs ${checks}) +prog(csprogs client) target_compile_definitions(csprogs PRIVATE -DGAMEQC -DCSQC) +# set_prelude(csprogs "${PROJECT_SOURCE_DIR}/qcsrc/lib/_all.inc") -add_executable(progs qcsrc/server/progs.inc) -add_dependencies(${all} progs) -add_dependencies(progs ${checks}) +prog(progs server) target_compile_definitions(progs PRIVATE -DGAMEQC -DSVQC) -add_executable(menu qcsrc/menu/progs.inc) -add_dependencies(${all} menu) -add_dependencies(menu ${checks}) +prog(menu menu) target_compile_definitions(menu PRIVATE -DMENUQC) -function(set_prelude target prelude) - get_target_property(MY_PROJECT_SOURCES target SOURCES) - foreach (source IN LISTS MY_PROJECT_SOURCES) - set_property( - SOURCE ${source} - APPEND PROPERTY COMPILE_FLAGS - "-include ${PROJECT_SOURCE_DIR}/${prelude}" - ) - endforeach () -endfunction() -# set_prelude(csprogs qcsrc/lib/_all.inc) - function(copy prog) add_custom_command(TARGET ${prog} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "$/${prog}.dat" "${prog}.dat" diff --git a/defaultOverkill.cfg b/defaultOverkill.cfg index 2444301b9e..f63f689f34 100644 --- a/defaultOverkill.cfg +++ b/defaultOverkill.cfg @@ -28,7 +28,7 @@ set g_nades_nade_newton_style 2 set g_dodging 1 set sv_dodging_wall_dodging 1 -set g_spawn_near_teammate 1 +set g_spawn_near_teammate "!g_assault !g_freezetag" set g_spawn_near_teammate_ignore_spawnpoint 1 set g_spawnshieldtime 0.5 set g_respawn_delay_forced 2 diff --git a/mutators.cfg b/mutators.cfg index 23c72435c1..5eb8d1b9d6 100644 --- a/mutators.cfg +++ b/mutators.cfg @@ -108,7 +108,7 @@ set g_rocket_flying 0 "set to 1 to enable rocket flying in all balance configs" // spawn near teammate // ===================== seta cl_spawn_near_teammate 1 "toggle for spawning near teammates (only effective if g_spawn_near_teammate_ignore_spawnpoint is 2)" -set g_spawn_near_teammate 0 "if set, players prefer spawns near a team mate" +set g_spawn_near_teammate 0 "players prefer spawns near a team mate" set g_spawn_near_teammate_distance 640 "max distance to consider a spawn to be near a team mate" set g_spawn_near_teammate_ignore_spawnpoint 0 "ignore spawnpoints and spawn right at team mates, if 2, clients can ignore this option" set g_spawn_near_teammate_ignore_spawnpoint_max 10 "if set, test at most this many of the available teammates" diff --git a/qcsrc/client/main.qc b/qcsrc/client/main.qc index ae624ef85c..988cdf35e0 100644 --- a/qcsrc/client/main.qc +++ b/qcsrc/client/main.qc @@ -435,11 +435,10 @@ NET_HANDLE(ENT_CLIENT_SCORES, bool isnew) // RegisterPlayer(o); //playerchecker will do this for us later, if it has not already done so - int sf, lf; - sf = ReadShort(); - lf = ReadShort(); + int sf = ReadShort(); + int lf = ReadShort(); FOREACH(Scores, true, { - int p = 1 << (i % 16); + int p = 1 << (i % 16); if (sf & p) { if (lf & p) @@ -447,7 +446,7 @@ NET_HANDLE(ENT_CLIENT_SCORES, bool isnew) else o.(scores(it)) = ReadChar(); } - }); + }); return = true; @@ -461,24 +460,21 @@ NET_HANDLE(ENT_CLIENT_TEAMSCORES, bool isnew) { make_pure(this); int i; - entity o; this.team = ReadByte(); - o = this.owner = GetTeam(this.team, true); // these team numbers can always be trusted + entity o = this.owner = GetTeam(this.team, true); // these team numbers can always be trusted - int sf, lf; #if MAX_TEAMSCORE <= 8 - sf = ReadByte(); - lf = ReadByte(); + int sf = ReadByte(); + int lf = ReadByte(); #else - sf = ReadShort(); - lf = ReadShort(); + int sf = ReadShort(); + int lf = ReadShort(); #endif - int p; - for(i = 0, p = 1; i < MAX_TEAMSCORE; ++i, p *= 2) - if(sf & p) + for(i = 0; i < MAX_TEAMSCORE; ++i) + if(sf & BIT(i)) { - if(lf & p) + if(lf & BIT(i)) o.(teamscores(i)) = ReadInt24_t(); else o.(teamscores(i)) = ReadChar(); @@ -494,7 +490,7 @@ NET_HANDLE(ENT_CLIENT_CLIENTDATA, bool isnew) make_pure(this); float newspectatee_status; - int f = ReadByte(); + int f = ReadByte(); scoreboard_showscores_force = (f & BIT(0)); @@ -551,9 +547,9 @@ NET_HANDLE(ENT_CLIENT_CLIENTDATA, bool isnew) NET_HANDLE(ENT_CLIENT_NAGGER, bool isnew) { make_pure(this); - int i, j, b, f; + int i, j, b, f; - int nags = ReadByte(); // NAGS NAGS NAGS NAGS NAGS NAGS NADZ NAGS NAGS NAGS + int nags = ReadByte(); // NAGS NAGS NAGS NAGS NAGS NAGS NADZ NAGS NAGS NAGS if(!(nags & BIT(2))) { @@ -590,7 +586,7 @@ NET_HANDLE(ENT_CLIENT_NAGGER, bool isnew) for(i = 1; i <= maxclients; i += 8) { f = ReadByte(); - for(j = i-1, b = 1; b < 256; b *= 2, ++j) + for(j = i-1, b = BIT(0); b < BIT(8); b <<= 1, ++j) if (!(f & b)) if(playerslots[j]) playerslots[j].ready = 0; @@ -609,21 +605,23 @@ NET_HANDLE(ENT_CLIENT_NAGGER, bool isnew) NET_HANDLE(ENT_CLIENT_ELIMINATEDPLAYERS, bool isnew) { make_pure(this); - int i, j, b, f; - - int sf = ReadByte(); - if(sf & 1) - { - for(j = 0; j < maxclients; ++j) - if(playerslots[j]) + int sf; serialize(byte, 0, sf); + if (sf & 1) { + for (int j = 0; j < maxclients; ++j) { + if (playerslots[j]) { playerslots[j].eliminated = 1; - for(i = 1; i <= maxclients; i += 8) - { - f = ReadByte(); - for(j = i-1, b = 1; b < 256; b *= 2, ++j) - if (!(f & b)) - if(playerslots[j]) - playerslots[j].eliminated = 0; + } + } + for (int i = 1; i <= maxclients; i += 8) { + int f = 0; + serialize(byte, 0, f); + for (int b = 0; b < 8; ++b) { + if (f & BIT(b)) continue; + int j = i - 1 + b; + if (playerslots[j]) { + playerslots[j].eliminated = 0; + } + } } } return true; @@ -641,7 +639,7 @@ NET_HANDLE(ENT_CLIENT_RANDOMSEED, bool isnew) NET_HANDLE(ENT_CLIENT_ACCURACY, bool isnew) { make_pure(this); - int sf = ReadInt24_t(); + int sf = ReadInt24_t(); if (sf == 0) { for (int w = 0; w <= WEP_LAST - WEP_FIRST; ++w) weapon_accuracy[w] = -1; @@ -651,7 +649,7 @@ NET_HANDLE(ENT_CLIENT_ACCURACY, bool isnew) int f = 1; for (int w = 0; w <= WEP_LAST - WEP_FIRST; ++w) { if (sf & f) { - int b = ReadByte(); + int b = ReadByte(); if (b == 0) weapon_accuracy[w] = -1; else if (b == 255) diff --git a/qcsrc/client/mapvoting.qc b/qcsrc/client/mapvoting.qc index b34bd0f3a1..87b6d585b3 100644 --- a/qcsrc/client/mapvoting.qc +++ b/qcsrc/client/mapvoting.qc @@ -563,7 +563,7 @@ void MapVote_ReadMask() int i; if ( mv_num_maps < 24 ) { - int mask, power; + int mask; if(mv_num_maps < 8) mask = ReadByte(); else if(mv_num_maps < 16) @@ -571,9 +571,9 @@ void MapVote_ReadMask() else mask = ReadLong(); - for(i = 0, power = 1; i < mv_num_maps; ++i, power *= 2) + for(i = 0; i < mv_num_maps; ++i) { - if ( mask & power ) + if (mask & BIT(i)) mv_flags[i] |= GTV_AVAILABLE; else mv_flags[i] &= ~GTV_AVAILABLE; diff --git a/qcsrc/common/mutators/mutator/bloodloss/sv_bloodloss.qc b/qcsrc/common/mutators/mutator/bloodloss/sv_bloodloss.qc index 61b0c06016..1164e0ade6 100644 --- a/qcsrc/common/mutators/mutator/bloodloss/sv_bloodloss.qc +++ b/qcsrc/common/mutators/mutator/bloodloss/sv_bloodloss.qc @@ -1,6 +1,7 @@ #include "sv_bloodloss.qh" -REGISTER_MUTATOR(bloodloss, cvar("g_bloodloss")); +float autocvar_g_bloodloss; +REGISTER_MUTATOR(bloodloss, autocvar_g_bloodloss); .float bloodloss_timer; @@ -9,7 +10,7 @@ MUTATOR_HOOKFUNCTION(bloodloss, PlayerPreThink) entity player = M_ARGV(0, entity); if(IS_PLAYER(player)) - if(player.health <= autocvar_g_bloodloss && !IS_DEAD(player)) + if(GetResourceAmount(player, RESOURCE_HEALTH) <= autocvar_g_bloodloss && !IS_DEAD(player)) { PHYS_INPUT_BUTTON_CROUCH(player) = true; @@ -28,7 +29,7 @@ MUTATOR_HOOKFUNCTION(bloodloss, PlayerJump) { entity player = M_ARGV(0, entity); - if(player.health <= autocvar_g_bloodloss) + if(GetResourceAmount(player, RESOURCE_HEALTH) <= autocvar_g_bloodloss) return true; } diff --git a/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc b/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc index 925525f395..e039a96b95 100644 --- a/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc +++ b/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc @@ -424,12 +424,17 @@ void buff_Medic_Heal(entity this) { FOREACH_CLIENT(IS_PLAYER(it) && it != this && vdist(it.origin - this.origin, <=, autocvar_g_buffs_medic_heal_range), { - if(SAME_TEAM(it, this)) - if(it.health < autocvar_g_balance_health_regenstable) + if (!SAME_TEAM(it, this)) { - Send_Effect(EFFECT_HEALING, it.origin, '0 0 0', 1); - it.health = bound(0, it.health + autocvar_g_buffs_medic_heal_amount, autocvar_g_balance_health_regenstable); + continue; } + float hp = GetResourceAmount(it, RESOURCE_HEALTH); + if(hp >= autocvar_g_balance_health_regenstable) + { + continue; + } + Send_Effect(EFFECT_HEALING, it.origin, '0 0 0', 1); + SetResourceAmount(it, RESOURCE_HEALTH, bound(0, hp + autocvar_g_buffs_medic_heal_amount, autocvar_g_balance_health_regenstable)); }); } @@ -460,11 +465,11 @@ MUTATOR_HOOKFUNCTION(buffs, Damage_Calculate) frag_damage *= autocvar_g_buffs_speed_damage_take; if(frag_target.buffs & BUFF_MEDIC.m_itemid) - if((frag_target.health - frag_damage) <= 0) + if((GetResourceAmount(frag_target, RESOURCE_HEALTH) - frag_damage) <= 0) if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype)) if(frag_attacker) if(random() <= autocvar_g_buffs_medic_survive_chance) - frag_damage = max(5, frag_target.health - autocvar_g_buffs_medic_survive_health); + frag_damage = max(5, GetResourceAmount(frag_target, RESOURCE_HEALTH) - autocvar_g_buffs_medic_survive_health); if(frag_target.buffs & BUFF_JUMP.m_itemid) if(frag_deathtype == DEATH_FALL.m_id) @@ -537,9 +542,15 @@ MUTATOR_HOOKFUNCTION(buffs, Damage_Calculate) if(frag_target.takedamage) if(DIFF_TEAM(frag_attacker, frag_target)) { - frag_attacker.health = bound(0, frag_attacker.health + bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal, frag_target.health), g_pickup_healthsmall_max); - if(frag_target.armorvalue) - frag_attacker.armorvalue = bound(0, frag_attacker.armorvalue + bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal, frag_target.armorvalue), g_pickup_armorsmall_max); + float amount = bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal, + GetResourceAmount(frag_target, RESOURCE_HEALTH)); + GiveResourceWithLimit(frag_attacker, RESOURCE_HEALTH, amount, g_pickup_healthsmall_max); + if (frag_target.armorvalue) + { + amount = bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal, + GetResourceAmount(frag_target, RESOURCE_ARMOR)); + GiveResourceWithLimit(frag_attacker, RESOURCE_ARMOR, amount, g_pickup_armorsmall_max); + } } M_ARGV(4, float) = frag_damage; diff --git a/qcsrc/common/mutators/mutator/campcheck/sv_campcheck.qc b/qcsrc/common/mutators/mutator/campcheck/sv_campcheck.qc index 52fc52466d..987645aaa0 100644 --- a/qcsrc/common/mutators/mutator/campcheck/sv_campcheck.qc +++ b/qcsrc/common/mutators/mutator/campcheck/sv_campcheck.qc @@ -1,10 +1,11 @@ #include "sv_campcheck.qh" +string autocvar_g_campcheck; float autocvar_g_campcheck_damage; float autocvar_g_campcheck_distance; float autocvar_g_campcheck_interval; -REGISTER_MUTATOR(campcheck, cvar("g_campcheck")); +REGISTER_MUTATOR(campcheck, expr_evaluate(autocvar_g_campcheck)); .float campcheck_nextcheck; .float campcheck_traveled_distance; @@ -64,7 +65,7 @@ MUTATOR_HOOKFUNCTION(campcheck, PlayerPreThink) if(player.vehicle) Damage(player.vehicle, NULL, NULL, autocvar_g_campcheck_damage * 2, DEATH_CAMP.m_id, player.vehicle.origin, '0 0 0'); else - Damage(player, NULL, NULL, bound(0, autocvar_g_campcheck_damage, player.health + player.armorvalue * autocvar_g_balance_armor_blockpercent + 5), DEATH_CAMP.m_id, player.origin, '0 0 0'); + Damage(player, NULL, NULL, bound(0, autocvar_g_campcheck_damage, GetResourceAmount(player, RESOURCE_HEALTH) + GetResourceAmount(player, RESOURCE_ARMOR) * autocvar_g_balance_armor_blockpercent + 5), DEATH_CAMP.m_id, player.origin, '0 0 0'); } player.campcheck_nextcheck = time + autocvar_g_campcheck_interval; player.campcheck_traveled_distance = 0; diff --git a/qcsrc/common/mutators/mutator/cloaked/sv_cloaked.qc b/qcsrc/common/mutators/mutator/cloaked/sv_cloaked.qc index eb45d4baf4..a1fe27a877 100644 --- a/qcsrc/common/mutators/mutator/cloaked/sv_cloaked.qc +++ b/qcsrc/common/mutators/mutator/cloaked/sv_cloaked.qc @@ -1,6 +1,7 @@ #include "sv_cloaked.qh" -REGISTER_MUTATOR(cloaked, cvar("g_cloaked")); +string autocvar_g_cloaked; +REGISTER_MUTATOR(cloaked, expr_evaluate(autocvar_g_cloaked)); float autocvar_g_balance_cloaked_alpha; diff --git a/qcsrc/common/mutators/mutator/hook/sv_hook.qc b/qcsrc/common/mutators/mutator/hook/sv_hook.qc index 5dfdf43866..c396781197 100644 --- a/qcsrc/common/mutators/mutator/hook/sv_hook.qc +++ b/qcsrc/common/mutators/mutator/hook/sv_hook.qc @@ -5,7 +5,7 @@ #ifdef SVQC AUTOCVAR(g_grappling_hook_useammo, bool, false, "Use ammunition with the off-hand grappling hook"); -REGISTER_MUTATOR(hook, cvar("g_grappling_hook")) { +REGISTER_MUTATOR(hook, expr_evaluate(cvar_string("g_grappling_hook"))) { MUTATOR_ONADD { g_grappling_hook = true; if(!autocvar_g_grappling_hook_useammo) diff --git a/qcsrc/common/mutators/mutator/instagib/sv_instagib.qc b/qcsrc/common/mutators/mutator/instagib/sv_instagib.qc index fefad540b2..1561dc10db 100644 --- a/qcsrc/common/mutators/mutator/instagib/sv_instagib.qc +++ b/qcsrc/common/mutators/mutator/instagib/sv_instagib.qc @@ -1,5 +1,10 @@ #include "sv_instagib.qh" +bool autocvar_g_instagib_damagedbycontents = true; +bool autocvar_g_instagib_blaster_keepdamage = false; +bool autocvar_g_instagib_blaster_keepforce = false; +bool autocvar_g_instagib_mirrordamage; +bool autocvar_g_instagib_friendlypush = true; //int autocvar_g_instagib_ammo_drop; bool autocvar_g_instagib_ammo_convert_cells; bool autocvar_g_instagib_ammo_convert_rockets; @@ -12,7 +17,7 @@ float autocvar_g_instagib_speed_highspeed; #include -REGISTER_MUTATOR(mutator_instagib, cvar("g_instagib") && !g_nexball); +REGISTER_MUTATOR(mutator_instagib, autocvar_g_instagib && !g_nexball); spawnfunc(item_minst_cells) { @@ -55,7 +60,7 @@ void instagib_ammocheck(entity this) if(IS_DEAD(this) || game_stopped) instagib_stop_countdown(this); - else if (this.ammo_cells > 0 || (this.items & IT_UNLIMITED_WEAPON_AMMO) || (this.flags & FL_GODMODE)) + else if (GetResourceAmount(this, RESOURCE_CELLS) > 0 || (this.items & IT_UNLIMITED_WEAPON_AMMO) || (this.flags & FL_GODMODE)) instagib_stop_countdown(this); else if(autocvar_g_rm && autocvar_g_rm_laser) { @@ -67,53 +72,54 @@ void instagib_ammocheck(entity this) } else { + float hp = GetResourceAmount(this, RESOURCE_HEALTH); this.instagib_needammo = true; - if (this.health <= 5) + if (hp <= 5) { Damage(this, this, this, 5, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_INSTAGIB_TERMINATED); } - else if (this.health <= 10) + else if (hp <= 10) { Damage(this, this, this, 5, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_1); } - else if (this.health <= 20) + else if (hp <= 20) { Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_2); } - else if (this.health <= 30) + else if (hp <= 30) { Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_3); } - else if (this.health <= 40) + else if (hp <= 40) { Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_4); } - else if (this.health <= 50) + else if (hp <= 50) { Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_5); } - else if (this.health <= 60) + else if (hp <= 60) { Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_6); } - else if (this.health <= 70) + else if (hp <= 70) { Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_7); } - else if (this.health <= 80) + else if (hp <= 80) { Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_8); } - else if (this.health <= 90) + else if (hp <= 90) { Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_INSTAGIB_FINDAMMO); Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); @@ -289,13 +295,15 @@ MUTATOR_HOOKFUNCTION(mutator_instagib, Damage_Calculate) if(!autocvar_g_instagib_friendlypush && SAME_TEAM(frag_target, frag_attacker)) frag_force = '0 0 0'; - if(frag_target.armorvalue) + float armor = GetResourceAmount(frag_target, RESOURCE_ARMOR); + if(armor) { - frag_target.armorvalue -= 1; + armor -= 1; + SetResourceAmount(frag_target, RESOURCE_ARMOR, armor); frag_damage = 0; frag_target.damage_dealt += 1; frag_attacker.damage_dealt += 1; - Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_INSTAGIB_LIVES_REMAINING, frag_target.armorvalue); + Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_INSTAGIB_LIVES_REMAINING, armor); } } @@ -312,7 +320,7 @@ MUTATOR_HOOKFUNCTION(mutator_instagib, Damage_Calculate) if(frag_target != frag_attacker) { - if(frag_damage <= 0 && frag_target.health > 0) { Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_SECONDARY_NODAMAGE); } + if(frag_damage <= 0 && GetResourceAmount(frag_target, RESOURCE_HEALTH) > 0) { Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_SECONDARY_NODAMAGE); } if(!autocvar_g_instagib_blaster_keepforce) frag_force = '0 0 0'; } @@ -325,10 +333,12 @@ MUTATOR_HOOKFUNCTION(mutator_instagib, Damage_Calculate) if(frag_mirrordamage > 0) { // just lose extra LIVES, don't kill the player for mirror damage - if(frag_attacker.armorvalue > 0) + float armor = GetResourceAmount(frag_attacker, RESOURCE_ARMOR); + if(armor > 0) { - frag_attacker.armorvalue -= 1; - Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_INSTAGIB_LIVES_REMAINING, frag_attacker.armorvalue); + armor -= 1; + SetResourceAmount(frag_attacker, RESOURCE_ARMOR, armor); + Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_INSTAGIB_LIVES_REMAINING, armor); frag_attacker.damage_dealt += frag_mirrordamage; } frag_mirrordamage = 0; @@ -415,7 +425,7 @@ MUTATOR_HOOKFUNCTION(mutator_instagib, FilterItem) if(item.weapon == WEP_VAPORIZER.m_id && item.classname == "droppedweapon") { - item.ammo_cells = autocvar_g_instagib_ammo_drop; + SetResourceAmount(item, RESOURCE_CELLS, autocvar_g_instagib_ammo_drop); return false; } @@ -428,10 +438,11 @@ MUTATOR_HOOKFUNCTION(mutator_instagib, FilterItem) if(item.flags & FL_POWERUP) return false; - if(item.ammo_cells > autocvar_g_instagib_ammo_drop && item.classname != "item_minst_cells") - item.ammo_cells = autocvar_g_instagib_ammo_drop; + float cells = GetResourceAmount(item, RESOURCE_CELLS); + if(cells > autocvar_g_instagib_ammo_drop && item.classname != "item_minst_cells") + SetResourceAmount(item, RESOURCE_CELLS, autocvar_g_instagib_ammo_drop); - if(item.ammo_cells && !item.weapon) + if(cells && !item.weapon) return false; return true; @@ -464,26 +475,27 @@ MUTATOR_HOOKFUNCTION(mutator_instagib, ItemTouch) entity item = M_ARGV(0, entity); entity toucher = M_ARGV(1, entity); - if(item.ammo_cells) + if(GetResourceAmount(item, RESOURCE_CELLS)) { // play some cool sounds ;) + float hp = GetResourceAmount(toucher, RESOURCE_HEALTH); if (IS_CLIENT(toucher)) { - if(toucher.health <= 5) + if(hp <= 5) Send_Notification(NOTIF_ONE, toucher, MSG_ANNCE, ANNCE_INSTAGIB_LASTSECOND); - else if(toucher.health < 50) + else if(hp < 50) Send_Notification(NOTIF_ONE, toucher, MSG_ANNCE, ANNCE_INSTAGIB_NARROWLY); } - if(toucher.health < 100) - toucher.health = 100; + if(hp < 100) + SetResourceAmount(toucher, RESOURCE_HEALTH, 100); return MUT_ITEMTOUCH_CONTINUE; } if(item.itemdef == ITEM_ExtraLife) { - toucher.armorvalue = bound(toucher.armorvalue, 999, toucher.armorvalue + autocvar_g_instagib_extralives); + GiveResource(toucher, RESOURCE_ARMOR, autocvar_g_instagib_extralives); Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_EXTRALIVES); return MUT_ITEMTOUCH_PICKUP; } diff --git a/qcsrc/common/mutators/mutator/invincibleproj/sv_invincibleproj.qc b/qcsrc/common/mutators/mutator/invincibleproj/sv_invincibleproj.qc index 23e0d0d850..e68c687bde 100644 --- a/qcsrc/common/mutators/mutator/invincibleproj/sv_invincibleproj.qc +++ b/qcsrc/common/mutators/mutator/invincibleproj/sv_invincibleproj.qc @@ -1,6 +1,7 @@ #include "sv_invincibleproj.qh" -REGISTER_MUTATOR(invincibleprojectiles, cvar("g_invincible_projectiles")); +string autocvar_g_invincible_projectiles; +REGISTER_MUTATOR(invincibleprojectiles, expr_evaluate(autocvar_g_invincible_projectiles)); MUTATOR_HOOKFUNCTION(invincibleprojectiles, EditProjectile) { diff --git a/qcsrc/common/mutators/mutator/melee_only/sv_melee_only.qc b/qcsrc/common/mutators/mutator/melee_only/sv_melee_only.qc index a542921221..ac06a8f774 100644 --- a/qcsrc/common/mutators/mutator/melee_only/sv_melee_only.qc +++ b/qcsrc/common/mutators/mutator/melee_only/sv_melee_only.qc @@ -1,6 +1,7 @@ #include "sv_melee_only.qh" -REGISTER_MUTATOR(melee_only, cvar("g_melee_only") && !cvar("g_instagib") && !cvar("g_overkill") && !g_nexball); +string autocvar_g_melee_only; +REGISTER_MUTATOR(melee_only, expr_evaluate(autocvar_g_melee_only) && !cvar("g_instagib") && !cvar("g_overkill") && !g_nexball); MUTATOR_HOOKFUNCTION(melee_only, SetStartItems, CBC_ORDER_LAST) { diff --git a/qcsrc/common/mutators/mutator/midair/sv_midair.qc b/qcsrc/common/mutators/mutator/midair/sv_midair.qc index 92bbacc006..54b3673c6e 100644 --- a/qcsrc/common/mutators/mutator/midair/sv_midair.qc +++ b/qcsrc/common/mutators/mutator/midair/sv_midair.qc @@ -1,8 +1,9 @@ #include "sv_midair.qh" +string autocvar_g_midair; float autocvar_g_midair_shieldtime; -REGISTER_MUTATOR(midair, cvar("g_midair")); +REGISTER_MUTATOR(midair, expr_evaluate(autocvar_g_midair)); .float midair_shieldtime; diff --git a/qcsrc/common/mutators/mutator/multijump/multijump.qc b/qcsrc/common/mutators/mutator/multijump/multijump.qc index 47dcfd4afd..081a1fdb6f 100644 --- a/qcsrc/common/mutators/mutator/multijump/multijump.qc +++ b/qcsrc/common/mutators/mutator/multijump/multijump.qc @@ -9,7 +9,7 @@ #if defined(SVQC) -REGISTER_MUTATOR(multijump, cvar("g_multijump")); +REGISTER_MUTATOR(multijump, autocvar_g_multijump); #elif defined(CSQC) REGISTER_MUTATOR(multijump, true); #endif diff --git a/qcsrc/common/mutators/mutator/nades/nades.qc b/qcsrc/common/mutators/mutator/nades/nades.qc index 18edd482d3..92e16b48d2 100644 --- a/qcsrc/common/mutators/mutator/nades/nades.qc +++ b/qcsrc/common/mutators/mutator/nades/nades.qc @@ -81,7 +81,7 @@ MUTATOR_HOOKFUNCTION(cl_nades, EditProjectile) entity nade_type = Nade_FromProjectile(proj.cnt); if (nade_type == NADE_TYPE_Null) return; - if(STAT(NADES_SMALL, NULL)) + if(STAT(NADES_SMALL)) { proj.mins = '-8 -8 -8'; proj.maxs = '8 8 8'; @@ -150,7 +150,7 @@ void DrawAmmoNades(vector myPos, vector mySize, bool draw_expanding, float expan #include #include -REGISTER_MUTATOR(nades, cvar("g_nades")); +REGISTER_MUTATOR(nades, autocvar_g_nades); .float nade_time_primed; .float nade_lifetime; @@ -431,7 +431,7 @@ void nade_ice_think(entity this) float current_freeze_time = this.ltime - time - 0.1; - FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_nades_nade_radius, it != this && it.takedamage && !IS_DEAD(it) && it.health > 0 && current_freeze_time > 0, + FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_nades_nade_radius, it != this && it.takedamage && !IS_DEAD(it) && GetResourceAmount(it, RESOURCE_HEALTH) > 0 && current_freeze_time > 0, { if(!autocvar_g_nades_ice_teamcheck || (DIFF_TEAM(it, this.realowner) || it == this.realowner)) if(!it.revival_time || ((time - it.revival_time) >= 1.5)) @@ -623,13 +623,15 @@ void nade_heal_touch(entity this, entity toucher) if ( health_factor > 0 ) { maxhealth = (IS_MONSTER(toucher)) ? toucher.max_health : g_pickup_healthmega_max; - if ( toucher.health < maxhealth ) + float hp = GetResourceAmount(toucher, RESOURCE_HEALTH); + if (hp < maxhealth) { - if ( this.nade_show_particles ) + if (this.nade_show_particles) + { Send_Effect(EFFECT_HEALING, toucher.origin, '0 0 0', 1); - toucher.health = min(toucher.health+health_factor, maxhealth); + } + GiveResourceWithLimit(toucher, RESOURCE_HEALTH, health_factor, maxhealth); } - toucher.pauserothealth_finished = max(toucher.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot); } else if ( health_factor < 0 ) { @@ -768,7 +770,7 @@ void nade_touch(entity this, entity toucher) if(autocvar_g_nades_pickup) if(time >= this.spawnshieldtime) - if(!toucher.nade && this.health == this.max_health) // no boosted shot pickups, thank you very much + if(!toucher.nade && GetResourceAmount(this, RESOURCE_HEALTH) == this.max_health) // no boosted shot pickups, thank you very much if(CanThrowNade(toucher)) // prevent some obvious things, like dead players if(IS_REAL_CLIENT(toucher)) // above checks for IS_PLAYER, don't need to do it here { @@ -796,7 +798,7 @@ void nade_touch(entity this, entity toucher) //setsize(this, '-2 -2 -2', '2 2 2'); //UpdateCSQCProjectile(this); - if(this.health == this.max_health) + if(GetResourceAmount(this, RESOURCE_HEALTH) == this.max_health) { spamsound(this, CH_SHOTS, SND_GRENADE_BOUNCE_RANDOM(), VOL_BASE, ATTEN_NORM); return; @@ -860,19 +862,22 @@ void nade_damage(entity this, entity inflictor, entity attacker, float damage, i if(damage <= 0 || ((IS_ONGROUND(this)) && IS_PLAYER(attacker))) return; - if(this.health == this.max_health) + float hp = GetResourceAmount(this, RESOURCE_HEALTH); + if(hp == this.max_health) { sound(this, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, 0.5 *(ATTEN_LARGE + ATTEN_MAX)); this.nextthink = max(time + this.nade_lifetime, time); setthink(this, nade_beep); } - this.health -= damage; + hp -= damage; + SetResourceAmount(this, RESOURCE_HEALTH, hp); + if ( this.nade_type != NADE_TYPE_HEAL.m_id || IS_PLAYER(attacker) ) this.realowner = attacker; - if(this.health <= 0) + if(hp <= 0) W_PrepareExplosionByDamage(this, attacker, nade_boom); else nade_burn_spawn(this); @@ -928,7 +933,7 @@ void toss_nade(entity e, bool set_owner, vector _velocity, float _time) settouch(_nade, nade_touch); _nade.spawnshieldtime = time + 0.1; // prevent instantly picking up again - _nade.health = autocvar_g_nades_nade_health; + SetResourceAmount(_nade, RESOURCE_HEALTH, autocvar_g_nades_nade_health); _nade.max_health = _nade.health; _nade.takedamage = DAMAGE_AIM; _nade.event_damage = nade_damage; @@ -1294,7 +1299,7 @@ MUTATOR_HOOKFUNCTION(nades, PlayerPreThink) if(n && STAT(FROZEN, player) == 3) // OK, there is at least one teammate reviving us { player.revive_progress = bound(0, player.revive_progress + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1); - player.health = max(1, player.revive_progress * start_health); + SetResourceAmount(player, RESOURCE_HEALTH, max(1, player.revive_progress * start_health)); if(player.revive_progress >= 1) { @@ -1411,7 +1416,7 @@ MUTATOR_HOOKFUNCTION(nades, Damage_Calculate) if(time - frag_inflictor.toss_time <= 0.1) { Unfreeze(frag_target); - frag_target.health = autocvar_g_freezetag_revive_nade_health; + SetResourceAmount(frag_target, RESOURCE_HEALTH, autocvar_g_freezetag_revive_nade_health); Send_Effect(EFFECT_ICEORGLASS, frag_target.origin, '0 0 0', 3); M_ARGV(4, float) = 0; M_ARGV(6, vector) = '0 0 0'; diff --git a/qcsrc/common/mutators/mutator/new_toys/sv_new_toys.qc b/qcsrc/common/mutators/mutator/new_toys/sv_new_toys.qc index 288c2d5c83..af364995a1 100644 --- a/qcsrc/common/mutators/mutator/new_toys/sv_new_toys.qc +++ b/qcsrc/common/mutators/mutator/new_toys/sv_new_toys.qc @@ -68,9 +68,11 @@ roflsound "New toys, new toys!" sound. */ +string autocvar_g_new_toys; + bool nt_IsNewToy(int w); -REGISTER_MUTATOR(nt, cvar("g_new_toys") && !cvar("g_instagib") && !cvar("g_overkill")) +REGISTER_MUTATOR(nt, expr_evaluate(autocvar_g_new_toys) && !cvar("g_instagib") && !cvar("g_overkill")) { MUTATOR_ONADD { diff --git a/qcsrc/common/mutators/mutator/nix/sv_nix.qc b/qcsrc/common/mutators/mutator/nix/sv_nix.qc index 425b8c22b0..4de24a5898 100644 --- a/qcsrc/common/mutators/mutator/nix/sv_nix.qc +++ b/qcsrc/common/mutators/mutator/nix/sv_nix.qc @@ -1,5 +1,6 @@ #include "sv_nix.qh" +string autocvar_g_nix; int autocvar_g_balance_nix_ammo_cells; int autocvar_g_balance_nix_ammo_plasma; int autocvar_g_balance_nix_ammo_fuel; @@ -35,7 +36,7 @@ float nix_nextweapon; bool NIX_CanChooseWeapon(int wpn); -REGISTER_MUTATOR(nix, cvar("g_nix") && !cvar("g_instagib") && !cvar("g_overkill")) +REGISTER_MUTATOR(nix, expr_evaluate(autocvar_g_nix) && !cvar("g_instagib") && !cvar("g_overkill")) { MUTATOR_ONADD { @@ -56,12 +57,12 @@ REGISTER_MUTATOR(nix, cvar("g_nix") && !cvar("g_instagib") && !cvar("g_overkill" { // as the PlayerSpawn hook will no longer run, NIX is turned off by this! FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), { - it.ammo_cells = start_ammo_cells; - it.ammo_plasma = start_ammo_plasma; - it.ammo_shells = start_ammo_shells; - it.ammo_nails = start_ammo_nails; - it.ammo_rockets = start_ammo_rockets; - it.ammo_fuel = start_ammo_fuel; + SetResourceAmount(it, RESOURCE_SHELLS, start_ammo_shells); + SetResourceAmount(it, RESOURCE_BULLETS, start_ammo_nails); + SetResourceAmount(it, RESOURCE_ROCKETS, start_ammo_rockets); + SetResourceAmount(it, RESOURCE_CELLS, start_ammo_cells); + SetResourceAmount(it, RESOURCE_PLASMA, start_ammo_plasma); + SetResourceAmount(it, RESOURCE_FUEL, start_ammo_fuel); it.weapons = start_weapons; for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) { @@ -133,30 +134,34 @@ void NIX_GiveCurrentWeapon(entity this) if(nix_nextchange != this.nix_lastchange_id) // this shall only be called once per round! { - this.ammo_shells = this.ammo_nails = this.ammo_rockets = this.ammo_cells = this.ammo_plasma = this.ammo_fuel = 0; - + SetResourceAmount(this, RESOURCE_SHELLS, 0); + SetResourceAmount(this, RESOURCE_BULLETS, 0); + SetResourceAmount(this, RESOURCE_ROCKETS, 0); + SetResourceAmount(this, RESOURCE_CELLS, 0); + SetResourceAmount(this, RESOURCE_PLASMA, 0); + SetResourceAmount(this, RESOURCE_FUEL, 0); if(this.items & IT_UNLIMITED_WEAPON_AMMO) { switch(e.ammo_field) { - case ammo_shells: this.ammo_shells = autocvar_g_pickup_shells_max; break; - case ammo_nails: this.ammo_nails = autocvar_g_pickup_nails_max; break; - case ammo_rockets: this.ammo_rockets = autocvar_g_pickup_rockets_max; break; - case ammo_cells: this.ammo_cells = autocvar_g_pickup_cells_max; break; - case ammo_plasma: this.ammo_plasma = autocvar_g_pickup_plasma_max; break; - case ammo_fuel: this.ammo_fuel = autocvar_g_pickup_fuel_max; break; + case ammo_shells: SetResourceAmount(this, RESOURCE_SHELLS, autocvar_g_pickup_shells_max); break; + case ammo_nails: SetResourceAmount(this, RESOURCE_BULLETS, autocvar_g_pickup_nails_max); break; + case ammo_rockets: SetResourceAmount(this, RESOURCE_ROCKETS, autocvar_g_pickup_rockets_max); break; + case ammo_cells: SetResourceAmount(this, RESOURCE_CELLS, autocvar_g_pickup_cells_max); break; + case ammo_plasma: SetResourceAmount(this, RESOURCE_PLASMA, autocvar_g_pickup_plasma_max); break; + case ammo_fuel: SetResourceAmount(this, RESOURCE_FUEL, autocvar_g_pickup_fuel_max); break; } } else { switch(e.ammo_field) { - case ammo_shells: this.ammo_shells = autocvar_g_balance_nix_ammo_shells; break; - case ammo_nails: this.ammo_nails = autocvar_g_balance_nix_ammo_nails; break; - case ammo_rockets: this.ammo_rockets = autocvar_g_balance_nix_ammo_rockets; break; - case ammo_cells: this.ammo_cells = autocvar_g_balance_nix_ammo_cells; break; - case ammo_plasma: this.ammo_plasma = autocvar_g_balance_nix_ammo_plasma; break; - case ammo_fuel: this.ammo_fuel = autocvar_g_balance_nix_ammo_fuel; break; + case ammo_shells: SetResourceAmount(this, RESOURCE_SHELLS, autocvar_g_balance_nix_ammo_shells); break; + case ammo_nails: SetResourceAmount(this, RESOURCE_BULLETS, autocvar_g_balance_nix_ammo_nails); break; + case ammo_rockets: SetResourceAmount(this, RESOURCE_ROCKETS, autocvar_g_balance_nix_ammo_rockets); break; + case ammo_cells: SetResourceAmount(this, RESOURCE_CELLS, autocvar_g_balance_nix_ammo_cells); break; + case ammo_plasma: SetResourceAmount(this, RESOURCE_PLASMA, autocvar_g_balance_nix_ammo_plasma); break; + case ammo_fuel: SetResourceAmount(this, RESOURCE_FUEL, autocvar_g_balance_nix_ammo_fuel); break; } } @@ -204,12 +209,12 @@ void NIX_GiveCurrentWeapon(entity this) { switch(e.ammo_field) { - case ammo_shells: this.ammo_shells += autocvar_g_balance_nix_ammoincr_shells; break; - case ammo_nails: this.ammo_nails += autocvar_g_balance_nix_ammoincr_nails; break; - case ammo_rockets: this.ammo_rockets += autocvar_g_balance_nix_ammoincr_rockets; break; - case ammo_cells: this.ammo_cells += autocvar_g_balance_nix_ammoincr_cells; break; - case ammo_plasma: this.ammo_plasma += autocvar_g_balance_nix_ammoincr_plasma; break; - case ammo_fuel: this.ammo_fuel += autocvar_g_balance_nix_ammoincr_fuel; break; + case ammo_shells: GiveResource(this, RESOURCE_SHELLS, autocvar_g_balance_nix_ammoincr_shells); break; + case ammo_nails: GiveResource(this, RESOURCE_BULLETS, autocvar_g_balance_nix_ammoincr_nails); break; + case ammo_rockets: GiveResource(this, RESOURCE_ROCKETS, autocvar_g_balance_nix_ammoincr_rockets); break; + case ammo_cells: GiveResource(this, RESOURCE_CELLS, autocvar_g_balance_nix_ammoincr_cells); break; + case ammo_plasma: GiveResource(this, RESOURCE_PLASMA, autocvar_g_balance_nix_ammoincr_plasma); break; + case ammo_fuel: GiveResource(this, RESOURCE_FUEL, autocvar_g_balance_nix_ammoincr_fuel); break; } this.nix_nextincr = time + autocvar_g_balance_nix_incrtime; diff --git a/qcsrc/common/mutators/mutator/overkill/sv_overkill.qc b/qcsrc/common/mutators/mutator/overkill/sv_overkill.qc index d2a7308ded..b47e587511 100644 --- a/qcsrc/common/mutators/mutator/overkill/sv_overkill.qc +++ b/qcsrc/common/mutators/mutator/overkill/sv_overkill.qc @@ -3,6 +3,8 @@ #include "hmg.qh" #include "rpc.qh" +string autocvar_g_overkill; + bool autocvar_g_overkill_powerups_replace; bool autocvar_g_overkill_itemwaypoints = true; @@ -18,7 +20,7 @@ bool autocvar_g_overkill_filter_armormega; void ok_Initialize(); -REGISTER_MUTATOR(ok, cvar("g_overkill") && !cvar("g_instagib") && !g_nexball && cvar_string("g_mod_balance") == "Overkill") +REGISTER_MUTATOR(ok, expr_evaluate(autocvar_g_overkill) && !cvar("g_instagib") && !g_nexball && cvar_string("g_mod_balance") == "Overkill") { MUTATOR_ONADD { diff --git a/qcsrc/common/mutators/mutator/physical_items/sv_physical_items.qc b/qcsrc/common/mutators/mutator/physical_items/sv_physical_items.qc index 62781c9207..38cd7474b7 100644 --- a/qcsrc/common/mutators/mutator/physical_items/sv_physical_items.qc +++ b/qcsrc/common/mutators/mutator/physical_items/sv_physical_items.qc @@ -4,7 +4,7 @@ int autocvar_g_physical_items; float autocvar_g_physical_items_damageforcescale; float autocvar_g_physical_items_reset; -REGISTER_MUTATOR(physical_items, cvar("g_physical_items")) +REGISTER_MUTATOR(physical_items, autocvar_g_physical_items) { // check if we have a physics engine MUTATOR_ONADD diff --git a/qcsrc/common/mutators/mutator/pinata/sv_pinata.qc b/qcsrc/common/mutators/mutator/pinata/sv_pinata.qc index 53e4f49e60..1084ff7789 100644 --- a/qcsrc/common/mutators/mutator/pinata/sv_pinata.qc +++ b/qcsrc/common/mutators/mutator/pinata/sv_pinata.qc @@ -1,6 +1,7 @@ #include "sv_pinata.qh" -REGISTER_MUTATOR(pinata, cvar("g_pinata") && !cvar("g_instagib") && !cvar("g_overkill")); +string autocvar_g_pinata; +REGISTER_MUTATOR(pinata, expr_evaluate(autocvar_g_pinata) && !cvar("g_instagib") && !cvar("g_overkill")); MUTATOR_HOOKFUNCTION(pinata, PlayerDies) { diff --git a/qcsrc/common/mutators/mutator/rocketflying/sv_rocketflying.qc b/qcsrc/common/mutators/mutator/rocketflying/sv_rocketflying.qc index 9f0d8fbf0d..d3c1922b99 100644 --- a/qcsrc/common/mutators/mutator/rocketflying/sv_rocketflying.qc +++ b/qcsrc/common/mutators/mutator/rocketflying/sv_rocketflying.qc @@ -1,6 +1,7 @@ #include "sv_rocketflying.qh" -REGISTER_MUTATOR(rocketflying, cvar("g_rocket_flying")); +string autocvar_g_rocket_flying; +REGISTER_MUTATOR(rocketflying, expr_evaluate(autocvar_g_rocket_flying)); MUTATOR_HOOKFUNCTION(rocketflying, EditProjectile) { diff --git a/qcsrc/common/mutators/mutator/sandbox/sv_sandbox.qc b/qcsrc/common/mutators/mutator/sandbox/sv_sandbox.qc index 93dc602f02..d121cf1094 100644 --- a/qcsrc/common/mutators/mutator/sandbox/sv_sandbox.qc +++ b/qcsrc/common/mutators/mutator/sandbox/sv_sandbox.qc @@ -1,5 +1,6 @@ #include "sv_sandbox.qh" +string autocvar_g_sandbox; int autocvar_g_sandbox_info; bool autocvar_g_sandbox_readonly; string autocvar_g_sandbox_storage_name; @@ -18,7 +19,7 @@ float autocvar_g_sandbox_object_material_velocity_factor; float autosave_time; void sandbox_Database_Load(); -REGISTER_MUTATOR(sandbox, cvar("g_sandbox")) +REGISTER_MUTATOR(sandbox, expr_evaluate(autocvar_g_sandbox)) { MUTATOR_ONADD { diff --git a/qcsrc/common/mutators/mutator/spawn_near_teammate/sv_spawn_near_teammate.qc b/qcsrc/common/mutators/mutator/spawn_near_teammate/sv_spawn_near_teammate.qc index 9ff3644748..61c302c3e7 100644 --- a/qcsrc/common/mutators/mutator/spawn_near_teammate/sv_spawn_near_teammate.qc +++ b/qcsrc/common/mutators/mutator/spawn_near_teammate/sv_spawn_near_teammate.qc @@ -2,6 +2,7 @@ #include +string autocvar_g_spawn_near_teammate; float autocvar_g_spawn_near_teammate_distance; int autocvar_g_spawn_near_teammate_ignore_spawnpoint; int autocvar_g_spawn_near_teammate_ignore_spawnpoint_max; @@ -10,7 +11,7 @@ float autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death; bool autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health; bool autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath; -REGISTER_MUTATOR(spawn_near_teammate, cvar("g_spawn_near_teammate")); +REGISTER_MUTATOR(spawn_near_teammate, expr_evaluate(autocvar_g_spawn_near_teammate)); .entity msnt_lookat; @@ -20,6 +21,8 @@ REGISTER_MUTATOR(spawn_near_teammate, cvar("g_spawn_near_teammate")); MUTATOR_HOOKFUNCTION(spawn_near_teammate, Spawn_Score) { + if (!teamplay) return; + entity player = M_ARGV(0, entity); entity spawn_spot = M_ARGV(1, entity); vector spawn_score = M_ARGV(2, vector); @@ -29,9 +32,6 @@ MUTATOR_HOOKFUNCTION(spawn_near_teammate, Spawn_Score) spawn_spot.msnt_lookat = NULL; - if(!teamplay) - return; - RandomSelection_Init(); FOREACH_CLIENT(IS_PLAYER(it) && it != player && SAME_TEAM(it, player) && !IS_DEAD(it), { if(vdist(spawn_spot.origin - it.origin, >, autocvar_g_spawn_near_teammate_distance)) @@ -56,7 +56,8 @@ MUTATOR_HOOKFUNCTION(spawn_near_teammate, Spawn_Score) MUTATOR_HOOKFUNCTION(spawn_near_teammate, PlayerSpawn) { - if(!teamplay) { return; } + if (!teamplay) return; + entity player = M_ARGV(0, entity); entity spawn_spot = M_ARGV(1, entity); diff --git a/qcsrc/common/mutators/mutator/superspec/sv_superspec.qc b/qcsrc/common/mutators/mutator/superspec/sv_superspec.qc index 4c99095b79..eb20082359 100644 --- a/qcsrc/common/mutators/mutator/superspec/sv_superspec.qc +++ b/qcsrc/common/mutators/mutator/superspec/sv_superspec.qc @@ -1,6 +1,7 @@ #include "sv_superspec.qh" -REGISTER_MUTATOR(superspec, cvar("g_superspectate")); +string autocvar_g_superspectate; +REGISTER_MUTATOR(superspec, expr_evaluate(autocvar_g_superspectate)); #define _SSMAGIX "SUPERSPEC_OPTIONSFILE_V1" #define _ISLOCAL(ent) ((edict_num(1) == (ent)) ? true : false) diff --git a/qcsrc/common/mutators/mutator/touchexplode/sv_touchexplode.qc b/qcsrc/common/mutators/mutator/touchexplode/sv_touchexplode.qc index 3e6edb04b3..a1b38fb6e7 100644 --- a/qcsrc/common/mutators/mutator/touchexplode/sv_touchexplode.qc +++ b/qcsrc/common/mutators/mutator/touchexplode/sv_touchexplode.qc @@ -1,11 +1,12 @@ #include "sv_touchexplode.qh" +string autocvar_g_touchexplode; float autocvar_g_touchexplode_radius; float autocvar_g_touchexplode_damage; float autocvar_g_touchexplode_edgedamage; float autocvar_g_touchexplode_force; -REGISTER_MUTATOR(touchexplode, cvar("g_touchexplode")); +REGISTER_MUTATOR(touchexplode, expr_evaluate(autocvar_g_touchexplode)); .float touchexplode_time; diff --git a/qcsrc/common/mutators/mutator/vampire/sv_vampire.qc b/qcsrc/common/mutators/mutator/vampire/sv_vampire.qc index 92c5943c3d..199b4e202a 100644 --- a/qcsrc/common/mutators/mutator/vampire/sv_vampire.qc +++ b/qcsrc/common/mutators/mutator/vampire/sv_vampire.qc @@ -1,6 +1,7 @@ #include "sv_vampire.qh" -REGISTER_MUTATOR(vampire, cvar("g_vampire") && !cvar("g_instagib")); +string autocvar_g_vampire; +REGISTER_MUTATOR(vampire, expr_evaluate(autocvar_g_vampire) && !cvar("g_instagib")); MUTATOR_HOOKFUNCTION(vampire, PlayerDamage_SplitHealthArmor) { @@ -12,7 +13,8 @@ MUTATOR_HOOKFUNCTION(vampire, PlayerDamage_SplitHealthArmor) if(frag_target != frag_attacker) if(!IS_DEAD(frag_target)) { - GivePlayerHealth(frag_attacker, bound(0, damage_take, frag_target.health)); + GiveResource(frag_attacker, RESOURCE_HEALTH, + bound(0, damage_take, frag_target.health)); } } diff --git a/qcsrc/common/mutators/mutator/vampirehook/sv_vampirehook.qc b/qcsrc/common/mutators/mutator/vampirehook/sv_vampirehook.qc index e2b0f57d76..ce9e270654 100644 --- a/qcsrc/common/mutators/mutator/vampirehook/sv_vampirehook.qc +++ b/qcsrc/common/mutators/mutator/vampirehook/sv_vampirehook.qc @@ -1,6 +1,7 @@ #include "sv_vampirehook.qh" -REGISTER_MUTATOR(vh, cvar("g_vampirehook")); +string autocvar_g_vampirehook; +REGISTER_MUTATOR(vh, expr_evaluate(autocvar_g_vampirehook)); bool autocvar_g_vampirehook_teamheal; float autocvar_g_vampirehook_damage; diff --git a/qcsrc/common/mutators/mutator/walljump/walljump.qc b/qcsrc/common/mutators/mutator/walljump/walljump.qc index b0d95ea29e..c462a7e2b7 100644 --- a/qcsrc/common/mutators/mutator/walljump/walljump.qc +++ b/qcsrc/common/mutators/mutator/walljump/walljump.qc @@ -4,7 +4,7 @@ #ifdef CSQC REGISTER_MUTATOR(walljump, true); #elif defined(SVQC) -REGISTER_MUTATOR(walljump, cvar("g_walljump")); +REGISTER_MUTATOR(walljump, autocvar_g_walljump); #endif #define PHYS_WALLJUMP(s) STAT(WALLJUMP, s) diff --git a/qcsrc/common/physics/movetypes/movetypes.qh b/qcsrc/common/physics/movetypes/movetypes.qh index dbd765d983..85912ee1c3 100644 --- a/qcsrc/common/physics/movetypes/movetypes.qh +++ b/qcsrc/common/physics/movetypes/movetypes.qh @@ -13,18 +13,18 @@ const int WATERLEVEL_SUBMERGED = 3; #define SET_ONSLICK(s) ((s).flags |= FL_ONSLICK) #define UNSET_ONSLICK(s) ((s).flags &= ~FL_ONSLICK) -#define GAMEPLAYFIX_DOWNTRACEONGROUND(s) STAT(GAMEPLAYFIX_DOWNTRACEONGROUND, NULL) -#define GAMEPLAYFIX_EASIERWATERJUMP(s) STAT(GAMEPLAYFIX_EASIERWATERJUMP, NULL) -#define GAMEPLAYFIX_STEPDOWN(s) STAT(GAMEPLAYFIX_STEPDOWN, NULL) -#define GAMEPLAYFIX_STEPMULTIPLETIMES(s) STAT(GAMEPLAYFIX_STEPMULTIPLETIMES, NULL) -#define GAMEPLAYFIX_UNSTICKPLAYERS(s) STAT(GAMEPLAYFIX_UNSTICKPLAYERS, NULL) -#define GAMEPLAYFIX_WATERTRANSITION(s) STAT(GAMEPLAYFIX_WATERTRANSITION, NULL) -#define UPWARD_VELOCITY_CLEARS_ONGROUND(s) STAT(GAMEPLAYFIX_UPVELOCITYCLEARSONGROUND, NULL) - -#define PHYS_STEPHEIGHT(s) STAT(MOVEVARS_STEPHEIGHT, NULL) -#define PHYS_NOSTEP(s) STAT(NOSTEP, NULL) -#define PHYS_JUMPSTEP(s) STAT(MOVEVARS_JUMPSTEP, NULL) -#define PHYS_WALLFRICTION(s) STAT(MOVEVARS_WALLFRICTION, NULL) +#define GAMEPLAYFIX_DOWNTRACEONGROUND(s) STAT(GAMEPLAYFIX_DOWNTRACEONGROUND) +#define GAMEPLAYFIX_EASIERWATERJUMP(s) STAT(GAMEPLAYFIX_EASIERWATERJUMP) +#define GAMEPLAYFIX_STEPDOWN(s) STAT(GAMEPLAYFIX_STEPDOWN) +#define GAMEPLAYFIX_STEPMULTIPLETIMES(s) STAT(GAMEPLAYFIX_STEPMULTIPLETIMES) +#define GAMEPLAYFIX_UNSTICKPLAYERS(s) STAT(GAMEPLAYFIX_UNSTICKPLAYERS) +#define GAMEPLAYFIX_WATERTRANSITION(s) STAT(GAMEPLAYFIX_WATERTRANSITION) +#define UPWARD_VELOCITY_CLEARS_ONGROUND(s) STAT(GAMEPLAYFIX_UPVELOCITYCLEARSONGROUND) + +#define PHYS_STEPHEIGHT(s) STAT(MOVEVARS_STEPHEIGHT) +#define PHYS_NOSTEP(s) STAT(NOSTEP) +#define PHYS_JUMPSTEP(s) STAT(MOVEVARS_JUMPSTEP) +#define PHYS_WALLFRICTION(s) STAT(MOVEVARS_WALLFRICTION) #ifdef CSQC .float bouncestop; diff --git a/qcsrc/common/physics/player.qh b/qcsrc/common/physics/player.qh index d5a8e605af..ae59381e5c 100644 --- a/qcsrc/common/physics/player.qh +++ b/qcsrc/common/physics/player.qh @@ -80,7 +80,7 @@ bool IsFlying(entity a); #define PHYS_JETPACK_MAXSPEED_UP(s) STAT(JETPACK_MAXSPEED_UP, s) #define PHYS_JETPACK_REVERSE_THRUST(s) STAT(JETPACK_REVERSE_THRUST, s) -#define PHYS_JUMPSPEEDCAP_DISABLE_ONRAMPS(s) STAT(MOVEVARS_JUMPSPEEDCAP_DISABLE_ONRAMPS, NULL) +#define PHYS_JUMPSPEEDCAP_DISABLE_ONRAMPS(s) STAT(MOVEVARS_JUMPSPEEDCAP_DISABLE_ONRAMPS) #define PHYS_JUMPVELOCITY(s) STAT(MOVEVARS_JUMPVELOCITY, s) #define PHYS_MAXAIRSPEED(s) STAT(MOVEVARS_MAXAIRSPEED, s) @@ -97,7 +97,7 @@ bool IsFlying(entity a); #define PHYS_WARSOWBUNNY_TOPSPEED(s) STAT(MOVEVARS_WARSOWBUNNY_TOPSPEED, s) #define PHYS_WARSOWBUNNY_TURNACCEL(s) STAT(MOVEVARS_WARSOWBUNNY_TURNACCEL, s) -#define PHYS_SLICK_APPLYGRAVITY(s) STAT(SLICK_APPLYGRAVITY, NULL) +#define PHYS_SLICK_APPLYGRAVITY(s) STAT(SLICK_APPLYGRAVITY) #define PHYS_INPUT_BUTTON_ATCK(s) PHYS_INPUT_BUTTON_BUTTON1(s) #define PHYS_INPUT_BUTTON_JUMP(s) PHYS_INPUT_BUTTON_BUTTON2(s) diff --git a/qcsrc/common/t_items.qc b/qcsrc/common/t_items.qc index 6a6ce3fafd..c116b440c4 100644 --- a/qcsrc/common/t_items.qc +++ b/qcsrc/common/t_items.qc @@ -682,146 +682,29 @@ void Item_ScheduleInitialRespawn(entity e) Item_ScheduleRespawnIn(e, max(0, game_starttime - time) + ((e.respawntimestart) ? e.respawntimestart : ITEM_RESPAWNTIME_INITIAL(e))); } -void GivePlayerResource(entity player, .float resource_type, float amount) +float Item_GiveAmmoTo(entity item, entity player, int resource_type, float ammomax) { + float amount = GetResourceAmount(item, resource_type); if (amount == 0) { - return; - } - switch (resource_type) - { - case health: - { - // Ugly hack. We do not check if health goes beyond hard limit since - // currently it is done in player_regen. We need to bring back this - // check when other code is ported to this function. - player.health = bound(player.health, player.health + amount, - autocvar_g_balance_health_limit); - // Correct code: - //player.health = bound(player.health, player.health + amount, - // min(autocvar_g_balance_health_limit, - // RESOURCE_AMOUNT_HARD_LIMIT)); - player.pauserothealth_finished = max(player.pauserothealth_finished, - time + autocvar_g_balance_pause_health_rot); - return; - } - case armorvalue: - { - // Ugly hack. We do not check if armor goes beyond hard limit since - // currently it is done in player_regen. We need to bring back this - // check when other code is ported to this function. - player.armorvalue = bound(player.armorvalue, player.armorvalue + - amount, autocvar_g_balance_armor_limit); - // Correct code: - //player.armorvalue = bound(player.armorvalue, player.armorvalue + - // amount, min(autocvar_g_balance_armor_limit, - // RESOURCE_AMOUNT_HARD_LIMIT)); - player.pauserotarmor_finished = max(player.pauserotarmor_finished, - time + autocvar_g_balance_pause_armor_rot); - return; - } - case ammo_shells: - case ammo_nails: - case ammo_rockets: - case ammo_cells: - case ammo_plasma: - { - GivePlayerAmmo(player, resource_type, amount); - return; - } - case ammo_fuel: - { - player.ammo_fuel = bound(player.ammo_fuel, player.ammo_fuel + - amount, min(g_pickup_fuel_max, RESOURCE_AMOUNT_HARD_LIMIT)); - player.pauserotfuel_finished = max(player.pauserotfuel_finished, - time + autocvar_g_balance_pause_fuel_rot); - return; - } - } -} - -void GivePlayerHealth(entity player, float amount) -{ - GivePlayerResource(player, health, amount); -} - -void GivePlayerArmor(entity player, float amount) -{ - GivePlayerResource(player, armorvalue, amount); -} - -void GivePlayerAmmo(entity player, .float ammotype, float amount) -{ - if (amount == 0) - { - return; - } - float maxvalue = RESOURCE_AMOUNT_HARD_LIMIT; - switch (ammotype) - { - case ammo_shells: - { - maxvalue = g_pickup_shells_max; - break; - } - case ammo_cells: - { - maxvalue = g_pickup_cells_max; - break; - } - case ammo_rockets: - { - maxvalue = g_pickup_rockets_max; - break; - } - case ammo_plasma: - { - maxvalue = g_pickup_plasma_max; - break; - } - case ammo_nails: - { - maxvalue = g_pickup_nails_max; - break; - } - } - player.(ammotype) = min(player.(ammotype) + amount, - min(maxvalue, RESOURCE_AMOUNT_HARD_LIMIT)); -} - -void GivePlayerFuel(entity player, float amount) -{ - GivePlayerResource(player, ammo_fuel, amount); -} - -float Item_GiveAmmoTo(entity item, entity player, .float ammotype, float ammomax) -{ - if (!item.(ammotype)) return false; - + } + float player_amount = GetResourceAmount(player, resource_type); if (item.spawnshieldtime) { - if ((player.(ammotype) < ammomax) || item.pickup_anyway > 0) + if ((player_amount >= ammomax) && (item.pickup_anyway <= 0)) { - float amount = item.(ammotype); - if ((player.(ammotype) + amount) > ammomax) - { - amount = ammomax - player.(ammotype); - } - GivePlayerResource(player, ammotype, amount); - return true; + return false; } + GiveResourceWithLimit(player, resource_type, amount, ammomax); + return true; } - else if(g_weapon_stay == 2) + if (g_weapon_stay != 2) { - float mi = min(item.(ammotype), ammomax); - if (player.(ammotype) < mi) - { - GivePlayerResource(player, ammotype, mi - player.(ammotype)); - } - return true; + return false; } - return false; + GiveResourceWithLimit(player, resource_type, amount, min(amount, ammomax)); + return true; } float Item_GiveTo(entity item, entity player) @@ -850,14 +733,14 @@ float Item_GiveTo(entity item, entity player) } } } - pickedup |= Item_GiveAmmoTo(item, player, health, item.max_health); - pickedup |= Item_GiveAmmoTo(item, player, armorvalue, item.max_armorvalue); - pickedup |= Item_GiveAmmoTo(item, player, ammo_shells, g_pickup_shells_max); - pickedup |= Item_GiveAmmoTo(item, player, ammo_nails, g_pickup_nails_max); - pickedup |= Item_GiveAmmoTo(item, player, ammo_rockets, g_pickup_rockets_max); - pickedup |= Item_GiveAmmoTo(item, player, ammo_cells, g_pickup_cells_max); - pickedup |= Item_GiveAmmoTo(item, player, ammo_plasma, g_pickup_plasma_max); - pickedup |= Item_GiveAmmoTo(item, player, ammo_fuel, g_pickup_fuel_max); + pickedup |= Item_GiveAmmoTo(item, player, RESOURCE_HEALTH, item.max_health); + pickedup |= Item_GiveAmmoTo(item, player, RESOURCE_ARMOR, item.max_armorvalue); + pickedup |= Item_GiveAmmoTo(item, player, RESOURCE_SHELLS, g_pickup_shells_max); + pickedup |= Item_GiveAmmoTo(item, player, RESOURCE_BULLETS, g_pickup_nails_max); + pickedup |= Item_GiveAmmoTo(item, player, RESOURCE_ROCKETS, g_pickup_rockets_max); + pickedup |= Item_GiveAmmoTo(item, player, RESOURCE_CELLS, g_pickup_cells_max); + pickedup |= Item_GiveAmmoTo(item, player, RESOURCE_PLASMA, g_pickup_plasma_max); + pickedup |= Item_GiveAmmoTo(item, player, RESOURCE_FUEL, g_pickup_fuel_max); if (item.itemdef.instanceOfWeaponPickup) { WepSet w; @@ -1018,6 +901,7 @@ LABEL(pickup) if(it.itemdef) // is a registered item { Item_Show(it, -1); + it.scheduledrespawntime = 0; RandomSelection_AddEnt(it, it.cnt, 0); } }); diff --git a/qcsrc/common/t_items.qh b/qcsrc/common/t_items.qh index f557e10308..fa78ff4b33 100644 --- a/qcsrc/common/t_items.qh +++ b/qcsrc/common/t_items.qh @@ -4,9 +4,6 @@ #include #endif -/// \brief Unconditional maximum amount of resources the player can have. -const int RESOURCE_AMOUNT_HARD_LIMIT = 999; - const int AMMO_COUNT = 4; // amount of ammo types to show in the inventory panel // item networking @@ -87,39 +84,7 @@ void Item_ScheduleRespawn(entity e); void Item_ScheduleInitialRespawn(entity e); -/// \brief Gives player a resource such as health, armor or ammo. -/// \param[in,out] player Player to give resource to. -/// \param[in] resource_type Type of the resource. -/// \param[in] amount Amount of resource to give. -/// \return No return. -void GivePlayerResource(entity player, .float resource_type, float amount); - -/// \brief Gives health to the player. -/// \param[in,out] player Player to give health to. -/// \param[in] amount Amount of health to give. -/// \return No return. -void GivePlayerHealth(entity player, float amount); - -/// \brief Gives armor to the player. -/// \param[in,out] player Player to give armor to. -/// \param[in] amount Amount of armor to give. -/// \return No return. -void GivePlayerArmor(entity player, float amount); - -/// \brief Gives ammo of the specified type to the player. -/// \param[in,out] player Player to give ammo to. -/// \param[in] type Ammo type property. -/// \param[in] amount Amount of ammo to give. -/// \return No return. -void GivePlayerAmmo(entity player, .float ammotype, float amount); - -/// \brief Gives fuel to the player. -/// \param[in,out] player Player to give fuel to. -/// \param[in] amount Amount of fuel to give. -/// \return No return. -void GivePlayerFuel(entity player, float amount); - -float Item_GiveAmmoTo(entity item, entity player, .float ammotype, float ammomax); +float Item_GiveAmmoTo(entity item, entity player, int resource_type, float ammomax); float Item_GiveTo(entity item, entity player); diff --git a/qcsrc/common/weapons/all.qc b/qcsrc/common/weapons/all.qc index afbf79eb54..6605f00c26 100644 --- a/qcsrc/common/weapons/all.qc +++ b/qcsrc/common/weapons/all.qc @@ -530,7 +530,9 @@ void CL_WeaponEntity_SetModel(entity this, string name, bool _anim) int compressed_shotorg = compressShotOrigin(this.movedir); // make them match perfectly #ifdef SVQC - this.movedir = decompressShotOrigin(this.owner.stat_shotorg = compressed_shotorg); + // null during init + if (this.owner) this.owner.stat_shotorg = compressed_shotorg; + this.movedir = decompressShotOrigin(compressed_shotorg); #else this.movedir = decompressShotOrigin(compressed_shotorg); #endif diff --git a/qcsrc/common/weapons/projectiles.qh b/qcsrc/common/weapons/projectiles.qh index 73cd00d6c1..5a782b98f9 100644 --- a/qcsrc/common/weapons/projectiles.qh +++ b/qcsrc/common/weapons/projectiles.qh @@ -41,3 +41,5 @@ const int PROJECTILE_ARC_BOLT = 35; // projectile IDs 40-50 reserved const int PROJECTILE_RPC = 60; + +// projectile IDs 70-100 reserved diff --git a/qcsrc/dpdefs/post.qh b/qcsrc/dpdefs/post.qh index 16fd934503..70e5f37842 100644 --- a/qcsrc/dpdefs/post.qh +++ b/qcsrc/dpdefs/post.qh @@ -11,8 +11,8 @@ #undef setcolor #ifdef MENUQC - #define NULL (0, null_entity) + #define NULL (RVALUE, null_entity) #define world NULL #else - #define NULL (0, world) + #define NULL (RVALUE, world) #endif diff --git a/qcsrc/lib/_all.inc b/qcsrc/lib/_all.inc index 4da78f1444..9d58209103 100644 --- a/qcsrc/lib/_all.inc +++ b/qcsrc/lib/_all.inc @@ -182,7 +182,17 @@ void make_safe_for_remove(entity this); #define SV_Shutdown _SV_Shutdown void _StartFrame(); - void StartFrame() { if (_StartFrame) _StartFrame(); } + bool _StartFrame_init; + void spawnfunc_worldspawn(entity); + void StartFrame() { + if (!_StartFrame_init) { + _StartFrame_init = true; + float oldtime = time; time = 1; + __spawnfunc_expecting = 2; NULL.__spawnfunc_constructor(NULL); + time = oldtime; + } + if (_StartFrame) _StartFrame(); + } #define StartFrame _StartFrame void _SetNewParms(); diff --git a/qcsrc/lib/macro.qh b/qcsrc/lib/macro.qh index 1541b9997c..f2ec6df4a4 100644 --- a/qcsrc/lib/macro.qh +++ b/qcsrc/lib/macro.qh @@ -9,6 +9,9 @@ #define MACRO_END } while (0) #endif +/** Marker for use in (RVALUE, (expr)) */ +#define RVALUE 0 + #define _CAT(a, b) a ## b #define CAT(a, b) _CAT(a, b) diff --git a/qcsrc/lib/misc.qh b/qcsrc/lib/misc.qh index 21e0c5239f..6c29a4b88d 100644 --- a/qcsrc/lib/misc.qh +++ b/qcsrc/lib/misc.qh @@ -10,6 +10,7 @@ #include "p99.qh" #define OVERLOAD(F, ...) P99_IF_EMPTY(__VA_ARGS__)(P99_PASTE2(F, _00)())(P99_PASTE3(F, _, P00_NARG(__VA_ARGS__))(__VA_ARGS__)) + /** for use within a macro */ #define OVERLOAD_(F, ...) P99_IF_EMPTY(__VA_ARGS__)(P99_PASTE2(F, _00)())(P99_PASTE3(F, _, P00_NARG(__VA_ARGS__))(__VA_ARGS__)) #else #define EVAL(...) __VA_ARGS__ diff --git a/qcsrc/lib/net.qh b/qcsrc/lib/net.qh index 0a3dd8c662..b1f5326a9b 100644 --- a/qcsrc/lib/net.qh +++ b/qcsrc/lib/net.qh @@ -4,19 +4,30 @@ #include "sort.qh" #include "yenc.qh" +// netcode mismatch and not sure what caused it? developer_csqcentities 1 + .string netname; .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) +#define NET_GUARD(id) \ + bool Net_Handle_##id##_guard(entity this, entity sender, bool isNew) { \ + bool valid = false; \ + serialize_marker(to, valid); \ + if (!valid) LOG_FATALF("Last message not fully parsed: %s", _net_prevmsgstr); \ + _net_prevmsgstr = #id; \ + return Net_Handle_##id(this, sender, isNew); \ + } #ifdef CSQC +string _net_prevmsgstr; #define REGISTER_NET_TEMP(id) \ NET_HANDLE(id, bool); \ - REGISTER(TempEntities, NET, id, m_id, new_pure(net_temp_packet)) \ - { \ + NET_GUARD(id); \ + REGISTER(TempEntities, NET, id, m_id, new_pure(net_temp_packet)) { \ this.netname = #id; \ - this.m_read = Net_Handle_##id; \ + this.m_read = Net_Handle_##id##_guard; \ } #else #define REGISTER_NET_TEMP(id) \ @@ -45,10 +56,11 @@ STATIC_INIT(RegisterTempEntities_renumber) { FOREACH(TempEntities, true, it.m_id this.sourceLoc = __FILE__ ":" STR(__LINE__); \ if (!this) isnew = true; \ } \ + NET_GUARD(id); \ REGISTER(LinkedEntities, NET, id, m_id, new_pure(net_linked_packet)) \ { \ this.netname = #id; \ - this.m_read = Net_Handle_##id; \ + this.m_read = Net_Handle_##id##_guard; \ } #else #define REGISTER_NET_LINKED(id) \ @@ -205,6 +217,7 @@ STATIC_INIT(C2S_Protocol_renumber) { FOREACH(C2S_Protocol, true, it.m_id = i); } MACRO_BEGIN { \ if (NET_##id##_istemp) WriteByte(to, SVC_TEMPENTITY); \ WriteByte(to, NET_##id.m_id); \ + bool _net_valid = false; serialize_marker(to, _net_valid); \ } MACRO_END #endif @@ -219,7 +232,11 @@ USING(Stream, int); #define stream_writing(stream) false #endif -#define serialize(T, stream, ...) serialize_##T(stream, __VA_ARGS__) +#define serialize(T, stream, ...) \ +MACRO_BEGIN \ + noref Stream _stream = stream; \ + serialize_##T(_stream, __VA_ARGS__); \ +MACRO_END #if defined(SVQC) #define serialize_byte(stream, this) \ @@ -246,13 +263,27 @@ USING(Stream, int); #endif #define serialize_vector(stream, this) \ - MACRO_BEGIN \ +MACRO_BEGIN \ vector _v = this; \ serialize_float(stream, _v.x); \ serialize_float(stream, _v.y); \ serialize_float(stream, _v.z); \ this = _v; \ - MACRO_END +MACRO_END + +#define serialize_marker(stream, this) \ +MACRO_BEGIN \ + if (NDEBUG) { \ + this = true; \ + } else { \ + int _de = 0xDE, _ad = 0xAD, _be = 0xBE, _ef = 0xEF; \ + serialize_byte(stream, _de); \ + serialize_byte(stream, _ad); \ + serialize_byte(stream, _be); \ + serialize_byte(stream, _ef); \ + this = (_de == 0xDE && _ad == 0xAD && _be == 0xBE && _ef == 0xEF); \ + } \ +MACRO_END // serialization: old diff --git a/qcsrc/lib/registry.qh b/qcsrc/lib/registry.qh index 8ca07b43f3..2d41e5d431 100644 --- a/qcsrc/lib/registry.qh +++ b/qcsrc/lib/registry.qh @@ -187,7 +187,7 @@ void Registry_send(string id, string hash); #define EVAL_REGISTER_REGISTRY(...) __VA_ARGS__ #define REGISTER_REGISTRY_1(id) REGISTER_REGISTRY_2(id, #id) #define REGISTER_REGISTRY_2(id, str) \ - ACCUMULATE_FUNCTION(__static_init, Register##id) \ + ACCUMULATE_FUNCTION(__static_init_1, Register##id) \ CLASS(id##Registry, Object) \ ATTRIB(id##Registry, m_name, string, str); \ ATTRIB(id##Registry, REGISTRY_NEXT, entity, id##_first); \ diff --git a/qcsrc/lib/self.qh b/qcsrc/lib/self.qh index f4c246f330..0a61cc003d 100644 --- a/qcsrc/lib/self.qh +++ b/qcsrc/lib/self.qh @@ -1,5 +1,7 @@ #pragma once +#include "macro.qh" + // Transition from global 'self' to local 'this' // Step 1: auto oldself @@ -9,10 +11,10 @@ // Step 2: const self #if 1 - #define self (0, self) + #define self (RVALUE, self) [[alias("self")]] entity __self; #define setself(s) (__self = s) - #define WITHSELF(value, block) WITH(entity, __self, value, (0, block)) + #define WITHSELF(value, block) WITH(entity, __self, value, (RVALUE, block)) #endif // Step 3: propagate SELFPARAM() @@ -32,7 +34,7 @@ // Step 5: this should work #if 1 #undef self - #define self (0, this) + #define self (RVALUE, this) #endif // Step 6: remove SELFPARAM, add parameters @@ -56,11 +58,11 @@ noref entity _selftemp; #define SELFWRAP_SET(T, e, f) \ (_selftemp = (e), _selftemp.__##T = ((f) ? T##_self : func_null), _selftemp.self##T = (f)) #define SELFWRAP_GET(T, e) \ - (0, (e).self##T) + (RVALUE, (e).self##T) #define _SELFWRAP_SET(T, e, f) \ ((e).__##T = (f)) #define _SELFWRAP_GET(T, e) \ - (0, (e).__##T) + (RVALUE, (e).__##T) SELFWRAP(think, void, (), (entity this), (this)) #define setthink(e, f) SELFWRAP_SET(think, e, f) diff --git a/qcsrc/lib/spawnfunc.qh b/qcsrc/lib/spawnfunc.qh index e0605c9384..4638bca2d4 100644 --- a/qcsrc/lib/spawnfunc.qh +++ b/qcsrc/lib/spawnfunc.qh @@ -27,34 +27,77 @@ noref bool require_spawnfunc_prefix; #define _spawnfunc_check(fld) \ if (fieldname == #fld) continue; - noref bool __spawnfunc_expecting; + noref int __spawnfunc_expecting; noref entity __spawnfunc_expect; noref bool __spawnfunc_unreachable_workaround = true; + .void(entity) __spawnfunc_constructor; + noref IntrusiveList g_spawn_queue; + + #define SPAWNFUNC_INTERNAL_FIELDS(X) \ + X(string, classname, "spawnfunc") \ + X(string, targetname, string_null) \ + /**/ + + #define X(T, fld, def) .T fld, __spawnfunc_##fld; + SPAWNFUNC_INTERNAL_FIELDS(X) + #undef X + + void __spawnfunc_defer(entity prototype, void(entity) constructor) + { + IL_PUSH(g_spawn_queue, prototype); + #define X(T, fld, def) { prototype.__spawnfunc_##fld = prototype.fld; prototype.fld = def; } + SPAWNFUNC_INTERNAL_FIELDS(X); + #undef X + prototype.__spawnfunc_constructor = constructor; + } + + noref IntrusiveList g_map_entities; + #define __spawnfunc_spawn_all() MACRO_BEGIN \ + g_map_entities = IL_NEW(); \ + IL_EACH(g_spawn_queue, true, __spawnfunc_spawn(it)); \ + MACRO_END + + void __spawnfunc_spawn(entity prototype) + { + entity e = new(clone); + copyentity(prototype, e); + IL_PUSH(g_map_entities, e); + #define X(T, fld, def) { e.fld = e.__spawnfunc_##fld; e.__spawnfunc_##fld = def; } + SPAWNFUNC_INTERNAL_FIELDS(X); + #undef X + e.__spawnfunc_constructor(e); + } + + noref bool __spawnfunc_first; + #define spawnfunc_1(id) spawnfunc_2(id, FIELDS_UNION) #define spawnfunc_2(id, whitelist) \ void __spawnfunc_##id(entity this); \ [[accumulate]] void spawnfunc_##id(entity this) \ { \ - if (__spawnfunc_expecting) \ - { \ + if (!__spawnfunc_first) { \ + __spawnfunc_first = true; \ + static_init_early(); \ + } \ + bool dospawn = true; \ + if (__spawnfunc_expecting > 1) { __spawnfunc_expecting = false; } \ + else if (__spawnfunc_expecting) { \ /* engine call */ \ + if (!g_spawn_queue) { g_spawn_queue = IL_NEW(); } \ __spawnfunc_expecting = false; \ this = __spawnfunc_expect; \ __spawnfunc_expect = NULL; \ - } \ - else \ - { \ + dospawn = false; \ + } else { \ + /* userland call */ \ assert(this); \ } \ - if (!this.sourceLoc) \ - { \ + if (!this.sourceLoc) { \ this.sourceLoc = __FILE__ ":" STR(__LINE__); \ } \ - if (!this.spawnfunc_checked) \ - { \ - for (int i = 0, n = numentityfields(); i < n; ++i) \ - { \ + if (!this.spawnfunc_checked) { \ + for (int i = 0, n = numentityfields(); i < n; ++i) { \ string value = getentityfieldstring(i, this); \ string fieldname = entityfieldname(i); \ whitelist(_spawnfunc_checktypes) \ @@ -65,8 +108,15 @@ noref bool require_spawnfunc_prefix; LOG_WARNF(_("Entity field %s.%s (%s) is not whitelisted. If you believe this is an error, please file an issue."), #id, fieldname, value); \ } \ this.spawnfunc_checked = true; \ + if (this) { \ + /* not worldspawn, delay spawn */ \ + __spawnfunc_defer(this, __spawnfunc_##id); \ + } else { \ + /* world might not be "worldspawn" */ \ + this.__spawnfunc_constructor = __spawnfunc_##id; \ + } \ } \ - __spawnfunc_##id(this); \ + if (dospawn) { __spawnfunc_##id(this); } \ if (__spawnfunc_unreachable_workaround) return; \ } \ void __spawnfunc_##id(entity this) diff --git a/qcsrc/lib/static.qh b/qcsrc/lib/static.qh index e2c5fd4f00..6f511fcecf 100644 --- a/qcsrc/lib/static.qh +++ b/qcsrc/lib/static.qh @@ -1,14 +1,5 @@ #pragma once -void __static_init() {} -#define static_init() CALL_ACCUMULATED_FUNCTION(__static_init) -void __static_init_late() {} -#define static_init_late() CALL_ACCUMULATED_FUNCTION(__static_init_late) -void __static_init_precache() {} -#define static_init_precache() CALL_ACCUMULATED_FUNCTION(__static_init_precache) -void __shutdown() {} -#define shutdownhooks() CALL_ACCUMULATED_FUNCTION(__shutdown) - #define GETTIME_REALTIME 1 #ifdef MENUQC float(int tmr) _gettime = #67; @@ -25,12 +16,34 @@ void profile(string s) LOG_TRACEF("[%f] %s", rt - g_starttime, s); } -#define _STATIC_INIT(where, func) \ +#define _STATIC_INIT(func, where) \ [[accumulate]] void _static_##func() { profile(#func); } \ ACCUMULATE_FUNCTION(where, _static_##func) \ void _static_##func() -#define STATIC_INIT(func) _STATIC_INIT(__static_init, func) -#define STATIC_INIT_LATE(func) _STATIC_INIT(__static_init_late, func##_late) -#define PRECACHE(func) _STATIC_INIT(__static_init_precache, func##_precache) -#define SHUTDOWN(func) _STATIC_INIT(__shutdown, func##_shutdown) +/** before worldspawn */ +#define STATIC_INIT_EARLY(func) _STATIC_INIT(func##_0, __static_init_0) +#define static_init_early() CALL_ACCUMULATED_FUNCTION(__static_init_0) +void __static_init_0() {} + +/** during worldspawn */ +#define STATIC_INIT(func) _STATIC_INIT(func##_1, __static_init_1) +#define static_init() CALL_ACCUMULATED_FUNCTION(__static_init_1) +void __static_init_1() {} + +/** directly after STATIC_INIT */ +#define STATIC_INIT_LATE(func) _STATIC_INIT(func##_2, __static_init_2) +#define static_init_late() CALL_ACCUMULATED_FUNCTION(__static_init_2) +void __static_init_2() {} + +/** directly after STATIC_INIT_LATE */ +#define PRECACHE(func) _STATIC_INIT(func##_3, __static_init_3) +#define static_init_precache() CALL_ACCUMULATED_FUNCTION(__static_init_3) +void __static_init_3() {} + +/* other map entities spawn now */ + +/** before shutdown */ +#define SHUTDOWN(func) _STATIC_INIT(func##_shutdown, __shutdown) +#define shutdownhooks() CALL_ACCUMULATED_FUNCTION( __shutdown) +void __shutdown() {} diff --git a/qcsrc/lib/stats.qh b/qcsrc/lib/stats.qh index 4642f76403..1100c474cb 100644 --- a/qcsrc/lib/stats.qh +++ b/qcsrc/lib/stats.qh @@ -33,8 +33,8 @@ int g_magic_stats_hole = 0; void stats_get() {} #define STAT(...) EVAL_STAT(OVERLOAD(STAT, __VA_ARGS__)) #define EVAL_STAT(...) __VA_ARGS__ - #define STAT_1(id) STAT_2(id, NULL) - #define STAT_2(id, cl) (0, _STAT(id)) + #define STAT_1(id) (RVALUE, _STAT(id)) + #define STAT_2(id, cl) STAT_1(id) #define getstat_int(id) getstati(id, 0, 24) #define getstat_bool(id) boolean(getstati(id)) @@ -61,9 +61,14 @@ int g_magic_stats_hole = 0; } #define REGISTER_STAT_3(x, T, expr) REGISTER_STAT_2(x, T) #elif defined(SVQC) + /** Internal use only */ + entity STATS; /** Add all registered stats, access with `STAT(ID, player)` or `.type stat = _STAT(ID); player.stat` */ void stats_add() {} - #define STAT(id, cl) (cl)._STAT(id) + #define STAT(...) EVAL_STAT(OVERLOAD_(STAT, __VA_ARGS__)) + #define EVAL_STAT(...) __VA_ARGS__ + #define STAT_1(id) (RVALUE, STAT_2(id, STATS)) + #define STAT_2(id, cl) (cl)._STAT(id) #define addstat_int(id, fld) addstat(id, AS_INT, fld) #define addstat_bool(id, fld) addstat(id, AS_INT, fld) @@ -83,9 +88,10 @@ int g_magic_stats_hole = 0; const int AS_FLOAT = 8; .int __stat_null; - /** Prevent engine stats being sent */ - STATIC_INIT(stats_clear) + STATIC_INIT(stats) { + STATS = new(stats); + // Prevent engine stats being sent int r = STATS_ENGINE_RESERVE; for (int i = 0, n = 256 - r; i < n; ++i) { #define X(_, name, id) if (i == id) continue; @@ -111,10 +117,11 @@ int g_magic_stats_hole = 0; addstat_##T(STAT_##id.m_id, fld); \ } void GlobalStats_update(entity this) {} + /** TODO: do we want the global copy to update? */ #define REGISTER_STAT_3(id, T, expr) \ REGISTER_STAT_2(id, T); \ [[accumulate]] void GlobalStats_update(entity this) { STAT(id, this) = (expr); } \ - STATIC_INIT(worldstat_##id) { entity this = NULL; STAT(id, this) = (expr); } + STATIC_INIT(worldstat_##id) { entity this = STATS; STAT(id, this) = (expr); } #else #define REGISTER_STAT_2(id, type) #define REGISTER_STAT_3(id, T, expr) diff --git a/qcsrc/server/_mod.inc b/qcsrc/server/_mod.inc index 87a8d56892..99115fbdc4 100644 --- a/qcsrc/server/_mod.inc +++ b/qcsrc/server/_mod.inc @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include diff --git a/qcsrc/server/_mod.qh b/qcsrc/server/_mod.qh index 2967c110ce..3a88986703 100644 --- a/qcsrc/server/_mod.qh +++ b/qcsrc/server/_mod.qh @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include diff --git a/qcsrc/server/autocvars.qh b/qcsrc/server/autocvars.qh index f585a9a2c3..4303aa9764 100644 --- a/qcsrc/server/autocvars.qh +++ b/qcsrc/server/autocvars.qh @@ -88,7 +88,7 @@ float autocvar_g_balance_superweapons_time; float autocvar_g_balance_selfdamagepercent; bool autocvar_g_balance_teams; bool autocvar_g_balance_teams_prevent_imbalance; -float autocvar_g_balance_teams_scorefactor; +//float autocvar_g_balance_teams_scorefactor; float autocvar_g_ballistics_density_corpse; float autocvar_g_ballistics_density_player; float autocvar_g_ballistics_mindistance; @@ -165,12 +165,7 @@ int autocvar_g_maxplayers; float autocvar_g_maxplayers_spectator_blocktime; float autocvar_g_maxpushtime; float autocvar_g_maxspeed; -#define autocvar_g_instagib cvar("g_instagib") -bool autocvar_g_instagib_damagedbycontents = true; -bool autocvar_g_instagib_blaster_keepdamage = false; -bool autocvar_g_instagib_blaster_keepforce = false; -bool autocvar_g_instagib_mirrordamage; -bool autocvar_g_instagib_friendlypush = true; +bool autocvar_g_instagib; #define autocvar_g_mirrordamage cvar("g_mirrordamage") #define autocvar_g_mirrordamage_virtual cvar("g_mirrordamage_virtual") bool autocvar_g_mirrordamage_onlyweapons; @@ -416,7 +411,6 @@ float autocvar_g_monsters_armor_blockpercent; float autocvar_g_monsters_healthbars; bool autocvar_g_monsters_lineofsight = true; //bool autocvar_g_monsters_ignoretraces = true; -#define autocvar_g_bloodloss cvar("g_bloodloss") bool autocvar_g_nades; bool autocvar_g_nades_override_dropweapon = true; vector autocvar_g_nades_throw_offset; diff --git a/qcsrc/server/client.qc b/qcsrc/server/client.qc index f1d417d6ba..6a978c6aeb 100644 --- a/qcsrc/server/client.qc +++ b/qcsrc/server/client.qc @@ -12,6 +12,7 @@ #include "teamplay.qh" #include "playerdemo.qh" #include "spawnpoints.qh" +#include "resources.qh" #include "g_damage.qh" #include "g_hook.qh" #include "command/common.qh" @@ -268,7 +269,9 @@ void PutObserverInServer(entity this) if (mutator_returnvalue) { // mutator prevents resetting teams+score } else { + int oldteam = this.team; this.team = -1; // move this as it is needed to log the player spectating in eventlog + MUTATOR_CALLHOOK(Player_ChangedTeam, this, oldteam, this.team); this.frags = FRAGS_SPECTATOR; PlayerScore_Clear(this); // clear scores when needed } @@ -892,8 +895,10 @@ void ClientKill_Now(entity this) if(CS(this).killindicator_teamchange) ClientKill_Now_TeamChange(this); - if(!IS_SPEC(this) && !IS_OBSERVER(this)) + if (!IS_SPEC(this) && !IS_OBSERVER(this) && MUTATOR_CALLHOOK(ClientKill_Now, this) == false) + { Damage(this, this, this, 100000, DEATH_KILL.m_id, this.origin, '0 0 0'); + } // now I am sure the player IS dead } @@ -1628,8 +1633,8 @@ void player_regen(entity this) float mina, maxa, limith, limita; maxa = autocvar_g_balance_armor_rotstable; mina = autocvar_g_balance_armor_regenstable; - limith = autocvar_g_balance_health_limit; - limita = autocvar_g_balance_armor_limit; + limith = GetResourceLimit(this, RESOURCE_HEALTH); + limita = GetResourceLimit(this, RESOURCE_ARMOR); regen_health_rotstable = regen_health_rotstable * max_mod; regen_health_stable = regen_health_stable * max_mod; @@ -1656,7 +1661,7 @@ void player_regen(entity this) maxf = autocvar_g_balance_fuel_rotstable; minf = autocvar_g_balance_fuel_regenstable; - limitf = autocvar_g_balance_fuel_limit; + limitf = GetResourceLimit(this, RESOURCE_FUEL); this.ammo_fuel = CalcRotRegen(this.ammo_fuel, minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, frametime * (time > this.pauseregen_finished) * ((this.items & ITEM_JetpackRegen.m_itemid) != 0), maxf, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, frametime * (time > this.pauserotfuel_finished), limitf); } diff --git a/qcsrc/server/client.qh b/qcsrc/server/client.qh index 9674872c0f..2282c09cbb 100644 --- a/qcsrc/server/client.qh +++ b/qcsrc/server/client.qh @@ -224,8 +224,6 @@ METHOD(Client, m_unwind, bool(Client this)) return false; } -float c1, c2, c3, c4; - void play_countdown(entity this, float finished, Sound samp); float CalcRotRegen(float current, float regenstable, float regenfactor, float regenlinear, float regenframetime, float rotstable, float rotfactor, float rotlinear, float rotframetime, float limit); diff --git a/qcsrc/server/command/cmd.qc b/qcsrc/server/command/cmd.qc index a2c037c5d2..09a308ad5c 100644 --- a/qcsrc/server/command/cmd.qc +++ b/qcsrc/server/command/cmd.qc @@ -325,82 +325,90 @@ void ClientCommand_selectteam(entity caller, float request, float argc) { case CMD_REQUEST_COMMAND: { - if (argv(1) != "") + if (argv(1) == "") { - if (IS_CLIENT(caller)) + return; + } + if (!IS_CLIENT(caller)) + { + return; + } + if (!teamplay) + { + sprint(caller, "^7selectteam can only be used in teamgames\n"); + return; + } + if (caller.team_forced > 0) + { + sprint(caller, "^7selectteam can not be used as your team is forced\n"); + return; + } + if (lockteams) + { + sprint(caller, "^7The game has already begun, you must wait until the next map to be able to join a team.\n"); + return; + } + float selection; + switch (argv(1)) + { + case "red": { - if (teamplay) - { - if (caller.team_forced <= 0) - { - if (!lockteams) - { - float selection; - - switch (argv(1)) - { - case "red": selection = NUM_TEAM_1; - break; - case "blue": selection = NUM_TEAM_2; - break; - case "yellow": selection = NUM_TEAM_3; - break; - case "pink": selection = NUM_TEAM_4; - break; - case "auto": selection = (-1); - break; - - default: selection = 0; - break; - } - - if (selection) - { - if (caller.team == selection && selection != -1 && !IS_DEAD(caller)) - { - sprint(caller, "^7You already are on that team.\n"); - } - else if (CS(caller).wasplayer && autocvar_g_changeteam_banned) - { - sprint(caller, "^1You cannot change team, forbidden by the server.\n"); - } - else - { - if (autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance) - { - CheckAllowedTeams(caller); - GetTeamCounts(caller); - if (!TeamSmallerEqThanTeam(Team_TeamToNumber(selection), Team_TeamToNumber(caller.team), caller)) - { - Send_Notification(NOTIF_ONE, caller, MSG_INFO, INFO_TEAMCHANGE_LARGERTEAM); - return; - } - } - ClientKill_TeamChange(caller, selection); - } - if(!IS_PLAYER(caller)) - caller.team_selected = true; // avoids asking again for team selection on join - } - } - else - { - sprint(caller, "^7The game has already begun, you must wait until the next map to be able to join a team.\n"); - } - } - else - { - sprint(caller, "^7selectteam can not be used as your team is forced\n"); - } - } - else - { - sprint(caller, "^7selectteam can only be used in teamgames\n"); - } + selection = NUM_TEAM_1; + break; + } + case "blue": + { + selection = NUM_TEAM_2; + break; + } + case "yellow": + { + selection = NUM_TEAM_3; + break; + } + case "pink": + { + selection = NUM_TEAM_4; + break; } + case "auto": + { + selection = (-1); + break; + } + default: + { + return; + } + } + if (caller.team == selection && selection != -1 && !IS_DEAD(caller)) + { + sprint(caller, "^7You already are on that team.\n"); + return; + } + if (CS(caller).wasplayer && autocvar_g_changeteam_banned) + { + sprint(caller, "^1You cannot change team, forbidden by the server.\n"); return; } + if ((selection != -1) && autocvar_g_balance_teams && + autocvar_g_balance_teams_prevent_imbalance) + { + CheckAllowedTeams(caller); + GetTeamCounts(caller); + if ((BIT(Team_TeamToNumber(selection) - 1) & FindBestTeams(caller, false)) == 0) + { + Send_Notification(NOTIF_ONE, caller, MSG_INFO, INFO_TEAMCHANGE_LARGERTEAM); + return; + } + } + ClientKill_TeamChange(caller, selection); + if (!IS_PLAYER(caller)) + { + caller.team_selected = true; // avoids asking again for team selection on join + } + return; } - default: sprint(caller, "Incorrect parameters for ^2selectteam^7\n"); case CMD_REQUEST_USAGE: diff --git a/qcsrc/server/command/sv_cmd.qc b/qcsrc/server/command/sv_cmd.qc index 43ce29160b..6de4507b17 100644 --- a/qcsrc/server/command/sv_cmd.qc +++ b/qcsrc/server/command/sv_cmd.qc @@ -1095,9 +1095,15 @@ void GameCommand_moveplayer(float request, float argc) // If so, lets continue and finally move the player client.team_forced = 0; - MoveToTeam(client, team_id, 6); - successful = strcat(successful, (successful ? ", " : ""), playername(client, false)); - LOG_INFO("Player ", ftos(GetFilteredNumber(t)), " (", playername(client, false), ") has been moved to the ", Team_ColoredFullName(team_id), "^7."); + if (MoveToTeam(client, team_id, 6)) + { + successful = strcat(successful, (successful ? ", " : ""), playername(client, false)); + LOG_INFO("Player ", ftos(GetFilteredNumber(t)), " (", playername(client, false), ") has been moved to the ", Team_ColoredFullName(team_id), "^7."); + } + else + { + LOG_INFO("Unable to move player ", ftos(GetFilteredNumber(t)), " (", playername(client, false), ")"); + } continue; } else diff --git a/qcsrc/server/command/vote.qc b/qcsrc/server/command/vote.qc index 8cfed5bae4..1a786cc7fe 100644 --- a/qcsrc/server/command/vote.qc +++ b/qcsrc/server/command/vote.qc @@ -81,8 +81,9 @@ bool Nagger_SendEntity(entity this, entity to, float sendflags) { for (i = 1; i <= maxclients; i += 8) { - for (f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e)) - if (!IS_REAL_CLIENT(e) || e.ready) f |= b; + for (f = 0, e = edict_num(i), b = BIT(0); b < BIT(8); b <<= 1, e = nextent(e)) + if (!IS_REAL_CLIENT(e) || e.ready) + f |= b; WriteByte(MSG_ENTITY, f); } } diff --git a/qcsrc/server/g_damage.qc b/qcsrc/server/g_damage.qc index 8a0223804d..0270462961 100644 --- a/qcsrc/server/g_damage.qc +++ b/qcsrc/server/g_damage.qc @@ -9,6 +9,7 @@ #include "../common/state.qh" #include "../common/physics/player.qh" #include "../common/t_items.qh" +#include "resources.qh" #include "../common/vehicles/all.qh" #include "../common/items/_mod.qh" #include "../common/mutators/mutator/waypoints/waypointsprites.qh" @@ -275,7 +276,7 @@ bool frag_centermessage_override(entity attacker, entity targ, int deathtype, in if(deathtype == DEATH_FIRE.m_id) { Send_Notification(NOTIF_ONE, attacker, MSG_CHOICE, CHOICE_FRAG_FIRE, targ.netname, kill_count_to_attacker, (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)); - Send_Notification(NOTIF_ONE, targ, MSG_CHOICE, CHOICE_FRAGGED_FIRE, attacker.netname, kill_count_to_target, attacker.health, attacker.armorvalue, (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)); + Send_Notification(NOTIF_ONE, targ, MSG_CHOICE, CHOICE_FRAGGED_FIRE, attacker.netname, kill_count_to_target, GetResourceAmount(attacker, RESOURCE_HEALTH), GetResourceAmount(attacker, RESOURCE_ARMOR), (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)); return true; } @@ -427,8 +428,8 @@ void Obituary(entity attacker, entity inflictor, entity targ, int deathtype) CHOICE_TYPEFRAGGED, attacker.netname, kill_count_to_target, - attacker.health, - attacker.armorvalue, + GetResourceAmount(attacker, RESOURCE_HEALTH), + GetResourceAmount(attacker, RESOURCE_ARMOR), (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping) ); } @@ -450,8 +451,8 @@ void Obituary(entity attacker, entity inflictor, entity targ, int deathtype) CHOICE_FRAGGED, attacker.netname, kill_count_to_target, - attacker.health, - attacker.armorvalue, + GetResourceAmount(attacker, RESOURCE_HEALTH), + GetResourceAmount(attacker, RESOURCE_ARMOR), (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping) ); } @@ -543,7 +544,7 @@ void Freeze (entity targ, float freeze_time, float frozen_type, float show_waypo STAT(FROZEN, targ) = frozen_type; targ.revive_progress = ((frozen_type == 3) ? 1 : 0); - targ.health = ((frozen_type == 3) ? targ_maxhealth : 1); + SetResourceAmount(targ, RESOURCE_HEALTH, ((frozen_type == 3) ? targ_maxhealth : 1)); targ.revive_speed = freeze_time; if(targ.bot_attack) IL_REMOVE(g_bot_targets, targ); @@ -588,7 +589,7 @@ void Unfreeze (entity targ) if(STAT(FROZEN, targ) && STAT(FROZEN, targ) != 3) // only reset health if target was frozen { - targ.health = ((IS_PLAYER(targ)) ? start_health : targ.max_health); + SetResourceAmount(targ, RESOURCE_HEALTH, ((IS_PLAYER(targ)) ? start_health : targ.max_health)); targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen; } @@ -646,9 +647,9 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, int d // These are ALWAYS lethal // No damage modification here // Instead, prepare the victim for his death... - targ.armorvalue = 0; + SetResourceAmount(targ, RESOURCE_ARMOR, 0); targ.spawnshieldtime = 0; - targ.health = 0.9; // this is < 1 + SetResourceAmount(targ, RESOURCE_HEALTH, 0.9); // this is < 1 targ.flags -= targ.flags & FL_GODMODE; damage = 100000; } @@ -748,7 +749,7 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, int d if(damage >= autocvar_g_frozen_revive_falldamage) { Unfreeze(targ); - targ.health = autocvar_g_frozen_revive_falldamage_health; + SetResourceAmount(targ, RESOURCE_HEALTH, autocvar_g_frozen_revive_falldamage_health); Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3); Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname); Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF); diff --git a/qcsrc/server/g_world.qc b/qcsrc/server/g_world.qc index 64aa03b502..ce25dfa522 100644 --- a/qcsrc/server/g_world.qc +++ b/qcsrc/server/g_world.qc @@ -521,7 +521,7 @@ void detect_maptype() o.y += random() * (world.maxs.y - world.mins.y); o.z += random() * (world.maxs.z - world.mins.z); - tracebox(o, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), o - '0 0 32768', MOVE_WORLDONLY, NULL); + tracebox(o, STAT(PL_MIN), STAT(PL_MAX), o - '0 0 32768', MOVE_WORLDONLY, NULL); if(trace_fraction == 1) continue; @@ -580,7 +580,15 @@ spawnfunc(__init_dedicated_server) e = new(info_player_deathmatch); // safeguard against player joining - this.classname = "worldspawn"; // safeguard against various stuff ;) + // assign reflectively to avoid "assignment to world" warning + for (int i = 0, n = numentityfields(); i < n; ++i) { + string k = entityfieldname(i); + if (k == "classname") { + // safeguard against various stuff ;) + putentityfieldstring(i, this, "worldspawn"); + break; + } + } // needs to be done so early because of the constants they create static_init(); @@ -597,6 +605,14 @@ void __init_dedicated_server_shutdown() { MapInfo_Shutdown(); } +STATIC_INIT_EARLY(maxclients) +{ + maxclients = 0; + for (entity head = nextent(NULL); head; head = nextent(head)) { + ++maxclients; + } +} + void Map_MarkAsRecent(string m); float world_already_spawned; void Nagger_Init(); @@ -680,12 +696,6 @@ spawnfunc(worldspawn) cvar_changes_init(); // do this very early now so it REALLY matches the server config - maxclients = 0; - for (entity head = nextent(NULL); head; head = nextent(head)) - { - ++maxclients; - } - // needs to be done so early because of the constants they create static_init(); @@ -936,6 +946,7 @@ spawnfunc(worldspawn) WinningConditionHelper(this); // set worldstatus world_initialized = 1; + __spawnfunc_spawn_all(); } spawnfunc(light) diff --git a/qcsrc/server/miscfunctions.qc b/qcsrc/server/miscfunctions.qc index 7bb8e922d5..554da5f804 100644 --- a/qcsrc/server/miscfunctions.qc +++ b/qcsrc/server/miscfunctions.qc @@ -933,24 +933,21 @@ void InitializeEntitiesRun() .float(entity) isEliminated; bool EliminatedPlayers_SendEntity(entity this, entity to, float sendflags) { - float i, f, b; - entity e; - WriteHeader(MSG_ENTITY, ENT_CLIENT_ELIMINATEDPLAYERS); - WriteByte(MSG_ENTITY, sendflags); - - if(sendflags & 1) - { - for(i = 1; i <= maxclients; i += 8) - { - for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e)) - { - if(eliminatedPlayers.isEliminated(e)) - f |= b; + Stream out = MSG_ENTITY; + WriteHeader(out, ENT_CLIENT_ELIMINATEDPLAYERS); + serialize(byte, out, sendflags); + if (sendflags & 1) { + for (int i = 1; i <= maxclients; i += 8) { + int f = 0; + entity e = edict_num(i); + for (int b = 0; b < 8; ++b, e = nextent(e)) { + if (eliminatedPlayers.isEliminated(e)) { + f |= BIT(b); + } } - WriteByte(MSG_ENTITY, f); + serialize(byte, out, f); } } - return true; } diff --git a/qcsrc/server/mutators/events.qh b/qcsrc/server/mutators/events.qh index bc09d7a8cd..0110bdf1bd 100644 --- a/qcsrc/server/mutators/events.qh +++ b/qcsrc/server/mutators/events.qh @@ -130,6 +130,31 @@ MUTATOR_HOOKABLE(MatchEnd, EV_NO_ARGS); /**/ MUTATOR_HOOKABLE(CheckAllowedTeams, EV_CheckAllowedTeams); +/** return true to manually override team counts */ +MUTATOR_HOOKABLE(GetTeamCounts, EV_NO_ARGS); + +/** allow overriding of team counts */ +#define EV_GetTeamCount(i, o) \ + /** team to count */ i(float, MUTATOR_ARGV_0_float) \ + /** player to ignore */ i(entity, MUTATOR_ARGV_1_entity) \ + /** number of players in a team */ i(float, MUTATOR_ARGV_2_float) \ + /**/ o(float, MUTATOR_ARGV_2_float) \ + /** number of bots in a team */ i(float, MUTATOR_ARGV_3_float) \ + /**/ o(float, MUTATOR_ARGV_3_float) \ + /** lowest scoring human in a team */ i(entity, MUTATOR_ARGV_4_entity) \ + /**/ o(entity, MUTATOR_ARGV_4_entity) \ + /** lowest scoring bot in a team */ i(entity, MUTATOR_ARGV_5_entity) \ + /**/ o(entity, MUTATOR_ARGV_5_entity) \ + /**/ +MUTATOR_HOOKABLE(GetTeamCount, EV_GetTeamCount); + +/** allows overriding best teams */ +#define EV_FindBestTeams(i, o) \ + /** player checked */ i(entity, MUTATOR_ARGV_0_entity) \ + /** bitmask of teams */ o(float, MUTATOR_ARGV_1_float) \ + /**/ +MUTATOR_HOOKABLE(FindBestTeams, EV_FindBestTeams); + /** copies variables for spectating "spectatee" to "this" */ #define EV_SpectateCopy(i, o) \ /** spectatee */ i(entity, MUTATOR_ARGV_0_entity) \ @@ -620,6 +645,38 @@ enum { MUT_ITEMTOUCH_PICKUP // return this flag to have the item "picked up" and taken even after mutator handled it }; +/** Called when the amount of entity resources changes. Can be used to override +resource limit. */ +#define EV_GetResourceLimit(i, o) \ + /** checked entity */ i(entity, MUTATOR_ARGV_0_entity) \ + /** resource type */ i(int, MUTATOR_ARGV_1_int) \ + /** limit */ i(float, MUTATOR_ARGV_2_float) \ + /**/ o(float, MUTATOR_ARGV_2_float) \ + /**/ +MUTATOR_HOOKABLE(GetResourceLimit, EV_GetResourceLimit); + +/** Called when the amount of resource of an entity changes. See RESOURCE_* +constants for resource types. Return true to forbid the change. */ +#define EV_SetResourceAmount(i, o) \ + /** checked entity */ i(entity, MUTATOR_ARGV_0_entity) \ + /** resource type */ i(int, MUTATOR_ARGV_1_int) \ + /**/ o(int, MUTATOR_ARGV_1_int) \ + /** amount */ i(float, MUTATOR_ARGV_2_float) \ + /**/ o(float, MUTATOR_ARGV_2_float) \ + /**/ +MUTATOR_HOOKABLE(SetResourceAmount, EV_SetResourceAmount); + +/** Called when entity is being given some resource. See RESOURCE_* constants +for resource types. Return true to forbid giving. */ +#define EV_GiveResource(i, o) \ + /** receiver */ i(entity, MUTATOR_ARGV_0_entity) \ + /** resource type */ i(int, MUTATOR_ARGV_1_int) \ + /**/ o(int, MUTATOR_ARGV_1_int) \ + /** amount */ i(float, MUTATOR_ARGV_2_float) \ + /**/ o(float, MUTATOR_ARGV_2_float) \ + /**/ +MUTATOR_HOOKABLE(GiveResource, EV_GiveResource); + /** called at when a player connect */ #define EV_ClientConnect(i, o) \ /** player */ i(entity, MUTATOR_ARGV_0_entity) \ @@ -728,6 +785,12 @@ MUTATOR_HOOKABLE(Race_FinalCheckpoint, EV_Race_FinalCheckpoint); /**/ MUTATOR_HOOKABLE(ClientKill, EV_ClientKill); +/** called when player is about to be killed during kill command or changing teams */ +#define EV_ClientKill_Now(i, o) \ + /** player */ i(entity, MUTATOR_ARGV_0_entity) \ + /**/ +MUTATOR_HOOKABLE(ClientKill_Now, EV_ClientKill_Now); + #define EV_FixClientCvars(i, o) \ /** player */ i(entity, MUTATOR_ARGV_0_entity) \ /**/ @@ -875,7 +938,9 @@ MUTATOR_HOOKABLE(PrepareExplosionByDamage, EV_PrepareExplosionByDamage); /**/ MUTATOR_HOOKABLE(MonsterModel, EV_MonsterModel); -/**/ +/** + * Called before player changes their team. Return true to block team change. + */ #define EV_Player_ChangeTeam(i, o) \ /** player */ i(entity, MUTATOR_ARGV_0_entity) \ /** current team */ i(float, MUTATOR_ARGV_1_float) \ @@ -883,6 +948,24 @@ MUTATOR_HOOKABLE(MonsterModel, EV_MonsterModel); /**/ MUTATOR_HOOKABLE(Player_ChangeTeam, EV_Player_ChangeTeam); +/** + * Called after player has changed their team. + */ +#define EV_Player_ChangedTeam(i, o) \ + /** player */ i(entity, MUTATOR_ARGV_0_entity) \ + /** old team */ i(float, MUTATOR_ARGV_1_float) \ + /** current team */ i(float, MUTATOR_ARGV_2_float) \ + /**/ +MUTATOR_HOOKABLE(Player_ChangedTeam, EV_Player_ChangedTeam); + +/** + * Called when player is about to be killed when changing teams. Return true to block killing. + */ +#define EV_Player_ChangeTeamKill(i, o) \ + /** player */ i(entity, MUTATOR_ARGV_0_entity) \ + /**/ +MUTATOR_HOOKABLE(Player_ChangeTeamKill, EV_Player_ChangeTeamKill); + /**/ #define EV_URI_GetCallback(i, o) \ /** id */ i(float, MUTATOR_ARGV_0_float) \ diff --git a/qcsrc/server/player.qc b/qcsrc/server/player.qc index 44a877791b..dd06152626 100644 --- a/qcsrc/server/player.qc +++ b/qcsrc/server/player.qc @@ -668,15 +668,19 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage, } } -void MoveToTeam(entity client, int team_colour, int type) +bool MoveToTeam(entity client, int team_colour, int type) { int lockteams_backup = lockteams; // backup any team lock lockteams = 0; // disable locked teams TeamchangeFrags(client); // move the players frags - SetPlayerColors(client, team_colour - 1); // set the players colour + if (!SetPlayerTeamSimple(client, team_colour)) + { + return false; + } Damage(client, client, client, 100000, DEATH_AUTOTEAMCHANGE.m_id, client.origin, '0 0 0'); // kill the player lockteams = lockteams_backup; // restore the team lock LogTeamchange(client.playerid, client.team, type); + return true; } /** print(), but only print if the server is not local */ diff --git a/qcsrc/server/player.qh b/qcsrc/server/player.qh index 1834bb865f..dfa485e5f1 100644 --- a/qcsrc/server/player.qh +++ b/qcsrc/server/player.qh @@ -69,7 +69,12 @@ void calculate_player_respawn_time(entity this); void ClientKill_Now_TeamChange(entity this); -void MoveToTeam(entity client, float team_colour, float type); +/// \brief Moves player to the specified team. +/// \param[in,out] client Client to move. +/// \param[in] team_colour Color of the team. +/// \param[in] type ??? +/// \return True on success, false otherwise. +bool MoveToTeam(entity client, float team_colour, float type); void PlayerDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force); diff --git a/qcsrc/server/resources.qc b/qcsrc/server/resources.qc new file mode 100644 index 0000000000..edf4ff1623 --- /dev/null +++ b/qcsrc/server/resources.qc @@ -0,0 +1,190 @@ +#include "resources.qh" +/// \file +/// \brief Source file that contains implementation of the resource system. +/// \author Lyberta +/// \copyright GNU GPLv2 or any later version. + +#include "autocvars.qh" +#include "miscfunctions.qh" + +float GetResourceLimit(entity e, int resource_type) +{ + float limit; + switch (resource_type) + { + case RESOURCE_HEALTH: + { + limit = autocvar_g_balance_health_limit; + break; + } + case RESOURCE_ARMOR: + { + limit = autocvar_g_balance_armor_limit; + break; + } + case RESOURCE_SHELLS: + { + limit = g_pickup_shells_max; + break; + } + case RESOURCE_BULLETS: + { + limit = g_pickup_nails_max; + break; + } + case RESOURCE_ROCKETS: + { + limit = g_pickup_rockets_max; + break; + } + case RESOURCE_CELLS: + { + limit = g_pickup_cells_max; + break; + } + case RESOURCE_PLASMA: + { + limit = g_pickup_plasma_max; + break; + } + case RESOURCE_FUEL: + { + limit = autocvar_g_balance_fuel_limit; + break; + } + default: + { + error("GetResourceLimit: Invalid resource type."); + return 0; + } + } + MUTATOR_CALLHOOK(GetResourceLimit, e, resource_type, limit); + limit = M_ARGV(2, float); + if (limit > RESOURCE_AMOUNT_HARD_LIMIT) + { + limit = RESOURCE_AMOUNT_HARD_LIMIT; + } + return limit; +} + +float GetResourceAmount(entity e, int resource_type) +{ + .float resource_field = GetResourceField(resource_type); + return e.(resource_field); +} + +void SetResourceAmount(entity e, int resource_type, float amount) +{ + bool forbid = MUTATOR_CALLHOOK(SetResourceAmount, e, resource_type, amount); + if (forbid) + { + return; + } + resource_type = M_ARGV(1, int); + amount = M_ARGV(2, float); + .float resource_field = GetResourceField(resource_type); + if (e.(resource_field) == amount) + { + return; + } + float max_amount = GetResourceLimit(e, resource_type); + if (amount > max_amount) + { + amount = max_amount; + } + e.(resource_field) = amount; +} + +void GiveResource(entity receiver, int resource_type, float amount) +{ + if (amount == 0) + { + return; + } + bool forbid = MUTATOR_CALLHOOK(GiveResource, receiver, resource_type, + amount); + if (forbid) + { + return; + } + resource_type = M_ARGV(1, int); + amount = M_ARGV(2, float); + if (amount <= 0) + { + return; + } + SetResourceAmount(receiver, resource_type, + GetResourceAmount(receiver, resource_type) + amount); + switch (resource_type) + { + case RESOURCE_HEALTH: + { + receiver.pauserothealth_finished = + max(receiver.pauserothealth_finished, time + + autocvar_g_balance_pause_health_rot); + return; + } + case RESOURCE_ARMOR: + { + receiver.pauserotarmor_finished = + max(receiver.pauserotarmor_finished, time + + autocvar_g_balance_pause_armor_rot); + return; + } + case RESOURCE_FUEL: + { + receiver.pauserotfuel_finished = max(receiver.pauserotfuel_finished, + time + autocvar_g_balance_pause_fuel_rot); + return; + } + } +} + +void GiveResourceWithLimit(entity receiver, int resource_type, float amount, + float limit) +{ + if (amount == 0) + { + return; + } + float current_amount = GetResourceAmount(receiver, resource_type); + if (current_amount + amount > limit) + { + amount = limit - current_amount; + } + GiveResource(receiver, resource_type, amount); +} + +int GetResourceType(.float resource_field) +{ + switch (resource_field) + { + case health: { return RESOURCE_HEALTH; } + case armorvalue: { return RESOURCE_ARMOR; } + case ammo_shells: { return RESOURCE_SHELLS; } + case ammo_nails: { return RESOURCE_BULLETS; } + case ammo_rockets: { return RESOURCE_ROCKETS; } + case ammo_cells: { return RESOURCE_CELLS; } + case ammo_plasma: { return RESOURCE_PLASMA; } + case ammo_fuel: { return RESOURCE_FUEL; } + } + error("GetResourceType: Invalid field."); + return 0; +} + +.float GetResourceField(int resource_type) +{ + switch (resource_type) + { + case RESOURCE_HEALTH: { return health; } + case RESOURCE_ARMOR: { return armorvalue; } + case RESOURCE_SHELLS: { return ammo_shells; } + case RESOURCE_BULLETS: { return ammo_nails; } + case RESOURCE_ROCKETS: { return ammo_rockets; } + case RESOURCE_CELLS: { return ammo_cells; } + case RESOURCE_PLASMA: { return ammo_plasma; } + case RESOURCE_FUEL: { return ammo_fuel; } + } + error("GetResourceField: Invalid resource type."); + return health; +} diff --git a/qcsrc/server/resources.qh b/qcsrc/server/resources.qh new file mode 100644 index 0000000000..ce8e1e8e5d --- /dev/null +++ b/qcsrc/server/resources.qh @@ -0,0 +1,70 @@ +#pragma once +/// \file +/// \brief Header file that describes the resource system. +/// \author Lyberta +/// \copyright GNU GPLv2 or any later version. + +/// \brief Unconditional maximum amount of resources the entity can have. +const int RESOURCE_AMOUNT_HARD_LIMIT = 999; + +/// \brief Describes the available resource types. +enum +{ + RESOURCE_HEALTH = 1, ///< Health. + RESOURCE_ARMOR, ///< Armor. + RESOURCE_SHELLS, ///< Shells (used by shotgun). + RESOURCE_BULLETS, ///< Bullets (used by machinegun and rifle) + RESOURCE_ROCKETS, ///< Rockets (used by mortar, hagar, devastator, etc). + RESOURCE_CELLS, ///< Cells (used by electro, crylink, vortex, etc) + RESOURCE_PLASMA, ///< Plasma (unused). + RESOURCE_FUEL ///< Fuel (used by jetpack). +}; + +// ============================ Public API ==================================== + +/// \brief Returns the maximum amount of the given resource. +/// \param[in] e Entity to check. +/// \param[in] resource_type Type of the resource (a RESOURCE_* constant). +/// \return Maximum amount of the given resource. +float GetResourceLimit(entity e, int resource_type); + +/// \brief Returns the current amount of resource the given entity has. +/// \param[in] e Entity to check. +/// \param[in] resource_type Type of the resource (a RESOURCE_* constant). +/// \return Current amount of resource the given entity has. +float GetResourceAmount(entity e, int resource_type); + +/// \brief Sets the current amount of resource the given entity will have. +/// \param[in,out] e Entity to adjust. +/// \param[in] resource_type Type of the resource (a RESOURCE_* constant). +/// \param[in] amount Amount of resource to set. +/// \return No return. +void SetResourceAmount(entity e, int resource_type, float amount); + +/// \brief Gives an entity some resource. +/// \param[in,out] receiver Entity to give resource to. +/// \param[in] resource_type Type of the resource (a RESOURCE_* constant). +/// \param[in] amount Amount of resource to give. +/// \return No return. +void GiveResource(entity receiver, int resource_type, float amount); + +/// \brief Gives an entity some resource but not more than a limit. +/// \param[in,out] receiver Entity to give resource to. +/// \param[in] resource_type Type of the resource (a RESOURCE_* constant). +/// \param[in] amount Amount of resource to give. +/// \param[in] limit Limit of resources to give. +/// \return No return. +void GiveResourceWithLimit(entity receiver, int resource_type, float amount, + float limit); + +// ===================== Legacy and/or internal API =========================== + +/// \brief Converts an entity field to resource type. +/// \param[in] resource_field Entity field to convert. +/// \return Resource type (a RESOURCE_* constant). +int GetResourceType(.float resource_field); + +/// \brief Converts resource type (a RESOURCE_* constant) to entity field. +/// \param[in] resource_type Type of the resource. +/// \return Entity field for that resource. +.float GetResourceField(int resource_type); diff --git a/qcsrc/server/scores.qc b/qcsrc/server/scores.qc index 266f7734b6..11bc602382 100644 --- a/qcsrc/server/scores.qc +++ b/qcsrc/server/scores.qc @@ -54,7 +54,7 @@ vector ScoreField_Compare(entity t1, entity t2, .float field, float fieldflags, bool TeamScore_SendEntity(entity this, entity to, float sendflags) { - float i, p, longflags; + float i, longflags; WriteHeader(MSG_ENTITY, ENT_CLIENT_TEAMSCORES); int t = this.team - 1; @@ -62,9 +62,9 @@ bool TeamScore_SendEntity(entity this, entity to, float sendflags) WriteByte(MSG_ENTITY, t); longflags = 0; - for(i = 0, p = 1; i < MAX_TEAMSCORE; ++i, p *= 2) + for(i = 0; i < MAX_TEAMSCORE; ++i) if(this.(teamscores(i)) > 127 || this.(teamscores(i)) <= -128) - longflags |= p; + longflags |= BIT(i); #if MAX_TEAMSCORE <= 8 WriteByte(MSG_ENTITY, sendflags); @@ -73,10 +73,10 @@ bool TeamScore_SendEntity(entity this, entity to, float sendflags) WriteShort(MSG_ENTITY, sendflags); WriteShort(MSG_ENTITY, longflags); #endif - for(i = 0, p = 1; i < MAX_TEAMSCORE; ++i, p *= 2) - if(sendflags & p) + for(i = 0; i < MAX_TEAMSCORE; ++i) + if(sendflags & BIT(i)) { - if(longflags & p) + if(longflags & BIT(i)) WriteInt24_t(MSG_ENTITY, this.(teamscores(i))); else WriteChar(MSG_ENTITY, this.(teamscores(i))); diff --git a/qcsrc/server/scores_rules.qc b/qcsrc/server/scores_rules.qc index 2b539c9996..d46d95bd1c 100644 --- a/qcsrc/server/scores_rules.qc +++ b/qcsrc/server/scores_rules.qc @@ -5,6 +5,7 @@ #include "client.qh" #include "scores.qh" #include +#include "teamplay.qh" int ScoreRules_teams; diff --git a/qcsrc/server/sv_main.qc b/qcsrc/server/sv_main.qc index fcae79ea6c..34f3cdc21b 100644 --- a/qcsrc/server/sv_main.qc +++ b/qcsrc/server/sv_main.qc @@ -245,6 +245,77 @@ void StartFrame() .string gametypefilter; .string cvarfilter; bool DoesQ3ARemoveThisEntity(entity this); + +/** + * Evaluate an expression of the form: [+ | -]? [var[op]val | [op]var | val | var] ... + * +: all must match. this is the default + * -: one must NOT match + * + * var>x + * var=x + * var<=x + * var==x + * var!=x + * var===x + * var!==x + */ +bool expr_evaluate(string s) +{ + bool ret = false; + if (str2chr(s, 0) == '+') { + s = substring(s, 1, -1); + } else if (str2chr(s, 0) == '-') { + ret = true; + s = substring(s, 1, -1); + } + bool expr_fail = false; + for (int i = 0, n = tokenize_console(s); i < n; ++i) { + int o; + string k, v; + s = argv(i); + #define X(expr) \ + if (expr) { \ + continue; \ + } else { \ + expr_fail = true; \ + break; \ + } + #define BINOP(op, len, expr) \ + if ((o = strstrofs(s, op, 0)) >= 0) { \ + k = substring(s, 0, o); \ + v = substring(s, o + len, -1); \ + X(expr); \ + } + BINOP(">=", 2, cvar(k) >= stof(v)); + BINOP("<=", 2, cvar(k) <= stof(v)); + BINOP(">", 1, cvar(k) > stof(v)); + BINOP("<", 1, cvar(k) < stof(v)); + BINOP("==", 2, cvar(k) == stof(v)); + BINOP("!=", 2, cvar(k) != stof(v)); + BINOP("===", 3, cvar_string(k) == v); + BINOP("!==", 3, cvar_string(k) != v); + { + k = s; + bool b = true; + if (str2chr(k, 0) == '!') { + k = substring(s, 1, -1); + b = false; + } + float f = stof(k); + bool isnum = ftos(f) == k; + X(boolean(isnum ? f : cvar(k)) == b); + } + #undef BINOP + #undef X + } + if (!expr_fail) { + ret = !ret; + } + // now ret is true if we want to keep the item, and false if we want to get rid of it + return ret; +} + void SV_OnEntityPreSpawnFunction(entity this) { __spawnfunc_expecting = true; @@ -253,160 +324,44 @@ void SV_OnEntityPreSpawnFunction(entity this) if (this.gametypefilter != "") if (!isGametypeInFilter(MapInfo_LoadedGametype, teamplay, have_team_spawns, this.gametypefilter)) { - delete(this); - __spawnfunc_expecting = false; - return; + goto cleanup; } - if(this.cvarfilter != "") - { - float n, i, o, inv; - string s, k, v; - inv = 0; - - s = this.cvarfilter; - if(substring(s, 0, 1) == "+") - { - s = substring(s, 1, -1); - } - else if(substring(s, 0, 1) == "-") - { - inv = 1; - s = substring(s, 1, -1); - } - - n = tokenize_console(s); - for(i = 0; i < n; ++i) - { - s = argv(i); - // syntax: - // var>x - // var=x - // var<=x - // var==x - // var!=x - // var===x - // var!==x - if((o = strstrofs(s, ">=", 0)) >= 0) - { - k = substring(s, 0, o); - v = substring(s, o+2, -1); - if(cvar(k) < stof(v)) - goto cvar_fail; - } - else if((o = strstrofs(s, "<=", 0)) >= 0) - { - k = substring(s, 0, o); - v = substring(s, o+2, -1); - if(cvar(k) > stof(v)) - goto cvar_fail; - } - else if((o = strstrofs(s, ">", 0)) >= 0) - { - k = substring(s, 0, o); - v = substring(s, o+1, -1); - if(cvar(k) <= stof(v)) - goto cvar_fail; - } - else if((o = strstrofs(s, "<", 0)) >= 0) - { - k = substring(s, 0, o); - v = substring(s, o+1, -1); - if(cvar(k) >= stof(v)) - goto cvar_fail; - } - else if((o = strstrofs(s, "==", 0)) >= 0) - { - k = substring(s, 0, o); - v = substring(s, o+2, -1); - if(cvar(k) != stof(v)) - goto cvar_fail; - } - else if((o = strstrofs(s, "!=", 0)) >= 0) - { - k = substring(s, 0, o); - v = substring(s, o+2, -1); - if(cvar(k) == stof(v)) - goto cvar_fail; - } - else if((o = strstrofs(s, "===", 0)) >= 0) - { - k = substring(s, 0, o); - v = substring(s, o+2, -1); - if(cvar_string(k) != v) - goto cvar_fail; - } - else if((o = strstrofs(s, "!==", 0)) >= 0) - { - k = substring(s, 0, o); - v = substring(s, o+2, -1); - if(cvar_string(k) == v) - goto cvar_fail; - } - else if(substring(s, 0, 1) == "!") - { - k = substring(s, 1, -1); - if(cvar(k)) - goto cvar_fail; - } - else - { - k = s; - if (!cvar(k)) - goto cvar_fail; - } - } - inv = !inv; -LABEL(cvar_fail) - // now inv is 1 if we want to keep the item, and 0 if we want to get rid of it - if (!inv) - { - //print("cvarfilter fail\n"); - delete(this); - __spawnfunc_expecting = false; - return; - } + if (this.cvarfilter != "" && !expr_evaluate(this.cvarfilter)) { + goto cleanup; } - if(DoesQ3ARemoveThisEntity(this)) - { - delete(this); - __spawnfunc_expecting = false; - return; + if (DoesQ3ARemoveThisEntity(this)) { + goto cleanup; } set_movetype(this, this.movetype); - if(this.monster_attack) + if (this.monster_attack) { IL_PUSH(g_monster_targets, this); + } // support special -1 and -2 angle from radiant - if (this.angles == '0 -1 0') + if (this.angles == '0 -1 0') { this.angles = '-90 0 0'; - else if (this.angles == '0 -2 0') + } else if (this.angles == '0 -2 0') { this.angles = '+90 0 0'; - - if(this.originjitter.x != 0) - this.origin_x = this.origin.x + (random() * 2 - 1) * this.originjitter.x; - if(this.originjitter.y != 0) - this.origin_y = this.origin.y + (random() * 2 - 1) * this.originjitter.y; - if(this.originjitter.z != 0) - this.origin_z = this.origin.z + (random() * 2 - 1) * this.originjitter.z; - if(this.anglesjitter.x != 0) - this.angles_x = this.angles.x + (random() * 2 - 1) * this.anglesjitter.x; - if(this.anglesjitter.y != 0) - this.angles_y = this.angles.y + (random() * 2 - 1) * this.anglesjitter.y; - if(this.anglesjitter.z != 0) - this.angles_z = this.angles.z + (random() * 2 - 1) * this.anglesjitter.z; - if(this.anglejitter != 0) - this.angles_y = this.angles.y + (random() * 2 - 1) * this.anglejitter; - - if(MUTATOR_CALLHOOK(OnEntityPreSpawn, this)) - { - delete(this); - __spawnfunc_expecting = false; - return; + } + + #define X(out, in) MACRO_BEGIN \ + if (in != 0) { out = out + (random() * 2 - 1) * in; } \ + MACRO_END + X(this.origin.x, this.originjitter.x); X(this.origin.y, this.originjitter.y); X(this.origin.z, this.originjitter.z); + X(this.angles.x, this.anglesjitter.x); X(this.angles.y, this.anglesjitter.y); X(this.angles.z, this.anglesjitter.z); + X(this.angles.y, this.anglejitter); + #undef X + + if (MUTATOR_CALLHOOK(OnEntityPreSpawn, this)) { + goto cleanup; } + return; +LABEL(cleanup) + builtin_remove(this); + __spawnfunc_expecting = false; } void WarpZone_PostInitialize_Callback() diff --git a/qcsrc/server/sv_main.qh b/qcsrc/server/sv_main.qh index 6f70f09bee..7f86d19c01 100644 --- a/qcsrc/server/sv_main.qh +++ b/qcsrc/server/sv_main.qh @@ -1 +1,3 @@ #pragma once + +bool expr_evaluate(string s); diff --git a/qcsrc/server/teamplay.qc b/qcsrc/server/teamplay.qc index e93a03201a..24e9a5221e 100644 --- a/qcsrc/server/teamplay.qc +++ b/qcsrc/server/teamplay.qc @@ -43,8 +43,14 @@ void InitGameplayMode() // find out good world mins/maxs bounds, either the static bounds found by looking for solid, or the mapinfo specified bounds get_mi_min_max(1); - world.mins = mi_min; - world.maxs = mi_max; + // assign reflectively to avoid "assignment to world" warning + int done = 0; for (int i = 0, n = numentityfields(); i < n; ++i) { + string k = entityfieldname(i); vector v = (k == "mins") ? mi_min : (k == "maxs") ? mi_max : '0 0 0'; + if (v) { + putentityfieldstring(i, world, sprintf("%d %d %d", v)); + if (++done == 2) break; + } + } // currently, NetRadiant's limit is 131072 qu for each side // distance from one corner of a 131072qu cube to the opposite corner is approx. 227023 qu // set the distance according to map size but don't go over the limit to avoid issues with float precision @@ -160,58 +166,79 @@ void setcolor(entity this, int clr) #endif } -void SetPlayerColors(entity pl, float _color) +void SetPlayerColors(entity player, float _color) { - /*string s; - s = ftos(cl); - stuffcmd(pl, strcat("color ", s, " ", s, "\n") ); - pl.team = cl + 1; - //pl.clientcolors = pl.clientcolors - (pl.clientcolors & 15) + cl; - pl.clientcolors = 16*cl + cl;*/ - - float pants, shirt; - pants = _color & 0x0F; - shirt = _color & 0xF0; - - - if(teamplay) { - setcolor(pl, 16*pants + pants); - } else { - setcolor(pl, shirt + pants); + float pants = _color & 0x0F; + float shirt = _color & 0xF0; + if (teamplay) + { + setcolor(player, 16 * pants + pants); + } + else + { + setcolor(player, shirt + pants); } } -void SetPlayerTeam(entity pl, float t, float s, float noprint) +void KillPlayerForTeamChange(entity player) { - float _color; - - if(t == 4) - _color = NUM_TEAM_4 - 1; - else if(t == 3) - _color = NUM_TEAM_3 - 1; - else if(t == 2) - _color = NUM_TEAM_2 - 1; - else - _color = NUM_TEAM_1 - 1; - - SetPlayerColors(pl,_color); - - if(t != s) { - LogTeamchange(pl.playerid, pl.team, 3); // log manual team join + if (IS_DEAD(player)) + { + return; + } + if (MUTATOR_CALLHOOK(Player_ChangeTeamKill, player) == true) + { + return; + } + Damage(player, player, player, 100000, DEATH_TEAMCHANGE.m_id, player.origin, + '0 0 0'); +} - if(!noprint) - bprint(playername(pl, false), "^7 has changed from ", Team_NumberToColoredFullName(s), "^7 to ", Team_NumberToColoredFullName(t), "\n"); +bool SetPlayerTeamSimple(entity player, int team_num) +{ + if (player.team == team_num) + { + // This is important when players join the game and one of their color + // matches the team color while other doesn't. For example [BOT]Lion. + SetPlayerColors(player, team_num - 1); + return true; } + if (MUTATOR_CALLHOOK(Player_ChangeTeam, player, Team_TeamToNumber( + player.team), Team_TeamToNumber(team_num)) == true) + { + // Mutator has blocked team change. + return false; + } + int old_team = player.team; + SetPlayerColors(player, team_num - 1); + MUTATOR_CALLHOOK(Player_ChangedTeam, player, old_team, player.team); + return true; +} +bool SetPlayerTeam(entity player, int destination_team, int source_team, + bool no_print) +{ + int team_num = Team_NumberToTeam(destination_team); + if (!SetPlayerTeamSimple(player, team_num)) + { + return false; + } + LogTeamchange(player.playerid, player.team, 3); // log manual team join + if (no_print) + { + return true; + } + bprint(playername(player, false), "^7 has changed from ", Team_NumberToColoredFullName(source_team), "^7 to ", Team_NumberToColoredFullName(destination_team), "\n"); + return true; } // set c1...c4 to show what teams are allowed -void CheckAllowedTeams (entity for_whom) +void CheckAllowedTeams(entity for_whom) { int teams_mask = 0; c1 = c2 = c3 = c4 = -1; - cb1 = cb2 = cb3 = cb4 = 0; + num_bots_team1 = num_bots_team2 = num_bots_team3 = num_bots_team4 = 0; string teament_name = string_null; @@ -313,284 +340,570 @@ float PlayerValue(entity p) // teams that are allowed will now have their player counts stored in c1...c4 void GetTeamCounts(entity ignore) { - float value, bvalue; - // now count how many players are on each team already - - // FIXME: also find and memorize the lowest-scoring bot on each team (in case players must be shuffled around) - // also remember the lowest-scoring player - - FOREACH_CLIENT(true, { - float t; - if(IS_PLAYER(it) || it.caplayer) - t = it.team; - else if(it.team_forced > 0) - t = it.team_forced; // reserve the spot - else - continue; - if(it != ignore)// && it.netname != "") + if (MUTATOR_CALLHOOK(GetTeamCounts) == true) + { + if (c1 >= 0) { + MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_1, ignore, c1, + num_bots_team1, lowest_human_team1, lowest_bot_team1); + c1 = M_ARGV(2, float); + num_bots_team1 = M_ARGV(3, float); + lowest_human_team1 = M_ARGV(4, entity); + lowest_bot_team1 = M_ARGV(5, entity); + } + if (c2 >= 0) + { + MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_2, ignore, c2, + num_bots_team2, lowest_human_team2, lowest_bot_team2); + c2 = M_ARGV(2, float); + num_bots_team2 = M_ARGV(3, float); + lowest_human_team2 = M_ARGV(4, entity); + lowest_bot_team2 = M_ARGV(5, entity); + } + if (c3 >= 0) + { + MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_3, ignore, c3, + num_bots_team3, lowest_human_team3, lowest_bot_team3); + c3 = M_ARGV(2, float); + num_bots_team3 = M_ARGV(3, float); + lowest_human_team3 = M_ARGV(4, entity); + lowest_bot_team3 = M_ARGV(5, entity); + } + if (c4 >= 0) + { + MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_4, ignore, + c4, num_bots_team4, lowest_human_team4, lowest_bot_team4); + c4 = M_ARGV(2, float); + num_bots_team4 = M_ARGV(3, float); + lowest_human_team4 = M_ARGV(4, entity); + lowest_bot_team4 = M_ARGV(5, entity); + } + } + else + { + float value, bvalue; + // now count how many players are on each team already + float lowest_human_score1 = FLOAT_MAX; + float lowest_bot_score1 = FLOAT_MAX; + float lowest_human_score2 = FLOAT_MAX; + float lowest_bot_score2 = FLOAT_MAX; + float lowest_human_score3 = FLOAT_MAX; + float lowest_bot_score3 = FLOAT_MAX; + float lowest_human_score4 = FLOAT_MAX; + float lowest_bot_score4 = FLOAT_MAX; + FOREACH_CLIENT(true, + { + float t; + if (IS_PLAYER(it) || it.caplayer) + { + t = it.team; + } + else if (it.team_forced > 0) + { + t = it.team_forced; // reserve the spot + } + else + { + continue; + } + if (it == ignore) + { + continue; + } value = PlayerValue(it); - if(IS_BOT_CLIENT(it)) + if (IS_BOT_CLIENT(it)) + { bvalue = value; + } else + { bvalue = 0; - if(t == NUM_TEAM_1) + } + if (value == 0) { - if(c1 >= 0) - { - c1 = c1 + value; - cb1 = cb1 + bvalue; - } + continue; } - else if(t == NUM_TEAM_2) + switch (t) { - if(c2 >= 0) + case NUM_TEAM_1: { - c2 = c2 + value; - cb2 = cb2 + bvalue; + if (c1 < 0) + { + break; + } + c1 += value; + num_bots_team1 += bvalue; + float temp_score = PlayerScore_Get(it, SP_SCORE); + if (!bvalue) + { + if (temp_score < lowest_human_score1) + { + lowest_human_team1 = it; + lowest_human_score1 = temp_score; + } + break; + } + if (temp_score < lowest_bot_score1) + { + lowest_bot_team1 = it; + lowest_bot_score1 = temp_score; + } + break; } - } - else if(t == NUM_TEAM_3) - { - if(c3 >= 0) + case NUM_TEAM_2: { - c3 = c3 + value; - cb3 = cb3 + bvalue; + if (c2 < 0) + { + break; + } + c2 += value; + num_bots_team2 += bvalue; + float temp_score = PlayerScore_Get(it, SP_SCORE); + if (!bvalue) + { + if (temp_score < lowest_human_score2) + { + lowest_human_team2 = it; + lowest_human_score2 = temp_score; + } + break; + } + if (temp_score < lowest_bot_score2) + { + lowest_bot_team2 = it; + lowest_bot_score2 = temp_score; + } + break; } - } - else if(t == NUM_TEAM_4) - { - if(c4 >= 0) + case NUM_TEAM_3: + { + if (c3 < 0) + { + break; + } + c3 += value; + num_bots_team3 += bvalue; + float temp_score = PlayerScore_Get(it, SP_SCORE); + if (!bvalue) + { + if (temp_score < lowest_human_score3) + { + lowest_human_team3 = it; + lowest_human_score3 = temp_score; + } + break; + } + if (temp_score < lowest_bot_score3) + { + lowest_bot_team3 = it; + lowest_bot_score3 = temp_score; + } + break; + } + case NUM_TEAM_4: { - c4 = c4 + value; - cb4 = cb4 + bvalue; + if (c4 < 0) + { + break; + } + c4 += value; + num_bots_team4 += bvalue; + float temp_score = PlayerScore_Get(it, SP_SCORE); + if (!bvalue) + { + if (temp_score < lowest_human_score4) + { + lowest_human_team4 = it; + lowest_human_score4 = temp_score; + } + break; + } + if (temp_score < lowest_bot_score4) + { + lowest_bot_team4 = it; + lowest_bot_score4 = temp_score; + } + break; } } - } - }); + }); + } // if the player who has a forced team has not joined yet, reserve the spot if(autocvar_g_campaign) { switch(autocvar_g_campaign_forceteam) { - case 1: if(c1 == cb1) ++c1; break; - case 2: if(c2 == cb2) ++c2; break; - case 3: if(c3 == cb3) ++c3; break; - case 4: if(c4 == cb4) ++c4; break; + case 1: if(c1 == num_bots_team1) ++c1; break; + case 2: if(c2 == num_bots_team2) ++c2; break; + case 3: if(c3 == num_bots_team3) ++c3; break; + case 4: if(c4 == num_bots_team4) ++c4; break; } } } -float TeamSmallerEqThanTeam(float ta, float tb, entity e) +bool IsTeamSmallerThanTeam(int team_a, int team_b, entity player, + bool use_score) { + if (team_a == team_b) + { + return false; + } // we assume that CheckAllowedTeams and GetTeamCounts have already been called - float f; - float ca = -1, cb = -1, cba = 0, cbb = 0, sa = 0, sb = 0; - - switch(ta) + int num_players_team_a = -1, num_players_team_b = -1; + int num_bots_team_a = 0, num_bots_team_b = 0; + float score_team_a = 0, score_team_b = 0; + switch (team_a) { - case 1: ca = c1; cba = cb1; sa = team1_score; break; - case 2: ca = c2; cba = cb2; sa = team2_score; break; - case 3: ca = c3; cba = cb3; sa = team3_score; break; - case 4: ca = c4; cba = cb4; sa = team4_score; break; + case 1: + { + num_players_team_a = c1; + num_bots_team_a = num_bots_team1; + score_team_a = team1_score; + break; + } + case 2: + { + num_players_team_a = c2; + num_bots_team_a = num_bots_team2; + score_team_a = team2_score; + break; + } + case 3: + { + num_players_team_a = c3; + num_bots_team_a = num_bots_team3; + score_team_a = team3_score; + break; + } + case 4: + { + num_players_team_a = c4; + num_bots_team_a = num_bots_team4; + score_team_a = team4_score; + break; + } } - switch(tb) + switch (team_b) { - case 1: cb = c1; cbb = cb1; sb = team1_score; break; - case 2: cb = c2; cbb = cb2; sb = team2_score; break; - case 3: cb = c3; cbb = cb3; sb = team3_score; break; - case 4: cb = c4; cbb = cb4; sb = team4_score; break; + case 1: + { + num_players_team_b = c1; + num_bots_team_b = num_bots_team1; + score_team_b = team1_score; + break; + } + case 2: + { + num_players_team_b = c2; + num_bots_team_b = num_bots_team2; + score_team_b = team2_score; + break; + } + case 3: + { + num_players_team_b = c3; + num_bots_team_b = num_bots_team3; + score_team_b = team3_score; + break; + } + case 4: + { + num_players_team_b = c4; + num_bots_team_b = num_bots_team4; + score_team_b = team4_score; + break; + } } - // invalid - if(ca < 0 || cb < 0) + if (num_players_team_a < 0 || num_players_team_b < 0) + { return false; - - // equal - if(ta == tb) + } + if (IS_REAL_CLIENT(player) && bots_would_leave) + { + num_players_team_a -= num_bots_team_a; + num_players_team_b -= num_bots_team_b; + } + if (!use_score) + { + return num_players_team_a < num_players_team_b; + } + if (num_players_team_a < num_players_team_b) + { return true; + } + if (num_players_team_a > num_players_team_b) + { + return false; + } + return score_team_a < score_team_b; +} - if(IS_REAL_CLIENT(e)) +bool IsTeamEqualToTeam(int team_a, int team_b, entity player, bool use_score) +{ + if (team_a == team_b) + { + return true; + } + // we assume that CheckAllowedTeams and GetTeamCounts have already been called + int num_players_team_a = -1, num_players_team_b = -1; + int num_bots_team_a = 0, num_bots_team_b = 0; + float score_team_a = 0, score_team_b = 0; + switch (team_a) { - if(bots_would_leave) + case 1: + { + num_players_team_a = c1; + num_bots_team_a = num_bots_team1; + score_team_a = team1_score; + break; + } + case 2: + { + num_players_team_a = c2; + num_bots_team_a = num_bots_team2; + score_team_a = team2_score; + break; + } + case 3: + { + num_players_team_a = c3; + num_bots_team_a = num_bots_team3; + score_team_a = team3_score; + break; + } + case 4: { - ca -= cba * 0.999; - cb -= cbb * 0.999; + num_players_team_a = c4; + num_bots_team_a = num_bots_team4; + score_team_a = team4_score; + break; } } + switch (team_b) + { + case 1: + { + num_players_team_b = c1; + num_bots_team_b = num_bots_team1; + score_team_b = team1_score; + break; + } + case 2: + { + num_players_team_b = c2; + num_bots_team_b = num_bots_team2; + score_team_b = team2_score; + break; + } + case 3: + { + num_players_team_b = c3; + num_bots_team_b = num_bots_team3; + score_team_b = team3_score; + break; + } + case 4: + { + num_players_team_b = c4; + num_bots_team_b = num_bots_team4; + score_team_b = team4_score; + break; + } + } + // invalid + if (num_players_team_a < 0 || num_players_team_b < 0) + return false; - // keep teams alive (teams of size 0 always count as smaller, ignoring score) - if(ca < 1) - if(cb >= 1) - return true; - if(ca >= 1) - if(cb < 1) - return false; - - // first, normalize - f = max(ca, cb, 1); - ca /= f; - cb /= f; - f = max(sa, sb, 1); - sa /= f; - sb /= f; - - // the more we're at the end of the match, the more take scores into account - f = bound(0, game_completion_ratio * autocvar_g_balance_teams_scorefactor, 1); - ca += (sa - ca) * f; - cb += (sb - cb) * f; + if (IS_REAL_CLIENT(player) && bots_would_leave) + { + num_players_team_a -= num_bots_team_a; + num_players_team_b -= num_bots_team_b; + } + if (!use_score) + { + return num_players_team_a == num_players_team_b; + } + if (num_players_team_a != num_players_team_b) + { + return false; + } + return score_team_a == score_team_b; +} - return ca <= cb; +int FindBestTeams(entity player, bool use_score) +{ + if (MUTATOR_CALLHOOK(FindBestTeams, player) == true) + { + return M_ARGV(1, float); + } + int team_bits = 0; + int previous_team = 0; + if (c1 >= 0) + { + team_bits = BIT(0); + previous_team = 1; + } + if (c2 >= 0) + { + if (previous_team == 0) + { + team_bits = BIT(1); + previous_team = 2; + } + else if (IsTeamSmallerThanTeam(2, previous_team, player, use_score)) + { + team_bits = BIT(1); + previous_team = 2; + } + else if (IsTeamEqualToTeam(2, previous_team, player, use_score)) + { + team_bits |= BIT(1); + previous_team = 2; + } + } + if (c3 >= 0) + { + if (previous_team == 0) + { + team_bits = BIT(2); + previous_team = 3; + } + else if (IsTeamSmallerThanTeam(3, previous_team, player, use_score)) + { + team_bits = BIT(2); + previous_team = 3; + } + else if (IsTeamEqualToTeam(3, previous_team, player, use_score)) + { + team_bits |= BIT(2); + previous_team = 3; + } + } + if (c4 >= 0) + { + if (previous_team == 0) + { + team_bits = BIT(3); + } + else if (IsTeamSmallerThanTeam(4, previous_team, player, use_score)) + { + team_bits = BIT(3); + } + else if (IsTeamEqualToTeam(4, previous_team, player, use_score)) + { + team_bits |= BIT(3); + } + } + return team_bits; } // returns # of smallest team (1, 2, 3, 4) // NOTE: Assumes CheckAllowedTeams has already been called! -float FindSmallestTeam(entity pl, float ignore_pl) +int FindSmallestTeam(entity player, float ignore_player) { - int totalteams = 0; - int t = 1; // initialize with a random team? - if(c4 >= 0) t = 4; - if(c3 >= 0) t = 3; - if(c2 >= 0) t = 2; - if(c1 >= 0) t = 1; - - // find out what teams are available - //CheckAllowedTeams(); - - // make sure there are at least 2 teams to join - if(c1 >= 0) - totalteams = totalteams + 1; - if(c2 >= 0) - totalteams = totalteams + 1; - if(c3 >= 0) - totalteams = totalteams + 1; - if(c4 >= 0) - totalteams = totalteams + 1; - - if((autocvar_bot_vs_human || pl.team_forced > 0) && totalteams == 1) - totalteams += 1; - - if(totalteams <= 1) + // count how many players are in each team + if (ignore_player) { - if(autocvar_g_campaign && pl && IS_REAL_CLIENT(pl)) - return 1; // special case for campaign and player joining - else if(totalteams == 1) // single team - LOG_TRACEF("Only 1 team available for %s, you may need to fix your map", MapInfo_Type_ToString(MapInfo_CurrentGametype())); - else // no teams, major no no - error(sprintf("No teams available for %s\n", MapInfo_Type_ToString(MapInfo_CurrentGametype()))); + GetTeamCounts(player); } - - // count how many players are in each team - if(ignore_pl) - GetTeamCounts(pl); else + { GetTeamCounts(NULL); - + } + int team_bits = FindBestTeams(player, true); + if (team_bits == 0) + { + error(sprintf("No teams available for %s\n", MapInfo_Type_ToString(MapInfo_CurrentGametype()))); + } RandomSelection_Init(); - - if(TeamSmallerEqThanTeam(1, t, pl)) - t = 1; - if(TeamSmallerEqThanTeam(2, t, pl)) - t = 2; - if(TeamSmallerEqThanTeam(3, t, pl)) - t = 3; - if(TeamSmallerEqThanTeam(4, t, pl)) - t = 4; - - // now t is the minimum, or A minimum! - if(t == 1 || TeamSmallerEqThanTeam(1, t, pl)) + if ((team_bits & BIT(0)) != 0) + { RandomSelection_AddFloat(1, 1, 1); - if(t == 2 || TeamSmallerEqThanTeam(2, t, pl)) + } + if ((team_bits & BIT(1)) != 0) + { RandomSelection_AddFloat(2, 1, 1); - if(t == 3 || TeamSmallerEqThanTeam(3, t, pl)) + } + if ((team_bits & BIT(2)) != 0) + { RandomSelection_AddFloat(3, 1, 1); - if(t == 4 || TeamSmallerEqThanTeam(4, t, pl)) + } + if ((team_bits & BIT(3)) != 0) + { RandomSelection_AddFloat(4, 1, 1); - + } return RandomSelection_chosen_float; } -int JoinBestTeam(entity this, bool only_return_best, bool forcebestteam) +int JoinBestTeam(entity this, bool only_return_best, bool force_best_team) { - float smallest, selectedteam; - // don't join a team if we're not playing a team game - if(!teamplay) + if (!teamplay) + { return 0; + } // find out what teams are available CheckAllowedTeams(this); // if we don't care what team he ends up on, put him on whatever team he entered as. // if he's not on a valid team, then let other code put him on the smallest team - if(!forcebestteam) + if (!force_best_team) { + int selected_team; if( c1 >= 0 && this.team == NUM_TEAM_1) - selectedteam = this.team; + selected_team = this.team; else if(c2 >= 0 && this.team == NUM_TEAM_2) - selectedteam = this.team; + selected_team = this.team; else if(c3 >= 0 && this.team == NUM_TEAM_3) - selectedteam = this.team; + selected_team = this.team; else if(c4 >= 0 && this.team == NUM_TEAM_4) - selectedteam = this.team; + selected_team = this.team; else - selectedteam = -1; + selected_team = -1; - if(selectedteam > 0) + if (selected_team > 0) { - if(!only_return_best) + if (!only_return_best) { - SetPlayerColors(this, selectedteam - 1); + SetPlayerTeamSimple(this, selected_team); // when JoinBestTeam is called by client.qc/ClientKill_Now_TeamChange the players team is -1 and thus skipped // when JoinBestTeam is called by client.qc/ClientConnect the player_id is 0 the log attempt is rejected LogTeamchange(this.playerid, this.team, 99); } - return selectedteam; + return selected_team; } // otherwise end up on the smallest team (handled below) } - smallest = FindSmallestTeam(this, true); - - if(!only_return_best && !this.bot_forced_team) + int best_team = FindSmallestTeam(this, true); + if (only_return_best || this.bot_forced_team) { - TeamchangeFrags(this); - if(smallest == 1) - { - SetPlayerColors(this, NUM_TEAM_1 - 1); - } - else if(smallest == 2) - { - SetPlayerColors(this, NUM_TEAM_2 - 1); - } - else if(smallest == 3) - { - SetPlayerColors(this, NUM_TEAM_3 - 1); - } - else if(smallest == 4) - { - SetPlayerColors(this, NUM_TEAM_4 - 1); - } - else - { - error("smallest team: invalid team\n"); - } - - LogTeamchange(this.playerid, this.team, 2); // log auto join - - if(!IS_DEAD(this)) - Damage(this, this, this, 100000, DEATH_TEAMCHANGE.m_id, this.origin, '0 0 0'); + return best_team; } - - return smallest; + best_team = Team_NumberToTeam(best_team); + if (best_team == -1) + { + error("JoinBestTeam: invalid team\n"); + } + int old_team = Team_TeamToNumber(this.team); + TeamchangeFrags(this); + SetPlayerTeamSimple(this, best_team); + LogTeamchange(this.playerid, this.team, 2); // log auto join + if (!IS_BOT_CLIENT(this)) + { + AutoBalanceBots(old_team, Team_TeamToNumber(best_team)); + } + KillPlayerForTeamChange(this); + return best_team; } -//void() ctf_playerchanged; void SV_ChangeTeam(entity this, float _color) { - float scolor, dcolor, steam, dteam; //, dbotcount, scount, dcount; + float source_color, destination_color, source_team, destination_team; // in normal deathmatch we can just apply the color and we're done if(!teamplay) @@ -606,38 +919,23 @@ void SV_ChangeTeam(entity this, float _color) if(!teamplay) return; - scolor = this.clientcolors & 0x0F; - dcolor = _color & 0x0F; - - if(scolor == NUM_TEAM_1 - 1) - steam = 1; - else if(scolor == NUM_TEAM_2 - 1) - steam = 2; - else if(scolor == NUM_TEAM_3 - 1) - steam = 3; - else // if(scolor == NUM_TEAM_4 - 1) - steam = 4; - if(dcolor == NUM_TEAM_1 - 1) - dteam = 1; - else if(dcolor == NUM_TEAM_2 - 1) - dteam = 2; - else if(dcolor == NUM_TEAM_3 - 1) - dteam = 3; - else // if(dcolor == NUM_TEAM_4 - 1) - dteam = 4; + source_color = this.clientcolors & 0x0F; + destination_color = _color & 0x0F; + + source_team = Team_TeamToNumber(source_color + 1); + destination_team = Team_TeamToNumber(destination_color + 1); CheckAllowedTeams(this); - if(dteam == 1 && c1 < 0) dteam = 4; - if(dteam == 4 && c4 < 0) dteam = 3; - if(dteam == 3 && c3 < 0) dteam = 2; - if(dteam == 2 && c2 < 0) dteam = 1; + if (destination_team == 1 && c1 < 0) destination_team = 4; + if (destination_team == 4 && c4 < 0) destination_team = 3; + if (destination_team == 3 && c3 < 0) destination_team = 2; + if (destination_team == 2 && c2 < 0) destination_team = 1; // not changing teams - if(scolor == dcolor) + if (source_color == destination_color) { - //bprint("same team change\n"); - SetPlayerTeam(this, dteam, steam, true); + SetPlayerTeam(this, destination_team, source_team, true); return; } @@ -647,169 +945,102 @@ void SV_ChangeTeam(entity this, float _color) } // autocvar_g_balance_teams_prevent_imbalance only makes sense if autocvar_g_balance_teams is on, as it makes the team selection dialog pointless - if(autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance) + if (autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance) { GetTeamCounts(this); - if(!TeamSmallerEqThanTeam(dteam, steam, this)) + if ((BIT(destination_team - 1) & FindBestTeams(this, false)) == 0) { Send_Notification(NOTIF_ONE, this, MSG_INFO, INFO_TEAMCHANGE_LARGERTEAM); return; } } - -// bprint("allow change teams from ", ftos(steam), " to ", ftos(dteam), "\n"); - - if(IS_PLAYER(this) && steam != dteam) + if(IS_PLAYER(this) && source_team != destination_team) { // reduce frags during a team change TeamchangeFrags(this); } - - MUTATOR_CALLHOOK(Player_ChangeTeam, this, steam, dteam); - - SetPlayerTeam(this, dteam, steam, !IS_CLIENT(this)); - - if(IS_PLAYER(this) && steam != dteam) + if (!SetPlayerTeam(this, destination_team, source_team, !IS_CLIENT(this))) + { + return; + } + AutoBalanceBots(source_team, destination_team); + if (!IS_PLAYER(this) || (source_team == destination_team)) { - // kill player when changing teams - if(!IS_DEAD(this)) - Damage(this, this, this, 100000, DEATH_TEAMCHANGE.m_id, this.origin, '0 0 0'); + return; } + KillPlayerForTeamChange(this); } -void ShufflePlayerOutOfTeam (float source_team) +void AutoBalanceBots(int source_team, int destination_team) { - float smallestteam, smallestteam_count, steam; - float lowest_bot_score, lowest_player_score; - entity lowest_bot, lowest_player, selected; - - smallestteam = 0; - smallestteam_count = 999999999; - - if(c1 >= 0 && c1 < smallestteam_count) - { - smallestteam = 1; - smallestteam_count = c1; - } - if(c2 >= 0 && c2 < smallestteam_count) - { - smallestteam = 2; - smallestteam_count = c2; - } - if(c3 >= 0 && c3 < smallestteam_count) + if ((source_team == -1) || (destination_team == -1)) { - smallestteam = 3; - smallestteam_count = c3; + return; } - if(c4 >= 0 && c4 < smallestteam_count) + if (!autocvar_g_balance_teams || + !autocvar_g_balance_teams_prevent_imbalance) { - smallestteam = 4; - smallestteam_count = c4; - } - - if(!smallestteam) - { - bprint("warning: no smallest team\n"); return; } - - if(source_team == 1) - steam = NUM_TEAM_1; - else if(source_team == 2) - steam = NUM_TEAM_2; - else if(source_team == 3) - steam = NUM_TEAM_3; - else // if(source_team == 4) - steam = NUM_TEAM_4; - - lowest_bot = NULL; - lowest_bot_score = 999999999; - lowest_player = NULL; - lowest_player_score = 999999999; - - // find the lowest-scoring player & bot of that team - FOREACH_CLIENT(IS_PLAYER(it) && it.team == steam, { - if(it.isbot) + int num_players_source_team = 0; + int num_players_destination_team = 0; + entity lowest_bot_destination_team = NULL; + switch (source_team) + { + case 1: { - if(it.totalfrags < lowest_bot_score) - { - lowest_bot = it; - lowest_bot_score = it.totalfrags; - } + num_players_source_team = c1; + break; } - else + case 2: { - if(it.totalfrags < lowest_player_score) - { - lowest_player = it; - lowest_player_score = it.totalfrags; - } + num_players_source_team = c2; + break; + } + case 3: + { + num_players_source_team = c3; + break; + } + case 4: + { + num_players_source_team = c4; + break; } - }); - - // prefers to move a bot... - if(lowest_bot != NULL) - selected = lowest_bot; - // but it will move a player if it has to - else - selected = lowest_player; - // don't do anything if it couldn't find anyone - if(!selected) - { - bprint("warning: couldn't find a player to move from team\n"); - return; - } - - // smallest team gains a member - if(smallestteam == 1) - { - c1 = c1 + 1; - } - else if(smallestteam == 2) - { - c2 = c2 + 1; - } - else if(smallestteam == 3) - { - c3 = c3 + 1; - } - else if(smallestteam == 4) - { - c4 = c4 + 1; - } - else - { - bprint("warning: destination team invalid\n"); - return; - } - // source team loses a member - if(source_team == 1) - { - c1 = c1 + 1; - } - else if(source_team == 2) - { - c2 = c2 + 2; - } - else if(source_team == 3) - { - c3 = c3 + 3; } - else if(source_team == 4) + switch (destination_team) { - c4 = c4 + 4; + case 1: + { + num_players_destination_team = c1; + lowest_bot_destination_team = lowest_bot_team1; + break; + } + case 2: + { + num_players_destination_team = c2; + lowest_bot_destination_team = lowest_bot_team2; + break; + } + case 3: + { + num_players_destination_team = c3; + lowest_bot_destination_team = lowest_bot_team3; + break; + } + case 4: + { + num_players_destination_team = c4; + lowest_bot_destination_team = lowest_bot_team4; + break; + } } - else + if ((num_players_destination_team <= num_players_source_team) || + (lowest_bot_destination_team == NULL)) { - bprint("warning: source team invalid\n"); return; } - - // move the player to the new team - TeamchangeFrags(selected); - SetPlayerTeam(selected, smallestteam, source_team, false); - - if(!IS_DEAD(selected)) - Damage(selected, selected, selected, 100000, DEATH_AUTOTEAMCHANGE.m_id, selected.origin, '0 0 0'); - Send_Notification(NOTIF_ONE, selected, MSG_CENTER, CENTER_DEATH_SELF_AUTOTEAMCHANGE, selected.team); + SetPlayerTeamSimple(lowest_bot_destination_team, + Team_NumberToTeam(source_team)); + KillPlayerForTeamChange(lowest_bot_destination_team); } diff --git a/qcsrc/server/teamplay.qh b/qcsrc/server/teamplay.qh index 8d0ea9cb8a..1813db04d8 100644 --- a/qcsrc/server/teamplay.qh +++ b/qcsrc/server/teamplay.qh @@ -3,10 +3,29 @@ string cache_mutatormsg; string cache_lastmutatormsg; -// client counts for each team -//float c1, c2, c3, c4; -// # of bots on those teams -float cb1, cb2, cb3, cb4; +// The following variables are used for balancing. They are not updated +// automatically. You need to call CheckAllowedTeams and GetTeamCounts to get +// proper values. + +// These four have 2 different states. If they are equal to -1, it means that +// the player can't join the team. Zero or positive value means that player can +// join the team and means the number of players on that team. +float c1; +float c2; +float c3; +float c4; +float num_bots_team1; ///< Number of bots in the first team. +float num_bots_team2; ///< Number of bots in the second team. +float num_bots_team3; ///< Number of bots in the third team. +float num_bots_team4; ///< Number of bots in the fourth team. +entity lowest_human_team1; ///< Human with the lowest score in the first team. +entity lowest_human_team2; ///< Human with the lowest score in the second team. +entity lowest_human_team3; ///< Human with the lowest score in the third team. +entity lowest_human_team4; ///< Human with the lowest score in the fourth team. +entity lowest_bot_team1; ///< Bot with the lowest score in the first team. +entity lowest_bot_team2; ///< Bot with the lowest score in the second team. +entity lowest_bot_team3; ///< Bot with the lowest score in the third team. +entity lowest_bot_team4; ///< Bot with the lowest score in the fourth team. int redowned, blueowned, yellowowned, pinkowned; @@ -24,12 +43,30 @@ string GetClientVersionMessage(entity this); string getwelcomemessage(entity this); -void SetPlayerColors(entity pl, float _color); +void SetPlayerColors(entity player, float _color); -void SetPlayerTeam(entity pl, float t, float s, float noprint); +/// \brief Kills player as a result of team change. +/// \param[in,out] player Player to kill. +/// \return No return. +void KillPlayerForTeamChange(entity player); + +/// \brief Sets the team of the player. +/// \param[in,out] player Player to adjust. +/// \param[in] team_num Team number to set. See TEAM_NUM constants. +/// \return True if team switch was successful, false otherwise. +bool SetPlayerTeamSimple(entity player, int team_num); + +/// \brief Sets the team of the player. +/// \param[in,out] player Player to adjust. +/// \param[in] destination_team Team to set. +/// \param[in] source_team Previous team of the player. +/// \param[in] no_print Whether to print this event to players' console. +/// \return True if team switch was successful, false otherwise. +bool SetPlayerTeam(entity player, int destination_team, int source_team, + bool no_print); // set c1...c4 to show what teams are allowed -void CheckAllowedTeams (entity for_whom); +void CheckAllowedTeams(entity for_whom); float PlayerValue(entity p); @@ -37,16 +74,47 @@ float PlayerValue(entity p); // teams that are allowed will now have their player counts stored in c1...c4 void GetTeamCounts(entity ignore); -float TeamSmallerEqThanTeam(float ta, float tb, entity e); +/// \brief Returns whether one team is smaller than the other. +/// \param[in] team_a First team. +/// \param[in] team_b Second team. +/// \param[in] player Player to check. +/// \param[in] use_score Whether to take into account team scores. +/// \return True if first team is smaller than the second one, false otherwise. +/// \note This function assumes that CheckAllowedTeams and GetTeamCounts have +/// been called. +bool IsTeamSmallerThanTeam(int team_a, int team_b, entity player, + bool use_score); + +/// \brief Returns whether one team is equal to the other. +/// \param[in] team_a First team. +/// \param[in] team_b Second team. +/// \param[in] player Player to check. +/// \param[in] use_score Whether to take into account team scores. +/// \return True if first team is equal to the second one, false otherwise. +/// \note This function assumes that CheckAllowedTeams and GetTeamCounts have +/// been called. +bool IsTeamEqualToTeam(int team_a, int team_b, entity player, bool use_score); + +/// \brief Returns the bitmask of the best teams for the player to join. +/// \param[in] player Player to check. +/// \param[in] use_score Whether to take into account team scores. +/// \return Bitmask of the best teams for the player to join. +/// \note This function assumes that CheckAllowedTeams and GetTeamCounts have +/// been called. +int FindBestTeams(entity player, bool use_score); // returns # of smallest team (1, 2, 3, 4) // NOTE: Assumes CheckAllowedTeams has already been called! -float FindSmallestTeam(entity pl, float ignore_pl); - -int JoinBestTeam(entity this, bool only_return_best, bool forcebestteam); +int FindSmallestTeam(entity player, float ignore_player); -//void() ctf_playerchanged; +int JoinBestTeam(entity this, bool only_return_best, bool force_best_team); -void ShufflePlayerOutOfTeam (float source_team); +/// \brief Auto balances bots in teams after the player has changed team. +/// \param[in] source_team Previous team of the player (1, 2, 3, 4). +/// \param[in] destination_team Current team of the player (1, 2, 3, 4). +/// \return No return. +/// \note This function assumes that CheckAllowedTeams and GetTeamCounts have +/// been called. +void AutoBalanceBots(int source_team, int destination_team); void setcolor(entity this, int clr); diff --git a/qcsrc/server/utils.qh b/qcsrc/server/utils.qh index da5c7a56a7..4a603c0225 100644 --- a/qcsrc/server/utils.qh +++ b/qcsrc/server/utils.qh @@ -41,7 +41,7 @@ const string STR_OBSERVER = "observer"; } \ } MACRO_END -#define FOREACH_CLIENT(cond, body) FOREACH_CLIENTSLOT(IS_CLIENT(it) && (cond), body) +#define FOREACH_CLIENT(cond, body) FOREACH_CLIENTSLOT(IS_CLIENT(it) && (cond), LAMBDA(body)) // using the "inside out" version of knuth-fisher-yates shuffle // https://en.wikipedia.org/wiki/Fisher–Yates_shuffle diff --git a/qcsrc/tools/compilationunits.sh b/qcsrc/tools/compilationunits.sh index e835fa6713..e3d1eb0136 100755 --- a/qcsrc/tools/compilationunits.sh +++ b/qcsrc/tools/compilationunits.sh @@ -74,6 +74,12 @@ function check() { done } -check client -check server -check menu +if [ ${#@} -eq 0 ]; then + check client + check server + check menu +else + for var in ${@}; do + check ${var} + done +fi