- wget -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints
- wget -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache
- make
- - EXPECT=ed9be8d1b1a544f89bcdd7d36876fede
+ - EXPECT=0a08daa9132d147f533776deda07643e
- HASH=$(${ENGINE} -noconfig -nohome +exec serverbench.cfg
| tee /dev/stderr
| grep '^:'
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_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
// 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"
float nb_pb_period;
// Spectating
+// -1 - observing
+// 0 - playing
+// >0 - id of spectated player
float spectatee_status;
// short mapname
#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) {
+ } 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
+
// screen coords only
vector screen_pos = vec2(vid_conwidth * autocvar_cl_damagetext_2d_pos.x, vid_conheight * autocvar_cl_damagetext_2d_pos.y);
IL_EACH(g_drawables_2d, it.instanceOfDamageText && it.m_screen_coords && it.m_group == server_entity_index, {
#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)
{
- case ammo_shells: this.ammo_shells = autocvar_g_pickup_shells_max; break;
- case ammo_nails: this.ammo_nails = autocvar_g_pickup_nails_max; break;
- case ammo_rockets: this.ammo_rockets = autocvar_g_pickup_rockets_max; break;
- case ammo_cells: this.ammo_cells = autocvar_g_pickup_cells_max; break;
- case ammo_plasma: this.ammo_plasma = autocvar_g_pickup_plasma_max; break;
- case ammo_fuel: this.ammo_fuel = autocvar_g_pickup_fuel_max; break;
+ case ammo_shells: SetResourceAmount(this, RESOURCE_SHELLS, autocvar_g_pickup_shells_max); break;
+ case ammo_nails: SetResourceAmount(this, RESOURCE_BULLETS, autocvar_g_pickup_nails_max); break;
+ case ammo_rockets: SetResourceAmount(this, RESOURCE_ROCKETS, autocvar_g_pickup_rockets_max); break;
+ case ammo_cells: SetResourceAmount(this, RESOURCE_CELLS, autocvar_g_pickup_cells_max); break;
+ case ammo_plasma: SetResourceAmount(this, RESOURCE_PLASMA, autocvar_g_pickup_plasma_max); break;
+ case ammo_fuel: SetResourceAmount(this, RESOURCE_FUEL, autocvar_g_pickup_fuel_max); break;
}
}
else
{
switch(e.ammo_field)
{
- case ammo_shells: this.ammo_shells = autocvar_g_balance_nix_ammo_shells; break;
- case ammo_nails: this.ammo_nails = autocvar_g_balance_nix_ammo_nails; break;
- case ammo_rockets: this.ammo_rockets = autocvar_g_balance_nix_ammo_rockets; break;
- case ammo_cells: this.ammo_cells = autocvar_g_balance_nix_ammo_cells; break;
- case ammo_plasma: this.ammo_plasma = autocvar_g_balance_nix_ammo_plasma; break;
- case ammo_fuel: this.ammo_fuel = autocvar_g_balance_nix_ammo_fuel; break;
+ case ammo_shells: SetResourceAmount(this, RESOURCE_SHELLS, autocvar_g_balance_nix_ammo_shells); break;
+ case ammo_nails: SetResourceAmount(this, RESOURCE_BULLETS, autocvar_g_balance_nix_ammo_nails); break;
+ case ammo_rockets: SetResourceAmount(this, RESOURCE_ROCKETS, autocvar_g_balance_nix_ammo_rockets); break;
+ case ammo_cells: SetResourceAmount(this, RESOURCE_CELLS, autocvar_g_balance_nix_ammo_cells); break;
+ case ammo_plasma: SetResourceAmount(this, RESOURCE_PLASMA, autocvar_g_balance_nix_ammo_plasma); break;
+ case ammo_fuel: SetResourceAmount(this, RESOURCE_FUEL, autocvar_g_balance_nix_ammo_fuel); break;
}
}
{
switch(e.ammo_field)
{
- case ammo_shells: this.ammo_shells += autocvar_g_balance_nix_ammoincr_shells; break;
- case ammo_nails: this.ammo_nails += autocvar_g_balance_nix_ammoincr_nails; break;
- case ammo_rockets: this.ammo_rockets += autocvar_g_balance_nix_ammoincr_rockets; break;
- case ammo_cells: this.ammo_cells += autocvar_g_balance_nix_ammoincr_cells; break;
- case ammo_plasma: this.ammo_plasma += autocvar_g_balance_nix_ammoincr_plasma; break;
- case ammo_fuel: this.ammo_fuel += autocvar_g_balance_nix_ammoincr_fuel; break;
+ case ammo_shells: GiveResource(this, RESOURCE_SHELLS, autocvar_g_balance_nix_ammoincr_shells); break;
+ case ammo_nails: GiveResource(this, RESOURCE_BULLETS, autocvar_g_balance_nix_ammoincr_nails); break;
+ case ammo_rockets: GiveResource(this, RESOURCE_ROCKETS, autocvar_g_balance_nix_ammoincr_rockets); break;
+ case ammo_cells: GiveResource(this, RESOURCE_CELLS, autocvar_g_balance_nix_ammoincr_cells); break;
+ case ammo_plasma: GiveResource(this, RESOURCE_PLASMA, autocvar_g_balance_nix_ammoincr_plasma); break;
+ case ammo_fuel: GiveResource(this, RESOURCE_FUEL, autocvar_g_balance_nix_ammoincr_fuel); break;
}
this.nix_nextincr = time + autocvar_g_balance_nix_incrtime;
#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)
#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)
Item_ScheduleRespawnIn(e, max(0, game_starttime - time) + ((e.respawntimestart) ? e.respawntimestart : ITEM_RESPAWNTIME_INITIAL(e)));
}
-void GivePlayerResource(entity player, .float resource_type, float amount)
+float Item_GiveAmmoTo(entity item, entity player, int resource_type, float ammomax)
{
+ float amount = GetResourceAmount(item, resource_type);
if (amount == 0)
{
- return;
- }
- switch (resource_type)
- {
- case health:
- {
- // Ugly hack. We do not check if health goes beyond hard limit since
- // currently it is done in player_regen. We need to bring back this
- // check when other code is ported to this function.
- player.health = bound(player.health, player.health + amount,
- autocvar_g_balance_health_limit);
- // Correct code:
- //player.health = bound(player.health, player.health + amount,
- // min(autocvar_g_balance_health_limit,
- // RESOURCE_AMOUNT_HARD_LIMIT));
- player.pauserothealth_finished = max(player.pauserothealth_finished,
- time + autocvar_g_balance_pause_health_rot);
- return;
- }
- case armorvalue:
- {
- // Ugly hack. We do not check if armor goes beyond hard limit since
- // currently it is done in player_regen. We need to bring back this
- // check when other code is ported to this function.
- player.armorvalue = bound(player.armorvalue, player.armorvalue +
- amount, autocvar_g_balance_armor_limit);
- // Correct code:
- //player.armorvalue = bound(player.armorvalue, player.armorvalue +
- // amount, min(autocvar_g_balance_armor_limit,
- // RESOURCE_AMOUNT_HARD_LIMIT));
- player.pauserotarmor_finished = max(player.pauserotarmor_finished,
- time + autocvar_g_balance_pause_armor_rot);
- return;
- }
- case ammo_shells:
- case ammo_nails:
- case ammo_rockets:
- case ammo_cells:
- case ammo_plasma:
- {
- GivePlayerAmmo(player, resource_type, amount);
- return;
- }
- case ammo_fuel:
- {
- player.ammo_fuel = bound(player.ammo_fuel, player.ammo_fuel +
- amount, min(g_pickup_fuel_max, RESOURCE_AMOUNT_HARD_LIMIT));
- player.pauserotfuel_finished = max(player.pauserotfuel_finished,
- time + autocvar_g_balance_pause_fuel_rot);
- return;
- }
- }
-}
-
-void GivePlayerHealth(entity player, float amount)
-{
- GivePlayerResource(player, health, amount);
-}
-
-void GivePlayerArmor(entity player, float amount)
-{
- GivePlayerResource(player, armorvalue, amount);
-}
-
-void GivePlayerAmmo(entity player, .float ammotype, float amount)
-{
- if (amount == 0)
- {
- return;
- }
- float maxvalue = RESOURCE_AMOUNT_HARD_LIMIT;
- switch (ammotype)
- {
- case ammo_shells:
- {
- maxvalue = g_pickup_shells_max;
- break;
- }
- case ammo_cells:
- {
- maxvalue = g_pickup_cells_max;
- break;
- }
- case ammo_rockets:
- {
- maxvalue = g_pickup_rockets_max;
- break;
- }
- case ammo_plasma:
- {
- maxvalue = g_pickup_plasma_max;
- break;
- }
- case ammo_nails:
- {
- maxvalue = g_pickup_nails_max;
- break;
- }
- }
- player.(ammotype) = min(player.(ammotype) + amount,
- min(maxvalue, RESOURCE_AMOUNT_HARD_LIMIT));
-}
-
-void GivePlayerFuel(entity player, float amount)
-{
- GivePlayerResource(player, ammo_fuel, amount);
-}
-
-float Item_GiveAmmoTo(entity item, entity player, .float ammotype, float ammomax)
-{
- if (!item.(ammotype))
return false;
-
+ }
+ float player_amount = GetResourceAmount(player, resource_type);
if (item.spawnshieldtime)
{
- if ((player.(ammotype) < ammomax) || item.pickup_anyway > 0)
+ if ((player_amount >= ammomax) && (item.pickup_anyway <= 0))
{
- float amount = item.(ammotype);
- if ((player.(ammotype) + amount) > ammomax)
- {
- amount = ammomax - player.(ammotype);
- }
- GivePlayerResource(player, ammotype, amount);
- return true;
+ return false;
}
+ GiveResourceWithLimit(player, resource_type, amount, ammomax);
+ return true;
}
- else if(g_weapon_stay == 2)
+ if (g_weapon_stay != 2)
{
- float mi = min(item.(ammotype), ammomax);
- if (player.(ammotype) < mi)
- {
- GivePlayerResource(player, ammotype, mi - player.(ammotype));
- }
- return true;
+ return false;
}
- return false;
+ GiveResourceWithLimit(player, resource_type, amount, min(amount, ammomax));
+ return true;
}
float Item_GiveTo(entity item, entity player)
}
}
}
- 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;
#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.
-/// \return No return.
-void GivePlayerFuel(entity player, float amount);
-
-float Item_GiveAmmoTo(entity item, entity player, .float ammotype, float ammomax);
+float Item_GiveAmmoTo(entity item, entity player, int resource_type, float ammomax);
float Item_GiveTo(entity item, entity player);
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
#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();
#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__
#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
+
+ void __spawnfunc_spawn(entity prototype)
+ {
+ entity e = new(clone);
+ copyentity(prototype, e);
+ IL_PUSH(g_map_entities, e);
+ #define X(T, fld, def) { e.fld = e.__spawnfunc_##fld; e.__spawnfunc_##fld = def; }
+ SPAWNFUNC_INTERNAL_FIELDS(X);
+ #undef X
+ e.__spawnfunc_constructor(e);
+ }
+
#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) \
- { \
+ 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)
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/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/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_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 "g_hook.qh"
#include "command/common.qh"
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);
}
#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;
WinningConditionHelper(this); // set worldstatus
world_initialized = 1;
+ __spawnfunc_spawn_all();
}
spawnfunc(light)
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) \
--- /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.
+
+/// \brief Unconditional maximum amount of resources the entity can have.
+const int RESOURCE_AMOUNT_HARD_LIMIT = 999;
+
+/// \brief Describes the available resource types.
+enum
+{
+ RESOURCE_HEALTH = 1, ///< Health.
+ RESOURCE_ARMOR, ///< Armor.
+ RESOURCE_SHELLS, ///< Shells (used by shotgun).
+ RESOURCE_BULLETS, ///< Bullets (used by machinegun and rifle)
+ RESOURCE_ROCKETS, ///< Rockets (used by mortar, hagar, devastator, etc).
+ RESOURCE_CELLS, ///< Cells (used by electro, crylink, vortex, etc)
+ RESOURCE_PLASMA, ///< Plasma (unused).
+ RESOURCE_FUEL ///< Fuel (used by jetpack).
+};
+
+// ============================ Public API ====================================
+
+/// \brief Returns the maximum amount of the given resource.
+/// \param[in] e Entity to check.
+/// \param[in] resource_type Type of the resource (a RESOURCE_* constant).
+/// \return Maximum amount of the given resource.
+float GetResourceLimit(entity e, int resource_type);
+
+/// \brief Returns the current amount of resource the given entity has.
+/// \param[in] e Entity to check.
+/// \param[in] resource_type Type of the resource (a RESOURCE_* constant).
+/// \return Current amount of resource the given entity has.
+float GetResourceAmount(entity e, int resource_type);
+
+/// \brief Sets the current amount of resource the given entity will have.
+/// \param[in,out] e Entity to adjust.
+/// \param[in] resource_type Type of the resource (a RESOURCE_* constant).
+/// \param[in] amount Amount of resource to set.
+/// \return No return.
+void SetResourceAmount(entity e, int resource_type, float amount);
+
+/// \brief Gives an entity some resource.
+/// \param[in,out] receiver Entity to give resource to.
+/// \param[in] resource_type Type of the resource (a RESOURCE_* constant).
+/// \param[in] amount Amount of resource to give.
+/// \return No return.
+void GiveResource(entity receiver, int resource_type, float amount);
+
+/// \brief Gives an entity some resource but not more than a limit.
+/// \param[in,out] receiver Entity to give resource to.
+/// \param[in] resource_type Type of the resource (a RESOURCE_* constant).
+/// \param[in] amount Amount of resource to give.
+/// \param[in] limit Limit of resources to give.
+/// \return No return.
+void GiveResourceWithLimit(entity receiver, int resource_type, float amount,
+ float limit);
+
+// ===================== Legacy and/or internal API ===========================
+
+/// \brief Converts an entity field to resource type.
+/// \param[in] resource_field Entity field to convert.
+/// \return Resource type (a RESOURCE_* constant).
+int GetResourceType(.float resource_field);
+
+/// \brief Converts resource type (a RESOURCE_* constant) to entity field.
+/// \param[in] resource_type Type of the resource.
+/// \return Entity field for that resource.
+.float GetResourceField(int resource_type);
.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;
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);
+ __spawnfunc_expecting = false;
}
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("%d %d %d", v));
+ if (++done == 2) break;
+ }
+ }
// currently, NetRadiant's limit is 131072 qu for each side
// distance from one corner of a 131072qu cube to the opposite corner is approx. 227023 qu
// set the distance according to map size but don't go over the limit to avoid issues with float precision
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