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)
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)
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 "$<TARGET_FILE_DIR:${prog}>/${prog}.dat" "${prog}.dat"
set g_start_ammo_cells 0
set g_start_ammo_plasma 0
set g_start_ammo_fuel 0
+set g_random_start_weapons_count 0
+set g_random_start_weapons "machinegun mortar electro crylink vortex hagar devastator"
+set g_random_start_shells 15
+set g_random_start_bullets 80
+set g_random_start_rockets 40
+set g_random_start_cells 30
+set g_random_start_plasma 30
set g_warmup_start_health 100 "starting values when being in warmup-stage"
set g_warmup_start_armor 100 "starting values when being in warmup-stage"
set g_warmup_start_ammo_shells 30 "starting values when being in warmup-stage"
set g_start_ammo_cells 0
set g_start_ammo_plasma 0
set g_start_ammo_fuel 0
+set g_random_start_weapons_count 0
+set g_random_start_weapons "machinegun mortar electro crylink vortex hagar devastator"
+set g_random_start_shells 15
+set g_random_start_bullets 80
+set g_random_start_rockets 15
+set g_random_start_cells 25
+set g_random_start_plasma 25
set g_warmup_start_health 250 "starting values when being in warmup-stage"
set g_warmup_start_armor 100 "starting values when being in warmup-stage"
set g_warmup_start_ammo_shells 50 "starting values when being in warmup-stage"
set g_start_ammo_cells 0
set g_start_ammo_plasma 0
set g_start_ammo_fuel 0
+set g_random_start_weapons_count 0
+set g_random_start_weapons "machinegun mortar electro crylink vortex hagar devastator"
+set g_random_start_shells 15
+set g_random_start_bullets 80
+set g_random_start_rockets 40
+set g_random_start_cells 30
+set g_random_start_plasma 30
set g_warmup_start_health 100 "starting values when being in warmup-stage"
set g_warmup_start_armor 100 "starting values when being in warmup-stage"
set g_warmup_start_ammo_shells 30 "starting values when being in warmup-stage"
set g_start_ammo_cells 0
set g_start_ammo_plasma 0
set g_start_ammo_fuel 0
+set g_random_start_weapons_count 0
+set g_random_start_weapons "machinegun mortar electro crylink vortex hagar devastator"
+set g_random_start_shells 15
+set g_random_start_bullets 80
+set g_random_start_rockets 40
+set g_random_start_cells 30
+set g_random_start_plasma 30
set g_warmup_start_health 100 "starting values when being in warmup-stage"
set g_warmup_start_armor 100 "starting values when being in warmup-stage"
set g_warmup_start_ammo_shells 30 "starting values when being in warmup-stage"
set g_start_ammo_cells 0
set g_start_ammo_plasma 0
set g_start_ammo_fuel 0
+set g_random_start_weapons_count 0
+set g_random_start_weapons "machinegun mortar electro crylink vortex hagar devastator"
+set g_random_start_shells 15
+set g_random_start_bullets 80
+set g_random_start_rockets 40
+set g_random_start_cells 30
+set g_random_start_plasma 30
set g_warmup_start_health 100 "starting values when being in warmup-stage"
set g_warmup_start_armor 100 "starting values when being in warmup-stage"
set g_warmup_start_ammo_shells 30 "starting values when being in warmup-stage"
set g_start_ammo_cells 0
set g_start_ammo_plasma 0
set g_start_ammo_fuel 0
+set g_random_start_weapons_count 0
+set g_random_start_weapons "machinegun mortar electro crylink vortex hagar devastator"
+set g_random_start_shells 15
+set g_random_start_bullets 80
+set g_random_start_rockets 40
+set g_random_start_cells 30
+set g_random_start_plasma 30
set g_warmup_start_health 100 "starting values when being in warmup-stage"
set g_warmup_start_armor 100 "starting values when being in warmup-stage"
set g_warmup_start_ammo_shells 30 "starting values when being in warmup-stage"
set g_start_ammo_cells 0
set g_start_ammo_plasma 0
set g_start_ammo_fuel 0
+set g_random_start_weapons_count 0
+set g_random_start_weapons "machinegun mortar electro crylink vortex hagar devastator"
+set g_random_start_shells 15
+set g_random_start_bullets 80
+set g_random_start_rockets 40
+set g_random_start_cells 30
+set g_random_start_plasma 30
set g_warmup_start_health 100 "starting values when being in warmup-stage"
set g_warmup_start_armor 100 "starting values when being in warmup-stage"
set g_warmup_start_ammo_shells 30 "starting values when being in warmup-stage"
#: qcsrc/common/notifications/all.inc:463
#, c-format
-msgid "^BG%s%s^K1 got too close ^BG%s^K1's rocket%s%s"
+msgid "^BG%s%s^K1 got too close to ^BG%s^K1's rocket%s%s"
msgstr ""
#: qcsrc/common/notifications/all.inc:464
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
set sv_simple_items 1 "allow or forbid client use of simple items"
set sv_showspectators 1 "Show who's spectating who in the player info panel when client has cl_showspectators on. Shouldn't be used on competitive servers, also disable when watching a suspected cheater"
+
+set sv_damagetext 2 "<= 0: disabled, >= 1: visible to spectators, >= 2: visible to attacker, >= 3: all players see everyone's damage"
// 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"
// running guns
// ==============
set g_running_guns 0 "... or wonder, till it drives you mad, what would have followed if you had."
+
+// ==================
+// dynamic handicap
+// ==================
+set g_dynamic_handicap 0 "Whether to enable dynamic handicap."
+set g_dynamic_handicap_scale 0.2 "The scale of the handicap. Larger values mean more penalties for strong players and more buffs for weak players."
+set g_dynamic_handicap_exponent 1 "The exponent used to calculate handicap. 1 means linear scale. Values more than 1 mean stronger non-linear handicap. Values less than 1 mean weaker non-linear handicap"
+set g_dynamic_handicap_min 0 "The minimum value of the handicap."
+set g_dynamic_handicap_max 0 "The maximum value of the handicap."
void DrawAmmoNades(vector myPos, vector mySize, bool draw_expanding, float expand_time); // TODO: mutator
-void DrawAmmoItem(vector myPos, vector mySize, .int ammoType, bool isCurrent, bool isInfinite)
+void DrawAmmoItem(vector myPos, vector mySize, int ammoType, bool isCurrent, bool isInfinite)
{
TC(bool, isCurrent); TC(bool, isInfinite);
- if(ammoType == ammo_none)
+ if(ammoType == RESOURCE_NONE)
return;
// Initialize variables
int ammo;
if(autocvar__hud_configure)
{
- isCurrent = (ammoType == ammo_rockets); // Rockets always current
+ isCurrent = (ammoType == RESOURCE_ROCKETS); // Rockets always current
ammo = 60;
}
else
{
if(autocvar__hud_configure)
{
- DrawAmmoItem(pos, ammo_size, ammo_rockets, true, false);
+ DrawAmmoItem(pos, ammo_size, RESOURCE_ROCKETS, true, false);
}
else
{
DrawAmmoItem(
pos,
ammo_size,
- wep.ammo_field,
+ wep.ammo_type,
true,
infinite_ammo
);
}
else
{
- .int ammotype;
+ int ammotype;
row = column = 0;
for(i = 0; i < AMMO_COUNT; ++i)
{
- ammotype = GetAmmoFieldFromNum(i);
+ ammotype = GetAmmoTypeFromNum(i);
DrawAmmoItem(
pos + vec2(column * (ammo_size.x + offset.x), row * (ammo_size.y + offset.y)),
ammo_size,
ammotype,
- (wep.ammo_field == ammotype),
+ (wep.ammo_type == ammotype),
infinite_ammo
);
}
// draw ammo status bar
- if(!infinite_ammo && autocvar_hud_panel_weapons_ammo && (it.ammo_field != ammo_none))
+ if(!infinite_ammo && autocvar_hud_panel_weapons_ammo && (it.ammo_type != RESOURCE_NONE))
{
float ammo_full;
- a = getstati(GetAmmoStat(it.ammo_field)); // how much ammo do we have?
+ a = getstati(GetAmmoStat(it.ammo_type)); // how much ammo do we have?
if(a > 0)
{
- switch(it.ammo_field)
+ switch (it.ammo_type)
{
- case ammo_shells: ammo_full = autocvar_hud_panel_weapons_ammo_full_shells; break;
- case ammo_nails: ammo_full = autocvar_hud_panel_weapons_ammo_full_nails; break;
- case ammo_rockets: ammo_full = autocvar_hud_panel_weapons_ammo_full_rockets; break;
- case ammo_cells: ammo_full = autocvar_hud_panel_weapons_ammo_full_cells; break;
- case ammo_plasma: ammo_full = autocvar_hud_panel_weapons_ammo_full_plasma; break;
- case ammo_fuel: ammo_full = autocvar_hud_panel_weapons_ammo_full_fuel; break;
+ case RESOURCE_SHELLS: ammo_full = autocvar_hud_panel_weapons_ammo_full_shells; break;
+ case RESOURCE_BULLETS: ammo_full = autocvar_hud_panel_weapons_ammo_full_nails; break;
+ case RESOURCE_ROCKETS: ammo_full = autocvar_hud_panel_weapons_ammo_full_rockets; break;
+ case RESOURCE_CELLS: ammo_full = autocvar_hud_panel_weapons_ammo_full_cells; break;
+ case RESOURCE_PLASMA: ammo_full = autocvar_hud_panel_weapons_ammo_full_plasma; break;
+ case RESOURCE_FUEL: ammo_full = autocvar_hud_panel_weapons_ammo_full_fuel; break;
default: ammo_full = 60;
}
// 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)
else
o.(scores(it)) = ReadChar();
}
- });
+ });
return = true;
{
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();
make_pure(this);
float newspectatee_status;
- int f = ReadByte();
+ int f = ReadByte();
scoreboard_showscores_force = (f & BIT(0));
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)))
{
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;
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 = 0;
+ 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;
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;
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)
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)
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;
/** True when private information such as origin is available */
.bool m_entcs_private;
+
/** True when origin is available */
+// FIXME: it seems sometimes this is false when observing even though observers should be able to know about all players
+// easily reproducible on heart_v2 or The_Yard with bots - might be because they lack waypoints and bots stand still
+// it has happened in matches with players and no bots but much more rarely
.bool has_origin;
+
/** True when a recent server sent origin has been received */
.bool has_sv_origin;
#include <common/mutators/mutator/damagetext/_mod.inc>
#include <common/mutators/mutator/dodging/_mod.inc>
#include <common/mutators/mutator/doublejump/_mod.inc>
+#include <common/mutators/mutator/dynamic_handicap/_mod.inc>
#include <common/mutators/mutator/globalforces/_mod.inc>
#include <common/mutators/mutator/hook/_mod.inc>
#include <common/mutators/mutator/instagib/_mod.inc>
#include <common/mutators/mutator/damagetext/_mod.qh>
#include <common/mutators/mutator/dodging/_mod.qh>
#include <common/mutators/mutator/doublejump/_mod.qh>
+#include <common/mutators/mutator/dynamic_handicap/_mod.qh>
#include <common/mutators/mutator/globalforces/_mod.qh>
#include <common/mutators/mutator/hook/_mod.qh>
#include <common/mutators/mutator/instagib/_mod.qh>
#include "sv_bloodloss.qh"
-REGISTER_MUTATOR(bloodloss, cvar("g_bloodloss"));
+float autocvar_g_bloodloss;
+REGISTER_MUTATOR(bloodloss, autocvar_g_bloodloss);
.float bloodloss_timer;
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;
{
entity player = M_ARGV(0, entity);
- if(player.health <= autocvar_g_bloodloss)
+ if(GetResourceAmount(player, RESOURCE_HEALTH) <= autocvar_g_bloodloss)
return true;
}
{
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));
});
}
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)
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;
#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;
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;
#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;
}
make_impure(NEW(DamageText, server_entity_index, entcs.origin, false, health, armor, potential_damage, deathtype, friendlyfire));
} else if (autocvar_cl_damagetext_2d && spectatee_status != -1) {
- // never show 2d damagetext when observing
- // on some maps (hearth_v2, The_Yard), sometimes has_origin is false even though observers should know about all players
- // it happens mostly with bots but occasionally also with players
+ // never show 2d damagetext when observing - might be a bug in .has_origin
// screen coords only
vector screen_pos = vec2(vid_conwidth * autocvar_cl_damagetext_2d_pos.x, vid_conheight * autocvar_cl_damagetext_2d_pos.y);
--- /dev/null
+// generated file; do not modify
+#ifdef SVQC
+ #include <common/mutators/mutator/dynamic_handicap/sv_dynamic_handicap.qc>
+#endif
--- /dev/null
+// generated file; do not modify
+#ifdef SVQC
+ #include <common/mutators/mutator/dynamic_handicap/sv_dynamic_handicap.qh>
+#endif
--- /dev/null
+#include "sv_dynamic_handicap.qh"
+/// \file
+/// \brief Source file that contains implementation of the Dynamic handicap
+/// mutator.
+/// \author Lyberta
+/// \copyright GNU GPLv2 or any later version.
+
+//======================= Global variables ====================================
+
+int autocvar_g_dynamic_handicap; ///< Whether to enable dynamic handicap.
+/// \brief The scale of the handicap. Larget values mean more penalties for
+/// strong players and more buffs for weak players.
+float autocvar_g_dynamic_handicap_scale;
+/// \brief The exponent used to calculate handicap. 1 means linear scale. Values
+/// more than 1 mean stronger non-linear handicap. Values less than 1 mean
+/// weaker non-linear handicap.
+float autocvar_g_dynamic_handicap_exponent;
+float autocvar_g_dynamic_handicap_min; ///< The minimum value of the handicap.
+float autocvar_g_dynamic_handicap_max; ///< The maximum value of the handicap.
+
+//====================== Forward declarations =================================
+
+/// \brief Clamps the value of the handicap.
+/// \param[in] handicap Value to clamp.
+/// \return Clamped value.
+float DynamicHandicap_ClampHandicap(float handicap);
+
+//========================= Free functions ====================================
+
+/// \brief Updates the handicap of all players.
+/// \return No return.
+void DynamicHandicap_UpdateHandicap()
+{
+ float total_score = 0;
+ float total_players = 0;
+ FOREACH_CLIENT(IS_PLAYER(it),
+ {
+ total_score += PlayerScore_Get(it, SP_SCORE);
+ ++total_players;
+ });
+ float mean_score = total_score / total_players;
+ FOREACH_CLIENT(true,
+ {
+ float score = PlayerScore_Get(it, SP_SCORE);
+ float handicap = fabs((score - mean_score) *
+ autocvar_g_dynamic_handicap_scale);
+ handicap = handicap ** autocvar_g_dynamic_handicap_exponent;
+ if (score < mean_score)
+ {
+ handicap = -handicap;
+ }
+ if (handicap >= 0)
+ {
+ handicap += 1;
+ }
+ else
+ {
+ handicap = 1 / (fabs(handicap) + 1);
+ }
+ handicap = DynamicHandicap_ClampHandicap(handicap);
+ Handicap_SetForcedHandicap(it, handicap);
+ });
+}
+
+float DynamicHandicap_ClampHandicap(float handicap)
+{
+ if ((autocvar_g_dynamic_handicap_min >= 0) && (handicap <
+ autocvar_g_dynamic_handicap_min))
+ {
+ handicap = autocvar_g_dynamic_handicap_min;
+ }
+ if ((autocvar_g_dynamic_handicap_max > 0) && (handicap >
+ autocvar_g_dynamic_handicap_max))
+ {
+ handicap = autocvar_g_dynamic_handicap_max;
+ }
+ return handicap;
+}
+
+//============================= Hooks ========================================
+
+REGISTER_MUTATOR(dynamic_handicap, autocvar_g_dynamic_handicap);
+
+MUTATOR_HOOKFUNCTION(dynamic_handicap, BuildMutatorsString)
+{
+ M_ARGV(0, string) = strcat(M_ARGV(0, string), ":handicap");
+}
+
+MUTATOR_HOOKFUNCTION(dynamic_handicap, BuildMutatorsPrettyString)
+{
+ M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Dynamic handicap");
+}
+
+MUTATOR_HOOKFUNCTION(dynamic_handicap, ClientDisconnect)
+{
+ DynamicHandicap_UpdateHandicap();
+}
+
+MUTATOR_HOOKFUNCTION(dynamic_handicap, PutClientInServer)
+{
+ DynamicHandicap_UpdateHandicap();
+}
+
+MUTATOR_HOOKFUNCTION(dynamic_handicap, MakePlayerObserver)
+{
+ DynamicHandicap_UpdateHandicap();
+}
+
+MUTATOR_HOOKFUNCTION(dynamic_handicap, AddedPlayerScore)
+{
+ if (M_ARGV(0, entity) != SP_SCORE)
+ {
+ return;
+ }
+ DynamicHandicap_UpdateHandicap();
+}
--- /dev/null
+#pragma once
#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)
#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;
#include <common/items/_mod.qh>
-REGISTER_MUTATOR(mutator_instagib, cvar("g_instagib") && !g_nexball);
+REGISTER_MUTATOR(mutator_instagib, autocvar_g_instagib && !g_nexball);
spawnfunc(item_minst_cells)
{
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)
{
}
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');
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);
}
}
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';
}
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;
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;
}
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;
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;
}
#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)
{
#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)
{
#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;
#if defined(SVQC)
-REGISTER_MUTATOR(multijump, cvar("g_multijump"));
+REGISTER_MUTATOR(multijump, autocvar_g_multijump);
#elif defined(CSQC)
REGISTER_MUTATOR(multijump, true);
#endif
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';
#include <common/monsters/sv_monsters.qh>
#include <server/g_subs.qh>
-REGISTER_MUTATOR(nades, cvar("g_nades"));
+REGISTER_MUTATOR(nades, autocvar_g_nades);
.float nade_time_primed;
.float nade_lifetime;
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))
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 )
{
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
{
//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;
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);
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;
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)
{
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';
*/
+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
{
#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;
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
{
{
// 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)
{
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)
+ switch (e.ammo_type)
{
- 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 RESOURCE_SHELLS: SetResourceAmount(this, RESOURCE_SHELLS, autocvar_g_pickup_shells_max); break;
+ case RESOURCE_BULLETS: SetResourceAmount(this, RESOURCE_BULLETS, autocvar_g_pickup_nails_max); break;
+ case RESOURCE_ROCKETS: SetResourceAmount(this, RESOURCE_ROCKETS, autocvar_g_pickup_rockets_max); break;
+ case RESOURCE_CELLS: SetResourceAmount(this, RESOURCE_CELLS, autocvar_g_pickup_cells_max); break;
+ case RESOURCE_PLASMA: SetResourceAmount(this, RESOURCE_PLASMA, autocvar_g_pickup_plasma_max); break;
+ case RESOURCE_FUEL: SetResourceAmount(this, RESOURCE_FUEL, autocvar_g_pickup_fuel_max); break;
}
}
else
{
- switch(e.ammo_field)
+ switch (e.ammo_type)
{
- 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 RESOURCE_SHELLS: SetResourceAmount(this, RESOURCE_SHELLS, autocvar_g_balance_nix_ammo_shells); break;
+ case RESOURCE_BULLETS: SetResourceAmount(this, RESOURCE_BULLETS, autocvar_g_balance_nix_ammo_nails); break;
+ case RESOURCE_ROCKETS: SetResourceAmount(this, RESOURCE_ROCKETS, autocvar_g_balance_nix_ammo_rockets); break;
+ case RESOURCE_CELLS: SetResourceAmount(this, RESOURCE_CELLS, autocvar_g_balance_nix_ammo_cells); break;
+ case RESOURCE_PLASMA: SetResourceAmount(this, RESOURCE_PLASMA, autocvar_g_balance_nix_ammo_plasma); break;
+ case RESOURCE_FUEL: SetResourceAmount(this, RESOURCE_FUEL, autocvar_g_balance_nix_ammo_fuel); break;
}
}
if(!(this.items & IT_UNLIMITED_WEAPON_AMMO) && time > this.nix_nextincr)
{
- switch(e.ammo_field)
+ switch (e.ammo_type)
{
- 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 RESOURCE_SHELLS: GiveResource(this, RESOURCE_SHELLS, autocvar_g_balance_nix_ammoincr_shells); break;
+ case RESOURCE_BULLETS: GiveResource(this, RESOURCE_BULLETS, autocvar_g_balance_nix_ammoincr_nails); break;
+ case RESOURCE_ROCKETS: GiveResource(this, RESOURCE_ROCKETS, autocvar_g_balance_nix_ammoincr_rockets); break;
+ case RESOURCE_CELLS: GiveResource(this, RESOURCE_CELLS, autocvar_g_balance_nix_ammoincr_cells); break;
+ case RESOURCE_PLASMA: GiveResource(this, RESOURCE_PLASMA, autocvar_g_balance_nix_ammoincr_plasma); break;
+ case RESOURCE_FUEL: GiveResource(this, RESOURCE_FUEL, autocvar_g_balance_nix_ammoincr_fuel); break;
}
this.nix_nextincr = time + autocvar_g_balance_nix_incrtime;
METHOD(HeavyMachineGun, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
{
- float ammo_amount = actor.ammo_nails >= WEP_CVAR(hmg, ammo);
+ float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(hmg, ammo);
if(autocvar_g_balance_hmg_reload_ammo)
ammo_amount += actor.(weaponentity).(weapon_load[WEP_HMG.m_id]) >= WEP_CVAR(hmg, ammo);
METHOD(HeavyMachineGun, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
{
- float ammo_amount = actor.ammo_nails >= WEP_CVAR(hmg, ammo);
+ float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(hmg, ammo);
if(autocvar_g_balance_hmg_reload_ammo)
ammo_amount += actor.(weaponentity).(weapon_load[WEP_HMG.m_id]) >= WEP_CVAR(hmg, ammo);
#pragma once
CLASS(HeavyMachineGun, Weapon)
-/* ammotype */ ATTRIB(HeavyMachineGun, ammo_field, .int, ammo_nails);
+/* ammotype */ ATTRIB(HeavyMachineGun, ammo_type, int, RESOURCE_BULLETS);
/* impulse */ ATTRIB(HeavyMachineGun, impulse, int, 3);
/* flags */ ATTRIB(HeavyMachineGun, spawnflags, int, WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_HIDDEN | WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_SUPERWEAPON);
/* rating */ ATTRIB(HeavyMachineGun, bot_pickupbasevalue, float, 10000);
METHOD(RocketPropelledChainsaw, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
{
- float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR(rpc, ammo);
+ float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(rpc, ammo);
ammo_amount += actor.(weaponentity).(weapon_load[WEP_RPC.m_id]) >= WEP_CVAR(rpc, ammo);
return ammo_amount;
}
#pragma once
CLASS(RocketPropelledChainsaw, Weapon)
-/* ammotype */ ATTRIB(RocketPropelledChainsaw, ammo_field, .int, ammo_rockets);
+/* ammotype */ ATTRIB(RocketPropelledChainsaw, ammo_type, int, RESOURCE_ROCKETS);
/* impulse */ ATTRIB(RocketPropelledChainsaw, impulse, int, 9);
/* flags */ ATTRIB(RocketPropelledChainsaw, spawnflags, int, WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_HIDDEN | WEP_FLAG_NORMAL | WEP_FLAG_CANCLIMB | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH | WEP_FLAG_SUPERWEAPON);
/* rating */ ATTRIB(RocketPropelledChainsaw, bot_pickupbasevalue, float, 10000);
#include "hmg.qh"
#include "rpc.qh"
+string autocvar_g_overkill;
+
bool autocvar_g_overkill_powerups_replace;
bool autocvar_g_overkill_itemwaypoints = true;
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
{
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
#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)
{
#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)
{
#include "sv_sandbox.qh"
+string autocvar_g_sandbox;
int autocvar_g_sandbox_info;
bool autocvar_g_sandbox_readonly;
string autocvar_g_sandbox_storage_name;
float autosave_time;
void sandbox_Database_Load();
-REGISTER_MUTATOR(sandbox, cvar("g_sandbox"))
+REGISTER_MUTATOR(sandbox, expr_evaluate(autocvar_g_sandbox))
{
MUTATOR_ONADD
{
#include <lib/float.qh>
+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;
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;
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);
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))
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);
#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)
#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;
#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)
{
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));
}
}
#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;
#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)
MSG_INFO_NOTIF(WEAPON_CRYLINK_MURDER, N_CONSOLE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponcrylink", _("^BG%s%s^K1 felt the strong pull of ^BG%s^K1's Crylink%s%s"), "")
MSG_INFO_NOTIF(WEAPON_CRYLINK_SUICIDE, N_CONSOLE, 2, 1, "s1 s2loc spree_lost", "s1", "weaponcrylink", _("^BG%s^K1 felt the strong pull of their Crylink%s%s"), "")
MSG_INFO_NOTIF(WEAPON_DEVASTATOR_MURDER_DIRECT, N_CONSOLE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponrocketlauncher", _("^BG%s%s^K1 ate ^BG%s^K1's rocket%s%s"), "")
- MSG_INFO_NOTIF(WEAPON_DEVASTATOR_MURDER_SPLASH, N_CONSOLE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponrocketlauncher", _("^BG%s%s^K1 got too close ^BG%s^K1's rocket%s%s"), "")
+ MSG_INFO_NOTIF(WEAPON_DEVASTATOR_MURDER_SPLASH, N_CONSOLE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponrocketlauncher", _("^BG%s%s^K1 got too close to ^BG%s^K1's rocket%s%s"), "")
MSG_INFO_NOTIF(WEAPON_DEVASTATOR_SUICIDE, N_CONSOLE, 2, 1, "s1 s2loc spree_lost", "s1", "weaponrocketlauncher", _("^BG%s^K1 blew themself up with their Devastator%s%s"), "")
MSG_INFO_NOTIF(WEAPON_ELECTRO_MURDER_BOLT, N_CONSOLE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponelectro", _("^BG%s%s^K1 was blasted by ^BG%s^K1's Electro bolt%s%s"), "")
MSG_INFO_NOTIF(WEAPON_ELECTRO_MURDER_COMBO, N_CONSOLE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponelectro", _("^BG%s%s^K1 felt the electrifying air of ^BG%s^K1's Electro combo%s%s"), "")
#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;
#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)
#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)
--- /dev/null
+#pragma once
+
+/// \file
+/// \brief Header file that describes resource types.
+/// \author Lyberta
+/// \copyright GNU GPLv2 or any later version.
+
+/// \brief Describes the available resource types.
+enum
+{
+ RESOURCE_NONE, ///< Indicates the lack of resource. Use with caution.
+ RESOURCE_HEALTH, ///< Health.
+ RESOURCE_ARMOR, ///< Armor.
+ RESOURCE_SHELLS, ///< Shells (used by shotgun).
+ RESOURCE_BULLETS, ///< Bullets (used by machinegun, rifle, HMG)
+ 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).
+};
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)
+void GiveRandomWeapons(entity receiver, int num_weapons, string weapon_names,
+ entity ammo_entity)
{
- if (amount == 0)
+ if (num_weapons == 0)
{
return;
}
- switch (resource_type)
+ int num_potential_weapons = tokenize_console(weapon_names);
+ for (int give_attempt = 0; give_attempt < num_weapons; ++give_attempt)
{
- case health:
+ RandomSelection_Init();
+ for (int weapon_index = 0; weapon_index < num_potential_weapons;
+ ++weapon_index)
{
- // 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;
+ string weapon = argv(weapon_index);
+ FOREACH(Weapons, it != WEP_Null,
+ {
+ // Finding a weapon which player doesn't have.
+ if (!(receiver.weapons & it.m_wepset) && (it.netname == weapon))
+ {
+ RandomSelection_AddEnt(it, 1, 1);
+ break;
+ }
+ });
}
- case armorvalue:
+ if (RandomSelection_chosen_ent == NULL)
{
- // 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:
+ receiver.weapons |= RandomSelection_chosen_ent.m_wepset;
+ if (RandomSelection_chosen_ent.ammo_type == RESOURCE_NONE)
{
- GivePlayerAmmo(player, resource_type, amount);
- return;
+ continue;
}
- case ammo_fuel:
+ if (GetResourceAmount(receiver,
+ RandomSelection_chosen_ent.ammo_type) != 0)
{
- 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;
+ continue;
}
+ GiveResource(receiver, RandomSelection_chosen_ent.ammo_type,
+ GetResourceAmount(ammo_entity,
+ RandomSelection_chosen_ent.ammo_type));
}
}
-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)
+float Item_GiveAmmoTo(entity item, entity player, int resource_type, float ammomax)
{
+ float amount = GetResourceAmount(item, resource_type);
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)
}
}
}
- 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;
if(it.itemdef) // is a registered item
{
Item_Show(it, -1);
+ it.scheduledrespawntime = 0;
RandomSelection_AddEnt(it, it.cnt, 0);
}
});
if(!(player.weapons & (it.m_wepset)))
continue;
- switch(it.ammo_field)
+ switch(it.ammo_type)
{
- case ammo_shells: need_shells = true; break;
- case ammo_nails: need_nails = true; break;
- case ammo_rockets: need_rockets = true; break;
- case ammo_cells: need_cells = true; break;
- case ammo_plasma: need_plasma = true; break;
- case ammo_fuel: need_fuel = true; break;
+ case RESOURCE_SHELLS: need_shells = true; break;
+ case RESOURCE_BULLETS: need_nails = true; break;
+ case RESOURCE_ROCKETS: need_rockets = true; break;
+ case RESOURCE_CELLS: need_cells = true; break;
+ case RESOURCE_PLASMA: need_plasma = true; break;
+ case RESOURCE_FUEL: need_fuel = true; break;
}
});
rating = item.bot_pickupbasevalue;
#include <server/defs.qh>
#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
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.
+/// \brief Give several random weapons and ammo to the entity.
+/// \param[in,out] receiver Entity to give weapons to.
+/// \param[in] num_weapons Number of weapons to give.
+/// \param[in] weapon_names Names of weapons to give separated by spaces.
+/// \param[in] ammo Entity containing the ammo amount for each possible weapon.
/// \return No return.
-void GivePlayerFuel(entity player, float amount);
+void GiveRandomWeapons(entity receiver, int num_weapons, string weapon_names,
+ entity ammo_entity);
-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);
*/
void door_go_down(entity this);
-void door_go_up(entity this);
+void door_go_up(entity this, entity actor, entity trigger);
void door_rotating_go_down(entity this);
void door_rotating_go_up(entity this, entity oth);
if (this.state == STATE_DOWN)
if (this.classname == "door")
{
- door_go_up (this);
+ door_go_up (this, NULL, NULL);
} else
{
door_rotating_go_up(this, blocker);
SUB_CalcMove (this, this.pos1, TSPEED_LINEAR, this.speed, door_hit_bottom);
}
-void door_go_up(entity this)
+void door_go_up(entity this, entity actor, entity trigger)
{
if (this.state == STATE_UP)
return; // already going up
string oldmessage;
oldmessage = this.message;
this.message = "";
- SUB_UseTargets(this, NULL, NULL);
+ SUB_UseTargets(this, actor, trigger);
this.message = oldmessage;
}
entity e = this;
do {
if (e.classname == "door") {
- door_go_up(e);
+ door_go_up(e, actor, trigger);
} else {
// if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
if ((e.spawnflags & 2) && trigger.trigger_reverse!=0 && e.lip != 666 && e.state == STATE_BOTTOM) {
e.weapons = result;
}
-string GetAmmoPicture(.int ammotype)
+string GetAmmoPicture(int ammotype)
{
switch (ammotype)
{
- case ammo_shells: return ITEM_Shells.m_icon;
- case ammo_nails: return ITEM_Bullets.m_icon;
- case ammo_rockets: return ITEM_Rockets.m_icon;
- case ammo_cells: return ITEM_Cells.m_icon;
- case ammo_plasma: return ITEM_Plasma.m_icon;
- case ammo_fuel: return ITEM_JetpackFuel.m_icon;
+ case RESOURCE_SHELLS: return ITEM_Shells.m_icon;
+ case RESOURCE_BULLETS: return ITEM_Bullets.m_icon;
+ case RESOURCE_ROCKETS: return ITEM_Rockets.m_icon;
+ case RESOURCE_CELLS: return ITEM_Cells.m_icon;
+ case RESOURCE_PLASMA: return ITEM_Plasma.m_icon;
+ case RESOURCE_FUEL: return ITEM_JetpackFuel.m_icon;
default: return ""; // wtf, no ammo type?
}
}
#ifdef CSQC
- .int GetAmmoFieldFromNum(int i)
+int GetAmmoTypeFromNum(int i)
+{
+ switch (i)
{
- switch (i)
- {
- case 0: return ammo_shells;
- case 1: return ammo_nails;
- case 2: return ammo_rockets;
- case 3: return ammo_cells;
- case 4: return ammo_plasma;
- case 5: return ammo_fuel;
- default: return ammo_none;
- }
+ case 0: return RESOURCE_SHELLS;
+ case 1: return RESOURCE_BULLETS;
+ case 2: return RESOURCE_ROCKETS;
+ case 3: return RESOURCE_CELLS;
+ case 4: return RESOURCE_PLASMA;
+ case 5: return RESOURCE_FUEL;
+ default: return RESOURCE_NONE;
}
+}
- int GetAmmoStat(.int ammotype)
+int GetAmmoStat(int ammotype)
+{
+ switch (ammotype)
{
- switch (ammotype)
- {
- case ammo_shells: return STAT_SHELLS;
- case ammo_nails: return STAT_NAILS;
- case ammo_rockets: return STAT_ROCKETS;
- case ammo_cells: return STAT_CELLS;
- case ammo_plasma: return STAT_PLASMA.m_id;
- case ammo_fuel: return STAT_FUEL.m_id;
- default: return -1;
- }
+ case RESOURCE_SHELLS: return STAT_SHELLS;
+ case RESOURCE_BULLETS: return STAT_NAILS;
+ case RESOURCE_ROCKETS: return STAT_ROCKETS;
+ case RESOURCE_CELLS: return STAT_CELLS;
+ case RESOURCE_PLASMA: return STAT_PLASMA.m_id;
+ case RESOURCE_FUEL: return STAT_FUEL.m_id;
+ default: return -1;
}
+}
#endif
string W_Sound(string w_snd)
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
// projectile IDs 40-50 reserved
const int PROJECTILE_RPC = 60;
+
+// projectile IDs 70-100 reserved
#pragma once
+#include <common/resources.qh>
#include <common/items/item/pickup.qh>
#include <common/stats.qh>
ATTRIB(Weapon, m_id, int, 0);
/** A: WEPSET_id : WEPSET_... */
ATTRIB(Weapon, weapons, WepSet, '0 0 0');
- /** M: ammotype : main ammo field */
- ATTRIB(Weapon, ammo_field, .int, ammo_none);
+ /** M: ammotype : main ammo type */
+ ATTRIB(Weapon, ammo_type, int, RESOURCE_NONE);
/** M: impulse : weapon impulse */
ATTRIB(Weapon, impulse, int, -1);
/** M: flags : WEPSPAWNFLAG_... combined */
string W_FixWeaponOrder_ForceComplete(string order);
void W_RandomWeapons(entity e, int n);
-string GetAmmoPicture(.int ammotype);
+string GetAmmoPicture(int ammotype);
#ifdef CSQC
-.int GetAmmoFieldFromNum(int i);
-int GetAmmoStat(.int ammotype);
+int GetAmmoTypeFromNum(int i);
+int GetAmmoStat(int ammotype);
#endif
string W_Sound(string w_snd);
if(rootammo)
{
- coefficient = min(coefficient, own.(thiswep.ammo_field) / rootammo);
- own.(thiswep.ammo_field) = max(0, own.(thiswep.ammo_field) - (rootammo * frametime));
+ coefficient = min(coefficient, GetResourceAmount(own, thiswep.ammo_type) / rootammo);
+ SetResourceAmount(own, thiswep.ammo_type, max(0, GetResourceAmount(own, thiswep.ammo_type) - (rootammo * frametime)));
}
}
float heat_speed = burst ? WEP_CVAR(arc, burst_heat) : WEP_CVAR(arc, beam_heat);
}
METHOD(Arc, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
{
- return ((!WEP_CVAR(arc, beam_ammo)) || (actor.(thiswep.ammo_field) > 0));
+ return ((!WEP_CVAR(arc, beam_ammo)) || (GetResourceAmount(actor, thiswep.ammo_type) > 0));
}
METHOD(Arc, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
{
if(WEP_CVAR(arc, bolt))
{
- float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR(arc, bolt_ammo);
+ float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(arc, bolt_ammo);
ammo_amount += actor.(weaponentity).(weapon_load[WEP_ARC.m_id]) >= WEP_CVAR(arc, bolt_ammo);
return ammo_amount;
}
else
return WEP_CVAR(arc, overheat_max) > 0 &&
- ((!WEP_CVAR(arc, burst_ammo)) || (actor.(thiswep.ammo_field) > 0));
+ ((!WEP_CVAR(arc, burst_ammo)) || (GetResourceAmount(actor, thiswep.ammo_type) > 0));
}
METHOD(Arc, wr_killmessage, Notification(entity thiswep))
{
#pragma once
CLASS(Arc, Weapon)
-/* ammotype */ ATTRIB(Arc, ammo_field, .int, ammo_cells);
+/* ammotype */ ATTRIB(Arc, ammo_type, int, RESOURCE_CELLS);
/* impulse */ ATTRIB(Arc, impulse, int, 3);
/* flags */ ATTRIB(Arc, spawnflags, int, WEP_TYPE_HITSCAN);
/* rating */ ATTRIB(Arc, bot_pickupbasevalue, float, 8000);
#pragma once
CLASS(Blaster, Weapon)
-/* ammotype */ //ATTRIB(Blaster, ammo_field, .int, ammo_none);
+/* ammotype */ //ATTRIB(Blaster, ammo_type, int, RESOURCE_NONE);
/* impulse */ ATTRIB(Blaster, impulse, int, 1);
/* flags */ ATTRIB(Blaster, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH);
/* rating */ ATTRIB(Blaster, bot_pickupbasevalue, float, 0);
if(actor.(weaponentity).crylink_lastgroup && actor.(weaponentity).crylink_waitrelease)
return true;
- float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR_PRI(crylink, ammo);
+ float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(crylink, ammo);
ammo_amount += actor.(weaponentity).(weapon_load[WEP_CRYLINK.m_id]) >= WEP_CVAR_PRI(crylink, ammo);
return ammo_amount;
}
if(actor.(weaponentity).crylink_lastgroup && actor.(weaponentity).crylink_waitrelease)
return true;
- float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR_SEC(crylink, ammo);
+ float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(crylink, ammo);
ammo_amount += actor.(weaponentity).(weapon_load[WEP_CRYLINK.m_id]) >= WEP_CVAR_SEC(crylink, ammo);
return ammo_amount;
}
#pragma once
CLASS(Crylink, Weapon)
-/* ammotype */ ATTRIB(Crylink, ammo_field, .int, ammo_cells);
+/* ammotype */ ATTRIB(Crylink, ammo_type, int, RESOURCE_CELLS);
/* impulse */ ATTRIB(Crylink, impulse, int, 6);
/* flags */ ATTRIB(Crylink, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH | WEP_FLAG_CANCLIMB | WEP_FLAG_NODUAL);
/* rating */ ATTRIB(Crylink, bot_pickupbasevalue, float, 6000);
.entity weaponentity = this.weaponentity_fld;
if(this.realowner.(weaponentity).m_weapon == thiswep)
{
- if(this.realowner.(thiswep.ammo_field) < WEP_CVAR(devastator, ammo))
+ if(GetResourceAmount(this.realowner, thiswep.ammo_type) < WEP_CVAR(devastator, ammo))
if(!(this.realowner.items & IT_UNLIMITED_WEAPON_AMMO))
{
this.realowner.cnt = WEP_DEVASTATOR.m_id;
Weapon thiswep = WEP_DEVASTATOR;
if(this.realowner.(weaponentity).m_weapon == thiswep)
{
- if(this.realowner.(thiswep.ammo_field) < WEP_CVAR(devastator, ammo))
+ if(GetResourceAmount(this.realowner, thiswep.ammo_type) < WEP_CVAR(devastator, ammo))
if(!(this.realowner.items & IT_UNLIMITED_WEAPON_AMMO))
{
this.realowner.cnt = WEP_DEVASTATOR.m_id;
ammo_amount = false;
if(WEP_CVAR(devastator, reload_ammo))
{
- if(actor.(thiswep.ammo_field) < WEP_CVAR(devastator, ammo) && actor.(weaponentity).(weapon_load[WEP_DEVASTATOR.m_id]) < WEP_CVAR(devastator, ammo))
+ if(GetResourceAmount(actor, thiswep.ammo_type) < WEP_CVAR(devastator, ammo) && actor.(weaponentity).(weapon_load[WEP_DEVASTATOR.m_id]) < WEP_CVAR(devastator, ammo))
ammo_amount = true;
}
- else if(actor.(thiswep.ammo_field) < WEP_CVAR(devastator, ammo))
+ else if(GetResourceAmount(actor, thiswep.ammo_type) < WEP_CVAR(devastator, ammo))
ammo_amount = true;
return !ammo_amount;
}
#if 0
if(actor.rl_release == 0)
{
- LOG_INFOF("W_Devastator(WR_CHECKAMMO1): %d, %.2f, %d: TRUE", actor.rl_release, actor.(thiswep.ammo_field), WEP_CVAR(devastator, ammo));
+ LOG_INFOF("W_Devastator(WR_CHECKAMMO1): %d, %.2f, %d: TRUE", actor.rl_release, GetResourceAmount(actor, thiswep.ammo_type), WEP_CVAR(devastator, ammo));
return true;
}
else
{
- ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR(devastator, ammo);
+ ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(devastator, ammo);
ammo_amount += actor.(weaponentity).(weapon_load[WEP_DEVASTATOR.m_id]) >= WEP_CVAR(devastator, ammo);
- LOG_INFOF("W_Devastator(WR_CHECKAMMO1): %d, %.2f, %d: %s", actor.rl_release, actor.(thiswep.ammo_field), WEP_CVAR(devastator, ammo), (ammo_amount ? "TRUE" : "FALSE"));
+ LOG_INFOF("W_Devastator(WR_CHECKAMMO1): %d, %.2f, %d: %s", actor.rl_release, GetResourceAmount(actor, thiswep.ammo_type), WEP_CVAR(devastator, ammo), (ammo_amount ? "TRUE" : "FALSE"));
return ammo_amount;
}
#else
- float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR(devastator, ammo);
+ float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(devastator, ammo);
ammo_amount += actor.(weaponentity).(weapon_load[WEP_DEVASTATOR.m_id]) >= WEP_CVAR(devastator, ammo);
return ammo_amount;
#endif
#pragma once
CLASS(Devastator, Weapon)
-/* ammotype */ ATTRIB(Devastator, ammo_field, .int, ammo_rockets);
+/* ammotype */ ATTRIB(Devastator, ammo_type, int, RESOURCE_ROCKETS);
/* impulse */ ATTRIB(Devastator, impulse, int, 9);
/* flags */ ATTRIB(Devastator, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH | WEP_FLAG_NODUAL);
/* rating */ ATTRIB(Devastator, bot_pickupbasevalue, float, 8000);
}
METHOD(Electro, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
{
- float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR_PRI(electro, ammo);
+ float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(electro, ammo);
ammo_amount += actor.(weaponentity).(weapon_load[WEP_ELECTRO.m_id]) >= WEP_CVAR_PRI(electro, ammo);
return ammo_amount;
}
float ammo_amount;
if(WEP_CVAR(electro, combo_safeammocheck)) // true if you can fire at least one secondary blob AND one primary shot after it, otherwise false.
{
- ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR_SEC(electro, ammo) + WEP_CVAR_PRI(electro, ammo);
+ ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(electro, ammo) + WEP_CVAR_PRI(electro, ammo);
ammo_amount += actor.(weaponentity).(weapon_load[WEP_ELECTRO.m_id]) >= WEP_CVAR_SEC(electro, ammo) + WEP_CVAR_PRI(electro, ammo);
}
else
{
- ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR_SEC(electro, ammo);
+ ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(electro, ammo);
ammo_amount += actor.(weaponentity).(weapon_load[WEP_ELECTRO.m_id]) >= WEP_CVAR_SEC(electro, ammo);
}
return ammo_amount;
#pragma once
CLASS(Electro, Weapon)
-/* ammotype */ ATTRIB(Electro, ammo_field, .int, ammo_cells);
+/* ammotype */ ATTRIB(Electro, ammo_type, int, RESOURCE_CELLS);
/* impulse */ ATTRIB(Electro, impulse, int, 5);
/* flags */ ATTRIB(Electro, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH);
/* rating */ ATTRIB(Electro, bot_pickupbasevalue, float, 5000);
#pragma once
CLASS(Fireball, Weapon)
-/* ammotype */ //ATTRIB(Fireball, ammo_field, .int, ammo_none);
+/* ammotype */ //ATTRIB(Fireball, ammo_type, int, RESOURCE_NONE);
/* impulse */ ATTRIB(Fireball, impulse, int, 9);
/* flags */ ATTRIB(Fireball, spawnflags, int, WEP_FLAG_SUPERWEAPON | WEP_TYPE_SPLASH | WEP_FLAG_NODUAL);
/* rating */ ATTRIB(Fireball, bot_pickupbasevalue, float, 5000);
else if(autocvar_g_balance_hagar_reload_ammo)
enough_ammo = actor.(weaponentity).(weapon_load[WEP_HAGAR.m_id]) >= WEP_CVAR_SEC(hagar, ammo);
else
- enough_ammo = actor.(thiswep.ammo_field) >= WEP_CVAR_SEC(hagar, ammo);
+ enough_ammo = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(hagar, ammo);
bool stopped = loaded || !enough_ammo;
}
METHOD(Hagar, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
{
- float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR_PRI(hagar, ammo);
+ float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(hagar, ammo);
ammo_amount += actor.(weaponentity).(weapon_load[WEP_HAGAR.m_id]) >= WEP_CVAR_PRI(hagar, ammo);
return ammo_amount;
}
METHOD(Hagar, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
{
- float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR_SEC(hagar, ammo);
+ float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(hagar, ammo);
ammo_amount += actor.(weaponentity).(weapon_load[WEP_HAGAR.m_id]) >= WEP_CVAR_SEC(hagar, ammo);
return ammo_amount;
}
#pragma once
CLASS(Hagar, Weapon)
-/* ammotype */ ATTRIB(Hagar, ammo_field, .int, ammo_rockets);
+/* ammotype */ ATTRIB(Hagar, ammo_type, int, RESOURCE_ROCKETS);
/* impulse */ ATTRIB(Hagar, impulse, int, 8);
/* flags */ ATTRIB(Hagar, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH);
/* rating */ ATTRIB(Hagar, bot_pickupbasevalue, float, 6000);
}
METHOD(HLAC, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
{
- float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR_PRI(hlac, ammo);
+ float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(hlac, ammo);
ammo_amount += actor.(weaponentity).(weapon_load[WEP_HLAC.m_id]) >= WEP_CVAR_PRI(hlac, ammo);
return ammo_amount;
}
METHOD(HLAC, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
{
- float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR_SEC(hlac, ammo);
+ float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(hlac, ammo);
ammo_amount += actor.(weaponentity).(weapon_load[WEP_HLAC.m_id]) >= WEP_CVAR_SEC(hlac, ammo);
return ammo_amount;
}
#pragma once
CLASS(HLAC, Weapon)
-/* ammotype */ ATTRIB(HLAC, ammo_field, .int, ammo_cells);
+/* ammotype */ ATTRIB(HLAC, ammo_type, int, RESOURCE_CELLS);
/* impulse */ ATTRIB(HLAC, impulse, int, 6);
/* flags */ ATTRIB(HLAC, spawnflags, int, WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH);
/* rating */ ATTRIB(HLAC, bot_pickupbasevalue, float, 4000);
#pragma once
CLASS(Hook, Weapon)
-/* ammotype */ ATTRIB(Hook, ammo_field, .int, ammo_fuel);
+/* ammotype */ ATTRIB(Hook, ammo_type, int, RESOURCE_FUEL);
/* impulse */ ATTRIB(Hook, impulse, int, 0);
/* flags */ ATTRIB(Hook, spawnflags, int, WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH);
/* rating */ ATTRIB(Hook, bot_pickupbasevalue, float, 0);
{
float ammo_amount;
if(WEP_CVAR(machinegun, mode) == 1)
- ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR(machinegun, sustained_ammo);
+ ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(machinegun, sustained_ammo);
else
- ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR(machinegun, first_ammo);
+ ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(machinegun, first_ammo);
if(WEP_CVAR(machinegun, reload_ammo))
{
{
float ammo_amount;
if(WEP_CVAR(machinegun, mode) == 1)
- ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR(machinegun, burst_ammo);
+ ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(machinegun, burst_ammo);
else
- ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR(machinegun, first_ammo);
+ ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(machinegun, first_ammo);
if(WEP_CVAR(machinegun, reload_ammo))
{
#pragma once
CLASS(MachineGun, Weapon)
-/* ammotype */ ATTRIB(MachineGun, ammo_field, .int, ammo_nails);
+/* ammotype */ ATTRIB(MachineGun, ammo_type, int, RESOURCE_BULLETS);
/* impulse */ ATTRIB(MachineGun, impulse, int, 3);
/* flags */ ATTRIB(MachineGun, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_PENETRATEWALLS);
/* rating */ ATTRIB(MachineGun, bot_pickupbasevalue, float, 7000);
if(autocvar_g_balance_minelayer_reload_ammo && actor.(weaponentity).clip_load < WEP_CVAR(minelayer, ammo)) // forced reload
{
// not if we're holding the minelayer without enough ammo, but can detonate existing mines
- if(!(W_MineLayer_PlacedMines(actor, weaponentity, false) && actor.(thiswep.ammo_field) < WEP_CVAR(minelayer, ammo))) {
+ if(!(W_MineLayer_PlacedMines(actor, weaponentity, false) && GetResourceAmount(actor, thiswep.ammo_type) < WEP_CVAR(minelayer, ammo))) {
thiswep.wr_reload(thiswep, actor, weaponentity);
}
}
// actually do // don't switch while placing a mine
//if(ATTACK_FINISHED(actor, slot) <= time || PS(actor).m_weapon != WEP_MINE_LAYER)
//{
- float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR(minelayer, ammo);
+ float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(minelayer, ammo);
ammo_amount += actor.(weaponentity).(weapon_load[WEP_MINE_LAYER.m_id]) >= WEP_CVAR(minelayer, ammo);
return ammo_amount;
//}
#pragma once
CLASS(MineLayer, Weapon)
-/* ammotype */ ATTRIB(MineLayer, ammo_field, .int, ammo_rockets);
+/* ammotype */ ATTRIB(MineLayer, ammo_type, int, RESOURCE_ROCKETS);
/* impulse */ ATTRIB(MineLayer, impulse, int, 4);
/* flags */ ATTRIB(MineLayer, spawnflags, int, WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH);
/* rating */ ATTRIB(MineLayer, bot_pickupbasevalue, float, 7000);
}
METHOD(Mortar, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
{
- float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR_PRI(mortar, ammo);
+ float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(mortar, ammo);
ammo_amount += actor.(weaponentity).(weapon_load[WEP_MORTAR.m_id]) >= WEP_CVAR_PRI(mortar, ammo);
return ammo_amount;
}
METHOD(Mortar, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
{
- float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR_SEC(mortar, ammo);
+ float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(mortar, ammo);
ammo_amount += actor.(weaponentity).(weapon_load[WEP_MORTAR.m_id]) >= WEP_CVAR_SEC(mortar, ammo);
return ammo_amount;
}
#pragma once
CLASS(Mortar, Weapon)
-/* ammotype */ ATTRIB(Mortar, ammo_field, .int, ammo_rockets);
+/* ammotype */ ATTRIB(Mortar, ammo_type, int, RESOURCE_ROCKETS);
/* impulse */ ATTRIB(Mortar, impulse, int, 4);
/* flags */ ATTRIB(Mortar, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH);
/* rating */ ATTRIB(Mortar, bot_pickupbasevalue, float, 7000);
#pragma once
CLASS(PortoLaunch, Weapon)
-/* ammotype */ ATTRIB(PortoLaunch, ammo_field, .int, ammo_none);
+/* ammotype */ ATTRIB(PortoLaunch, ammo_type, int, RESOURCE_NONE);
/* impulse */ ATTRIB(PortoLaunch, impulse, int, 0);
/* flags */ ATTRIB(PortoLaunch, spawnflags, int, WEP_TYPE_OTHER | WEP_FLAG_SUPERWEAPON | WEP_FLAG_NODUAL);
/* rating */ ATTRIB(PortoLaunch, bot_pickupbasevalue, float, 0);
}
METHOD(Rifle, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
{
- float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR_PRI(rifle, ammo);
+ float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(rifle, ammo);
ammo_amount += actor.(weaponentity).(weapon_load[WEP_RIFLE.m_id]) >= WEP_CVAR_PRI(rifle, ammo);
return ammo_amount;
}
METHOD(Rifle, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
{
- float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR_SEC(rifle, ammo);
+ float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(rifle, ammo);
ammo_amount += actor.(weaponentity).(weapon_load[WEP_RIFLE.m_id]) >= WEP_CVAR_SEC(rifle, ammo);
return ammo_amount;
}
#pragma once
CLASS(Rifle, Weapon)
-/* ammotype */ ATTRIB(Rifle, ammo_field, .int, ammo_nails);
+/* ammotype */ ATTRIB(Rifle, ammo_type, int, RESOURCE_BULLETS);
/* impulse */ ATTRIB(Rifle, impulse, int, 7);
/* flags */ ATTRIB(Rifle, spawnflags, int, WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_PENETRATEWALLS);
/* rating */ ATTRIB(Rifle, bot_pickupbasevalue, float, 7000);
Weapon thiswep = WEP_SEEKER;
.entity weaponentity = this.weaponentity_fld;
- if((!(this.realowner.items & IT_UNLIMITED_AMMO) && this.realowner.(thiswep.ammo_field) < WEP_CVAR(seeker, missile_ammo)) || (this.cnt <= -1) || (IS_DEAD(this.realowner)) || (this.realowner.(weaponentity).m_switchweapon != WEP_SEEKER))
+ if((!(this.realowner.items & IT_UNLIMITED_AMMO) && GetResourceAmount(this.realowner, thiswep.ammo_type) < WEP_CVAR(seeker, missile_ammo)) || (this.cnt <= -1) || (IS_DEAD(this.realowner)) || (this.realowner.(weaponentity).m_switchweapon != WEP_SEEKER))
{
delete(this);
return;
float ammo_amount;
if(WEP_CVAR(seeker, type) == 1)
{
- ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR(seeker, missile_ammo);
+ ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(seeker, missile_ammo);
ammo_amount += actor.(weaponentity).(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, missile_ammo);
}
else
{
- ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR(seeker, tag_ammo);
+ ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(seeker, tag_ammo);
ammo_amount += actor.(weaponentity).(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, tag_ammo);
}
return ammo_amount;
float ammo_amount;
if(WEP_CVAR(seeker, type) == 1)
{
- ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR(seeker, tag_ammo);
+ ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(seeker, tag_ammo);
ammo_amount += actor.(weaponentity).(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, tag_ammo);
}
else
{
- ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR(seeker, flac_ammo);
+ ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(seeker, flac_ammo);
ammo_amount += actor.(weaponentity).(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, flac_ammo);
}
return ammo_amount;
#pragma once
CLASS(Seeker, Weapon)
-/* ammotype */ ATTRIB(Seeker, ammo_field, .int, ammo_rockets);
+/* ammotype */ ATTRIB(Seeker, ammo_type, int, RESOURCE_ROCKETS);
/* impulse */ ATTRIB(Seeker, impulse, int, 8);
/* flags */ ATTRIB(Seeker, spawnflags, int, WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH);
/* rating */ ATTRIB(Seeker, bot_pickupbasevalue, float, 5000);
#pragma once
CLASS(Shockwave, Weapon)
-/* ammotype */ //ATTRIB(Shockwave, ammo_field, .int, ammo_none);
+/* ammotype */ //ATTRIB(Shockwave, ammo_type, int, RESOURCE_NONE);
/* impulse */ ATTRIB(Shockwave, impulse, int, 2);
/* flags */ ATTRIB(Shockwave, spawnflags, int, WEP_TYPE_HITSCAN | WEP_FLAG_CANCLIMB | WEP_TYPE_MELEE_SEC);
/* rating */ ATTRIB(Shockwave, bot_pickupbasevalue, float, 3000);
}
if(actor.(weaponentity).clip_load >= 0) // we are not currently reloading
if(WEP_CVAR(shotgun, secondary) == 1)
- if(((fire & 1) && actor.(thiswep.ammo_field) <= 0 && !(actor.items & IT_UNLIMITED_WEAPON_AMMO)) || (fire & 2))
+ if(((fire & 1) && GetResourceAmount(actor, thiswep.ammo_type) <= 0 && !(actor.items & IT_UNLIMITED_WEAPON_AMMO)) || (fire & 2))
if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(shotgun, refire)))
{
// attempt forcing playback of the anim by switching to another anim (that we never play) here...
}
METHOD(Shotgun, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
{
- float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR_PRI(shotgun, ammo);
+ float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(shotgun, ammo);
ammo_amount += actor.(weaponentity).(weapon_load[WEP_SHOTGUN.m_id]) >= WEP_CVAR_PRI(shotgun, ammo);
return ammo_amount;
}
case 1: return true; // melee does not use ammo
case 2: // secondary triple shot
{
- float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR_PRI(shotgun, ammo);
+ float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(shotgun, ammo);
ammo_amount += actor.(weaponentity).(weapon_load[WEP_SHOTGUN.m_id]) >= WEP_CVAR_PRI(shotgun, ammo);
return ammo_amount;
}
#pragma once
CLASS(Shotgun, Weapon)
-/* ammotype */ ATTRIB(Shotgun, ammo_field, .int, ammo_shells);
+/* ammotype */ ATTRIB(Shotgun, ammo_type, int, RESOURCE_SHELLS);
/* impulse */ ATTRIB(Shotgun, impulse, int, 2);
/* flags */ ATTRIB(Shotgun, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_TYPE_MELEE_SEC);
/* rating */ ATTRIB(Shotgun, bot_pickupbasevalue, float, 6000);
METHOD(Vaporizer, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
{
- if(actor.(thiswep.ammo_field) > 0)
+ if(GetResourceAmount(actor, thiswep.ammo_type) > 0)
PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, 1000000, 0, 1, false);
else
PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, WEP_CVAR_SEC(vaporizer, speed), 0, WEP_CVAR_SEC(vaporizer, lifetime), false); // WEAPONTODO: replace with proper vaporizer cvars
METHOD(Vaporizer, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
{
float vaporizer_ammo = ((g_instagib) ? 1 : WEP_CVAR_PRI(vaporizer, ammo));
- float ammo_amount = actor.(thiswep.ammo_field) >= vaporizer_ammo;
+ float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= vaporizer_ammo;
ammo_amount += actor.(weaponentity).(weapon_load[WEP_VAPORIZER.m_id]) >= vaporizer_ammo;
return ammo_amount;
}
{
if(!WEP_CVAR_SEC(vaporizer, ammo))
return true;
- float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR_SEC(vaporizer, ammo);
+ float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(vaporizer, ammo);
ammo_amount += actor.(weaponentity).(weapon_load[WEP_VAPORIZER.m_id]) >= WEP_CVAR_SEC(vaporizer, ammo);
return ammo_amount;
}
#pragma once
CLASS(Vaporizer, Weapon)
-/* ammotype */ ATTRIB(Vaporizer, ammo_field, .int, ammo_cells);
+/* ammotype */ ATTRIB(Vaporizer, ammo_type, int, RESOURCE_CELLS);
/* impulse */ ATTRIB(Vaporizer, impulse, int, 7);
/* flags */ ATTRIB(Vaporizer, spawnflags, int, WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_FLAG_SUPERWEAPON | WEP_TYPE_HITSCAN | WEP_FLAG_NODUAL);
/* rating */ ATTRIB(Vaporizer, bot_pickupbasevalue, float, 10000);
}
else
{
- dt = min(dt, (actor.(thiswep.ammo_field) - WEP_CVAR_PRI(vortex, ammo)) / WEP_CVAR_SEC(vortex, ammo));
+ dt = min(dt, (GetResourceAmount(actor, thiswep.ammo_type) - WEP_CVAR_PRI(vortex, ammo)) / WEP_CVAR_SEC(vortex, ammo));
dt = max(0, dt);
if(dt > 0)
{
- actor.(thiswep.ammo_field) = max(WEP_CVAR_SEC(vortex, ammo), actor.(thiswep.ammo_field) - WEP_CVAR_SEC(vortex, ammo) * dt);
+ SetResourceAmount(actor, thiswep.ammo_type, max(WEP_CVAR_SEC(vortex, ammo), GetResourceAmount(actor, thiswep.ammo_type) - WEP_CVAR_SEC(vortex, ammo) * dt));
}
}
}
}
METHOD(Vortex, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
{
- float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR_PRI(vortex, ammo);
+ float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(vortex, ammo);
ammo_amount += (autocvar_g_balance_vortex_reload_ammo && actor.(weaponentity).(weapon_load[WEP_VORTEX.m_id]) >= WEP_CVAR_PRI(vortex, ammo));
return ammo_amount;
}
if(WEP_CVAR(vortex, secondary))
{
// don't allow charging if we don't have enough ammo
- float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR_SEC(vortex, ammo);
+ float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(vortex, ammo);
ammo_amount += actor.(weaponentity).(weapon_load[WEP_VORTEX.m_id]) >= WEP_CVAR_SEC(vortex, ammo);
return ammo_amount;
}
#pragma once
CLASS(Vortex, Weapon)
-/* ammotype */ ATTRIB(Vortex, ammo_field, .int, ammo_cells);
+/* ammotype */ ATTRIB(Vortex, ammo_type, int, RESOURCE_CELLS);
/* impulse */ ATTRIB(Vortex, impulse, int, 7);
/* flags */ ATTRIB(Vortex, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_NODUAL);
/* rating */ ATTRIB(Vortex, bot_pickupbasevalue, float, 8000);
#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
#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();
void SV_OnEntityPreSpawnFunction()
{
ENGINE_EVENT();
- if (_SV_OnEntityPreSpawnFunction) _SV_OnEntityPreSpawnFunction(this);
+ __spawnfunc_expecting = true;
+ __spawnfunc_expect = this;
}
#define SV_OnEntityPreSpawnFunction _SV_OnEntityPreSpawnFunction
#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)
#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__
#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) \
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) \
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
#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) \
#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
#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); \
#pragma once
+#include "macro.qh"
+
// Transition from global 'self' to local 'this'
// Step 1: auto oldself
// 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()
// Step 5: this should work
#if 1
#undef self
- #define self (0, this)
+ #define self (RVALUE, this)
#endif
// Step 6: remove SELFPARAM, add parameters
#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)
#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
+#ifdef SVQC
+ void _SV_OnEntityPreSpawnFunction(entity this);
+#endif
+ 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
+#ifdef SVQC
+ _SV_OnEntityPreSpawnFunction(e);
+ if (wasfreed(e)) {
+ return;
+ }
+#endif
+ 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) \
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)
#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;
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() {}
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))
}
#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)
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;
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)
#include <server/g_models.qc>
#include <server/g_subs.qc>
#include <server/g_world.qc>
+#include <server/handicap.qc>
#include <server/impulse.qc>
#include <server/ipban.qc>
#include <server/item_key.qc>
#include <server/playerdemo.qc>
#include <server/portals.qc>
#include <server/race.qc>
+#include <server/resources.qc>
#include <server/round_handler.qc>
#include <server/scores.qc>
#include <server/scores_rules.qc>
#include <server/g_models.qh>
#include <server/g_subs.qh>
#include <server/g_world.qh>
+#include <server/handicap.qh>
#include <server/impulse.qh>
#include <server/ipban.qh>
#include <server/item_key.qh>
#include <server/playerdemo.qh>
#include <server/portals.qh>
#include <server/race.qh>
+#include <server/resources.qh>
#include <server/round_handler.qh>
#include <server/scores.qh>
#include <server/scores_rules.qh>
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;
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;
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;
#include "teamplay.qh"
#include "playerdemo.qh"
#include "spawnpoints.qh"
+#include "resources.qh"
#include "g_damage.qh"
+#include "handicap.qh"
#include "g_hook.qh"
#include "command/common.qh"
#include "cheats.qh"
bool mutator_returnvalue = MUTATOR_CALLHOOK(MakePlayerObserver, this);
PlayerState_detach(this);
- if (IS_PLAYER(this) && this.health >= 1) {
- // despawn effect
- Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1);
+ if (IS_PLAYER(this))
+ {
+ if(this.health >= 1)
+ {
+ // despawn effect
+ Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1);
+ }
+
+ // was a player, recount votes and ready status
+ if(IS_REAL_CLIENT(this))
+ {
+ if (vote_called) { VoteCount(false); }
+ ReadyCount();
+ }
}
{
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
}
this.flags |= FL_NOTARGET;
this.takedamage = DAMAGE_AIM;
this.effects = EF_TELEPORT_BIT | EF_RESTARTANIM_BIT;
- this.dmg = 2; // WTF
if (warmup_stage) {
this.ammo_shells = warmup_start_ammo_shells;
this.health = start_health;
this.armorvalue = start_armorvalue;
this.weapons = start_weapons;
+ GiveRandomWeapons(this, random_start_weapons_count,
+ autocvar_g_random_start_weapons, random_start_ammo);
}
SetSpectatee_status(this, 0);
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
}
it.init_for_player(it, this);
});
+ Handicap_Initialize(this);
+
MUTATOR_CALLHOOK(ClientConnect, this);
if (IS_REAL_CLIENT(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;
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);
}
if(this.air_finished < time)
PlayerSound(this, playersound_gasp, CH_PLAYER, VOL_BASE, VOICETYPE_PLAYERSOUND);
this.air_finished = time + autocvar_g_balance_contents_drowndelay;
- this.dmg = 2;
}
else if (this.air_finished < time)
{ // drown!
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);
{
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:
// 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
{
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);
}
}
int VoteCommand_parse(entity caller, string vote_command, string vote_list, float startpos, float argc)
{
- string first_command;
+ string first_command = argv(startpos);
+ int missing_chars = argv_start_index(startpos);
- first_command = argv(startpos);
-
- /*printf("VoteCommand_parse(): Command: '%s', Length: %f.\n",
- substring(vote_command, argv_start_index(startpos), strlen(vote_command) - argv_start_index(startpos)),
- strlen(substring(vote_command, argv_start_index(startpos), strlen(vote_command) - argv_start_index(startpos)))
- );*/
-
- if (
- (autocvar_sv_vote_limit > 0)
- &&
- (strlen(substring(vote_command, argv_start_index(startpos), strlen(vote_command) - argv_start_index(startpos))) > autocvar_sv_vote_limit)
- ) return 0;
+ if (autocvar_sv_vote_limit > 0 && strlen(vote_command) > autocvar_sv_vote_limit)
+ return 0;
if (!VoteCommand_checkinlist(first_command, vote_list)) return 0;
if (accepted > 0)
{
- string reason = ((argc > next_token) ? substring(vote_command, argv_start_index(next_token), strlen(vote_command) - argv_start_index(next_token)) : "No reason provided");
- string command_arguments;
+ string reason = "No reason provided";
+ if(argc > next_token)
+ reason = substring(vote_command, argv_start_index(next_token) - missing_chars, -1);
- if (first_command == "kickban") command_arguments = strcat(ftos(autocvar_g_ban_default_bantime), " ", ftos(autocvar_g_ban_default_masksize), " ~");
- else command_arguments = reason;
+ string command_arguments = reason;
+ if (first_command == "kickban")
+ command_arguments = strcat(ftos(autocvar_g_ban_default_bantime), " ", ftos(autocvar_g_ban_default_masksize), " ~");
vote_parsed_command = strcat(first_command, " # ", ftos(etof(victim)), " ", command_arguments);
- vote_parsed_display = strcat("^1", vote_command, " (^7", victim.netname, "^1): ", reason);
+ vote_parsed_display = sprintf("^1%s #%d ^7%s^1 %s", first_command, etof(victim), victim.netname, reason);
}
else { print_to(caller, strcat("vcall: ", GetClientErrorString(accepted, argv(startpos + 1)), ".\n")); return 0; }
if(parse_error == 0)
print_to(caller, "^1This command is not acceptable, see 'vhelp' for more info.");
}
-
else // everything went okay, continue with calling the vote
{
vote_caller = caller; // remember who called the vote
}
FOREACH_CLIENT(IS_REAL_CLIENT(it), { ++tmp_playercount; });
- if (tmp_playercount > 1) Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_CALL); // don't announce a "vote now" sound if player is alone
+ if (tmp_playercount > 1)
+ Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_CALL);
bprint("\{1}^2* ^3", OriginalCallerName(), "^2 calls a vote for ", vote_called_display, "\n");
- if (autocvar_sv_eventlog) GameLogEcho(strcat(":vote:vcall:", ftos(vote_caller.playerid), ":", vote_called_display));
+ if (autocvar_sv_eventlog)
+ GameLogEcho(strcat(":vote:vcall:", ftos(vote_caller.playerid), ":", vote_called_display));
Nagger_VoteChanged();
VoteCount(true); // needed if you are the only one
}
int parse_error;
vote_command = VoteCommand_extractcommand(vote_command, 3, argc);
- if (!caller.vote_master) { print_to(caller, "^1You do not have vote master privelages."); }
+ if (!caller.vote_master)
+ print_to(caller, "^1You do not have vote master privileges.");
else if (!VoteCommand_checknasty(vote_command))
{
print_to(caller, "^1Syntax error in command, see 'vhelp' for more info.");
if(parse_error == 0)
print_to(caller, "^1This command is not acceptable, see 'vhelp' for more info.");
}
-
else // everything went okay, proceed with command
{
localcmd(strcat(vote_parsed_command, "\n"));
print_to(caller, strcat("Executing command '", vote_parsed_display, "' on server."));
bprint("\{1}^2* ^3", GetCallerName(caller), "^2 used their ^3master^2 status to do \"^2", vote_parsed_display, "^2\".\n");
- if (autocvar_sv_eventlog) GameLogEcho(strcat(":vote:vdo:", ftos(caller.playerid), ":", vote_parsed_display)); }
+ if (autocvar_sv_eventlog)
+ GameLogEcho(strcat(":vote:vdo:", ftos(caller.playerid), ":", vote_parsed_display));
+ }
return;
}
{
print_to(caller, strcat("Rejected vote master login from ", GetCallerName(caller)));
}
-
else // everything went okay, proceed with giving this player master privilages
{
caller.vote_master = true;
print_to(caller, strcat("Accepted vote master login from ", GetCallerName(caller)));
bprint("\{1}^2* ^3", GetCallerName(caller), "^2 logged in as ^3master^2\n");
- if (autocvar_sv_eventlog) GameLogEcho(strcat(":vote:vlogin:", ftos(caller.playerid))); }
+ if (autocvar_sv_eventlog)
+ GameLogEcho(strcat(":vote:vlogin:", ftos(caller.playerid)));
+ }
return;
}
{
print_to(caller, "^1You can not call a vote while a timeout is active.");
}
-
else // everything went okay, continue with creating vote
{
vote_caller = caller;
caller.vote_waittime = time + autocvar_sv_vote_wait;
bprint("\{1}^2* ^3", OriginalCallerName(), "^2 calls a vote to become ^3master^2.\n");
- if (autocvar_sv_eventlog) GameLogEcho(strcat(":vote:vcall:", ftos(vote_caller.playerid), ":", vote_called_display));
+ if (autocvar_sv_eventlog)
+ GameLogEcho(strcat(":vote:vcall:", ftos(vote_caller.playerid), ":", vote_called_display));
Nagger_VoteChanged();
VoteCount(true); // needed if you are the only one
}
print_to(caller, "^1You rejected the vote.");
caller.vote_selection = VOTE_SELECT_REJECT;
msg_entity = caller;
- if (!autocvar_sv_vote_singlecount) VoteCount(false); }
+ if (!autocvar_sv_vote_singlecount)
+ VoteCount(false);
+ }
return;
}
{
print_to(caller, "^1You have already voted.");
}
-
else // everything went okay, continue changing vote
{
print_to(caller, "^1You accepted the vote.");
caller.vote_selection = VOTE_SELECT_ACCEPT;
msg_entity = caller;
- if (!autocvar_sv_vote_singlecount) VoteCount(false); }
+ if (!autocvar_sv_vote_singlecount)
+ VoteCount(false);
+ }
return;
}
#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"
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;
}
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)
);
}
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)
);
}
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);
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;
}
// 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;
}
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);
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;
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();
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();
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();
WinningConditionHelper(this); // set worldstatus
world_initialized = 1;
+ __spawnfunc_spawn_all();
}
spawnfunc(light)
--- /dev/null
+#include "handicap.qh"
+
+/// \file
+/// \brief Source file that contains implementation of the handicap system.
+/// \author Lyberta
+/// \copyright GNU GPLv2 or any later version.
+
+#include <common/state.qh>
+#include "client.qh"
+
+.float m_handicap; ///< Holds the handicap value.
+
+void Handicap_Initialize(entity player)
+{
+ CS(player).m_handicap = 1;
+}
+
+float Handicap_GetVoluntaryHandicap(entity player)
+{
+ return bound(1.0, CS(player).cvar_cl_handicap, 10.0);
+}
+
+float Handicap_GetForcedHandicap(entity player)
+{
+ return CS(player).m_handicap;
+}
+
+void Handicap_SetForcedHandicap(entity player, float value)
+{
+ if (value <= 0)
+ {
+ error("Handicap_SetForcedHandicap: Invalid handicap value.");
+ }
+ CS(player).m_handicap = value;
+}
+
+float Handicap_GetTotalHandicap(entity player)
+{
+ return Handicap_GetForcedHandicap(player) * Handicap_GetVoluntaryHandicap(
+ player);
+}
--- /dev/null
+#pragma once
+
+/// \file
+/// \brief Header file that describes the handicap system.
+/// \author Lyberta
+/// \copyright GNU GPLv2 or any later version.
+
+// Handicap is used to make the game harder for strong players and easier for
+// weak players. Values greater than 1 make the game harder and values less than
+// 1 make the game easier. Right now handicap only affects damage. There are 2
+// types of handicap: voluntary and forced. Voluntary handicap can be set via
+// cl_handicap cvar. For obvious reasons, it can't be less than 1. Forced
+// handicap can be set by server mutators. The total handicap is the product of
+// voluntary and forced handicap.
+
+/// \brief Initializes handicap to its default value.
+/// \param[in,out] player Player to initialize.
+/// \return No return.
+void Handicap_Initialize(entity player);
+
+/// \brief Returns the voluntary handicap of the player.
+/// \param[in] player Player to check.
+/// \return Voluntary handicap of the player.
+float Handicap_GetVoluntaryHandicap(entity player);
+
+/// \brief Returns the forced handicap of the player.
+/// \param[in] player Player to check.
+/// \return Forced handicap of the player.
+float Handicap_GetForcedHandicap(entity player);
+
+/// \brief Sets the forced handicap of the player.
+/// \param[in] player Player to alter.
+/// \param[in] value Handicap value to set.
+/// \return No return.
+void Handicap_SetForcedHandicap(entity player, float value);
+
+/// \brief Returns the total handicap of the player.
+/// \param[in] player Player to check.
+/// \return Total handicap of the player.
+float Handicap_GetTotalHandicap(entity player);
#include "ipban.qh"
#include "mutators/_mod.qh"
#include "../common/t_items.qh"
+#include "resources.qh"
#include "weapons/accuracy.qh"
#include "weapons/csqcprojectile.qh"
#include "weapons/selection.qh"
string AmmoNameFromWeaponentity(entity wpn)
{
string ammoitems = "batteries";
- switch((wpn.m_weapon).ammo_field)
+ switch ((wpn.m_weapon).ammo_type)
{
- case ammo_shells: ammoitems = ITEM_Shells.m_name; break;
- case ammo_nails: ammoitems = ITEM_Bullets.m_name; break;
- case ammo_rockets: ammoitems = ITEM_Rockets.m_name; break;
- case ammo_cells: ammoitems = ITEM_Cells.m_name; break;
- case ammo_plasma: ammoitems = ITEM_Plasma.m_name; break;
- case ammo_fuel: ammoitems = ITEM_JetpackFuel.m_name; break;
+ case RESOURCE_SHELLS: ammoitems = ITEM_Shells.m_name; break;
+ case RESOURCE_BULLETS: ammoitems = ITEM_Bullets.m_name; break;
+ case RESOURCE_ROCKETS: ammoitems = ITEM_Rockets.m_name; break;
+ case RESOURCE_CELLS: ammoitems = ITEM_Cells.m_name; break;
+ case RESOURCE_PLASMA: ammoitems = ITEM_Plasma.m_name; break;
+ case RESOURCE_FUEL: ammoitems = ITEM_JetpackFuel.m_name; break;
}
return ammoitems;
}
start_ammo_rockets = 0;
start_ammo_cells = 0;
start_ammo_plasma = 0;
+ if (random_start_ammo == NULL)
+ {
+ random_start_ammo = spawn();
+ }
start_health = cvar("g_balance_health_start");
start_armorvalue = cvar("g_balance_armor_start");
start_ammo_cells = cvar("g_start_ammo_cells");
start_ammo_plasma = cvar("g_start_ammo_plasma");
start_ammo_fuel = cvar("g_start_ammo_fuel");
+ random_start_weapons_count = cvar("g_random_start_weapons_count");
+ SetResourceAmount(random_start_ammo, RESOURCE_SHELLS, cvar(
+ "g_random_start_shells"));
+ SetResourceAmount(random_start_ammo, RESOURCE_BULLETS, cvar(
+ "g_random_start_bullets"));
+ SetResourceAmount(random_start_ammo, RESOURCE_ROCKETS,
+ cvar("g_random_start_rockets"));
+ SetResourceAmount(random_start_ammo, RESOURCE_CELLS, cvar(
+ "g_random_start_cells"));
+ SetResourceAmount(random_start_ammo, RESOURCE_PLASMA, cvar(
+ "g_random_start_plasma"));
}
if (warmup_stage)
start_ammo_cells = max(0, start_ammo_cells);
start_ammo_plasma = max(0, start_ammo_plasma);
start_ammo_fuel = max(0, start_ammo_fuel);
+ SetResourceAmount(random_start_ammo, RESOURCE_SHELLS, max(0,
+ GetResourceAmount(random_start_ammo, RESOURCE_SHELLS)));
+ SetResourceAmount(random_start_ammo, RESOURCE_BULLETS, max(0,
+ GetResourceAmount(random_start_ammo, RESOURCE_BULLETS)));
+ SetResourceAmount(random_start_ammo, RESOURCE_ROCKETS, max(0,
+ GetResourceAmount(random_start_ammo, RESOURCE_ROCKETS)));
+ SetResourceAmount(random_start_ammo, RESOURCE_CELLS, max(0,
+ GetResourceAmount(random_start_ammo, RESOURCE_CELLS)));
+ SetResourceAmount(random_start_ammo, RESOURCE_PLASMA, max(0,
+ GetResourceAmount(random_start_ammo, RESOURCE_PLASMA)));
warmup_start_ammo_shells = max(0, warmup_start_ammo_shells);
warmup_start_ammo_nails = max(0, warmup_start_ammo_nails);
.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;
}
float start_ammo_cells;
float start_ammo_plasma;
float start_ammo_fuel;
+/// \brief Number of random start weapons to give to players.
+int random_start_weapons_count;
+/// \brief Holds a list of possible random start weapons.
+string autocvar_g_random_start_weapons;
+/// \brief Entity that contains amount of ammo to give with random start
+/// weapons.
+entity random_start_ammo;
float start_health;
float start_armorvalue;
WepSet warmup_start_weapons;
/**/
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) \
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) \
/**/
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) \
/**/
/**/
MUTATOR_HOOKABLE(AddPlayerScore, EV_AddPlayerScore);
+#define EV_AddedPlayerScore(i, o) \
+ /** score field */ i(entity, MUTATOR_ARGV_0_entity) \
+ /** score */ i(float, MUTATOR_ARGV_1_float) \
+ /** player */ i(entity, MUTATOR_ARGV_2_entity) \
+ /**/
+MUTATOR_HOOKABLE(AddedPlayerScore, EV_AddPlayerScore);
+
#define EV_GetPlayerStatus(i, o) \
/** player */ i(entity, MUTATOR_ARGV_0_entity) \
/**/
/**/
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) \
/**/
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) \
#include "bot/api.qh"
#include "cheats.qh"
#include "g_damage.qh"
+#include "handicap.qh"
#include "g_subs.qh"
#include "miscfunctions.qh"
#include "portals.qh"
if(!DEATH_ISSPECIAL(deathtype))
{
- damage *= bound(1.0, CS(this).cvar_cl_handicap, 10.0);
- if(this != attacker && IS_PLAYER(attacker))
- damage /= bound(1.0, CS(attacker).cvar_cl_handicap, 10.0);
+ damage *= Handicap_GetTotalHandicap(this);
+ if (this != attacker && IS_PLAYER(attacker))
+ {
+ damage /= Handicap_GetTotalHandicap(attacker);
+ }
}
if (time < this.spawnshieldtime && autocvar_g_spawnshield_blockdamage < 1)
}
}
-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 */
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);
--- /dev/null
+#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;
+}
--- /dev/null
+#pragma once
+
+/// \file
+/// \brief Header file that describes the resource system.
+/// \author Lyberta
+/// \copyright GNU GPLv2 or any later version.
+
+#include <common/resources.qh>
+
+/// \brief Unconditional maximum amount of resources the entity can have.
+const int RESOURCE_AMOUNT_HARD_LIMIT = 999;
+
+// ============================ 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);
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;
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);
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)));
s.SendFlags |= (2 ** (scorefield.m_id % 16));
if(!warmup_stage)
PS_GR_P_ADDVAL(s.owner, strcat(PLAYERSTATS_TOTAL, scores_label(scorefield)), score);
- return (s.(scores(scorefield)) += score);
+ s.(scores(scorefield)) += score;
+ MUTATOR_CALLHOOK(AddedPlayerScore, scorefield, score, player);
+ return s.(scores(scorefield));
}
float PlayerTeamScore_Add(entity player, PlayerScoreField pscorefield, float tscorefield, float score)
#include "client.qh"
#include "scores.qh"
#include <common/gamemodes/rules.qh>
+#include "teamplay.qh"
int ScoreRules_teams;
this.dmgtime = 0;
}
this.air_finished = time + 12;
- this.dmg = 2;
}
}
.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
+ * 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;
- __spawnfunc_expect = this;
if (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
- // 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);
}
void WarpZone_PostInitialize_Callback()
#pragma once
+
+bool expr_evaluate(string s);
// 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("%v", 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
#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;
// 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)
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;
}
}
// 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);
}
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;
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);
// 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);
} \
} 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
#include "spawning.qh"
#include "weaponsystem.qh"
+#include "../resources.qh"
#include "../mutators/_mod.qh"
#include <common/t_items.qh>
#include <common/weapons/_all.qh>
this.superweapons_finished = autocvar_g_balance_superweapons_time;
// if we don't already have ammo, give us some ammo
- if (!this.(wpn.ammo_field))
+ if ((wpn.ammo_type != RESOURCE_NONE) && !GetResourceAmount(this, wpn.ammo_type))
{
- switch (wpn.ammo_field)
+ switch (wpn.ammo_type)
{
- case ammo_shells: this.ammo_shells = cvar("g_pickup_shells_weapon"); break;
- case ammo_nails: this.ammo_nails = cvar("g_pickup_nails_weapon"); break;
- case ammo_rockets: this.ammo_rockets = cvar("g_pickup_rockets_weapon"); break;
- case ammo_cells: this.ammo_cells = cvar("g_pickup_cells_weapon"); break;
- case ammo_plasma: this.ammo_plasma = cvar("g_pickup_plasma_weapon"); break;
- case ammo_fuel: this.ammo_fuel = cvar("g_pickup_fuel_weapon"); break;
+ case RESOURCE_SHELLS: SetResourceAmount(this, wpn.ammo_type, cvar("g_pickup_shells_weapon")); break;
+ case RESOURCE_BULLETS: SetResourceAmount(this, wpn.ammo_type, cvar("g_pickup_nails_weapon")); break;
+ case RESOURCE_ROCKETS: SetResourceAmount(this, wpn.ammo_type, cvar("g_pickup_rockets_weapon")); break;
+ case RESOURCE_CELLS: SetResourceAmount(this, wpn.ammo_type, cvar("g_pickup_cells_weapon")); break;
+ case RESOURCE_PLASMA: SetResourceAmount(this, wpn.ammo_type, cvar("g_pickup_plasma_weapon")); break;
+ case RESOURCE_FUEL: SetResourceAmount(this, wpn.ammo_type, cvar("g_pickup_fuel_weapon")); break;
}
}
#include "throwing.qh"
#include "weaponsystem.qh"
+#include "../resources.qh"
#include "../mutators/_mod.qh"
#include <common/t_items.qh>
#include "../g_damage.qh"
float thisammo;
string s;
Weapon info = Weapons_from(wpn);
- var .int ammotype = info.ammo_field;
+ int ammotype = info.ammo_type;
entity wep = new(droppedweapon);
wep.pickup_anyway = true; // these are ALWAYS pickable
//wa = W_AmmoItemCode(wpn);
- if(ammotype == ammo_none)
+ if(ammotype == RESOURCE_NONE)
{
return "";
}
int i = own.(weaponentity).m_weapon.m_id;
if(own.(weaponentity).(weapon_load[i]) > 0)
{
- own.(ammotype) += own.(weaponentity).(weapon_load[i]);
+ GiveResource(own, ammotype, own.(weaponentity).(weapon_load[i]));
own.(weaponentity).(weapon_load[i]) = -1; // schedule the weapon for reloading
}
-
- wep.(ammotype) = 0;
+ SetResourceAmount(wep, ammotype, 0);
}
else if(doreduce)
{
int i = own.(weaponentity).m_weapon.m_id;
if(own.(weaponentity).(weapon_load[i]) > 0)
{
- own.(ammotype) += own.(weaponentity).(weapon_load[i]);
+ GiveResource(own, ammotype, own.(weaponentity).(weapon_load[i]));
own.(weaponentity).(weapon_load[i]) = -1; // schedule the weapon for reloading
}
- thisammo = min(own.(ammotype), wep.(ammotype));
- wep.(ammotype) = thisammo;
- own.(ammotype) -= thisammo;
+ float ownderammo = GetResourceAmount(own, ammotype);
+ thisammo = min(ownderammo, GetResourceAmount(wep, ammotype));
+ SetResourceAmount(wep, ammotype, thisammo);
+ SetResourceAmount(own, ammotype, ownderammo - thisammo);
- switch(ammotype)
+ switch (ammotype)
{
- case ammo_shells: s = sprintf("%s and %d shells", s, thisammo); break;
- case ammo_nails: s = sprintf("%s and %d nails", s, thisammo); break;
- case ammo_rockets: s = sprintf("%s and %d rockets", s, thisammo); break;
- case ammo_cells: s = sprintf("%s and %d cells", s, thisammo); break;
- case ammo_plasma: s = sprintf("%s and %d plasma", s, thisammo); break;
- case ammo_fuel: s = sprintf("%s and %d fuel", s, thisammo); break;
+ case RESOURCE_SHELLS: s = sprintf("%s and %d shells", s, thisammo); break;
+ case RESOURCE_BULLETS: s = sprintf("%s and %d nails", s, thisammo); break;
+ case RESOURCE_ROCKETS: s = sprintf("%s and %d rockets", s, thisammo); break;
+ case RESOURCE_CELLS: s = sprintf("%s and %d cells", s, thisammo); break;
+ case RESOURCE_PLASMA: s = sprintf("%s and %d plasma", s, thisammo); break;
+ case RESOURCE_FUEL: s = sprintf("%s and %d fuel", s, thisammo); break;
}
s = substring(s, 5, -1);
// start weapons that take no ammo can't be dropped (this prevents dropping the laser, as long as it continues to use no ammo)
if(start_items & IT_UNLIMITED_WEAPON_AMMO)
return false;
- if((Weapons_from(w)).ammo_field == ammo_none)
+ if((Weapons_from(w)).ammo_type == RESOURCE_NONE)
return false;
}
return true;
#include "../command/common.qh"
#include "../mutators/_mod.qh"
#include "../round_handler.qh"
+#include "../resources.qh"
#include <common/t_items.qh>
#include <common/animdecide.qh>
#include <common/constants.qh>
w_ent.clip_load -= ammo_use;
w_ent.(weapon_load[w_ent.m_weapon.m_id]) = w_ent.clip_load;
}
- else if (wep.ammo_field != ammo_none)
+ else if (wep.ammo_type != RESOURCE_NONE)
{
- actor.(wep.ammo_field) -= ammo_use;
- if (actor.(wep.ammo_field) < 0)
+ float ammo = GetResourceAmount(actor, wep.ammo_type);
+ if (ammo < ammo_use)
{
backtrace(sprintf(
"W_DecreaseAmmo(%.2f): '%s' subtracted too much %s from '%s', resulting with '%.2f' left... "
"Please notify Samual immediately with a copy of this backtrace!\n",
ammo_use,
wep.netname,
- GetAmmoPicture(wep.ammo_field),
+ GetAmmoPicture(wep.ammo_type),
actor.netname,
- actor.(wep.ammo_field)
+ ammo
));
}
+ SetResourceAmount(actor, wep.ammo_type, ammo - ammo_use);
}
}
w_ent.clip_load = w_ent.old_clip_load; // restore the ammo counter, in case we still had ammo in the weapon before reloading
// if the gun uses no ammo, max out weapon load, else decrease ammo as we increase weapon load
- if (!w_ent.reload_ammo_min || (actor.items & IT_UNLIMITED_WEAPON_AMMO) || wpn.ammo_field == ammo_none)
+ if (!w_ent.reload_ammo_min || (actor.items & IT_UNLIMITED_WEAPON_AMMO) || wpn.ammo_type == RESOURCE_NONE)
{
w_ent.clip_load = w_ent.reload_ammo_amount;
}
else
{
// make sure we don't add more ammo than we have
- float load = min(w_ent.reload_ammo_amount - w_ent.clip_load, actor.(wpn.ammo_field));
+ float ammo = GetResourceAmount(actor, wpn.ammo_type);
+ float load = min(w_ent.reload_ammo_amount - w_ent.clip_load, ammo);
w_ent.clip_load += load;
- actor.(wpn.ammo_field) -= load;
+ SetResourceAmount(actor, wpn.ammo_type, ammo - load);
}
w_ent.(weapon_load[w_ent.m_weapon.m_id]) = w_ent.clip_load;
if (this.clip_load >= this.reload_ammo_amount) return;
// no ammo, so nothing to load
- if (e.ammo_field != ammo_none)
+ if (e.ammo_type != RESOURCE_NONE)
{
- if (!actor.(e.ammo_field) && this.reload_ammo_min)
+ if (!GetResourceAmount(actor, e.ammo_type) && this.reload_ammo_min)
{
if (!(actor.items & IT_UNLIMITED_WEAPON_AMMO))
{
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
function genmod() {
# use context to work around cmake issue #12619
CTX="${PWD#$ROOT}/"
- oldHashC=$(hash ${MOD}.inc)
- oldTimeC=$(stat -c "%Y" ${MOD}.inc)
- oldHashH=$(hash ${MOD}.qh)
- oldTimeH=$(stat -c "%Y" ${MOD}.qh)
+ if [ -f ${MOD}.inc ]; then
+ oldHashC=$(hash ${MOD}.inc)
+ oldTimeC=$(stat -c "%Y" ${MOD}.inc)
+ fi
+ if [ -f ${MOD}.qh ]; then
+ oldHashH=$(hash ${MOD}.qh)
+ oldTimeH=$(stat -c "%Y" ${MOD}.qh)
+ fi
echo '// generated file; do not modify' > ${MOD}.inc
echo '// generated file; do not modify' > ${MOD}.qh
for f in $(ls | sort -k 1,1 -t .); do