]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'martin-t/aesinfo' into 'master'
authorMario <zacjardine@y7mail.com>
Sat, 25 Nov 2017 02:36:33 +0000 (02:36 +0000)
committerMario <zacjardine@y7mail.com>
Sat, 25 Nov 2017 02:36:33 +0000 (02:36 +0000)
Show tooltip mentioning crypto_aeslevel on the join dialog (tiny MR)

See merge request xonotic/xonotic-data.pk3dir!478

193 files changed:
CMakeLists.txt
balance-mario.cfg
balance-nexuiz25.cfg
balance-overkill.cfg
balance-samual.cfg
balance-xdf.cfg
balance-xonotic.cfg
balance-xpm.cfg
common.pot
defaultOverkill.cfg
defaultServer.cfg
mutators.cfg
physics.cfg
physicsCPMA.cfg
physicsFruit.cfg
physicsHavoc.cfg
physicsLeeStricklin-ModdedFruit.cfg
physicsLeeStricklin.cfg
physicsLeeStricklinOld.cfg
physicsLzd.cfg
physicsNexuiz10.cfg
physicsNexuiz11.cfg
physicsNexuiz151.cfg
physicsNexuiz151b.cfg
physicsNexuiz16rc1.cfg
physicsNexuiz20.cfg
physicsNexuiz25.cfg
physicsNexuiz26.cfg
physicsNoQWBunny-nexbased.cfg
physicsOverkill.cfg
physicsQ.cfg
physicsQ2.cfg
physicsQ2a.cfg
physicsQ3.cfg
physicsQBF.cfg
physicsQBFplus.cfg
physicsSamual.cfg
physicsWarsow.cfg
physicsWarsowClassicBunny.cfg
physicsWarsowDev.cfg
physicsX.cfg
physicsX010.cfg
physicsX07.cfg
physicsXDF.cfg
physicsXDFLight.cfg
qcsrc/client/defs.qh
qcsrc/client/hud/panel/ammo.qc
qcsrc/client/hud/panel/physics.qc
qcsrc/client/hud/panel/weapons.qc
qcsrc/client/main.qc
qcsrc/client/mapvoting.qc
qcsrc/common/ent_cs.qh
qcsrc/common/mutators/mutator/_mod.inc
qcsrc/common/mutators/mutator/_mod.qh
qcsrc/common/mutators/mutator/bloodloss/sv_bloodloss.qc
qcsrc/common/mutators/mutator/buffs/sv_buffs.qc
qcsrc/common/mutators/mutator/campcheck/sv_campcheck.qc
qcsrc/common/mutators/mutator/cloaked/sv_cloaked.qc
qcsrc/common/mutators/mutator/damagetext/cl_damagetext.qc
qcsrc/common/mutators/mutator/dodging/sv_dodging.qc
qcsrc/common/mutators/mutator/dynamic_handicap/_mod.inc [new file with mode: 0644]
qcsrc/common/mutators/mutator/dynamic_handicap/_mod.qh [new file with mode: 0644]
qcsrc/common/mutators/mutator/dynamic_handicap/sv_dynamic_handicap.qc [new file with mode: 0644]
qcsrc/common/mutators/mutator/dynamic_handicap/sv_dynamic_handicap.qh [new file with mode: 0644]
qcsrc/common/mutators/mutator/hook/sv_hook.qc
qcsrc/common/mutators/mutator/instagib/sv_instagib.qc
qcsrc/common/mutators/mutator/invincibleproj/sv_invincibleproj.qc
qcsrc/common/mutators/mutator/melee_only/sv_melee_only.qc
qcsrc/common/mutators/mutator/midair/sv_midair.qc
qcsrc/common/mutators/mutator/multijump/multijump.qc
qcsrc/common/mutators/mutator/nades/nades.qc
qcsrc/common/mutators/mutator/new_toys/sv_new_toys.qc
qcsrc/common/mutators/mutator/nix/sv_nix.qc
qcsrc/common/mutators/mutator/overkill/hmg.qc
qcsrc/common/mutators/mutator/overkill/hmg.qh
qcsrc/common/mutators/mutator/overkill/rpc.qc
qcsrc/common/mutators/mutator/overkill/rpc.qh
qcsrc/common/mutators/mutator/overkill/sv_overkill.qc
qcsrc/common/mutators/mutator/physical_items/sv_physical_items.qc
qcsrc/common/mutators/mutator/pinata/sv_pinata.qc
qcsrc/common/mutators/mutator/rocketflying/sv_rocketflying.qc
qcsrc/common/mutators/mutator/sandbox/sv_sandbox.qc
qcsrc/common/mutators/mutator/spawn_near_teammate/sv_spawn_near_teammate.qc
qcsrc/common/mutators/mutator/stale_move_negation/_mod.inc [new file with mode: 0644]
qcsrc/common/mutators/mutator/stale_move_negation/_mod.qh [new file with mode: 0644]
qcsrc/common/mutators/mutator/stale_move_negation/sv_stale_move_negation.qc [new file with mode: 0644]
qcsrc/common/mutators/mutator/stale_move_negation/sv_stale_move_negation.qh [new file with mode: 0644]
qcsrc/common/mutators/mutator/superspec/sv_superspec.qc
qcsrc/common/mutators/mutator/touchexplode/sv_touchexplode.qc
qcsrc/common/mutators/mutator/vampire/sv_vampire.qc
qcsrc/common/mutators/mutator/vampirehook/sv_vampirehook.qc
qcsrc/common/mutators/mutator/walljump/walljump.qc
qcsrc/common/notifications/all.inc
qcsrc/common/physics/movetypes/movetypes.qh
qcsrc/common/physics/player.qc
qcsrc/common/physics/player.qh
qcsrc/common/playerstats.qc
qcsrc/common/playerstats.qh
qcsrc/common/resources.qh [new file with mode: 0644]
qcsrc/common/stats.qh
qcsrc/common/t_items.qc
qcsrc/common/t_items.qh
qcsrc/common/triggers/func/breakable.qc
qcsrc/common/triggers/func/button.qc
qcsrc/common/triggers/func/button.qh
qcsrc/common/triggers/func/door.qc
qcsrc/common/triggers/trigger/heal.qc
qcsrc/common/triggers/trigger/jumppads.qc
qcsrc/common/triggers/trigger/relay.qc
qcsrc/common/weapons/all.qc
qcsrc/common/weapons/projectiles.qh
qcsrc/common/weapons/weapon.qh
qcsrc/common/weapons/weapon/arc.qc
qcsrc/common/weapons/weapon/arc.qh
qcsrc/common/weapons/weapon/blaster.qh
qcsrc/common/weapons/weapon/crylink.qc
qcsrc/common/weapons/weapon/crylink.qh
qcsrc/common/weapons/weapon/devastator.qc
qcsrc/common/weapons/weapon/devastator.qh
qcsrc/common/weapons/weapon/electro.qc
qcsrc/common/weapons/weapon/electro.qh
qcsrc/common/weapons/weapon/fireball.qh
qcsrc/common/weapons/weapon/hagar.qc
qcsrc/common/weapons/weapon/hagar.qh
qcsrc/common/weapons/weapon/hlac.qc
qcsrc/common/weapons/weapon/hlac.qh
qcsrc/common/weapons/weapon/hook.qh
qcsrc/common/weapons/weapon/machinegun.qc
qcsrc/common/weapons/weapon/machinegun.qh
qcsrc/common/weapons/weapon/minelayer.qc
qcsrc/common/weapons/weapon/minelayer.qh
qcsrc/common/weapons/weapon/mortar.qc
qcsrc/common/weapons/weapon/mortar.qh
qcsrc/common/weapons/weapon/porto.qh
qcsrc/common/weapons/weapon/rifle.qc
qcsrc/common/weapons/weapon/rifle.qh
qcsrc/common/weapons/weapon/seeker.qc
qcsrc/common/weapons/weapon/seeker.qh
qcsrc/common/weapons/weapon/shockwave.qh
qcsrc/common/weapons/weapon/shotgun.qc
qcsrc/common/weapons/weapon/shotgun.qh
qcsrc/common/weapons/weapon/vaporizer.qc
qcsrc/common/weapons/weapon/vaporizer.qh
qcsrc/common/weapons/weapon/vortex.qc
qcsrc/common/weapons/weapon/vortex.qh
qcsrc/dpdefs/post.qh
qcsrc/ecs/systems/sv_physics.qc
qcsrc/lib/_all.inc
qcsrc/lib/macro.qh
qcsrc/lib/misc.qh
qcsrc/lib/net.qh
qcsrc/lib/registry.qh
qcsrc/lib/self.qh
qcsrc/lib/spawnfunc.qh
qcsrc/lib/static.qh
qcsrc/lib/stats.qh
qcsrc/lib/string.qh
qcsrc/server/_mod.inc
qcsrc/server/_mod.qh
qcsrc/server/anticheat.qc
qcsrc/server/autocvars.qh
qcsrc/server/client.qc
qcsrc/server/client.qh
qcsrc/server/command/cmd.qc
qcsrc/server/command/sv_cmd.qc
qcsrc/server/command/vote.qc
qcsrc/server/compat/quake3.qc
qcsrc/server/defs.qh
qcsrc/server/g_damage.qc
qcsrc/server/g_world.qc
qcsrc/server/handicap.qc [new file with mode: 0644]
qcsrc/server/handicap.qh [new file with mode: 0644]
qcsrc/server/miscfunctions.qc
qcsrc/server/miscfunctions.qh
qcsrc/server/mutators/events.qh
qcsrc/server/mutators/mutator/gamemode_ca.qc
qcsrc/server/player.qc
qcsrc/server/player.qh
qcsrc/server/resources.qc [new file with mode: 0644]
qcsrc/server/resources.qh [new file with mode: 0644]
qcsrc/server/scores.qc
qcsrc/server/scores_rules.qc
qcsrc/server/sv_main.qc
qcsrc/server/sv_main.qh
qcsrc/server/teamplay.qc
qcsrc/server/teamplay.qh
qcsrc/server/utils.qh
qcsrc/server/weapons/spawning.qc
qcsrc/server/weapons/throwing.qc
qcsrc/server/weapons/weaponsystem.qc
qcsrc/tools/compilationunits.sh
qcsrc/tools/genmod.sh
scripts/ok_nade_counter.shader

index 2e5fc791f8585428c314a2ae9293bb011d371e3a..9a66f9fd2ef4985cf06e873e7435c6735b8d1f91 100644 (file)
@@ -8,9 +8,18 @@ add_custom_target(${all})
 set(checks qc-checks)
 add_custom_target(${checks})
 
-# depend on qcc
-if (TARGET gmqcc)
-    add_dependencies(${checks} gmqcc)
+if (gmqcc_BINARY_DIR)
+    set(compilerinfo "${gmqcc_BINARY_DIR}/gmqcc.h")
+    add_custom_command(
+            OUTPUT "${compilerinfo}"
+            DEPENDS "${gmqcc_BINARY_DIR}/gmqcc"
+            VERBATIM
+            COMMAND ${CMAKE_COMMAND} -E
+                md5sum "${gmqcc_BINARY_DIR}/gmqcc" > "${compilerinfo}"
+            )
+    add_custom_target(qcc ALL
+            DEPENDS "${compilerinfo}"
+            )
 endif ()
 
 add_dependencies(${checks} data-check-cvars)
@@ -45,6 +54,25 @@ add_custom_target(qc-whitespace
         VERBATIM COMMAND ./tools/whitespace.sh
         )
 
+function(prog name dir)
+    add_executable(${name} qcsrc/${dir}/progs.inc)
+    add_dependencies(${all} ${name})
+    add_dependencies(${name} ${checks})
+    add_dependencies(${name} qcc)
+    set_source_files_properties(qcsrc/${dir}/progs.inc PROPERTIES OBJECT_DEPENDS "${compilerinfo}")
+endfunction()
+
+function(set_prelude target prelude)
+    get_target_property(MY_PROJECT_SOURCES ${target} SOURCES)
+    foreach (source IN LISTS MY_PROJECT_SOURCES)
+        set_property(
+                SOURCE ${source}
+                APPEND PROPERTY COMPILE_FLAGS
+                "-include ${prelude}"
+        )
+    endforeach ()
+endfunction()
+
 include_directories(qcsrc)
 
 add_definitions(-DXONOTIC=1)
@@ -75,33 +103,16 @@ set_source_files_properties(
         HEADER_FILE_ONLY FALSE
 )
 
-add_executable(csprogs qcsrc/client/progs.inc)
-add_dependencies(${all} csprogs)
-add_dependencies(csprogs ${checks})
+prog(csprogs client)
 target_compile_definitions(csprogs PRIVATE -DGAMEQC -DCSQC)
+# set_prelude(csprogs "${PROJECT_SOURCE_DIR}/qcsrc/lib/_all.inc")
 
-add_executable(progs qcsrc/server/progs.inc)
-add_dependencies(${all} progs)
-add_dependencies(progs ${checks})
+prog(progs server)
 target_compile_definitions(progs PRIVATE -DGAMEQC -DSVQC)
 
-add_executable(menu qcsrc/menu/progs.inc)
-add_dependencies(${all} menu)
-add_dependencies(menu ${checks})
+prog(menu menu)
 target_compile_definitions(menu PRIVATE -DMENUQC)
 
-function(set_prelude target prelude)
-    get_target_property(MY_PROJECT_SOURCES target SOURCES)
-    foreach (source IN LISTS MY_PROJECT_SOURCES)
-        set_property(
-                SOURCE ${source}
-                APPEND PROPERTY COMPILE_FLAGS
-                "-include ${PROJECT_SOURCE_DIR}/${prelude}"
-        )
-    endforeach ()
-endfunction()
-# set_prelude(csprogs qcsrc/lib/_all.inc)
-
 function(copy prog)
     add_custom_command(TARGET ${prog} POST_BUILD
             COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_FILE_DIR:${prog}>/${prog}.dat" "${prog}.dat"
index a8f59a8cd75a31c5f5b23b1dc4590007dd7bf8df..28100182ea5fe41b0b1d028b020a93503228c9f5 100644 (file)
@@ -9,6 +9,13 @@ set g_start_ammo_rockets 0
 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"
index 4967fa7e47936208ca64c9e719e5e41fa373b968..37099c1226dfa6ff992a97b74ef1217b91111b31 100644 (file)
@@ -9,6 +9,13 @@ set g_start_ammo_rockets 0
 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"
index 3f025655c8826083e1280112a4550d85100d12d8..3a321ce53d461b2eca19452f5ad420975f9f2c12 100644 (file)
@@ -9,6 +9,13 @@ set g_start_ammo_rockets 0
 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"
index 52fd03dc7e32dcc3223fa5508c0b7b19bd6c1b32..48c68d81be66e77c96cf8f55c417fd4b8565fe0f 100644 (file)
@@ -9,6 +9,13 @@ set g_start_ammo_rockets 0
 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"
index 4d26972067abdcf3fd4a18f1e3db231bd19e4958..562506283c44ad965d5d7d0f641a96a910aa11d3 100644 (file)
@@ -9,6 +9,13 @@ set g_start_ammo_rockets 0
 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"
index fece8cd726072f6b9f0ca81677ce05ffc00815d7..32924a72d2cee7cca9d636330661abe49047f404 100644 (file)
@@ -9,6 +9,13 @@ set g_start_ammo_rockets 0
 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"
index 984972623228b5a3c98c0bf6a035db3d9a7b83d9..6901eda28a8c130622dc43db632f7475965050d3 100644 (file)
@@ -9,6 +9,13 @@ set g_start_ammo_rockets 0
 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"
index 055fa19dc645ea65091b0e6a78d6be596f171352..d0c8c9ce60535857a82023b1090f62e02c11baaf 100644 (file)
@@ -2844,7 +2844,7 @@ msgstr ""
 
 #: 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
index 2444301b9e7b34c75b8bacf3ebdd367615ec4939..f63f689f34bda0b1cd03bcb62a1f944cbac88a92 100644 (file)
@@ -28,7 +28,7 @@ set g_nades_nade_newton_style 2
 set g_dodging 1
 set sv_dodging_wall_dodging 1
 
-set g_spawn_near_teammate 1
+set g_spawn_near_teammate "!g_assault !g_freezetag"
 set g_spawn_near_teammate_ignore_spawnpoint 1
 set g_spawnshieldtime 0.5
 set g_respawn_delay_forced 2
index 596576a097fb93c6841a9e76ba1927a5909a1e13..99845f6480f256dd2eaf79057b48ce76997b5a5b 100644 (file)
@@ -77,6 +77,7 @@ set sv_jumpspeedcap_min "" "lower bound on the baseline velocity of a jump; fina
 set sv_jumpspeedcap_max "" "upper bound on the baseline velocity of a jump; final velocity will be <= (jumpheight * max + jumpheight)"
 set sv_jumpspeedcap_max_disable_on_ramps 0 "disable upper baseline velocity bound on ramps to preserve the old rampjump style"
 set sv_track_canjump 0 "track if the player released the jump key between 2 jumps to decide if they are able to jump or not"
+set sv_jumpvelocity_crouch 0 "jump height while crouching, set to 0 to use regular jump height"
 
 set sv_precacheplayermodels 1
 set sv_precacheweapons 0
@@ -549,3 +550,5 @@ set sv_join_notices_time 15
 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"
index 23c72435c1e399233872a18114c265debb945150..c16fda72e32b16f7d7f54df0d66afe772f065147 100644 (file)
@@ -12,17 +12,21 @@ seta cl_dodging_timeout 0.2 "determines how long apart (in seconds) two taps on
 
 set sv_dodging_air_dodging 0
 set sv_dodging_wall_dodging 0 "allow dodging off walls"
-set sv_dodging_delay 0.5 "determines how long a player has to wait to be able to dodge again after dodging"
+set sv_dodging_delay 0.6 "determines how long a player has to wait to be able to dodge again after dodging"
 set sv_dodging_up_speed 200 "the jump velocity of the dodge"
-set sv_dodging_horiz_speed 400 "the horizontal velocity of the dodge"
-set sv_dodging_horiz_speed_frozen 200 "the horizontal velocity of the dodge while frozen"
+set sv_dodging_horiz_speed_min 200 "the lower bound of current velocity for force scaling"
+set sv_dodging_horiz_speed_max 1000 "the upper bound of current velocity for force scaling"
+set sv_dodging_horiz_force_slowest 400 "the horizontal velocity of the dodge when current velocity is <= sv_dodging_horiz_speed_min, values between min and max are linearly scaled"
+set sv_dodging_horiz_force_fastest 400 "the horizontal velocity of the dodge when current velocity is >= sv_dodging_horiz_speed_max, values between min and max are linearly scaled"
+set sv_dodging_horiz_force_frozen 200 "the horizontal velocity of the dodge while frozen"
 set sv_dodging_ramp_time 0.1 "a ramp so that the horizontal part of the dodge is added smoothly (seconds)"
 set sv_dodging_height_threshold 10 "the maximum height above ground where to allow dodging"
 set sv_dodging_wall_distance_threshold 10 "the maximum distance from a wall that still allows dodging"
 set sv_dodging_sound 1 "if 1 dodging makes a sound. if 0 dodging is silent"
 set sv_dodging_frozen 0 "allow dodging while frozen"
 set sv_dodging_frozen_doubletap 0
-set sv_dodging_maxspeed 450 "maximum speed a player can be moving at before they dodge again"
+set sv_dodging_maxspeed 350 "maximum speed a player can be moving at to use the standard dodging from an (almost) standstill"
+set sv_dodging_air_maxspeed 450 "maximum speed a player can be moving at before they dodge again when air dodging is enabled"
 
 
 // ===========
@@ -108,7 +112,7 @@ set g_rocket_flying 0 "set to 1 to enable rocket flying in all balance configs"
 //  spawn near teammate
 // =====================
 seta cl_spawn_near_teammate 1 "toggle for spawning near teammates (only effective if g_spawn_near_teammate_ignore_spawnpoint is 2)"
-set g_spawn_near_teammate 0 "if set, players prefer spawns near a team mate"
+set g_spawn_near_teammate 0 "players prefer spawns near a team mate"
 set g_spawn_near_teammate_distance 640 "max distance to consider a spawn to be near a team mate"
 set g_spawn_near_teammate_ignore_spawnpoint 0 "ignore spawnpoints and spawn right at team mates, if 2, clients can ignore this option"
 set g_spawn_near_teammate_ignore_spawnpoint_max 10 "if set, test at most this many of the available teammates"
@@ -464,3 +468,20 @@ set g_bugrigs_steer 1      "steering amount"
 //  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."
+
+// =====================
+//  stale-move negation
+// =====================
+set g_smneg 0 "Stale-move negation: penalize repeated use of the same weapon"
+set g_smneg_bonus 1 "Stale-move negation: allow weapons to become stronger than their baseline"
+set g_smneg_bonus_asymptote 4 "Stale-move negation: damage = infinity at this bonus level"
+set g_smneg_cooldown_factor 0.25 "Stale-move negation: penalty cooldown factor"
index 88742ff0b89943675ba5347037376d6fe96325a6..b74f68b3db4d1c3c341c70216e80765f7c28f8cc 100644 (file)
@@ -18,6 +18,7 @@ set g_physics_xonotic_airstrafeaccel_qw -0.95
 set g_physics_xonotic_airspeedlimit_nonqw 900
 set g_physics_xonotic_maxspeed 360
 set g_physics_xonotic_jumpvelocity 260
+set g_physics_xonotic_jumpvelocity_crouch 0
 set g_physics_xonotic_maxairstrafespeed 100
 set g_physics_xonotic_maxairspeed 360
 set g_physics_xonotic_airstrafeaccelerate 18
@@ -48,6 +49,7 @@ set g_physics_nexuiz_airstrafeaccel_qw 0
 set g_physics_nexuiz_airspeedlimit_nonqw 0
 set g_physics_nexuiz_maxspeed 400
 set g_physics_nexuiz_jumpvelocity 300 "333 to match xonotic physics"
+set g_physics_nexuiz_jumpvelocity_crouch 0 "333 to match xonotic physics"
 set g_physics_nexuiz_maxairstrafespeed 0
 set g_physics_nexuiz_maxairspeed 220
 set g_physics_nexuiz_airstrafeaccelerate 0
@@ -78,6 +80,7 @@ set g_physics_quake_airstrafeaccel_qw 0
 set g_physics_quake_airspeedlimit_nonqw 0
 set g_physics_quake_maxspeed 320
 set g_physics_quake_jumpvelocity 270
+set g_physics_quake_jumpvelocity_crouch 0
 set g_physics_quake_maxairstrafespeed 0
 set g_physics_quake_maxairspeed 30
 set g_physics_quake_airstrafeaccelerate 0
@@ -108,6 +111,7 @@ set g_physics_warsow_airstrafeaccel_qw 0
 set g_physics_warsow_airspeedlimit_nonqw 0
 set g_physics_warsow_maxspeed 320
 set g_physics_warsow_jumpvelocity 280
+set g_physics_warsow_jumpvelocity_crouch 0
 set g_physics_warsow_maxairstrafespeed 30
 set g_physics_warsow_maxairspeed 320
 set g_physics_warsow_airstrafeaccelerate 70
@@ -138,6 +142,7 @@ set g_physics_defrag_airstrafeaccel_qw 1
 set g_physics_defrag_airspeedlimit_nonqw 0
 set g_physics_defrag_maxspeed 320
 set g_physics_defrag_jumpvelocity 270
+set g_physics_defrag_jumpvelocity_crouch 0
 set g_physics_defrag_maxairstrafespeed 30
 set g_physics_defrag_maxairspeed 320
 set g_physics_defrag_airstrafeaccelerate 70
@@ -168,6 +173,7 @@ set g_physics_quake3_airstrafeaccel_qw 0
 set g_physics_quake3_airspeedlimit_nonqw 0
 set g_physics_quake3_maxspeed 320
 set g_physics_quake3_jumpvelocity 270
+set g_physics_quake3_jumpvelocity_crouch 0
 set g_physics_quake3_maxairstrafespeed 0
 set g_physics_quake3_maxairspeed 320
 set g_physics_quake3_airstrafeaccelerate 0
@@ -198,6 +204,7 @@ set g_physics_vecxis_airstrafeaccel_qw 0
 set g_physics_vecxis_airspeedlimit_nonqw 0
 set g_physics_vecxis_maxspeed 400
 set g_physics_vecxis_jumpvelocity 300 "333 to match xonotic physics"
+set g_physics_vecxis_jumpvelocity_crouch 0 "333 to match xonotic physics"
 set g_physics_vecxis_maxairstrafespeed 0
 set g_physics_vecxis_maxairspeed 220
 set g_physics_vecxis_airstrafeaccelerate 0
@@ -228,6 +235,7 @@ set g_physics_quake2_airstrafeaccel_qw 0
 set g_physics_quake2_airspeedlimit_nonqw 0
 set g_physics_quake2_maxspeed 300
 set g_physics_quake2_jumpvelocity 270
+set g_physics_quake2_jumpvelocity_crouch 0
 set g_physics_quake2_maxairstrafespeed 0
 set g_physics_quake2_maxairspeed 300
 set g_physics_quake2_airstrafeaccelerate 0
@@ -258,6 +266,7 @@ set g_physics_bones_airstrafeaccel_qw 1
 set g_physics_bones_airspeedlimit_nonqw 0
 set g_physics_bones_maxspeed 320
 set g_physics_bones_jumpvelocity 270
+set g_physics_bones_jumpvelocity_crouch 0
 set g_physics_bones_maxairstrafespeed 30
 set g_physics_bones_maxairspeed 320
 set g_physics_bones_airstrafeaccelerate 70
@@ -288,6 +297,7 @@ set g_physics_overkill_airstrafeaccel_qw -0.95
 set g_physics_overkill_airspeedlimit_nonqw 900
 set g_physics_overkill_maxspeed 400
 set g_physics_overkill_jumpvelocity 260
+set g_physics_overkill_jumpvelocity_crouch 0
 set g_physics_overkill_maxairstrafespeed 100
 set g_physics_overkill_maxairspeed 360
 set g_physics_overkill_airstrafeaccelerate 24
index 612a779dac5fb69ad978febcc0a3cbdc9b7279aa..3c1614dcd3b1943061a3c35ca3489127fdb7f2e1 100644 (file)
@@ -11,6 +11,7 @@ sv_friction 8
 edgefriction 1
 sv_stepheight 18
 sv_jumpvelocity 270
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate 4
 sv_waterfriction 1
 sv_airaccel_sideways_friction 0
index 8e0a7f0fe73f8084a776eac1274dd255a71a5f9c..8c034b8a9010faf99eab9bab51d2f1cd7a662bb5 100644 (file)
@@ -11,6 +11,7 @@ sv_friction 8
 edgefriction 1
 sv_stepheight 34
 sv_jumpvelocity 270
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate -1
 sv_waterfriction -1
 sv_airaccel_sideways_friction 0 // breaks strafing?
index e5d431b3e7b6c7d3f5babb72291b6cd144a30361..f45a73f14ca2eb09447feb72868eb1abd3861b91 100644 (file)
@@ -11,6 +11,7 @@ sv_friction 7
 edgefriction 1
 sv_stepheight 34
 sv_jumpvelocity 300
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate -1
 sv_waterfriction -1
 sv_airaccel_sideways_friction 0.65
index 81d54d2eede716dc852bde66054b6063b5130bf3..b2c35d086b6cd2241877cf61c879659f35e0a098 100644 (file)
@@ -18,6 +18,7 @@ sv_stepheight 26
 // actually, what we want is 266.6666 for 180bpm
 // but 260 takes same amount of frames and is nicer to mappers
 sv_jumpvelocity 260
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate -1
 sv_waterfriction -1
 sv_airaccel_sideways_friction 0 // breaks strafing?
index 741224b1195736d1f0b1bcb35499eb8cdadbda0f..f529ed66dcc993101481bcca323b95948dc40d12 100644 (file)
@@ -18,6 +18,7 @@ sv_stepheight 26
 // actually, what we want is 266.6666 for 180bpm
 // but 260 takes same amount of frames and is nicer to mappers
 sv_jumpvelocity 260
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate -1
 sv_waterfriction -1
 sv_airaccel_sideways_friction 0
index 83689d1614e26474644270f2ecfd3ebbc5d28c55..522c30197186f99c6fa06c6d3164f93e87ec3382 100644 (file)
@@ -13,6 +13,7 @@ sv_friction 9.6 // higher values make you slide less
 edgefriction 1 // div0 says no! lol
 sv_stepheight 26
 sv_jumpvelocity 304
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate -1
 sv_waterfriction -1
 sv_airaccel_sideways_friction 0 // pain in the ass to tweak without screwing up the strafing
index de271c2987b5e75cce1b3b3293c1d8fdc8fef502..0c32adb2ef49436e331559d07bfb793b5a747c43 100644 (file)
@@ -12,6 +12,7 @@ sv_friction 5
 edgefriction 1
 sv_stepheight 34
 sv_jumpvelocity 310
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate -1
 sv_waterfriction -1
 sv_airaccel_sideways_friction 0.35
index 28d7c7f2cebce23ea74fe1a0e9b4d5d9ba875008..1f0b8bde150e79bab98a249c31669c6689a57af5 100644 (file)
@@ -11,6 +11,7 @@ sv_friction 5
 edgefriction 1
 sv_stepheight 34
 sv_jumpvelocity 300
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate -1
 sv_waterfriction -1
 sv_airaccel_sideways_friction 0
index 7633e5a28878384f09a1418796937a91d0cdb979..51ef497e7fcdc61d47f6041898805bca772c743f 100644 (file)
@@ -11,6 +11,7 @@ sv_friction 5
 edgefriction 1
 sv_stepheight 34
 sv_jumpvelocity 300
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate -1
 sv_waterfriction -1
 sv_airaccel_sideways_friction 0
index aaec2756f6bece0a7a24572766dd275b299cbddd..0dd5b0da2948dcbc8ebb9b32d97c6d80c2b21818 100644 (file)
@@ -11,6 +11,7 @@ sv_friction 5
 edgefriction 1
 sv_stepheight 34
 sv_jumpvelocity 300
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate -1
 sv_waterfriction -1
 sv_airaccel_sideways_friction 0
index 3cd9df61ee398d349f77e55b87b43553f76c6b78..5b9b21bf6717c88494704ddc4000065130da157d 100644 (file)
@@ -11,6 +11,7 @@ sv_friction 5
 edgefriction 1
 sv_stepheight 34
 sv_jumpvelocity 300
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate -1
 sv_waterfriction -1
 sv_airaccel_sideways_friction 0
index 17b8de8e4c0a88138d661683a27e63e6f685f74b..4bad86850de9b29cab187f76d94296855f8995e5 100644 (file)
@@ -11,6 +11,7 @@ sv_friction 5
 edgefriction 1
 sv_stepheight 34
 sv_jumpvelocity 300
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate -1
 sv_waterfriction -1
 sv_airaccel_sideways_friction 0
index 168ec2d349a65ebdd3256e7fca7992a4255244b7..531952a36533c992e390871454a511b7cafb0a96 100644 (file)
@@ -11,6 +11,7 @@ sv_friction 5
 edgefriction 1
 sv_stepheight 34
 sv_jumpvelocity 300
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate -1
 sv_waterfriction -1
 sv_airaccel_sideways_friction 0.3
index 6038e7b6af70ea7a654e9c2b5de3105db5a7bbb5..4de91d19a812122e2659726b1214597770626b38 100644 (file)
@@ -11,6 +11,7 @@ sv_friction 7
 edgefriction 1
 sv_stepheight 34
 sv_jumpvelocity 300
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate -1
 sv_waterfriction -1
 sv_airaccel_sideways_friction 0.35
index cb088f9df067d3f0f8360c70941f58787057c707..151d360cb6179aac640729e144bfd9e1d9337406 100644 (file)
@@ -11,6 +11,7 @@ sv_friction 7
 edgefriction 1
 sv_stepheight 34
 sv_jumpvelocity 300
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate -1
 sv_waterfriction -1
 sv_airaccel_sideways_friction 0.35
index 2b054788a1cf314fc17a257c3543c2acb15b500d..7acc35544a1c4211a6859383d45f37debfaa05a5 100644 (file)
@@ -19,6 +19,7 @@ sv_stepheight 26
 // actually, what we want is 266.6666 for 180bpm
 // but 260 takes same amount of frames and is nicer to mappers
 sv_jumpvelocity 260
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate -1
 sv_waterfriction -1
 sv_airaccel_sideways_friction 0
index 526f7e22226d4442519db20892e50c6e6ff5fb9c..a6f36a77e8320a7f8d4719a4f57987b88553cc91 100644 (file)
@@ -24,6 +24,7 @@ sv_stepheight 31
 // this is smaller than 112 qu, so a 112 qu high corridor (7 of 8 grid units in
 // the 16 grid, and the 8th unit used for wall/floor) just lets a player jump!
 sv_jumpvelocity 260
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate -1
 sv_waterfriction -1
 sv_airaccel_sideways_friction 0
index c880f511e4d678f7c958d047ef46062ed283da8e..4f880c90fc128acd897c7007fb9e60769c81ee31 100644 (file)
@@ -12,6 +12,7 @@ sv_friction 4
 edgefriction 1
 sv_stepheight 18
 sv_jumpvelocity 270
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate -1
 sv_waterfriction -1
 sv_airaccel_sideways_friction 0
index 992cb3e62b7740b9015fe88233b9953be0212d47..f45a81b7739fb4f55ed8dd63d5504b01c2a6a236 100644 (file)
@@ -11,6 +11,7 @@ sv_friction 6
 edgefriction 1
 sv_stepheight 18
 sv_jumpvelocity 270
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate -1
 sv_waterfriction 1
 sv_airaccel_sideways_friction 0
index ce323269699ac3a5189d4ec5b399b4ffcb617d22..585262a2ed0080811a1be8d47ac7fa926de4c1b9 100644 (file)
@@ -12,6 +12,7 @@ sv_friction 6
 edgefriction 1
 sv_stepheight 18
 sv_jumpvelocity 270
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate -1
 sv_waterfriction 1
 sv_airaccel_sideways_friction 0
index a6d46ca820ba0022beda3220eaa9e860bac5244e..3ce18b52a98bef30d6c6f32da3ba0fa0a2a22f5d 100644 (file)
@@ -11,6 +11,7 @@ sv_friction 6
 edgefriction 1
 sv_stepheight 18
 sv_jumpvelocity 270
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate 4
 sv_waterfriction 1
 sv_airaccel_sideways_friction 0
index 39b705637a312df7c3cbe1bf69da98c26a76aed0..6b210f55b92e61f893298fc23c2dffb35acd79b1 100644 (file)
@@ -11,6 +11,7 @@ sv_friction 5
 edgefriction 1
 sv_stepheight 34
 sv_jumpvelocity 300
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate -1
 sv_waterfriction -1
 sv_airaccel_sideways_friction 0.8
index 2e092b1230cb00afc2feb3607d529091385dcdf6..e8772b3f3f48037726fb5cfe2397f8a4d8561cff 100644 (file)
@@ -11,6 +11,7 @@ sv_friction 5
 edgefriction 1
 sv_stepheight 34
 sv_jumpvelocity 300
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate -1
 sv_waterfriction -1
 sv_airaccel_sideways_friction 0.5
index 8506e2255c5e54150ed95f66de075d9178402e85..4f22085f9a11d8c1829e2024b4f880f11ceb0347 100644 (file)
@@ -11,6 +11,7 @@ sv_friction 4
 edgefriction 1
 sv_stepheight 34
 sv_jumpvelocity 300
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate -1
 sv_waterfriction -1
 sv_airaccel_sideways_friction 0.3
index 1bd23b647f636fca8aa9d759a133c69bffff0e08..de7352aab54e56de9803d38cdec59a0298038583 100644 (file)
@@ -11,6 +11,7 @@ sv_friction 8
 edgefriction 1
 sv_stepheight 18
 sv_jumpvelocity 280
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate 10
 sv_waterfriction 1
 sv_airaccel_sideways_friction 0
index 985be7e95cf7a1fa82eec39bb8b67c0b6ea172e1..7705a4d512d70856f008aa0d2c1148d282581525 100644 (file)
@@ -11,6 +11,7 @@ sv_friction 8
 edgefriction 1
 sv_stepheight 18
 sv_jumpvelocity 280
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate 10
 sv_waterfriction 1
 sv_airaccel_sideways_friction 0
index dff451087b10aacd24bb57504002da5d61a47717..9009beab33a14e949a7f0d575d67dc7673617387 100644 (file)
@@ -11,6 +11,7 @@ sv_friction 8
 edgefriction 1
 sv_stepheight 18
 sv_jumpvelocity 280
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate 10
 sv_waterfriction 1
 sv_airaccel_sideways_friction 0
index 6b154508ec63d798e0f2ba18eae78287e7f8f971..6076dd73d19702226a4fe2efe3a842c4582a843f 100644 (file)
@@ -25,6 +25,7 @@ sv_stepheight 31
 // this is smaller than 112 qu, so a 112 qu high corridor (7 of 8 grid units in
 // the 16 grid, and the 8th unit used for wall/floor) just lets a player jump!
 sv_jumpvelocity 260
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate -1
 sv_waterfriction -1
 sv_airaccel_sideways_friction 0
index 9c5a7d334a214804d0a84a0daf2a18e768e70be8..a5349c98fdfbd4212256335b808f8df22325e2c1 100644 (file)
@@ -18,6 +18,7 @@ sv_stepheight 26
 // actually, what we want is 266.6666 for 180bpm
 // but 260 takes same amount of frames and is nicer to mappers
 sv_jumpvelocity 260
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate -1
 sv_waterfriction -1
 sv_airaccel_sideways_friction 0
index 1fd86cd8000140b33d0af6001cd0b513ef26a794..8ae771f1cf468a9ac21ec05d4b94cdc4e2782708 100644 (file)
@@ -24,6 +24,7 @@ sv_stepheight 26
 // this is smaller than 112 qu, so a 112 qu high corridor (7 of 8 grid units in
 // the 16 grid, and the 8th unit used for wall/floor) just lets a player jump!
 sv_jumpvelocity 260
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate -1
 sv_waterfriction -1
 sv_airaccel_sideways_friction 0
index e192ab47627b613e10f21974d0c1cdac813dc956..e0bea6a0ea560a17c0c442a280c7293417b84505 100644 (file)
@@ -16,6 +16,7 @@ edgefriction 1
 sv_stepheight 26
 // CPMA: 18
 sv_jumpvelocity 270
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate 4
 sv_waterfriction 1
 sv_airaccel_sideways_friction 0
index 24705deb89a93b23e972c173ec33c476577906c0..f9bed43f6e65096b7edcfa4309e7e969237b2168 100644 (file)
@@ -16,6 +16,7 @@ edgefriction 1
 sv_stepheight 26
 // CPMA: 18
 sv_jumpvelocity 270
+sv_jumpvelocity_crouch 0
 sv_wateraccelerate 4
 sv_waterfriction 1
 sv_airaccel_sideways_friction 0
index d36994ec3d2e69bbc6849d82eaf3272686c01949..9a5335eff06b7c4ca56a73b17d3324b2a2b9618e 100644 (file)
@@ -74,6 +74,9 @@ float race_myrank;
 float nb_pb_period;
 
 // Spectating
+// -1 - observing
+// 0 - playing
+// >0 - id of spectated player
 float spectatee_status;
 
 // short mapname
index 9e8320452a5e7dcf8db20213e12091dbd9ee1216..0636f3f2e972cfc551eb991b4631732d90aa79ab 100644 (file)
@@ -21,10 +21,10 @@ void DrawNadeProgressBar(vector myPos, vector mySize, float progress, vector col
 
 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
@@ -32,7 +32,7 @@ void DrawAmmoItem(vector myPos, vector mySize, .int ammoType, bool isCurrent, bo
        int ammo;
        if(autocvar__hud_configure)
        {
-               isCurrent = (ammoType == ammo_rockets); // Rockets always current
+               isCurrent = (ammoType == RESOURCE_ROCKETS); // Rockets always current
                ammo = 60;
        }
        else
@@ -184,14 +184,14 @@ void HUD_Ammo()
        {
                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
                        );
@@ -206,16 +206,16 @@ void HUD_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
                        );
 
index e1fffb59f34e52684ee1ac0ec2191322e61e8c59..a6c65183d4541cc0cb23c1cdf133e594fc3c1e0c 100644 (file)
@@ -88,10 +88,10 @@ void HUD_Physics()
        const int acc_decimals = 2;
        if(time > physics_update_time)
        {
+               discrete_acceleration = acceleration;
                // workaround for ftos_decimals returning a negative 0
                if(discrete_acceleration > -1 / (10 ** acc_decimals) && discrete_acceleration < 0)
                        discrete_acceleration = 0;
-               discrete_acceleration = acceleration;
                discrete_speed = speed;
                physics_update_time += autocvar_hud_panel_physics_update_interval;
                if(physics_update_time < time)
index a7c0ade00b473de2f7e49cd3604464f657e1fe59..cebd8f5d65c162b6e3d15d90a5e7a496bbbc0858 100644 (file)
@@ -479,21 +479,21 @@ void HUD_Weapons()
                        }
 
                        // 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;
                                        }
 
index ae624ef85c88c1a7c2b6a09d0fb7617629cfb089..609e03775ece1454ceaab0db07e21c7d50ad47f9 100644 (file)
@@ -435,11 +435,10 @@ NET_HANDLE(ENT_CLIENT_SCORES, bool isnew)
        //      RegisterPlayer(o);
        //playerchecker will do this for us later, if it has not already done so
 
-    int sf, lf;
-       sf = ReadShort();
-       lf = ReadShort();
+       int sf = ReadShort();
+       int lf = ReadShort();
        FOREACH(Scores, true, {
-        int p = 1 << (i % 16);
+               int p = 1 << (i % 16);
                if (sf & p)
                {
                        if (lf & p)
@@ -447,7 +446,7 @@ NET_HANDLE(ENT_CLIENT_SCORES, bool isnew)
                        else
                                o.(scores(it)) = ReadChar();
                }
-    });
+       });
 
        return = true;
 
@@ -461,24 +460,21 @@ NET_HANDLE(ENT_CLIENT_TEAMSCORES, bool isnew)
 {
        make_pure(this);
        int i;
-       entity o;
 
        this.team = ReadByte();
-       o = this.owner = GetTeam(this.team, true); // these team numbers can always be trusted
+       entity o = this.owner = GetTeam(this.team, true); // these team numbers can always be trusted
 
-    int sf, lf;
 #if MAX_TEAMSCORE <= 8
-       sf = ReadByte();
-       lf = ReadByte();
+       int sf = ReadByte();
+       int lf = ReadByte();
 #else
-       sf = ReadShort();
-       lf = ReadShort();
+       int sf = ReadShort();
+       int lf = ReadShort();
 #endif
-       int p;
-       for(i = 0, p = 1; i < MAX_TEAMSCORE; ++i, p *= 2)
-               if(sf & p)
+       for(i = 0; i < MAX_TEAMSCORE; ++i)
+               if(sf & BIT(i))
                {
-                       if(lf & p)
+                       if(lf & BIT(i))
                                o.(teamscores(i)) = ReadInt24_t();
                        else
                                o.(teamscores(i)) = ReadChar();
@@ -494,7 +490,7 @@ NET_HANDLE(ENT_CLIENT_CLIENTDATA, bool isnew)
        make_pure(this);
        float newspectatee_status;
 
-    int f = ReadByte();
+       int f = ReadByte();
 
        scoreboard_showscores_force = (f & BIT(0));
 
@@ -551,9 +547,9 @@ NET_HANDLE(ENT_CLIENT_CLIENTDATA, bool isnew)
 NET_HANDLE(ENT_CLIENT_NAGGER, bool isnew)
 {
        make_pure(this);
-    int i, j, b, f;
+       int i, j, b, f;
 
-    int nags = ReadByte(); // NAGS NAGS NAGS NAGS NAGS NAGS NADZ NAGS NAGS NAGS
+       int nags = ReadByte(); // NAGS NAGS NAGS NAGS NAGS NAGS NADZ NAGS NAGS NAGS
 
        if(!(nags & BIT(2)))
        {
@@ -590,7 +586,7 @@ NET_HANDLE(ENT_CLIENT_NAGGER, bool isnew)
                for(i = 1; i <= maxclients; i += 8)
                {
                        f = ReadByte();
-                       for(j = i-1, b = 1; b < 256; b *= 2, ++j)
+                       for(j = i-1, b = BIT(0); b < BIT(8); b <<= 1, ++j)
                                if (!(f & b))
                                        if(playerslots[j])
                                                playerslots[j].ready = 0;
@@ -609,21 +605,24 @@ NET_HANDLE(ENT_CLIENT_NAGGER, bool isnew)
 NET_HANDLE(ENT_CLIENT_ELIMINATEDPLAYERS, bool isnew)
 {
        make_pure(this);
-    int i, j, b, f;
-
-    int sf = ReadByte();
-       if(sf & 1)
-       {
-               for(j = 0; j < maxclients; ++j)
-                       if(playerslots[j])
+       int sf = 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;
@@ -641,7 +640,7 @@ NET_HANDLE(ENT_CLIENT_RANDOMSEED, bool isnew)
 NET_HANDLE(ENT_CLIENT_ACCURACY, bool isnew)
 {
        make_pure(this);
-    int sf = ReadInt24_t();
+       int sf = ReadInt24_t();
        if (sf == 0) {
                for (int w = 0; w <= WEP_LAST - WEP_FIRST; ++w)
                        weapon_accuracy[w] = -1;
@@ -651,7 +650,7 @@ NET_HANDLE(ENT_CLIENT_ACCURACY, bool isnew)
        int f = 1;
        for (int w = 0; w <= WEP_LAST - WEP_FIRST; ++w) {
                if (sf & f) {
-            int b = ReadByte();
+                       int b = ReadByte();
                        if (b == 0)
                                weapon_accuracy[w] = -1;
                        else if (b == 255)
index b34bd0f3a1476b9fda20e2da89b0c7249c648a03..87b6d585b38c769cd8d20d65d0be24ad3303d52d 100644 (file)
@@ -563,7 +563,7 @@ void MapVote_ReadMask()
        int i;
        if ( mv_num_maps < 24 )
        {
-               int mask, power;
+               int mask;
                if(mv_num_maps < 8)
                        mask = ReadByte();
                else if(mv_num_maps < 16)
@@ -571,9 +571,9 @@ void MapVote_ReadMask()
                else
                        mask = ReadLong();
 
-               for(i = 0, power = 1; i < mv_num_maps; ++i, power *= 2)
+               for(i = 0; i < mv_num_maps; ++i)
                {
-                       if ( mask & power )
+                       if (mask & BIT(i))
                                mv_flags[i] |= GTV_AVAILABLE;
                        else
                                mv_flags[i] &= ~GTV_AVAILABLE;
index 14a758b2615a88f9d8bf825eab629dad3f1877ae..0180fea784793f921036cf52bc4a2a91a1ad0aa1 100644 (file)
@@ -9,8 +9,13 @@ REGISTER_NET_TEMP(CLIENT_ENTCS)
 
 /** 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;
 
index 0d6326fef0712b405f0ad88a856c27a61ef646ab..1ba168f0732f4ca2961dfe9738327876efa901e2 100644 (file)
@@ -9,6 +9,7 @@
 #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>
@@ -29,6 +30,7 @@
 #include <common/mutators/mutator/running_guns/_mod.inc>
 #include <common/mutators/mutator/sandbox/_mod.inc>
 #include <common/mutators/mutator/spawn_near_teammate/_mod.inc>
+#include <common/mutators/mutator/stale_move_negation/_mod.inc>
 #include <common/mutators/mutator/superspec/_mod.inc>
 #include <common/mutators/mutator/touchexplode/_mod.inc>
 #include <common/mutators/mutator/vampire/_mod.inc>
index 917dc6557ceb02b2415649bc6d9546e6a400c9c6..e7859d3cb1d12b3814f6af7bdcac2644dba1ce3f 100644 (file)
@@ -9,6 +9,7 @@
 #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>
@@ -29,6 +30,7 @@
 #include <common/mutators/mutator/running_guns/_mod.qh>
 #include <common/mutators/mutator/sandbox/_mod.qh>
 #include <common/mutators/mutator/spawn_near_teammate/_mod.qh>
+#include <common/mutators/mutator/stale_move_negation/_mod.qh>
 #include <common/mutators/mutator/superspec/_mod.qh>
 #include <common/mutators/mutator/touchexplode/_mod.qh>
 #include <common/mutators/mutator/vampire/_mod.qh>
index 61b0c06016923ad311c1d03e15a9adaf2852fe42..1164e0ade66efd9dcf05c924fa76ff109dcd0e65 100644 (file)
@@ -1,6 +1,7 @@
 #include "sv_bloodloss.qh"
 
-REGISTER_MUTATOR(bloodloss, cvar("g_bloodloss"));
+float autocvar_g_bloodloss;
+REGISTER_MUTATOR(bloodloss, autocvar_g_bloodloss);
 
 .float bloodloss_timer;
 
@@ -9,7 +10,7 @@ MUTATOR_HOOKFUNCTION(bloodloss, PlayerPreThink)
        entity player = M_ARGV(0, entity);
 
        if(IS_PLAYER(player))
-       if(player.health <= autocvar_g_bloodloss && !IS_DEAD(player))
+       if(GetResourceAmount(player, RESOURCE_HEALTH) <= autocvar_g_bloodloss && !IS_DEAD(player))
        {
                PHYS_INPUT_BUTTON_CROUCH(player) = true;
 
@@ -28,7 +29,7 @@ MUTATOR_HOOKFUNCTION(bloodloss, PlayerJump)
 {
        entity player = M_ARGV(0, entity);
 
-       if(player.health <= autocvar_g_bloodloss)
+       if(GetResourceAmount(player, RESOURCE_HEALTH) <= autocvar_g_bloodloss)
                return true;
 }
 
index 925525f395a80e672113ff19e6f5768e81ac1d17..e039a96b9501a9e99900884377b625fcab16861c 100644 (file)
@@ -424,12 +424,17 @@ void buff_Medic_Heal(entity this)
 {
        FOREACH_CLIENT(IS_PLAYER(it) && it != this && vdist(it.origin - this.origin, <=, autocvar_g_buffs_medic_heal_range),
        {
-               if(SAME_TEAM(it, this))
-               if(it.health < autocvar_g_balance_health_regenstable)
+               if (!SAME_TEAM(it, this))
                {
-                       Send_Effect(EFFECT_HEALING, it.origin, '0 0 0', 1);
-                       it.health = bound(0, it.health + autocvar_g_buffs_medic_heal_amount, autocvar_g_balance_health_regenstable);
+                       continue;
                }
+               float hp = GetResourceAmount(it, RESOURCE_HEALTH);
+               if(hp >= autocvar_g_balance_health_regenstable)
+               {
+                       continue;
+               }
+               Send_Effect(EFFECT_HEALING, it.origin, '0 0 0', 1);
+               SetResourceAmount(it, RESOURCE_HEALTH, bound(0, hp + autocvar_g_buffs_medic_heal_amount, autocvar_g_balance_health_regenstable));
        });
 }
 
@@ -460,11 +465,11 @@ MUTATOR_HOOKFUNCTION(buffs, Damage_Calculate)
                frag_damage *= autocvar_g_buffs_speed_damage_take;
 
        if(frag_target.buffs & BUFF_MEDIC.m_itemid)
-       if((frag_target.health - frag_damage) <= 0)
+       if((GetResourceAmount(frag_target, RESOURCE_HEALTH) - frag_damage) <= 0)
        if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype))
        if(frag_attacker)
        if(random() <= autocvar_g_buffs_medic_survive_chance)
-               frag_damage = max(5, frag_target.health - autocvar_g_buffs_medic_survive_health);
+               frag_damage = max(5, GetResourceAmount(frag_target, RESOURCE_HEALTH) - autocvar_g_buffs_medic_survive_health);
 
        if(frag_target.buffs & BUFF_JUMP.m_itemid)
        if(frag_deathtype == DEATH_FALL.m_id)
@@ -537,9 +542,15 @@ MUTATOR_HOOKFUNCTION(buffs, Damage_Calculate)
        if(frag_target.takedamage)
        if(DIFF_TEAM(frag_attacker, frag_target))
        {
-               frag_attacker.health = bound(0, frag_attacker.health + bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal, frag_target.health), g_pickup_healthsmall_max);
-               if(frag_target.armorvalue)
-                       frag_attacker.armorvalue = bound(0, frag_attacker.armorvalue + bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal, frag_target.armorvalue), g_pickup_armorsmall_max);
+               float amount = bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal,
+                       GetResourceAmount(frag_target, RESOURCE_HEALTH));
+               GiveResourceWithLimit(frag_attacker, RESOURCE_HEALTH, amount, g_pickup_healthsmall_max);
+               if (frag_target.armorvalue)
+               {
+                       amount = bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal,
+                               GetResourceAmount(frag_target, RESOURCE_ARMOR));
+                       GiveResourceWithLimit(frag_attacker, RESOURCE_ARMOR, amount, g_pickup_armorsmall_max);
+               }
        }
 
        M_ARGV(4, float) = frag_damage;
index 52fc52466d5f93ec8203f3b104ac2b72e295dba1..987645aaa0c719a5546ceeb03d47ee9b2901c804 100644 (file)
@@ -1,10 +1,11 @@
 #include "sv_campcheck.qh"
 
+string autocvar_g_campcheck;
 float autocvar_g_campcheck_damage;
 float autocvar_g_campcheck_distance;
 float autocvar_g_campcheck_interval;
 
-REGISTER_MUTATOR(campcheck, cvar("g_campcheck"));
+REGISTER_MUTATOR(campcheck, expr_evaluate(autocvar_g_campcheck));
 
 .float campcheck_nextcheck;
 .float campcheck_traveled_distance;
@@ -64,7 +65,7 @@ MUTATOR_HOOKFUNCTION(campcheck, PlayerPreThink)
                                if(player.vehicle)
                                        Damage(player.vehicle, NULL, NULL, autocvar_g_campcheck_damage * 2, DEATH_CAMP.m_id, player.vehicle.origin, '0 0 0');
                                else
-                                       Damage(player, NULL, NULL, bound(0, autocvar_g_campcheck_damage, player.health + player.armorvalue * autocvar_g_balance_armor_blockpercent + 5), DEATH_CAMP.m_id, player.origin, '0 0 0');
+                                       Damage(player, NULL, NULL, bound(0, autocvar_g_campcheck_damage, GetResourceAmount(player, RESOURCE_HEALTH) + GetResourceAmount(player, RESOURCE_ARMOR) * autocvar_g_balance_armor_blockpercent + 5), DEATH_CAMP.m_id, player.origin, '0 0 0');
                        }
                        player.campcheck_nextcheck = time + autocvar_g_campcheck_interval;
                        player.campcheck_traveled_distance = 0;
index eb45d4baf40a5950f81bdb466c0305cf616da70e..a1fe27a877c3e46e5e4dfe59a15239c583930f0a 100644 (file)
@@ -1,6 +1,7 @@
 #include "sv_cloaked.qh"
 
-REGISTER_MUTATOR(cloaked, cvar("g_cloaked"));
+string autocvar_g_cloaked;
+REGISTER_MUTATOR(cloaked, expr_evaluate(autocvar_g_cloaked));
 
 float autocvar_g_balance_cloaked_alpha;
 
index 53042b8ef2779199c1a8412c138eacc2ab9c59bf..0977b62cebe380b5172e0ef03c6d11f38546da54 100644 (file)
@@ -232,7 +232,9 @@ NET_HANDLE(damagetext, bool isNew)
             }
         }
         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 - 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);
         IL_EACH(g_drawables_2d, it.instanceOfDamageText && it.m_screen_coords && it.m_group == server_entity_index, {
index 70aac4d9a3fcda663448d035199f3d46b93dc1a0..6640cb8bf23a51a48c08cdd4662fbd89a4fcefcf 100644 (file)
@@ -1,32 +1,42 @@
 #include "sv_dodging.qh"
 
+// TODO the CSQC blocks in this sv_ file are currently not compiled but will be when dodging prediction gets enabled
+
 #define PHYS_DODGING                                           g_dodging
 #define PHYS_DODGING_DELAY                                     autocvar_sv_dodging_delay
 #define PHYS_DODGING_DISTANCE_THRESHOLD        autocvar_sv_dodging_wall_distance_threshold
-#define PHYS_DODGING_FROZEN_NODOUBLETAP                autocvar_sv_dodging_frozen_doubletap
+#define PHYS_DODGING_FROZEN_DOUBLETAP          autocvar_sv_dodging_frozen_doubletap
 #define PHYS_DODGING_HEIGHT_THRESHOLD          autocvar_sv_dodging_height_threshold
-#define PHYS_DODGING_HORIZ_SPEED                       autocvar_sv_dodging_horiz_speed
-#define PHYS_DODGING_HORIZ_SPEED_FROZEN        autocvar_sv_dodging_horiz_speed_frozen
+#define PHYS_DODGING_HORIZ_SPEED_MIN           autocvar_sv_dodging_horiz_speed_min
+#define PHYS_DODGING_HORIZ_SPEED_MAX           autocvar_sv_dodging_horiz_speed_max
+#define PHYS_DODGING_HORIZ_FORCE_SLOWEST       autocvar_sv_dodging_horiz_force_slowest
+#define PHYS_DODGING_HORIZ_FORCE_FASTEST       autocvar_sv_dodging_horiz_force_fastest
+#define PHYS_DODGING_HORIZ_FORCE_FROZEN        autocvar_sv_dodging_horiz_force_frozen
 #define PHYS_DODGING_RAMP_TIME                                 autocvar_sv_dodging_ramp_time
 #define PHYS_DODGING_UP_SPEED                          autocvar_sv_dodging_up_speed
 #define PHYS_DODGING_WALL                                      autocvar_sv_dodging_wall_dodging
 #define PHYS_DODGING_AIR                                       autocvar_sv_dodging_air_dodging
 #define PHYS_DODGING_MAXSPEED                          autocvar_sv_dodging_maxspeed
+#define PHYS_DODGING_AIR_MAXSPEED                      autocvar_sv_dodging_air_maxspeed
 
 // we ran out of stats slots! TODO: re-enable this when prediction is available for dodging
 #if 0
 #define PHYS_DODGING                                           STAT(DODGING, this)
 #define PHYS_DODGING_DELAY                                     STAT(DODGING_DELAY, this)
 #define PHYS_DODGING_DISTANCE_THRESHOLD        STAT(DODGING_DISTANCE_THRESHOLD, this)
-#define PHYS_DODGING_FROZEN_NODOUBLETAP                STAT(DODGING_FROZEN_NO_DOUBLETAP, this)
+#define PHYS_DODGING_FROZEN_DOUBLETAP          STAT(DODGING_FROZEN_DOUBLETAP, this)
 #define PHYS_DODGING_HEIGHT_THRESHOLD          STAT(DODGING_HEIGHT_THRESHOLD, this)
-#define PHYS_DODGING_HORIZ_SPEED                       STAT(DODGING_HORIZ_SPEED, this)
-#define PHYS_DODGING_HORIZ_SPEED_FROZEN        STAT(DODGING_HORIZ_SPEED_FROZEN, this)
+#define PHYS_DODGING_HORIZ_SPEED_MIN           STAT(DODGING_HORIZ_SPEED_MIN, this)
+#define PHYS_DODGING_HORIZ_SPEED_MAX           STAT(DODGING_HORIZ_SPEED_MAX, this)
+#define PHYS_DODGING_HORIZ_FORCE_SLOWEST       STAT(DODGING_HORIZ_FORCE_SLOWEST, this)
+#define PHYS_DODGING_HORIZ_FORCE_FASTEST       STAT(DODGING_HORIZ_FORCE_FASTEST, this)
+#define PHYS_DODGING_HORIZ_FORCE_FROZEN        STAT(DODGING_HORIZ_FORCE_FROZEN, this)
 #define PHYS_DODGING_RAMP_TIME                                 STAT(DODGING_RAMP_TIME, this)
 #define PHYS_DODGING_UP_SPEED                          STAT(DODGING_UP_SPEED, this)
 #define PHYS_DODGING_WALL                                      STAT(DODGING_WALL, this)
 #define PHYS_DODGING_AIR                                       STAT(DODGING_AIR, this)
 #define PHYS_DODGING_MAXSPEED                          STAT(DODGING_MAXSPEED, this)
+#define PHYS_DODGING_AIR_MAXSPEED                      STAT(DODGING_AIR_MAXSPEED, this)
 #endif
 
 #ifdef CSQC
 
 bool autocvar_sv_dodging_sound;
 
-// set to 1 to indicate dodging has started.. reset by physics hook after dodge has been done..
-.float dodging_action;
-
-// the jump part of the dodge cannot be ramped
-.float dodging_single_action;
-
 #include <common/animdecide.qh>
 #include <common/physics/player.qh>
 
@@ -81,7 +85,6 @@ REGISTER_MUTATOR(dodging, true);
 // the jump part of the dodge cannot be ramped
 .float dodging_single_action;
 
-
 // these are used to store the last key press time for each of the keys..
 .float last_FORWARD_KEY_time;
 .float last_BACKWARD_KEY_time;
@@ -95,123 +98,126 @@ REGISTER_MUTATOR(dodging, true);
 // and to ramp up the dodge acceleration in the physics hook.
 .float last_dodging_time;
 
-// This is the velocity gain to be added over the ramp time.
-// It will decrease from frame to frame during dodging_action = 1
-// until it's 0.
-.float dodging_velocity_gain;
+// the total speed that will be added over the ramp time
+.float dodging_force_total;
+// the part of total yet to be added
+.float dodging_force_remaining;
 
 #ifdef CSQC
 .int pressedkeys;
 #endif
 
-// returns true if the player is close to a wall
-bool check_close_to_wall(entity this, float threshold)
-{
-       if (PHYS_DODGING_WALL == 0) { return false; }
-
 #define X(dir) \
        tracebox(this.origin, this.mins, this.maxs, this.origin + threshold * dir, true, this); \
        if (trace_fraction < 1 && !(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)) \
                return true;
 
+// returns true if the player is close to a wall
+bool is_close_to_wall(entity this, float threshold)
+{
        X(v_right);
        X(-v_right);
        X(v_forward);
        X(-v_forward);
-#undef X
 
        return false;
 }
 
-bool check_close_to_ground(entity this, float threshold)
+bool is_close_to_ground(entity this, float threshold)
 {
-       return IS_ONGROUND(this) ? true : false;
+       if (IS_ONGROUND(this)) return true;
+       X(-v_up); // necessary for dodging down a slope using doubletap (using `+dodge` works anyway)
+
+       return false;
+}
+
+#undef X
+
+float determine_force(entity player) {
+       if (PHYS_FROZEN(player)) return PHYS_DODGING_HORIZ_FORCE_FROZEN;
+
+       float horiz_vel = vlen(vec2(player.velocity));
+       return map_bound_ranges(horiz_vel,
+                               PHYS_DODGING_HORIZ_SPEED_MIN, PHYS_DODGING_HORIZ_SPEED_MAX,
+                               PHYS_DODGING_HORIZ_FORCE_SLOWEST, PHYS_DODGING_HORIZ_FORCE_FASTEST);
 }
 
 bool PM_dodging_checkpressedkeys(entity this)
 {
-       if(!PHYS_DODGING)
-               return false;
-
        bool frozen_dodging = (PHYS_FROZEN(this) && PHYS_DODGING_FROZEN(this));
-       bool frozen_no_doubletap = (frozen_dodging && !PHYS_DODGING_FROZEN_NODOUBLETAP);
+       bool frozen_no_doubletap = (frozen_dodging && !PHYS_DODGING_FROZEN_DOUBLETAP);
+
+       float tap_direction_x = 0;
+       float tap_direction_y = 0;
+       bool dodge_detected = false;
+       vector mymovement = PHYS_CS(this).movement;
 
-       // first check if the last dodge is far enough back in time so we can dodge again
+       #define X(COND,BTN,RESULT)                                                                                                                                                              \
+       if (mymovement_##COND) {                                                                                                                                                                \
+               /* is this a state change? */                                                                                                                                           \
+               if(!(PHYS_DODGING_PRESSED_KEYS(this) & KEY_##BTN) || frozen_no_doubletap) {                                                     \
+                       tap_direction_##RESULT;                                                                                                                                                 \
+                       if ((time - this.last_##BTN##_KEY_time) < PHYS_DODGING_TIMEOUT(this) || frozen_no_doubletap) {  \
+                               dodge_detected = true;                                                                                                                                          \
+                       } else if(PHYS_INPUT_BUTTON_DODGE(this)) {                                                                                                              \
+                               dodge_detected = true;                                                                                                                                          \
+                       }                                                                                                                                                                                               \
+                       this.last_##BTN##_KEY_time = time;                                                                                                                              \
+               }                                                                                                                                                                                                       \
+       }
+       X(x < 0, BACKWARD,      x--);
+       X(x > 0, FORWARD,       x++);
+       X(y < 0, LEFT,          y--);
+       X(y > 0, RIGHT,         y++);
+       #undef X
+
+       if (!dodge_detected) return false;
+
+       // this check has to be after checking keys:
+       // the first key press of the double tap is allowed to be before dodging delay,
+       // only the second has to be after, otherwise +dodge gives an advantage because typical repress time is 0.1 s
+       // or higher which means players using +dodge would be able to do it more often
        if ((time - this.last_dodging_time) < PHYS_DODGING_DELAY)
                return false;
 
        makevectors(this.angles);
 
-       bool wall_dodge = false;
+       bool can_dodge = (is_close_to_ground(this, PHYS_DODGING_HEIGHT_THRESHOLD) && (PHYS_DODGING_MAXSPEED == 0 || vdist(this.velocity, <, PHYS_DODGING_MAXSPEED)));
+       bool can_wall_dodge = (PHYS_DODGING_WALL && is_close_to_wall(this, PHYS_DODGING_DISTANCE_THRESHOLD));
+       bool can_air_dodge = (PHYS_DODGING_AIR && (PHYS_DODGING_AIR_MAXSPEED == 0 || vdist(this.velocity, <, PHYS_DODGING_AIR_MAXSPEED)));
+       if (!can_dodge && !can_wall_dodge && !can_air_dodge) return false;
 
-       if(!PHYS_DODGING_AIR)
-       if(!check_close_to_ground(this, PHYS_DODGING_HEIGHT_THRESHOLD))
-       {
-               wall_dodge = check_close_to_wall(this, PHYS_DODGING_DISTANCE_THRESHOLD);
-               if(!wall_dodge) // we're not on the ground, and wall dodging isn't allowed, end it!
-                       return true;
-       }
+       this.last_dodging_time = time;
 
-       if(!wall_dodge && PHYS_DODGING_MAXSPEED && vdist(this.velocity, >, PHYS_DODGING_MAXSPEED))
-               return false;
+       this.dodging_action = 1;
+       this.dodging_single_action = 1;
 
-       float tap_direction_x = 0;
-       float tap_direction_y = 0;
-       bool dodge_detected = false;
-       vector mymovement = PHYS_CS(this).movement;
+       this.dodging_force_total = determine_force(this);
+       this.dodging_force_remaining = this.dodging_force_total;
 
-       #define X(COND,BTN,RESULT)                                                                                                                      \
-       if (mymovement_##COND)                                                                                          \
-               /* is this a state change? */                                                                                                   \
-               if(!(PHYS_DODGING_PRESSED_KEYS(this) & KEY_##BTN) || frozen_no_doubletap) {             \
-                               tap_direction_##RESULT;                                                                                                 \
-                               if ((time - this.last_##BTN##_KEY_time) < PHYS_DODGING_TIMEOUT(this) || frozen_no_doubletap)    \
-                                       dodge_detected = true;                                                                                          \
-                               if(PHYS_INPUT_BUTTON_DODGE(this))                                                                               \
-                                       dodge_detected = true;                                                                                          \
-                               this.last_##BTN##_KEY_time = time;                                                                              \
-               }
-       X(x < 0, BACKWARD,      x--);
-       X(x > 0, FORWARD,       x++);
-       X(y < 0, LEFT,          y--);
-       X(y > 0, RIGHT,         y++);
-       #undef X
-
-       if (dodge_detected)
-       {
-               this.last_dodging_time = time;
-
-               this.dodging_action = 1;
-               this.dodging_single_action = 1;
+       this.dodging_direction.x = tap_direction_x;
+       this.dodging_direction.y = tap_direction_y;
 
-               this.dodging_velocity_gain = PHYS_DODGING_HORIZ_SPEED;
+       // normalize the dodging_direction vector.. (unlike UT99) XD
+       float length = sqrt(this.dodging_direction.x ** 2 + this.dodging_direction.y ** 2);
 
-               this.dodging_direction_x = tap_direction_x;
-               this.dodging_direction_y = tap_direction_y;
+       this.dodging_direction.x = this.dodging_direction.x / length;
+       this.dodging_direction.y = this.dodging_direction.y / length;
 
-               // normalize the dodging_direction vector.. (unlike UT99) XD
-               float length = this.dodging_direction_x * this.dodging_direction_x
-                                       + this.dodging_direction_y * this.dodging_direction_y;
-               length = sqrt(length);
-
-               this.dodging_direction_x = this.dodging_direction_x * 1.0 / length;
-               this.dodging_direction_y = this.dodging_direction_y * 1.0 / length;
-               return true;
-       }
-       return false;
+       return true;
 }
 
 void PM_dodging(entity this)
 {
-       if (!PHYS_DODGING)
-               return;
+       // can't use return value from PM_dodging_checkpressedkeys because they're called from different hooks
+       if (!this.dodging_action) return;
 
        // when swimming or dead, no dodging allowed..
        if (this.waterlevel >= WATERLEVEL_SWIMMING || IS_DEAD(this))
        {
                this.dodging_action = 0;
-               this.dodging_direction_x = 0;
-               this.dodging_direction_y = 0;
+               this.dodging_direction.x = 0;
+               this.dodging_direction.y = 0;
                return;
        }
 
@@ -221,31 +227,18 @@ void PM_dodging(entity this)
        else
                makevectors(this.angles);
 
+       // fraction of the force to apply each frame
        // if we have e.g. 0.5 sec ramptime and a frametime of 0.25, then the ramp code
        // will be called ramp_time/frametime times = 2 times. so, we need to
        // add 0.5 * the total speed each frame until the dodge action is done..
        float common_factor = PHYS_DODGING_FRAMETIME / PHYS_DODGING_RAMP_TIME;
+       // NOTE: depending on cl_netfps the client may (and probably will) send more input frames during each server frame
+       // but common_factor uses server frame rate so players with higher cl_netfps will ramp slightly faster
 
-       // if ramp time is smaller than frametime we get problems ;D
-       common_factor = min(common_factor, 1);
-
-       float horiz_speed = PHYS_FROZEN(this) ? PHYS_DODGING_HORIZ_SPEED_FROZEN : PHYS_DODGING_HORIZ_SPEED;
-       float new_velocity_gain = this.dodging_velocity_gain - (common_factor * horiz_speed);
-       new_velocity_gain = max(0, new_velocity_gain);
-
-       float velocity_difference = this.dodging_velocity_gain - new_velocity_gain;
-
-       // ramp up dodging speed by adding some velocity each frame.. TODO: do it! :D
-       if (this.dodging_action == 1)
-       {
-               //disable jump key during dodge accel phase
-               if(PHYS_CS(this).movement.z > 0) { PHYS_CS(this).movement_z = 0; }
-
-               this.velocity += ((this.dodging_direction_y * velocity_difference) * v_right)
-                                       + ((this.dodging_direction_x * velocity_difference) * v_forward);
-
-               this.dodging_velocity_gain = this.dodging_velocity_gain - velocity_difference;
-       }
+       float velocity_increase = min(common_factor * this.dodging_force_total, this.dodging_force_remaining);
+       this.dodging_force_remaining -= velocity_increase;
+       this.velocity += this.dodging_direction.x * velocity_increase * v_forward
+                      + this.dodging_direction.y * velocity_increase * v_right;
 
        // the up part of the dodge is a single shot action
        if (this.dodging_single_action == 1)
@@ -264,21 +257,18 @@ void PM_dodging(entity this)
                this.dodging_single_action = 0;
        }
 
-       // are we done with the dodging ramp yet?
-       if((this.dodging_action == 1) && ((time - this.last_dodging_time) > PHYS_DODGING_RAMP_TIME))
+       if(this.dodging_force_remaining <= 0)
        {
                // reset state so next dodge can be done correctly
                this.dodging_action = 0;
-               this.dodging_direction_x = 0;
-               this.dodging_direction_y = 0;
+               this.dodging_direction.x = 0;
+               this.dodging_direction.y = 0;
        }
 }
 
+#ifdef CSQC
 void PM_dodging_GetPressedKeys(entity this)
 {
-#ifdef CSQC
-       if(!PHYS_DODGING) { return; }
-
        PM_dodging_checkpressedkeys(this);
 
        int keys = this.pressedkeys;
@@ -292,15 +282,16 @@ void PM_dodging_GetPressedKeys(entity this)
        keys = BITSET(keys, KEY_ATCK,           PHYS_INPUT_BUTTON_ATCK(this));
        keys = BITSET(keys, KEY_ATCK2,          PHYS_INPUT_BUTTON_ATCK2(this));
        this.pressedkeys = keys;
-#endif
 }
+#endif
 
 MUTATOR_HOOKFUNCTION(dodging, PlayerPhysics)
 {
-    entity player = M_ARGV(0, entity);
+       entity player = M_ARGV(0, entity);
 
-       // print("dodging_PlayerPhysics\n");
+#ifdef CSQC
        PM_dodging_GetPressedKeys(player);
+#endif
        PM_dodging(player);
 }
 
@@ -308,13 +299,6 @@ MUTATOR_HOOKFUNCTION(dodging, PlayerPhysics)
 
 REPLICATE(cvar_cl_dodging_timeout, float, "cl_dodging_timeout");
 
-MUTATOR_HOOKFUNCTION(dodging, PlayerPreThink)
-{
-       entity player = M_ARGV(0, entity);
-
-       STAT(DODGING_TIMEOUT, player) = CS(player).cvar_cl_dodging_timeout;
-}
-
 MUTATOR_HOOKFUNCTION(dodging, GetPressedKeys)
 {
        entity player = M_ARGV(0, entity);
diff --git a/qcsrc/common/mutators/mutator/dynamic_handicap/_mod.inc b/qcsrc/common/mutators/mutator/dynamic_handicap/_mod.inc
new file mode 100644 (file)
index 0000000..f4b0a30
--- /dev/null
@@ -0,0 +1,4 @@
+// generated file; do not modify
+#ifdef SVQC
+    #include <common/mutators/mutator/dynamic_handicap/sv_dynamic_handicap.qc>
+#endif
diff --git a/qcsrc/common/mutators/mutator/dynamic_handicap/_mod.qh b/qcsrc/common/mutators/mutator/dynamic_handicap/_mod.qh
new file mode 100644 (file)
index 0000000..10aeb22
--- /dev/null
@@ -0,0 +1,4 @@
+// generated file; do not modify
+#ifdef SVQC
+    #include <common/mutators/mutator/dynamic_handicap/sv_dynamic_handicap.qh>
+#endif
diff --git a/qcsrc/common/mutators/mutator/dynamic_handicap/sv_dynamic_handicap.qc b/qcsrc/common/mutators/mutator/dynamic_handicap/sv_dynamic_handicap.qc
new file mode 100644 (file)
index 0000000..d5d3ba4
--- /dev/null
@@ -0,0 +1,116 @@
+#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();
+}
diff --git a/qcsrc/common/mutators/mutator/dynamic_handicap/sv_dynamic_handicap.qh b/qcsrc/common/mutators/mutator/dynamic_handicap/sv_dynamic_handicap.qh
new file mode 100644 (file)
index 0000000..6f70f09
--- /dev/null
@@ -0,0 +1 @@
+#pragma once
index 5dfdf4386642933e5d98404e5be6914d785c2f0c..c3967811973a9ec009bbe17d120a0be38f86387d 100644 (file)
@@ -5,7 +5,7 @@
 #ifdef SVQC
 AUTOCVAR(g_grappling_hook_useammo, bool, false, "Use ammunition with the off-hand grappling hook");
 
-REGISTER_MUTATOR(hook, cvar("g_grappling_hook")) {
+REGISTER_MUTATOR(hook, expr_evaluate(cvar_string("g_grappling_hook"))) {
     MUTATOR_ONADD {
         g_grappling_hook = true;
         if(!autocvar_g_grappling_hook_useammo)
index fefad540b24c6b18cb76a1f1d22196023458ce9e..1561dc10db9a326acf932de70df86365e179bd4f 100644 (file)
@@ -1,5 +1,10 @@
 #include "sv_instagib.qh"
 
+bool autocvar_g_instagib_damagedbycontents = true;
+bool autocvar_g_instagib_blaster_keepdamage = false;
+bool autocvar_g_instagib_blaster_keepforce = false;
+bool autocvar_g_instagib_mirrordamage;
+bool autocvar_g_instagib_friendlypush = true;
 //int autocvar_g_instagib_ammo_drop;
 bool autocvar_g_instagib_ammo_convert_cells;
 bool autocvar_g_instagib_ammo_convert_rockets;
@@ -12,7 +17,7 @@ float autocvar_g_instagib_speed_highspeed;
 
 #include <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)
 {
@@ -55,7 +60,7 @@ void instagib_ammocheck(entity this)
 
        if(IS_DEAD(this) || game_stopped)
                instagib_stop_countdown(this);
-       else if (this.ammo_cells > 0 || (this.items & IT_UNLIMITED_WEAPON_AMMO) || (this.flags & FL_GODMODE))
+       else if (GetResourceAmount(this, RESOURCE_CELLS) > 0 || (this.items & IT_UNLIMITED_WEAPON_AMMO) || (this.flags & FL_GODMODE))
                instagib_stop_countdown(this);
        else if(autocvar_g_rm && autocvar_g_rm_laser)
        {
@@ -67,53 +72,54 @@ void instagib_ammocheck(entity this)
        }
        else
        {
+               float hp = GetResourceAmount(this, RESOURCE_HEALTH);
                this.instagib_needammo = true;
-               if (this.health <= 5)
+               if (hp <= 5)
                {
                        Damage(this, this, this, 5, DEATH_NOAMMO.m_id, this.origin, '0 0 0');
                        Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_INSTAGIB_TERMINATED);
                }
-               else if (this.health <= 10)
+               else if (hp <= 10)
                {
                        Damage(this, this, this, 5, DEATH_NOAMMO.m_id, this.origin, '0 0 0');
                        Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_1);
                }
-               else if (this.health <= 20)
+               else if (hp <= 20)
                {
                        Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0');
                        Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_2);
                }
-               else if (this.health <= 30)
+               else if (hp <= 30)
                {
                        Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0');
                        Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_3);
                }
-               else if (this.health <= 40)
+               else if (hp <= 40)
                {
                        Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0');
                        Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_4);
                }
-               else if (this.health <= 50)
+               else if (hp <= 50)
                {
                        Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0');
                        Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_5);
                }
-               else if (this.health <= 60)
+               else if (hp <= 60)
                {
                        Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0');
                        Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_6);
                }
-               else if (this.health <= 70)
+               else if (hp <= 70)
                {
                        Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0');
                        Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_7);
                }
-               else if (this.health <= 80)
+               else if (hp <= 80)
                {
                        Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0');
                        Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_8);
                }
-               else if (this.health <= 90)
+               else if (hp <= 90)
                {
                        Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_INSTAGIB_FINDAMMO);
                        Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0');
@@ -289,13 +295,15 @@ MUTATOR_HOOKFUNCTION(mutator_instagib, Damage_Calculate)
                        if(!autocvar_g_instagib_friendlypush && SAME_TEAM(frag_target, frag_attacker))
                                frag_force = '0 0 0';
 
-                       if(frag_target.armorvalue)
+                       float armor = GetResourceAmount(frag_target, RESOURCE_ARMOR);
+                       if(armor)
                        {
-                               frag_target.armorvalue -= 1;
+                               armor -= 1;
+                               SetResourceAmount(frag_target, RESOURCE_ARMOR, armor);
                                frag_damage = 0;
                                frag_target.damage_dealt += 1;
                                frag_attacker.damage_dealt += 1;
-                               Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_INSTAGIB_LIVES_REMAINING, frag_target.armorvalue);
+                               Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_INSTAGIB_LIVES_REMAINING, armor);
                        }
                }
 
@@ -312,7 +320,7 @@ MUTATOR_HOOKFUNCTION(mutator_instagib, Damage_Calculate)
 
                                if(frag_target != frag_attacker)
                                {
-                                       if(frag_damage <= 0 && frag_target.health > 0) { Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_SECONDARY_NODAMAGE); }
+                                       if(frag_damage <= 0 && GetResourceAmount(frag_target, RESOURCE_HEALTH) > 0) { Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_SECONDARY_NODAMAGE); }
                                        if(!autocvar_g_instagib_blaster_keepforce)
                                                frag_force = '0 0 0';
                                }
@@ -325,10 +333,12 @@ MUTATOR_HOOKFUNCTION(mutator_instagib, Damage_Calculate)
        if(frag_mirrordamage > 0)
        {
                // just lose extra LIVES, don't kill the player for mirror damage
-               if(frag_attacker.armorvalue > 0)
+               float armor = GetResourceAmount(frag_attacker, RESOURCE_ARMOR);
+               if(armor > 0)
                {
-                       frag_attacker.armorvalue -= 1;
-                       Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_INSTAGIB_LIVES_REMAINING, frag_attacker.armorvalue);
+                       armor -= 1;
+                       SetResourceAmount(frag_attacker, RESOURCE_ARMOR, armor);
+                       Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_INSTAGIB_LIVES_REMAINING, armor);
                        frag_attacker.damage_dealt += frag_mirrordamage;
                }
                frag_mirrordamage = 0;
@@ -415,7 +425,7 @@ MUTATOR_HOOKFUNCTION(mutator_instagib, FilterItem)
 
        if(item.weapon == WEP_VAPORIZER.m_id && item.classname == "droppedweapon")
        {
-               item.ammo_cells = autocvar_g_instagib_ammo_drop;
+               SetResourceAmount(item, RESOURCE_CELLS, autocvar_g_instagib_ammo_drop);
                return false;
        }
 
@@ -428,10 +438,11 @@ MUTATOR_HOOKFUNCTION(mutator_instagib, FilterItem)
        if(item.flags & FL_POWERUP)
                return false;
 
-       if(item.ammo_cells > autocvar_g_instagib_ammo_drop && item.classname != "item_minst_cells")
-               item.ammo_cells = autocvar_g_instagib_ammo_drop;
+       float cells = GetResourceAmount(item, RESOURCE_CELLS);
+       if(cells > autocvar_g_instagib_ammo_drop && item.classname != "item_minst_cells")
+               SetResourceAmount(item, RESOURCE_CELLS, autocvar_g_instagib_ammo_drop);
 
-       if(item.ammo_cells && !item.weapon)
+       if(cells && !item.weapon)
                return false;
 
        return true;
@@ -464,26 +475,27 @@ MUTATOR_HOOKFUNCTION(mutator_instagib, ItemTouch)
        entity item = M_ARGV(0, entity);
        entity toucher = M_ARGV(1, entity);
 
-       if(item.ammo_cells)
+       if(GetResourceAmount(item, RESOURCE_CELLS))
        {
                // play some cool sounds ;)
+               float hp = GetResourceAmount(toucher, RESOURCE_HEALTH);
                if (IS_CLIENT(toucher))
                {
-                       if(toucher.health <= 5)
+                       if(hp <= 5)
                                Send_Notification(NOTIF_ONE, toucher, MSG_ANNCE, ANNCE_INSTAGIB_LASTSECOND);
-                       else if(toucher.health < 50)
+                       else if(hp < 50)
                                Send_Notification(NOTIF_ONE, toucher, MSG_ANNCE, ANNCE_INSTAGIB_NARROWLY);
                }
 
-               if(toucher.health < 100)
-                       toucher.health = 100;
+               if(hp < 100)
+                       SetResourceAmount(toucher, RESOURCE_HEALTH, 100);
 
                return MUT_ITEMTOUCH_CONTINUE;
        }
 
        if(item.itemdef == ITEM_ExtraLife)
        {
-               toucher.armorvalue = bound(toucher.armorvalue, 999, toucher.armorvalue + autocvar_g_instagib_extralives);
+               GiveResource(toucher, RESOURCE_ARMOR, autocvar_g_instagib_extralives);
                Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_EXTRALIVES);
                return MUT_ITEMTOUCH_PICKUP;
        }
index 23e0d0d85033ac05004679c72606a9f005eb9f8c..e68c687bdeb65058bac0c887c5f8aab8967bd560 100644 (file)
@@ -1,6 +1,7 @@
 #include "sv_invincibleproj.qh"
 
-REGISTER_MUTATOR(invincibleprojectiles, cvar("g_invincible_projectiles"));
+string autocvar_g_invincible_projectiles;
+REGISTER_MUTATOR(invincibleprojectiles, expr_evaluate(autocvar_g_invincible_projectiles));
 
 MUTATOR_HOOKFUNCTION(invincibleprojectiles, EditProjectile)
 {
index a542921221a6a37bc7bddf4bb485f26fdcbd8d08..ac06a8f7747160259588667e74a20ba33878da9e 100644 (file)
@@ -1,6 +1,7 @@
 #include "sv_melee_only.qh"
 
-REGISTER_MUTATOR(melee_only, cvar("g_melee_only") && !cvar("g_instagib") && !cvar("g_overkill") && !g_nexball);
+string autocvar_g_melee_only;
+REGISTER_MUTATOR(melee_only, expr_evaluate(autocvar_g_melee_only) && !cvar("g_instagib") && !cvar("g_overkill") && !g_nexball);
 
 MUTATOR_HOOKFUNCTION(melee_only, SetStartItems, CBC_ORDER_LAST)
 {
index 92bbacc00663c2eb70d50dda92b913874eb2493d..54b3673c6e245331c69b72dc69a0bffaa3f5ad13 100644 (file)
@@ -1,8 +1,9 @@
 #include "sv_midair.qh"
 
+string autocvar_g_midair;
 float autocvar_g_midair_shieldtime;
 
-REGISTER_MUTATOR(midair, cvar("g_midair"));
+REGISTER_MUTATOR(midair, expr_evaluate(autocvar_g_midair));
 
 .float midair_shieldtime;
 
index 47dcfd4afd550230ab85fbff4872d03cdc6e13bd..081a1fdb6fd69d7767de22b0ebecd8c8445e93b4 100644 (file)
@@ -9,7 +9,7 @@
 
 
 #if defined(SVQC)
-REGISTER_MUTATOR(multijump, cvar("g_multijump"));
+REGISTER_MUTATOR(multijump, autocvar_g_multijump);
 #elif defined(CSQC)
 REGISTER_MUTATOR(multijump, true);
 #endif
index 18edd482d3c5b942092032ae2bf1371be5d2a2cf..92e16b48d22e04bc65a49f33b12dbdacbf17eee3 100644 (file)
@@ -81,7 +81,7 @@ MUTATOR_HOOKFUNCTION(cl_nades, EditProjectile)
 
        entity nade_type = Nade_FromProjectile(proj.cnt);
        if (nade_type == NADE_TYPE_Null) return;
-       if(STAT(NADES_SMALL, NULL))
+       if(STAT(NADES_SMALL))
        {
                proj.mins = '-8 -8 -8';
                proj.maxs = '8 8 8';
@@ -150,7 +150,7 @@ void DrawAmmoNades(vector myPos, vector mySize, bool draw_expanding, float expan
 #include <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;
@@ -431,7 +431,7 @@ void nade_ice_think(entity this)
 
        float current_freeze_time = this.ltime - time - 0.1;
 
-       FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_nades_nade_radius, it != this && it.takedamage && !IS_DEAD(it) && it.health > 0 && current_freeze_time > 0,
+       FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_nades_nade_radius, it != this && it.takedamage && !IS_DEAD(it) && GetResourceAmount(it, RESOURCE_HEALTH) > 0 && current_freeze_time > 0,
        {
                if(!autocvar_g_nades_ice_teamcheck || (DIFF_TEAM(it, this.realowner) || it == this.realowner))
                if(!it.revival_time || ((time - it.revival_time) >= 1.5))
@@ -623,13 +623,15 @@ void nade_heal_touch(entity this, entity toucher)
                if ( health_factor > 0 )
                {
                        maxhealth = (IS_MONSTER(toucher)) ? toucher.max_health : g_pickup_healthmega_max;
-                       if ( toucher.health < maxhealth )
+                       float hp = GetResourceAmount(toucher, RESOURCE_HEALTH);
+                       if (hp < maxhealth)
                        {
-                               if ( this.nade_show_particles )
+                               if (this.nade_show_particles)
+                               {
                                        Send_Effect(EFFECT_HEALING, toucher.origin, '0 0 0', 1);
-                               toucher.health = min(toucher.health+health_factor, maxhealth);
+                               }
+                               GiveResourceWithLimit(toucher, RESOURCE_HEALTH, health_factor, maxhealth);
                        }
-                       toucher.pauserothealth_finished = max(toucher.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
                }
                else if ( health_factor < 0 )
                {
@@ -768,7 +770,7 @@ void nade_touch(entity this, entity toucher)
 
        if(autocvar_g_nades_pickup)
        if(time >= this.spawnshieldtime)
-       if(!toucher.nade && this.health == this.max_health) // no boosted shot pickups, thank you very much
+       if(!toucher.nade && GetResourceAmount(this, RESOURCE_HEALTH) == this.max_health) // no boosted shot pickups, thank you very much
        if(CanThrowNade(toucher)) // prevent some obvious things, like dead players
        if(IS_REAL_CLIENT(toucher)) // above checks for IS_PLAYER, don't need to do it here
        {
@@ -796,7 +798,7 @@ void nade_touch(entity this, entity toucher)
 
        //setsize(this, '-2 -2 -2', '2 2 2');
        //UpdateCSQCProjectile(this);
-       if(this.health == this.max_health)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) == this.max_health)
        {
                spamsound(this, CH_SHOTS, SND_GRENADE_BOUNCE_RANDOM(), VOL_BASE, ATTEN_NORM);
                return;
@@ -860,19 +862,22 @@ void nade_damage(entity this, entity inflictor, entity attacker, float damage, i
        if(damage <= 0 || ((IS_ONGROUND(this)) && IS_PLAYER(attacker)))
                return;
 
-       if(this.health == this.max_health)
+       float hp = GetResourceAmount(this, RESOURCE_HEALTH);
+       if(hp == this.max_health)
        {
                sound(this, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, 0.5 *(ATTEN_LARGE + ATTEN_MAX));
                this.nextthink = max(time + this.nade_lifetime, time);
                setthink(this, nade_beep);
        }
 
-       this.health -= damage;
+       hp -= damage;
+       SetResourceAmount(this, RESOURCE_HEALTH, hp);
+
 
        if ( this.nade_type != NADE_TYPE_HEAL.m_id || IS_PLAYER(attacker) )
                this.realowner = attacker;
 
-       if(this.health <= 0)
+       if(hp <= 0)
                W_PrepareExplosionByDamage(this, attacker, nade_boom);
        else
                nade_burn_spawn(this);
@@ -928,7 +933,7 @@ void toss_nade(entity e, bool set_owner, vector _velocity, float _time)
 
        settouch(_nade, nade_touch);
        _nade.spawnshieldtime = time + 0.1; // prevent instantly picking up again
-       _nade.health = autocvar_g_nades_nade_health;
+       SetResourceAmount(_nade, RESOURCE_HEALTH, autocvar_g_nades_nade_health);
        _nade.max_health = _nade.health;
        _nade.takedamage = DAMAGE_AIM;
        _nade.event_damage = nade_damage;
@@ -1294,7 +1299,7 @@ MUTATOR_HOOKFUNCTION(nades, PlayerPreThink)
        if(n && STAT(FROZEN, player) == 3) // OK, there is at least one teammate reviving us
        {
                player.revive_progress = bound(0, player.revive_progress + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
-               player.health = max(1, player.revive_progress * start_health);
+               SetResourceAmount(player, RESOURCE_HEALTH, max(1, player.revive_progress * start_health));
 
                if(player.revive_progress >= 1)
                {
@@ -1411,7 +1416,7 @@ MUTATOR_HOOKFUNCTION(nades, Damage_Calculate)
        if(time - frag_inflictor.toss_time <= 0.1)
        {
                Unfreeze(frag_target);
-               frag_target.health = autocvar_g_freezetag_revive_nade_health;
+               SetResourceAmount(frag_target, RESOURCE_HEALTH, autocvar_g_freezetag_revive_nade_health);
                Send_Effect(EFFECT_ICEORGLASS, frag_target.origin, '0 0 0', 3);
                M_ARGV(4, float) = 0;
                M_ARGV(6, vector) = '0 0 0';
index 288c2d5c8363ea473d824edf867275700221b677..af364995a1e92bc13c42b287babb5e3471938441 100644 (file)
@@ -68,9 +68,11 @@ roflsound "New toys, new toys!" sound.
 
 */
 
+string autocvar_g_new_toys;
+
 bool nt_IsNewToy(int w);
 
-REGISTER_MUTATOR(nt, cvar("g_new_toys") && !cvar("g_instagib") && !cvar("g_overkill"))
+REGISTER_MUTATOR(nt, expr_evaluate(autocvar_g_new_toys) && !cvar("g_instagib") && !cvar("g_overkill"))
 {
        MUTATOR_ONADD
        {
index 425b8c22b0e66f12e20115acbdc5a4cd6bdd0330..886740aedcea031e9d31632b17c3a47fb7937fa4 100644 (file)
@@ -1,5 +1,6 @@
 #include "sv_nix.qh"
 
+string autocvar_g_nix;
 int autocvar_g_balance_nix_ammo_cells;
 int autocvar_g_balance_nix_ammo_plasma;
 int autocvar_g_balance_nix_ammo_fuel;
@@ -35,7 +36,7 @@ float nix_nextweapon;
 
 bool NIX_CanChooseWeapon(int wpn);
 
-REGISTER_MUTATOR(nix, cvar("g_nix") && !cvar("g_instagib") && !cvar("g_overkill"))
+REGISTER_MUTATOR(nix, expr_evaluate(autocvar_g_nix) && !cvar("g_instagib") && !cvar("g_overkill"))
 {
        MUTATOR_ONADD
        {
@@ -56,12 +57,12 @@ REGISTER_MUTATOR(nix, cvar("g_nix") && !cvar("g_instagib") && !cvar("g_overkill"
        {
                // as the PlayerSpawn hook will no longer run, NIX is turned off by this!
                FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), {
-                       it.ammo_cells = start_ammo_cells;
-                       it.ammo_plasma = start_ammo_plasma;
-                       it.ammo_shells = start_ammo_shells;
-                       it.ammo_nails = start_ammo_nails;
-                       it.ammo_rockets = start_ammo_rockets;
-                       it.ammo_fuel = start_ammo_fuel;
+                       SetResourceAmount(it, RESOURCE_SHELLS, start_ammo_shells);
+                       SetResourceAmount(it, RESOURCE_BULLETS, start_ammo_nails);
+                       SetResourceAmount(it, RESOURCE_ROCKETS, start_ammo_rockets);
+                       SetResourceAmount(it, RESOURCE_CELLS, start_ammo_cells);
+                       SetResourceAmount(it, RESOURCE_PLASMA, start_ammo_plasma);
+                       SetResourceAmount(it, RESOURCE_FUEL, start_ammo_fuel);
                        it.weapons = start_weapons;
                        for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
                        {
@@ -133,30 +134,34 @@ void NIX_GiveCurrentWeapon(entity this)
 
        if(nix_nextchange != this.nix_lastchange_id) // this shall only be called once per round!
        {
-               this.ammo_shells = this.ammo_nails = this.ammo_rockets = this.ammo_cells = this.ammo_plasma = this.ammo_fuel = 0;
-
+               SetResourceAmount(this, RESOURCE_SHELLS, 0);
+               SetResourceAmount(this, RESOURCE_BULLETS, 0);
+               SetResourceAmount(this, RESOURCE_ROCKETS, 0);
+               SetResourceAmount(this, RESOURCE_CELLS, 0);
+               SetResourceAmount(this, RESOURCE_PLASMA, 0);
+               SetResourceAmount(this, RESOURCE_FUEL, 0);
                if(this.items & IT_UNLIMITED_WEAPON_AMMO)
                {
-                       switch(e.ammo_field)
+                       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;
                        }
                }
 
@@ -202,14 +207,14 @@ void NIX_GiveCurrentWeapon(entity this)
 
        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;
index caf96a67b3fc7e11a7dad4b8a01d2587908e4eee..41327956f383b062deb21356e896170cb57c82f9 100644 (file)
@@ -83,7 +83,7 @@ METHOD(HeavyMachineGun, wr_think, void(entity thiswep, entity actor, .entity wea
 
 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);
@@ -93,7 +93,7 @@ METHOD(HeavyMachineGun, wr_checkammo1, bool(entity thiswep, entity actor, .entit
 
 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);
index 99eb4c8091f4d12ab72e674fdb5405be04ca0a95..07b31ea3cd107c6e9af918b749b17d2ac4c0673f 100644 (file)
@@ -1,7 +1,7 @@
 #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);
index e9a5ce2c3fc01b3a39a0ab055234b5f23170b4fd..f901d11593d2951ccf0e542002be2d4230886f85 100644 (file)
@@ -140,7 +140,7 @@ METHOD(RocketPropelledChainsaw, wr_think, void(entity thiswep, entity actor, .en
 
 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;
 }
index 417e9a6612883bd69363451566c6392f17dcecb8..560f7f4fa4313cdb68a2b69f1dc0306a696b96e0 100644 (file)
@@ -1,7 +1,7 @@
 #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);
index d2a7308dedf04267af31ea7b599a4abd447ca08f..b47e587511643e3076e172835052c16787d3079d 100644 (file)
@@ -3,6 +3,8 @@
 #include "hmg.qh"
 #include "rpc.qh"
 
+string autocvar_g_overkill;
+
 bool autocvar_g_overkill_powerups_replace;
 
 bool autocvar_g_overkill_itemwaypoints = true;
@@ -18,7 +20,7 @@ bool autocvar_g_overkill_filter_armormega;
 
 void ok_Initialize();
 
-REGISTER_MUTATOR(ok, cvar("g_overkill") && !cvar("g_instagib") && !g_nexball && cvar_string("g_mod_balance") == "Overkill")
+REGISTER_MUTATOR(ok, expr_evaluate(autocvar_g_overkill) && !cvar("g_instagib") && !g_nexball && cvar_string("g_mod_balance") == "Overkill")
 {
        MUTATOR_ONADD
        {
index 62781c92073cfbd11278ea0313364bdac5bc0d8e..38cd7474b786c34343e4acf30009421d3fb8ec05 100644 (file)
@@ -4,7 +4,7 @@ int autocvar_g_physical_items;
 float autocvar_g_physical_items_damageforcescale;
 float autocvar_g_physical_items_reset;
 
-REGISTER_MUTATOR(physical_items, cvar("g_physical_items"))
+REGISTER_MUTATOR(physical_items, autocvar_g_physical_items)
 {
        // check if we have a physics engine
        MUTATOR_ONADD
index 53e4f49e60aadf08b13f9eb0880d3fe18711acd1..1084ff77895aa079e2dd9104ffe1159551f54db2 100644 (file)
@@ -1,6 +1,7 @@
 #include "sv_pinata.qh"
 
-REGISTER_MUTATOR(pinata, cvar("g_pinata") && !cvar("g_instagib") && !cvar("g_overkill"));
+string autocvar_g_pinata;
+REGISTER_MUTATOR(pinata, expr_evaluate(autocvar_g_pinata) && !cvar("g_instagib") && !cvar("g_overkill"));
 
 MUTATOR_HOOKFUNCTION(pinata, PlayerDies)
 {
index 9f0d8fbf0d5ec4907204169f4a7e53d17e338dbb..d3c1922b997c9437ab82ba9f9c27603d1ed14e1f 100644 (file)
@@ -1,6 +1,7 @@
 #include "sv_rocketflying.qh"
 
-REGISTER_MUTATOR(rocketflying, cvar("g_rocket_flying"));
+string autocvar_g_rocket_flying;
+REGISTER_MUTATOR(rocketflying, expr_evaluate(autocvar_g_rocket_flying));
 
 MUTATOR_HOOKFUNCTION(rocketflying, EditProjectile)
 {
index 93dc602f0299df56c21597ea6cf966e951d2db78..d121cf1094685ac9263f55143b59da831cf37610 100644 (file)
@@ -1,5 +1,6 @@
 #include "sv_sandbox.qh"
 
+string autocvar_g_sandbox;
 int autocvar_g_sandbox_info;
 bool autocvar_g_sandbox_readonly;
 string autocvar_g_sandbox_storage_name;
@@ -18,7 +19,7 @@ float autocvar_g_sandbox_object_material_velocity_factor;
 float autosave_time;
 void sandbox_Database_Load();
 
-REGISTER_MUTATOR(sandbox, cvar("g_sandbox"))
+REGISTER_MUTATOR(sandbox, expr_evaluate(autocvar_g_sandbox))
 {
        MUTATOR_ONADD
        {
index 9ff3644748112eaad8c6dc1894487b52afe4dd21..61c302c3e7e0fa74bdd67d0a81a67ea00012efe1 100644 (file)
@@ -2,6 +2,7 @@
 
 #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;
@@ -10,7 +11,7 @@ float autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death;
 bool autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health;
 bool autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath;
 
-REGISTER_MUTATOR(spawn_near_teammate, cvar("g_spawn_near_teammate"));
+REGISTER_MUTATOR(spawn_near_teammate, expr_evaluate(autocvar_g_spawn_near_teammate));
 
 .entity msnt_lookat;
 
@@ -20,6 +21,8 @@ REGISTER_MUTATOR(spawn_near_teammate, cvar("g_spawn_near_teammate"));
 
 MUTATOR_HOOKFUNCTION(spawn_near_teammate, Spawn_Score)
 {
+       if (!teamplay) return;
+
        entity player = M_ARGV(0, entity);
        entity spawn_spot = M_ARGV(1, entity);
        vector spawn_score = M_ARGV(2, vector);
@@ -29,9 +32,6 @@ MUTATOR_HOOKFUNCTION(spawn_near_teammate, Spawn_Score)
 
        spawn_spot.msnt_lookat = NULL;
 
-       if(!teamplay)
-               return;
-
        RandomSelection_Init();
        FOREACH_CLIENT(IS_PLAYER(it) && it != player && SAME_TEAM(it, player) && !IS_DEAD(it), {
                if(vdist(spawn_spot.origin - it.origin, >, autocvar_g_spawn_near_teammate_distance))
@@ -56,7 +56,8 @@ MUTATOR_HOOKFUNCTION(spawn_near_teammate, Spawn_Score)
 
 MUTATOR_HOOKFUNCTION(spawn_near_teammate, PlayerSpawn)
 {
-       if(!teamplay) { return; }
+       if (!teamplay) return;
+
        entity player = M_ARGV(0, entity);
        entity spawn_spot = M_ARGV(1, entity);
 
diff --git a/qcsrc/common/mutators/mutator/stale_move_negation/_mod.inc b/qcsrc/common/mutators/mutator/stale_move_negation/_mod.inc
new file mode 100644 (file)
index 0000000..0e94058
--- /dev/null
@@ -0,0 +1,4 @@
+// generated file; do not modify
+#ifdef SVQC
+    #include <common/mutators/mutator/stale_move_negation/sv_stale_move_negation.qc>
+#endif
diff --git a/qcsrc/common/mutators/mutator/stale_move_negation/_mod.qh b/qcsrc/common/mutators/mutator/stale_move_negation/_mod.qh
new file mode 100644 (file)
index 0000000..a2c60b4
--- /dev/null
@@ -0,0 +1,4 @@
+// generated file; do not modify
+#ifdef SVQC
+    #include <common/mutators/mutator/stale_move_negation/sv_stale_move_negation.qh>
+#endif
diff --git a/qcsrc/common/mutators/mutator/stale_move_negation/sv_stale_move_negation.qc b/qcsrc/common/mutators/mutator/stale_move_negation/sv_stale_move_negation.qc
new file mode 100644 (file)
index 0000000..c54a8a6
--- /dev/null
@@ -0,0 +1,49 @@
+#include "sv_stale_move_negation.qh"
+
+AUTOCVAR(g_smneg, bool, false, "Stale-move negation: penalize repeated use of the same weapon");
+AUTOCVAR(g_smneg_bonus, bool, true, "Stale-move negation: allow weapons to become stronger than their baseline");
+AUTOCVAR(g_smneg_bonus_asymptote, float, 4, "Stale-move negation: damage = infinity at this bonus level");
+AUTOCVAR(g_smneg_cooldown_factor, float, 1 / 4, "Stale-move negation: penalty cooldown factor");
+REGISTER_MUTATOR(mutator_smneg, autocvar_g_smneg);
+
+MUTATOR_HOOKFUNCTION(mutator_smneg, BuildMutatorsString) {
+    M_ARGV(0, string) = strcat(M_ARGV(0, string), ":StaleMoveNegation");
+}
+
+MUTATOR_HOOKFUNCTION(mutator_smneg, BuildMutatorsPrettyString) {
+    M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Stale-move negation");
+}
+
+.float x_smneg_weight[Weapons_MAX];
+
+float smneg_multiplier(float weight) {
+    float a = autocvar_g_smneg_bonus_asymptote;
+    float x = max(
+        (!autocvar_g_smneg_bonus ? 0 : (-a + .1)),
+        weight / start_health
+    );
+    float z = (M_PI / 5) * a;
+    float f = (x > 0)
+        ? (atan(z / x) / (M_PI / 2))
+        : (tan(-(x / z)) + 1);
+    return f;
+}
+
+MUTATOR_HOOKFUNCTION(mutator_smneg, Damage_Calculate) {
+    float deathtype = M_ARGV(3, float);
+    Weapon w = DEATH_WEAPONOF(deathtype);
+    if (w == WEP_Null) return;
+
+    entity frag_attacker = M_ARGV(1, entity);
+    entity c = CS(frag_attacker);
+    float weight = c.x_smneg_weight[w.m_id];
+    float f = smneg_multiplier(weight);
+    float frag_damage = M_ARGV(4, float) = f * M_ARGV(4, float);
+    M_ARGV(6, vector) = f * M_ARGV(6, vector); // force
+
+    c.x_smneg_weight[w.m_id] = weight + frag_damage;
+    float restore = frag_damage * autocvar_g_smneg_cooldown_factor;
+    FOREACH(Weapons, it != WEP_Null && it != w, {
+        c.x_smneg_weight[it.m_id] -= restore;
+    });
+}
diff --git a/qcsrc/common/mutators/mutator/stale_move_negation/sv_stale_move_negation.qh b/qcsrc/common/mutators/mutator/stale_move_negation/sv_stale_move_negation.qh
new file mode 100644 (file)
index 0000000..6f70f09
--- /dev/null
@@ -0,0 +1 @@
+#pragma once
index 4c99095b79df43f68e4daa402d97599cda72ebae..eb20082359ec1f25f92643bdd3f6a2b4624ebe17 100644 (file)
@@ -1,6 +1,7 @@
 #include "sv_superspec.qh"
 
-REGISTER_MUTATOR(superspec, cvar("g_superspectate"));
+string autocvar_g_superspectate;
+REGISTER_MUTATOR(superspec, expr_evaluate(autocvar_g_superspectate));
 
 #define _SSMAGIX "SUPERSPEC_OPTIONSFILE_V1"
 #define _ISLOCAL(ent) ((edict_num(1) == (ent)) ? true : false)
index 3e6edb04b356cf4b722504cbe2d5b04af8f494c8..a1b38fb6e750a2a44b6638189b6d18f3d89a3f0d 100644 (file)
@@ -1,11 +1,12 @@
 #include "sv_touchexplode.qh"
 
+string autocvar_g_touchexplode;
 float autocvar_g_touchexplode_radius;
 float autocvar_g_touchexplode_damage;
 float autocvar_g_touchexplode_edgedamage;
 float autocvar_g_touchexplode_force;
 
-REGISTER_MUTATOR(touchexplode, cvar("g_touchexplode"));
+REGISTER_MUTATOR(touchexplode, expr_evaluate(autocvar_g_touchexplode));
 
 .float touchexplode_time;
 
index 92c5943c3d75455f7bc3d41b6f95184df7ba8555..199b4e202a7351a9b2d25bfb4364ec02119a259d 100644 (file)
@@ -1,6 +1,7 @@
 #include "sv_vampire.qh"
 
-REGISTER_MUTATOR(vampire, cvar("g_vampire") && !cvar("g_instagib"));
+string autocvar_g_vampire;
+REGISTER_MUTATOR(vampire, expr_evaluate(autocvar_g_vampire) && !cvar("g_instagib"));
 
 MUTATOR_HOOKFUNCTION(vampire, PlayerDamage_SplitHealthArmor)
 {
@@ -12,7 +13,8 @@ MUTATOR_HOOKFUNCTION(vampire, PlayerDamage_SplitHealthArmor)
        if(frag_target != frag_attacker)
        if(!IS_DEAD(frag_target))
        {
-               GivePlayerHealth(frag_attacker, bound(0, damage_take, frag_target.health));
+               GiveResource(frag_attacker, RESOURCE_HEALTH,
+                       bound(0, damage_take, frag_target.health));
        }
 }
 
index e2b0f57d760e8cd13ba17925c0211809f31f8d3b..ce9e270654cb498281e01db990aa652e0ae8f8c1 100644 (file)
@@ -1,6 +1,7 @@
 #include "sv_vampirehook.qh"
 
-REGISTER_MUTATOR(vh, cvar("g_vampirehook"));
+string autocvar_g_vampirehook;
+REGISTER_MUTATOR(vh, expr_evaluate(autocvar_g_vampirehook));
 
 bool autocvar_g_vampirehook_teamheal;
 float autocvar_g_vampirehook_damage;
index b0d95ea29eb784570f48fb4621724dd930e0e292..c462a7e2b7c869398e733019a107f69049d16472 100644 (file)
@@ -4,7 +4,7 @@
 #ifdef CSQC
 REGISTER_MUTATOR(walljump, true);
 #elif defined(SVQC)
-REGISTER_MUTATOR(walljump, cvar("g_walljump"));
+REGISTER_MUTATOR(walljump, autocvar_g_walljump);
 #endif
 
 #define PHYS_WALLJUMP(s)                                               STAT(WALLJUMP, s)
index cba6023bda8b10aadfcf99df8bdc8a47ffe22561..3b5492ac2d0adab12235d3512c13177e179b412b 100644 (file)
     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"), "")
index dbd765d983df928a9e6b262121cd9526b4813286..85912ee1c33f915ebdf59f907c616ef2f571c4f3 100644 (file)
@@ -13,18 +13,18 @@ const int WATERLEVEL_SUBMERGED = 3;
 #define SET_ONSLICK(s)                                         ((s).flags |= FL_ONSLICK)
 #define UNSET_ONSLICK(s)                                       ((s).flags &= ~FL_ONSLICK)
 
-#define GAMEPLAYFIX_DOWNTRACEONGROUND(s)    STAT(GAMEPLAYFIX_DOWNTRACEONGROUND, NULL)
-#define GAMEPLAYFIX_EASIERWATERJUMP(s)      STAT(GAMEPLAYFIX_EASIERWATERJUMP, NULL)
-#define GAMEPLAYFIX_STEPDOWN(s)             STAT(GAMEPLAYFIX_STEPDOWN, NULL)
-#define GAMEPLAYFIX_STEPMULTIPLETIMES(s)    STAT(GAMEPLAYFIX_STEPMULTIPLETIMES, NULL)
-#define GAMEPLAYFIX_UNSTICKPLAYERS(s)       STAT(GAMEPLAYFIX_UNSTICKPLAYERS, NULL)
-#define GAMEPLAYFIX_WATERTRANSITION(s)                 STAT(GAMEPLAYFIX_WATERTRANSITION, NULL)
-#define UPWARD_VELOCITY_CLEARS_ONGROUND(s)  STAT(GAMEPLAYFIX_UPVELOCITYCLEARSONGROUND, NULL)
-
-#define PHYS_STEPHEIGHT(s)                  STAT(MOVEVARS_STEPHEIGHT, NULL)
-#define PHYS_NOSTEP(s)                      STAT(NOSTEP, NULL)
-#define PHYS_JUMPSTEP(s)                    STAT(MOVEVARS_JUMPSTEP, NULL)
-#define PHYS_WALLFRICTION(s)                STAT(MOVEVARS_WALLFRICTION, NULL)
+#define GAMEPLAYFIX_DOWNTRACEONGROUND(s)    STAT(GAMEPLAYFIX_DOWNTRACEONGROUND)
+#define GAMEPLAYFIX_EASIERWATERJUMP(s)      STAT(GAMEPLAYFIX_EASIERWATERJUMP)
+#define GAMEPLAYFIX_STEPDOWN(s)             STAT(GAMEPLAYFIX_STEPDOWN)
+#define GAMEPLAYFIX_STEPMULTIPLETIMES(s)    STAT(GAMEPLAYFIX_STEPMULTIPLETIMES)
+#define GAMEPLAYFIX_UNSTICKPLAYERS(s)       STAT(GAMEPLAYFIX_UNSTICKPLAYERS)
+#define GAMEPLAYFIX_WATERTRANSITION(s)                 STAT(GAMEPLAYFIX_WATERTRANSITION)
+#define UPWARD_VELOCITY_CLEARS_ONGROUND(s)  STAT(GAMEPLAYFIX_UPVELOCITYCLEARSONGROUND)
+
+#define PHYS_STEPHEIGHT(s)                  STAT(MOVEVARS_STEPHEIGHT)
+#define PHYS_NOSTEP(s)                      STAT(NOSTEP)
+#define PHYS_JUMPSTEP(s)                    STAT(MOVEVARS_JUMPSTEP)
+#define PHYS_WALLFRICTION(s)                STAT(MOVEVARS_WALLFRICTION)
 
 #ifdef CSQC
 .float bouncestop;
index 20e2d4cc32f16d0b736e421d0a1172262492ff7c..20c580a8591b0e371981d671ba208a459229e2b2 100644 (file)
@@ -75,6 +75,7 @@ void Physics_UpdateStats(entity this)
        STAT(MOVEVARS_AIRACCELERATE, this) = Physics_ClientOption(this, "airaccelerate", autocvar_sv_airaccelerate);
        STAT(MOVEVARS_AIRSTOPACCELERATE, this) = Physics_ClientOption(this, "airstopaccelerate", autocvar_sv_airstopaccelerate);
        STAT(MOVEVARS_JUMPVELOCITY, this) = Physics_ClientOption(this, "jumpvelocity", autocvar_sv_jumpvelocity);
+       STAT(MOVEVARS_JUMPVELOCITY_CROUCH, this) = Physics_ClientOption(this, "jumpvelocity_crouch", autocvar_sv_jumpvelocity_crouch);
        STAT(MOVEVARS_TRACK_CANJUMP, this) = Physics_ClientOption(this, "track_canjump", autocvar_sv_track_canjump);
 }
 #endif
@@ -303,7 +304,7 @@ bool PlayerJump(entity this)
 #endif
 
        bool doublejump = false;
-       float mjumpheight = PHYS_JUMPVELOCITY(this);
+       float mjumpheight = ((PHYS_JUMPVELOCITY_CROUCH(this) && IS_DUCKED(this)) ? PHYS_JUMPVELOCITY_CROUCH(this) : PHYS_JUMPVELOCITY(this));
        bool track_jump = PHYS_CL_TRACK_CANJUMP(this);
 
        if (MUTATOR_CALLHOOK(PlayerJump, this, mjumpheight, doublejump))
index d5a8e605af8a5b1721fa4d478b35d7de88727cc7..0ebea585f80e61071cedd24e4c8e2d22abdec445 100644 (file)
@@ -80,8 +80,9 @@ bool IsFlying(entity a);
 #define PHYS_JETPACK_MAXSPEED_UP(s)         STAT(JETPACK_MAXSPEED_UP, s)
 #define PHYS_JETPACK_REVERSE_THRUST(s)         STAT(JETPACK_REVERSE_THRUST, s)
 
-#define PHYS_JUMPSPEEDCAP_DISABLE_ONRAMPS(s) STAT(MOVEVARS_JUMPSPEEDCAP_DISABLE_ONRAMPS, NULL)
+#define PHYS_JUMPSPEEDCAP_DISABLE_ONRAMPS(s) STAT(MOVEVARS_JUMPSPEEDCAP_DISABLE_ONRAMPS)
 #define PHYS_JUMPVELOCITY(s)                STAT(MOVEVARS_JUMPVELOCITY, s)
+#define PHYS_JUMPVELOCITY_CROUCH(s)         STAT(MOVEVARS_JUMPVELOCITY_CROUCH, s)
 
 #define PHYS_MAXAIRSPEED(s)                 STAT(MOVEVARS_MAXAIRSPEED, s)
 #define PHYS_MAXAIRSTRAFESPEED(s)           STAT(MOVEVARS_MAXAIRSTRAFESPEED, s)
@@ -97,7 +98,7 @@ bool IsFlying(entity a);
 #define PHYS_WARSOWBUNNY_TOPSPEED(s)        STAT(MOVEVARS_WARSOWBUNNY_TOPSPEED, s)
 #define PHYS_WARSOWBUNNY_TURNACCEL(s)       STAT(MOVEVARS_WARSOWBUNNY_TURNACCEL, s)
 
-#define PHYS_SLICK_APPLYGRAVITY(s)             STAT(SLICK_APPLYGRAVITY, NULL)
+#define PHYS_SLICK_APPLYGRAVITY(s)             STAT(SLICK_APPLYGRAVITY)
 
 #define PHYS_INPUT_BUTTON_ATCK(s)           PHYS_INPUT_BUTTON_BUTTON1(s)
 #define PHYS_INPUT_BUTTON_JUMP(s)           PHYS_INPUT_BUTTON_BUTTON2(s)
index d8af629624b5e8ea10c22f7cc446681fb8346362..0fcd70dfc08fd8e7afd9bdc2cd2b31c063331c79 100644 (file)
@@ -93,7 +93,6 @@ void PlayerStats_GameReport_AddEvent(string event_id)
        }
 }
 
-// referred to by PS_GR_P_ADDVAL and PS_GR_T_ADDVAL
 float PlayerStats_GameReport_Event(string prefix, string event_id, float value)
 {
        if((prefix == "") || PS_GR_OUT_DB < 0) { return 0; }
@@ -108,7 +107,8 @@ float PlayerStats_GameReport_Event(string prefix, string event_id, float value)
 void PlayerStats_GameReport_Accuracy(entity p)
 {
        #define ACCMAC(suffix, field) \
-               PS_GR_P_ADDVAL(p, sprintf("acc-%s-%s", it.netname, suffix), CS(p).accuracy.(field[i-1]));
+               PlayerStats_GameReport_Event_Player(p, \
+                       sprintf("acc-%s-%s", it.netname, suffix), CS(p).accuracy.(field[i-1]));
        FOREACH(Weapons, it != WEP_Null, {
                ACCMAC("hit", accuracy_hit)
                ACCMAC("fired", accuracy_fired)
@@ -126,7 +126,7 @@ void PlayerStats_GameReport_FinalizePlayer(entity p)
        // add global info!
        if(p.alivetime)
        {
-               PS_GR_P_ADDVAL(p, PLAYERSTATS_ALIVETIME, time - p.alivetime);
+               PlayerStats_GameReport_Event_Player(p, PLAYERSTATS_ALIVETIME, time - p.alivetime);
                p.alivetime = 0;
        }
 
@@ -139,7 +139,7 @@ void PlayerStats_GameReport_FinalizePlayer(entity p)
                db_put(PS_GR_OUT_DB, sprintf("%s:_team", p.playerstats_id), ftos(p.team));
 
        if(stof(db_get(PS_GR_OUT_DB, sprintf("%s:%s", p.playerstats_id, PLAYERSTATS_ALIVETIME))) > 0)
-               PS_GR_P_ADDVAL(p, PLAYERSTATS_JOINS, 1);
+               PlayerStats_GameReport_Event_Player(p, PLAYERSTATS_JOINS, 1);
 
        PlayerStats_GameReport_Accuracy(p);
        anticheat_report_to_playerstats(p);
@@ -149,7 +149,8 @@ void PlayerStats_GameReport_FinalizePlayer(entity p)
                if(CS(p).latency_cnt)
                {
                        float latency = (CS(p).latency_sum / CS(p).latency_cnt);
-                       if(latency) { PS_GR_P_ADDVAL(p, PLAYERSTATS_AVGLATENCY, latency); }
+                       if(latency)
+                               PlayerStats_GameReport_Event_Player(p, PLAYERSTATS_AVGLATENCY, latency);
                }
        }
 
@@ -167,16 +168,16 @@ void PlayerStats_GameReport(float finished)
 
        FOREACH_CLIENT(true, {
                // add personal score rank
-               PS_GR_P_ADDVAL(it, PLAYERSTATS_RANK, it.score_dummyfield);
+               PlayerStats_GameReport_Event_Player(it, PLAYERSTATS_RANK, it.score_dummyfield);
 
                // scoreboard data
                if(it.scoreboard_pos)
                {
                        // scoreboard is valid!
-                       PS_GR_P_ADDVAL(it, PLAYERSTATS_SCOREBOARD_VALID, 1);
+                       PlayerStats_GameReport_Event_Player(it, PLAYERSTATS_SCOREBOARD_VALID, 1);
 
                        // add scoreboard position
-                       PS_GR_P_ADDVAL(it, PLAYERSTATS_SCOREBOARD_POS, it.scoreboard_pos);
+                       PlayerStats_GameReport_Event_Player(it, PLAYERSTATS_SCOREBOARD_POS, it.scoreboard_pos);
 
                        // add scoreboard data
                        PlayerScore_PlayerStats(it);
@@ -184,8 +185,8 @@ void PlayerStats_GameReport(float finished)
                        // if the match ended normally, add winning info
                        if(finished)
                        {
-                               PS_GR_P_ADDVAL(it, PLAYERSTATS_WINS, it.winning);
-                               PS_GR_P_ADDVAL(it, PLAYERSTATS_MATCHES, 1);
+                               PlayerStats_GameReport_Event_Player(it, PLAYERSTATS_WINS, it.winning);
+                               PlayerStats_GameReport_Event_Player(it, PLAYERSTATS_MATCHES, 1);
                        }
                }
 
index 1fafb13978dc9c43c1467ff3accb10089c55b6ad..28f985e27d24a214afc7519a956f70dd79526cef 100644 (file)
@@ -69,8 +69,8 @@ void PlayerStats_GameReport_AddTeam(float t);
 void PlayerStats_GameReport_AddEvent(string event_id);
 
 // call on each event to track, or at player disconnect OR match end for "global stuff"
-#define PS_GR_P_ADDVAL(ent,eventid,val) PlayerStats_GameReport_Event(ent.playerstats_id, eventid, val)
-#define PS_GR_T_ADDVAL(team,eventid,val) PlayerStats_GameReport_Event(sprintf("team#%d", team), eventid, val)
+#define PlayerStats_GameReport_Event_Player(ent, eventid, val) PlayerStats_GameReport_Event(ent.playerstats_id, eventid, val)
+#define PlayerStats_GameReport_Event_Team(team, eventid, val) PlayerStats_GameReport_Event(sprintf("team#%d", team), eventid, val)
 float PlayerStats_GameReport_Event(string prefix, string event_id, float value);
 
 void PlayerStats_GameReport_Accuracy(entity p);
diff --git a/qcsrc/common/resources.qh b/qcsrc/common/resources.qh
new file mode 100644 (file)
index 0000000..a562292
--- /dev/null
@@ -0,0 +1,20 @@
+#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).
+};
index 94e408d7f614be2d769fc229625a390bfd357ba0..8da6bf5a738548a4e6b6e2e093c73baa7e61b954 100644 (file)
@@ -217,28 +217,36 @@ float autocvar_sv_dodging_wall_distance_threshold;
 bool autocvar_sv_dodging_frozen;
 bool autocvar_sv_dodging_frozen_doubletap;
 float autocvar_sv_dodging_height_threshold;
-float autocvar_sv_dodging_horiz_speed;
-float autocvar_sv_dodging_horiz_speed_frozen;
+float autocvar_sv_dodging_horiz_speed_min;
+float autocvar_sv_dodging_horiz_speed_max;
+float autocvar_sv_dodging_horiz_force_slowest;
+float autocvar_sv_dodging_horiz_force_fastest;
+float autocvar_sv_dodging_horiz_force_frozen;
 float autocvar_sv_dodging_ramp_time;
 float autocvar_sv_dodging_up_speed;
 bool autocvar_sv_dodging_wall_dodging;
 bool autocvar_sv_dodging_air_dodging;
-float autocvar_sv_dodging_maxspeed = 450;
+float autocvar_sv_dodging_maxspeed;
+float autocvar_sv_dodging_air_maxspeed;
 #endif
 
 #if 0
 REGISTER_STAT(DODGING, int, g_dodging)
 REGISTER_STAT(DODGING_DELAY, float, autocvar_sv_dodging_delay)
 REGISTER_STAT(DODGING_DISTANCE_THRESHOLD, float, autocvar_sv_dodging_wall_distance_threshold)
-REGISTER_STAT(DODGING_FROZEN_NO_DOUBLETAP, int, autocvar_sv_dodging_frozen_doubletap)
+REGISTER_STAT(DODGING_FROZEN_DOUBLETAP, int, autocvar_sv_dodging_frozen_doubletap)
 REGISTER_STAT(DODGING_HEIGHT_THRESHOLD, float, autocvar_sv_dodging_height_threshold)
-REGISTER_STAT(DODGING_HORIZ_SPEED, float, autocvar_sv_dodging_horiz_speed)
-REGISTER_STAT(DODGING_HORIZ_SPEED_FROZEN, float, autocvar_sv_dodging_horiz_speed_frozen)
+REGISTER_STAT(DODGING_HORIZ_SPEED_MIN, float, autocvar_sv_dodging_horiz_speed_min)
+REGISTER_STAT(DODGING_HORIZ_SPEED_MAX, float, autocvar_sv_dodging_horiz_speed_max)
+REGISTER_STAT(DODGING_HORIZ_FORCE_SLOWEST, float, autocvar_sv_dodging_horiz_force_slowest)
+REGISTER_STAT(DODGING_HORIZ_FORCE_FASTEST, float, autocvar_sv_dodging_horiz_force_fastest)
+REGISTER_STAT(DODGING_HORIZ_FORCE_FROZEN, float, autocvar_sv_dodging_horiz_force_frozen)
 REGISTER_STAT(DODGING_RAMP_TIME, float, autocvar_sv_dodging_ramp_time)
 REGISTER_STAT(DODGING_UP_SPEED, float, autocvar_sv_dodging_up_speed)
 REGISTER_STAT(DODGING_WALL, bool, autocvar_sv_dodging_wall_dodging)
 REGISTER_STAT(DODGING_AIR, bool, autocvar_sv_dodging_air_dodging)
 REGISTER_STAT(DODGING_MAXSPEED, float, autocvar_sv_dodging_maxspeed)
+REGISTER_STAT(DODGING_AIR_MAXSPEED, float, autocvar_sv_dodging_air_maxspeed)
 #endif
 /** cvar loopback */
 REGISTER_STAT(DODGING_FROZEN, int, autocvar_sv_dodging_frozen)
@@ -338,6 +346,7 @@ REGISTER_STAT(MOVEVARS_AIRACCELERATE, float)
 // FIXME: Was 0 on server, 1 on client. Still want that?
 REGISTER_STAT(MOVEVARS_ENTGRAVITY, float, (this.gravity) ? this.gravity : 1)
 REGISTER_STAT(MOVEVARS_JUMPVELOCITY, float)
+REGISTER_STAT(MOVEVARS_JUMPVELOCITY_CROUCH, float)
 REGISTER_STAT(MOVEVARS_MAXAIRSPEED, float)
 REGISTER_STAT(MOVEVARS_STEPHEIGHT, float, autocvar_sv_stepheight)
 REGISTER_STAT(MOVEVARS_AIRACCEL_QW, float)
index 6a6ce3fafdccd004a36a7c438b820b8423414040..50ae0c336c51393ee61d7a14e70875588fd1d6e3 100644 (file)
@@ -682,146 +682,74 @@ void Item_ScheduleInitialRespawn(entity e)
        Item_ScheduleRespawnIn(e, max(0, game_starttime - time) + ((e.respawntimestart) ? e.respawntimestart : ITEM_RESPAWNTIME_INITIAL(e)));
 }
 
-void GivePlayerResource(entity player, .float resource_type, float amount)
+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)
@@ -850,14 +778,14 @@ float Item_GiveTo(entity item, entity player)
                        }
                }
        }
-       pickedup |= Item_GiveAmmoTo(item, player, health, item.max_health);
-       pickedup |= Item_GiveAmmoTo(item, player, armorvalue, item.max_armorvalue);
-       pickedup |= Item_GiveAmmoTo(item, player, ammo_shells, g_pickup_shells_max);
-       pickedup |= Item_GiveAmmoTo(item, player, ammo_nails, g_pickup_nails_max);
-       pickedup |= Item_GiveAmmoTo(item, player, ammo_rockets, g_pickup_rockets_max);
-       pickedup |= Item_GiveAmmoTo(item, player, ammo_cells, g_pickup_cells_max);
-       pickedup |= Item_GiveAmmoTo(item, player, ammo_plasma, g_pickup_plasma_max);
-       pickedup |= Item_GiveAmmoTo(item, player, ammo_fuel, g_pickup_fuel_max);
+       pickedup |= Item_GiveAmmoTo(item, player, RESOURCE_HEALTH, item.max_health);
+       pickedup |= Item_GiveAmmoTo(item, player, RESOURCE_ARMOR, item.max_armorvalue);
+       pickedup |= Item_GiveAmmoTo(item, player, RESOURCE_SHELLS, g_pickup_shells_max);
+       pickedup |= Item_GiveAmmoTo(item, player, RESOURCE_BULLETS, g_pickup_nails_max);
+       pickedup |= Item_GiveAmmoTo(item, player, RESOURCE_ROCKETS, g_pickup_rockets_max);
+       pickedup |= Item_GiveAmmoTo(item, player, RESOURCE_CELLS, g_pickup_cells_max);
+       pickedup |= Item_GiveAmmoTo(item, player, RESOURCE_PLASMA, g_pickup_plasma_max);
+       pickedup |= Item_GiveAmmoTo(item, player, RESOURCE_FUEL, g_pickup_fuel_max);
        if (item.itemdef.instanceOfWeaponPickup)
        {
                WepSet w;
@@ -914,8 +842,6 @@ float Item_GiveTo(entity item, entity player)
                player.superweapons_finished = max(player.superweapons_finished, time) + item.superweapons_finished;
        }
 
-LABEL(skip)
-
        // always eat teamed entities
        if(item.team)
                pickedup = true;
@@ -1018,6 +944,7 @@ LABEL(pickup)
                                if(it.itemdef) // is a registered item
                                {
                                        Item_Show(it, -1);
+                                       it.scheduledrespawntime = 0;
                                        RandomSelection_AddEnt(it, it.cnt, 0);
                                }
                        });
@@ -1149,14 +1076,14 @@ float ammo_pickupevalfunc(entity player, entity item)
                        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;
index f557e10308cc0d961df222bcb9a021dc4fd33399..80385971ab34d40a1ea17dbcc42e73714042aa9a 100644 (file)
@@ -4,9 +4,6 @@
 #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
@@ -87,39 +84,16 @@ void Item_ScheduleRespawn(entity e);
 
 void Item_ScheduleInitialRespawn(entity e);
 
-/// \brief Gives player a resource such as health, armor or ammo.
-/// \param[in,out] player Player to give resource to.
-/// \param[in] resource_type Type of the resource.
-/// \param[in] amount Amount of resource to give.
-/// \return No return.
-void GivePlayerResource(entity player, .float resource_type, float amount);
-
-/// \brief Gives health to the player.
-/// \param[in,out] player Player to give health to.
-/// \param[in] amount Amount of health to give.
-/// \return No return.
-void GivePlayerHealth(entity player, float amount);
-
-/// \brief Gives armor to the player.
-/// \param[in,out] player Player to give armor to.
-/// \param[in] amount Amount of armor to give.
-/// \return No return.
-void GivePlayerArmor(entity player, float amount);
-
-/// \brief Gives ammo of the specified type to the player.
-/// \param[in,out] player Player to give ammo to.
-/// \param[in] type Ammo type property.
-/// \param[in] amount Amount of ammo to give.
-/// \return No return.
-void GivePlayerAmmo(entity player, .float ammotype, float amount);
-
-/// \brief Gives fuel to the player.
-/// \param[in,out] player Player to give fuel to.
-/// \param[in] amount Amount of fuel to give.
+/// \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);
 
index 79c2feb4eab85b8115a0ddcd00270a26323d7c81..6e0c2b3a3119fd690aa4bc54ef3f2a2009883a47 100644 (file)
@@ -178,7 +178,7 @@ void func_breakable_behave_restore(entity this)
 
 void func_breakable_init_for_player(entity this, entity player)
 {
-       if (this.noise1 && this.state == 0 && clienttype(player) == CLIENTTYPE_REAL)
+       if (this.noise1 && this.state == 0 && IS_REAL_CLIENT(player))
        {
                msg_entity = player;
                soundto (MSG_ONE, this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
index 8ded50d16b2da0d88a35c68eb333006786a15636..ddd62ae4e8f5f3ce5fc22b43d35086cc67d686ad 100644 (file)
@@ -94,11 +94,22 @@ void button_damage(entity this, entity inflictor, entity attacker, float damage,
        if(this.spawnflags & DOOR_NOSPLASH)
                if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
                        return;
-       this.health = this.health - damage;
-       if (this.health <= 0)
+       if (this.spawnflags & BUTTON_DONTACCUMULATEDMG)
        {
-               this.enemy = attacker;
-               button_fire(this);
+               if (this.health <= damage)
+               {
+                       this.enemy = attacker;
+                       button_fire(this);
+               }
+       }
+       else
+       {
+               this.health = this.health - damage;
+               if (this.health <= 0)
+               {
+                       this.enemy = attacker;
+                       button_fire(this);
+               }
        }
 }
 
index 6f70f09beec2219624baeca92e2cd7deaa104fb4..75a6006eb5e703e24b79362c9df7b0f39e328376 100644 (file)
@@ -1 +1,3 @@
 #pragma once
+
+const int BUTTON_DONTACCUMULATEDMG = 128;
index dc0be6ae24a9822b67865e2c4a2cb0c6b0b32c6b..392ab3e5f5f46b883793a34745656298176606b5 100644 (file)
@@ -22,7 +22,7 @@ THINK FUNCTIONS
 */
 
 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);
 
@@ -59,7 +59,7 @@ void door_blocked(entity this, entity blocker)
                                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);
@@ -123,7 +123,7 @@ void door_go_down(entity this)
        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
@@ -142,7 +142,7 @@ void door_go_up(entity this)
        string oldmessage;
        oldmessage = this.message;
        this.message = "";
-       SUB_UseTargets(this, NULL, NULL);
+       SUB_UseTargets(this, actor, trigger);
        this.message = oldmessage;
 }
 
@@ -237,7 +237,7 @@ void door_fire(entity this, entity actor, entity trigger)
        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) {
index e7b309062848d0fe673b8d797f4039512f292372..4447ea21e25f0cf7928378fc97a462eb004c2b05 100644 (file)
@@ -13,7 +13,9 @@ void trigger_heal_touch(entity this, entity toucher)
                if (!IS_DEAD(toucher))
                if (toucher.triggerhealtime < time)
                {
-                       EXACTTRIGGER_TOUCH(this, toucher);
+                       bool is_trigger = !boolean(!this.nottargeted && this.targetname != "");
+                       if(is_trigger)
+                               EXACTTRIGGER_TOUCH(this, toucher);
                        toucher.triggerhealtime = time + 1;
 
                        if (toucher.health < this.max_health)
@@ -26,6 +28,11 @@ void trigger_heal_touch(entity this, entity toucher)
        }
 }
 
+void trigger_heal_use(entity this, entity actor, entity trigger)
+{
+       trigger_heal_touch(this, actor);
+}
+
 spawnfunc(trigger_heal)
 {
        this.active = ACTIVE_ACTIVE;
@@ -40,4 +47,17 @@ spawnfunc(trigger_heal)
                this.noise = "misc/mediumhealth.wav";
        precache_sound(this.noise);
 }
+
+spawnfunc(target_heal)
+{
+       this.active = ACTIVE_ACTIVE;
+       this.use = trigger_heal_use;
+       if (!this.health)
+               this.health = 10;
+       if (!this.max_health)
+               this.max_health = 200; //Max health topoff for field
+       if(this.noise == "")
+               this.noise = "misc/mediumhealth.wav";
+       precache_sound(this.noise);
+}
 #endif
index df965074587315eebbc4781704aba2f7386410f9..822411c6a23a93d2b58093f3e28c1616564be7b1 100644 (file)
@@ -38,9 +38,9 @@ vector trigger_push_calculatevelocity(vector org, entity tgt, float ht)
 
        torg = tgt.origin + (tgt.mins + tgt.maxs) * 0.5;
 
-       grav = PHYS_GRAVITY(other);
-       if(PHYS_ENTGRAVITY(other))
-               grav *= PHYS_ENTGRAVITY(other);
+       grav = PHYS_GRAVITY(tgt);
+       if(PHYS_ENTGRAVITY(tgt))
+               grav *= PHYS_ENTGRAVITY(tgt);
 
        zdist = torg.z - org.z;
        sdist = vlen(torg - org - zdist * '0 0 1');
index 794f4dc112d017a4e9759dbe84095d628662602e..a82034edcf017d5abad297540444f5111f62110d 100644 (file)
@@ -8,4 +8,6 @@ spawnfunc(trigger_relay)
        this.use = SUB_UseTargets;
        this.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
 }
+
+spawnfunc(target_relay) { spawnfunc_trigger_relay(this); }
 #endif
index afbf79eb54777d0e2852366e9d44956ff69c3966..e430ec2e78c252dea99cfc5c8fdfcc1a874bce8a 100644 (file)
@@ -210,48 +210,48 @@ void W_RandomWeapons(entity e, int n)
        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)
@@ -530,7 +530,9 @@ void CL_WeaponEntity_SetModel(entity this, string name, bool _anim)
        int compressed_shotorg = compressShotOrigin(this.movedir);
        // make them match perfectly
 #ifdef SVQC
-       this.movedir = decompressShotOrigin(this.owner.stat_shotorg = compressed_shotorg);
+    // null during init
+    if (this.owner) this.owner.stat_shotorg = compressed_shotorg;
+       this.movedir = decompressShotOrigin(compressed_shotorg);
 #else
        this.movedir = decompressShotOrigin(compressed_shotorg);
 #endif
index 73cd00d6c1c3e9eea514afa31e9c00afea76609a..5a782b98f9e462210ae5de73ec15102b340507b5 100644 (file)
@@ -41,3 +41,5 @@ const int PROJECTILE_ARC_BOLT = 35;
 // projectile IDs 40-50 reserved
 
 const int PROJECTILE_RPC = 60;
+
+// projectile IDs 70-100 reserved
index 4be22446bf9dae2f8a314138be4e4b4fd4909426..71d7c3db39f464b975e9258764fa08676984eae5 100644 (file)
@@ -1,5 +1,6 @@
 #pragma once
 
+#include <common/resources.qh>
 #include <common/items/item/pickup.qh>
 #include <common/stats.qh>
 
@@ -59,8 +60,8 @@ CLASS(Weapon, Object)
        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 */
@@ -213,11 +214,11 @@ string W_FixWeaponOrder_AllowIncomplete(entity this, string order);
 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);
index 47b92cc9f3a3819f27b6fff8bbb9be1f5496d664..5b982219e7cc6708dd0ba994b0e5d2501be34797 100644 (file)
@@ -258,8 +258,8 @@ void W_Arc_Beam_Think(entity this)
 
                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);
@@ -697,19 +697,19 @@ METHOD(Arc, wr_init, void(entity thiswep))
 }
 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))
 {
index 01c4f2f6a3cf7b8a71c5a5fbdd50e0ded97fe9df..abd9933116b2d86af3506acd6013b3648f6796f9 100644 (file)
@@ -1,7 +1,7 @@
 #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);
index 990add96bd8f271fe30571b524b148ba38536245..972dcd003f23118654a332337c16904e93ca190d 100644 (file)
@@ -1,7 +1,7 @@
 #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);
index 82b47037615e1d51cf6808e5ba55cf9fcb7a9493..2e2cb519af644ad83fa9133dc6f711227542b72f 100644 (file)
@@ -582,7 +582,7 @@ METHOD(Crylink, wr_checkammo1, bool(entity thiswep, entity actor, .entity weapon
     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;
 }
@@ -592,7 +592,7 @@ METHOD(Crylink, wr_checkammo2, bool(entity thiswep, entity actor, .entity weapon
     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;
 }
index 326fdf15bec0aa4d970b2b4f3264776868a23845..e686cfa94cf87cbaf962ba52ecf77fb52a69e520 100644 (file)
@@ -1,7 +1,7 @@
 #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);
index 88d50f80a021eb01dadb77cb867a4bd7a85611fa..f4ef7ad32c08b320d139348908b42e7c95cb54bb 100644 (file)
@@ -47,7 +47,7 @@ void W_Devastator_Explode(entity this, entity directhitentity)
        .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;
@@ -139,7 +139,7 @@ void W_Devastator_DoRemoteExplode(entity this, .entity weaponentity)
        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;
@@ -478,10 +478,10 @@ METHOD(Devastator, wr_checkammo1, bool(entity thiswep, entity actor, .entity wea
         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;
     }
@@ -489,18 +489,18 @@ METHOD(Devastator, wr_checkammo1, bool(entity thiswep, entity actor, .entity wea
     #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
index dd68ccf58e3f6ad223ecc4b68a79366cebd98c3e..9c419751173120e17ba80911ea9aca37e9b30026 100644 (file)
@@ -1,7 +1,7 @@
 #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);
index f2fb14614004e9ef2982d5656753514ad7ecf016..8b3946e46e43d77b673b7023c89b3ab4c5afbf6c 100644 (file)
@@ -484,7 +484,7 @@ METHOD(Electro, wr_think, void(entity thiswep, entity actor, .entity weaponentit
 }
 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;
 }
@@ -493,12 +493,12 @@ METHOD(Electro, wr_checkammo2, bool(entity thiswep, entity actor, .entity weapon
     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;
index 32b45c6928f2bb7faf1fe94919ef03260f7ce5fe..07c967c49bee20b1bb4d31b2f6c87801e3dac863 100644 (file)
@@ -1,7 +1,7 @@
 #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);
index d6d8f017edc9c0027726200f00a89b689f895321..e973d28845cee8ad52c995be9992a5ffd2da3667 100644 (file)
@@ -1,7 +1,7 @@
 #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);
index 5272e6060a8fdab0c417ab986c96898fe2097f07..be95d5dde2a3650367840715bf865565d1d69256 100644 (file)
@@ -270,7 +270,7 @@ void W_Hagar_Attack2_Load(Weapon thiswep, entity actor, .entity weaponentity)
        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;
 
@@ -448,13 +448,13 @@ METHOD(Hagar, wr_setup, void(entity thiswep, entity actor, .entity weaponentity)
 }
 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;
 }
index 7dfd3c6312541e47f4534f3af1cb756a3a5abbc6..24c700cc84d9412bf5362bd6d827656cda909d0f 100644 (file)
@@ -1,7 +1,7 @@
 #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);
index 0193270a61e592108213e723afccba610d636e92..2fb16d6e35f0dd9a1c60ffc96a4034c22ff86596 100644 (file)
@@ -189,13 +189,13 @@ METHOD(HLAC, wr_think, void(entity thiswep, entity actor, .entity weaponentity,
 }
 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;
 }
index f965abcbf79e62fac163c5f2c52dec2e98fb476e..4664e54d96776a7b02bc3ccdb7d34aa91ebc392c 100644 (file)
@@ -1,7 +1,7 @@
 #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);
index f67db3d00d462ac141c52be86adc2d6879b49bdb..4988323fda187803dab6218b45002d4bede7bf90 100644 (file)
@@ -1,7 +1,7 @@
 #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);
index c4f3b9bb3b29183bbcd25d0243eefdde0f5bec76..8d96c4b1013ca875a0d581408a335877c83c160a 100644 (file)
@@ -258,9 +258,9 @@ METHOD(MachineGun, wr_checkammo1, bool(entity thiswep, entity actor, .entity wea
 {
     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))
     {
@@ -275,9 +275,9 @@ METHOD(MachineGun, wr_checkammo2, bool(entity thiswep, entity actor, .entity wea
 {
     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))
     {
index 5cd912a2aef278f800a2c86cc2b578ef36037ef6..2f0974971ecc80112eb285e589801f88a074dc71 100644 (file)
@@ -1,7 +1,7 @@
 #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);
index 463f5109b918187ed45bfe6b41e66f1eb440af76..99b00a814f20ffbdb4d3d62507ba6ccff96f2e80 100644 (file)
@@ -453,7 +453,7 @@ METHOD(MineLayer, wr_think, void(entity thiswep, entity actor, .entity weaponent
     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);
         }
     }
@@ -478,7 +478,7 @@ METHOD(MineLayer, wr_checkammo1, bool(entity thiswep, entity actor, .entity weap
     // 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;
     //}
index e867db8d31634764f5f459f8df028f346535b478..f113e6439e3bea5f42d569e36d86482ff9b9ffe2 100644 (file)
@@ -1,7 +1,7 @@
 #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);
index 186d1f139d3f52de758e69e0bdd5360c5e4fc7e3..6a3d2e1250a63294e400a4a283e850fef651eb6e 100644 (file)
@@ -325,13 +325,13 @@ METHOD(Mortar, wr_think, void(entity thiswep, entity actor, .entity weaponentity
 }
 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;
 }
index 58b526b8ec4eddf58e3804ceb4dd5277c9978688..4fc5ec9ad2995644c9a72c1f3d268be874f39edf 100644 (file)
@@ -1,7 +1,7 @@
 #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);
index 94c6e165c587f0f095740339c3e8e30d90e23bfe..b46e479aa9f383292498a75e9672cda2013786b4 100644 (file)
@@ -1,7 +1,7 @@
 #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);
index 92694e2e2248570b9bcc8ebd878bd8569c23aa83..54784251281c0b488c4867123984548e060ae028 100644 (file)
@@ -150,13 +150,13 @@ METHOD(Rifle, wr_think, void(entity thiswep, entity actor, .entity weaponentity,
 }
 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;
 }
index ed5496f050f28cb841949a0937ebd4a279c589d6..b1c01b86ff861238a106b1d03a2b8dad77d8e55a 100644 (file)
@@ -1,7 +1,7 @@
 #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);
index 35b48b8ea03159e9a40b448bda4be2103e794a2c..b2df4f0efb54c53e5b951f9c874e3ce651b4b25e 100644 (file)
@@ -348,7 +348,7 @@ void W_Seeker_Vollycontroller_Think(entity this) // TODO: Merge this with W_Seek
 
        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;
@@ -589,12 +589,12 @@ METHOD(Seeker, wr_checkammo1, bool(entity thiswep, entity actor, .entity weapone
     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;
@@ -604,12 +604,12 @@ METHOD(Seeker, wr_checkammo2, bool(entity thiswep, entity actor, .entity weapone
     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;
index 04567d1b9a583083e5cfa3dec19a804eaef7adcb..443d0843d01b3e1fea2bba4ba9d11a4cc182ba4d 100644 (file)
@@ -1,7 +1,7 @@
 #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);
index f863094c00f54582d8cf7a088adb04f33c5e27a3..89685376dad9a6fd52b6b61f966fdbe2d35ba7ac 100644 (file)
@@ -1,7 +1,7 @@
 #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);
index 3644c1357e3fa2517d7a6de28832c14f2de0c394..e163df4bc61d1958c0ff203bbd8937005abaddef 100644 (file)
@@ -216,7 +216,7 @@ METHOD(Shotgun, wr_think, void(entity thiswep, entity actor, .entity weaponentit
     }
     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...
@@ -225,7 +225,7 @@ METHOD(Shotgun, wr_think, void(entity thiswep, entity actor, .entity weaponentit
 }
 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;
 }
@@ -239,7 +239,7 @@ METHOD(Shotgun, wr_checkammo2, bool(entity thiswep, entity actor, .entity weapon
         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;
         }
index 2fd734d554b2ddb6dc43aa11042e41abbd02ae20..cd646a768f456aa2c74c9bdf8dda6f909ef8b305 100644 (file)
@@ -1,7 +1,7 @@
 #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);
index e47f8fa909565a3734188c1f57d376cc1d1a5899..73822b4c50fcbecad3a12f3189253ee4df03054c 100644 (file)
@@ -291,7 +291,7 @@ void W_RocketMinsta_Attack3 (entity actor, .entity weaponentity)
 
 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
@@ -378,7 +378,7 @@ METHOD(Vaporizer, wr_setup, void(entity thiswep, entity actor, .entity weaponent
 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;
 }
@@ -386,7 +386,7 @@ METHOD(Vaporizer, wr_checkammo2, bool(entity thiswep, entity actor, .entity weap
 {
     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;
 }
index f0b144341d538d1f24d39f47a43990b4e1be5e98..0c5c19200a437d67a063ffe58fd43785557bcafc 100644 (file)
@@ -1,7 +1,7 @@
 #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);
index 454cb8bfba1b3cb53d59bbb7ced9081fc265c5b5..202780e29afc4d982400df3cd2ee3ad98fa43f5b 100644 (file)
@@ -241,11 +241,11 @@ METHOD(Vortex, wr_think, void(entity thiswep, entity actor, .entity weaponentity
                                 }
                                 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));
                                     }
                                 }
                             }
@@ -277,7 +277,7 @@ METHOD(Vortex, wr_setup, void(entity thiswep, entity actor, .entity weaponentity
 }
 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;
 }
@@ -286,7 +286,7 @@ METHOD(Vortex, wr_checkammo2, bool(entity thiswep, entity actor, .entity weapone
     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;
     }
index 81ab7e6209d4389d9f8eac7765866623ab51e7c6..5a41b90d8efede1e64457ac4001c09c86ea9b7b1 100644 (file)
@@ -1,7 +1,7 @@
 #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);
index 16fd93450332e44fa0952d7003f6c50fb1fc6b03..70e5f378422af7850a91ec65cf8857d6c61ae0d8 100644 (file)
@@ -11,8 +11,8 @@
 #undef setcolor
 
 #ifdef MENUQC
-       #define NULL (0, null_entity)
+       #define NULL (RVALUE, null_entity)
        #define world NULL
 #else
-       #define NULL (0, world)
+       #define NULL (RVALUE, world)
 #endif
index c20ae8bda2065a58ffd01176ae3ebbe5aa4412f1..6731bdf9433b7e228481237974e79a020334ddfa 100644 (file)
@@ -64,7 +64,9 @@ void sys_phys_spectator_control(entity this)
                            || CS(this).impulse == 15
                            || CS(this).impulse == 18
                            || (CS(this).impulse >= 200 && CS(this).impulse <= 209)
-                          ) { this.spectatorspeed = bound(1, this.spectatorspeed + 0.5, 5); } else if (CS(this).impulse == 11) {
+                          ) {
+                               this.spectatorspeed = bound(1, this.spectatorspeed + 0.5, 5);
+                       } else if (CS(this).impulse == 11) {
                                this.spectatorspeed = maxspeed_mod;
                        } else if (CS(this).impulse == 12
                            || CS(this).impulse == 16
index 4da78f1444eb27b397d926442c7a153012adf01e..d44c5e89cdda2acacbd1b615bfbc55dc8bb93b9f 100644 (file)
@@ -182,7 +182,17 @@ void make_safe_for_remove(entity this);
        #define SV_Shutdown _SV_Shutdown
 
        void _StartFrame();
-       void StartFrame() { if (_StartFrame) _StartFrame(); }
+       bool _StartFrame_init;
+       void spawnfunc_worldspawn(entity);
+       void StartFrame() {
+               if (!_StartFrame_init) {
+                       _StartFrame_init = true;
+                       float oldtime = time; time = 1;
+                       __spawnfunc_expecting = 2; NULL.__spawnfunc_constructor(NULL);
+                       time = oldtime;
+        }
+        if (_StartFrame) _StartFrame();
+       }
        #define StartFrame _StartFrame
 
        void _SetNewParms();
@@ -231,7 +241,8 @@ void make_safe_for_remove(entity this);
        void SV_OnEntityPreSpawnFunction()
        {
                ENGINE_EVENT();
-               if (_SV_OnEntityPreSpawnFunction) _SV_OnEntityPreSpawnFunction(this);
+               __spawnfunc_expecting = true;
+               __spawnfunc_expect = this;
        }
        #define SV_OnEntityPreSpawnFunction _SV_OnEntityPreSpawnFunction
 
index 1541b9997cff5efb80c96fa1a3844c00b6dcc1ba..f2ec6df4a4206e8797f08ecb529fa0faa6adbcb3 100644 (file)
@@ -9,6 +9,9 @@
     #define MACRO_END } while (0)
 #endif
 
+/** Marker for use in (RVALUE, (expr)) */
+#define RVALUE 0
+
 #define _CAT(a, b) a ## b
 #define CAT(a, b) _CAT(a, b)
 
index 21e0c5239f59aa7399533f5c35aca8c0e0c2487b..6c29a4b88dd100f74ded51a0fe91ad82179c7d25 100644 (file)
@@ -10,6 +10,7 @@
 
        #include "p99.qh"
        #define OVERLOAD(F, ...) P99_IF_EMPTY(__VA_ARGS__)(P99_PASTE2(F, _00)())(P99_PASTE3(F, _, P00_NARG(__VA_ARGS__))(__VA_ARGS__))
+       /** for use within a macro */
        #define OVERLOAD_(F, ...) P99_IF_EMPTY(__VA_ARGS__)(P99_PASTE2(F, _00)())(P99_PASTE3(F, _, P00_NARG(__VA_ARGS__))(__VA_ARGS__))
 #else
        #define EVAL(...) __VA_ARGS__
index 0a3dd8c662a8ac8e1f91e26ab820b8cbe658f842..b1f5326a9bb5a441b5094de347cfe973086d5bd6 100644 (file)
@@ -4,19 +4,30 @@
 #include "sort.qh"
 #include "yenc.qh"
 
+// netcode mismatch and not sure what caused it? developer_csqcentities 1
+
 .string netname;
 .int m_id;
 .bool(entity this, entity sender, bool isNew) m_read;
 #define NET_HANDLE(id, param) bool Net_Handle_##id(entity this, entity sender, param)
 
+#define NET_GUARD(id) \
+    bool Net_Handle_##id##_guard(entity this, entity sender, bool isNew) { \
+        bool valid = false; \
+        serialize_marker(to, valid); \
+        if (!valid) LOG_FATALF("Last message not fully parsed: %s", _net_prevmsgstr); \
+        _net_prevmsgstr = #id; \
+        return Net_Handle_##id(this, sender, isNew); \
+    }
 
 #ifdef CSQC
+string _net_prevmsgstr;
        #define REGISTER_NET_TEMP(id) \
                NET_HANDLE(id, bool); \
-               REGISTER(TempEntities, NET, id, m_id, new_pure(net_temp_packet)) \
-               { \
+        NET_GUARD(id); \
+               REGISTER(TempEntities, NET, id, m_id, new_pure(net_temp_packet)) { \
                        this.netname = #id; \
-                       this.m_read = Net_Handle_##id; \
+                       this.m_read = Net_Handle_##id##_guard; \
                }
 #else
        #define REGISTER_NET_TEMP(id) \
@@ -45,10 +56,11 @@ STATIC_INIT(RegisterTempEntities_renumber) { FOREACH(TempEntities, true, it.m_id
                        this.sourceLoc = __FILE__ ":" STR(__LINE__); \
                        if (!this) isnew = true; \
                } \
+               NET_GUARD(id); \
                REGISTER(LinkedEntities, NET, id, m_id, new_pure(net_linked_packet)) \
                { \
                        this.netname = #id; \
-                       this.m_read = Net_Handle_##id; \
+                       this.m_read = Net_Handle_##id##_guard; \
                }
 #else
        #define REGISTER_NET_LINKED(id) \
@@ -205,6 +217,7 @@ STATIC_INIT(C2S_Protocol_renumber) { FOREACH(C2S_Protocol, true, it.m_id = i); }
                MACRO_BEGIN { \
                        if (NET_##id##_istemp) WriteByte(to, SVC_TEMPENTITY); \
                        WriteByte(to, NET_##id.m_id); \
+                       bool _net_valid = false; serialize_marker(to, _net_valid); \
                } MACRO_END
 #endif
 
@@ -219,7 +232,11 @@ USING(Stream, int);
        #define stream_writing(stream) false
 #endif
 
-#define serialize(T, stream, ...) serialize_##T(stream, __VA_ARGS__)
+#define serialize(T, stream, ...) \
+MACRO_BEGIN \
+    noref Stream _stream = stream; \
+    serialize_##T(_stream, __VA_ARGS__); \
+MACRO_END
 
 #if defined(SVQC)
        #define serialize_byte(stream, this) \
@@ -246,13 +263,27 @@ USING(Stream, int);
 #endif
 
 #define serialize_vector(stream, this) \
-    MACRO_BEGIN \
+MACRO_BEGIN \
     vector _v = this; \
     serialize_float(stream, _v.x); \
     serialize_float(stream, _v.y); \
     serialize_float(stream, _v.z); \
     this = _v; \
-    MACRO_END
+MACRO_END
+
+#define serialize_marker(stream, this) \
+MACRO_BEGIN \
+    if (NDEBUG) { \
+        this = true; \
+    } else { \
+        int _de = 0xDE, _ad = 0xAD, _be = 0xBE, _ef = 0xEF; \
+        serialize_byte(stream, _de); \
+        serialize_byte(stream, _ad); \
+        serialize_byte(stream, _be); \
+        serialize_byte(stream, _ef); \
+        this = (_de == 0xDE && _ad == 0xAD && _be == 0xBE && _ef == 0xEF); \
+    } \
+MACRO_END
 
 // serialization: old
 
index 8ca07b43f3d34ecaa83e63b1810f415e26ad47a6..2d41e5d431b3ba9c7b63ccbb87268a4b96615267 100644 (file)
@@ -187,7 +187,7 @@ void Registry_send(string id, string hash);
 #define EVAL_REGISTER_REGISTRY(...) __VA_ARGS__
 #define REGISTER_REGISTRY_1(id) REGISTER_REGISTRY_2(id, #id)
 #define REGISTER_REGISTRY_2(id, str) \
-       ACCUMULATE_FUNCTION(__static_init, Register##id) \
+       ACCUMULATE_FUNCTION(__static_init_1, Register##id) \
        CLASS(id##Registry, Object) \
                ATTRIB(id##Registry, m_name, string, str); \
                ATTRIB(id##Registry, REGISTRY_NEXT, entity, id##_first); \
index f4c246f33058997803e1291f243286cfce99362d..0a61cc003dfdc9046acf24dc7b33b057115732e8 100644 (file)
@@ -1,5 +1,7 @@
 #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()
@@ -32,7 +34,7 @@
 // Step 5: this should work
 #if 1
     #undef self
-    #define self (0, this)
+    #define self (RVALUE, this)
 #endif
 
 // Step 6: remove SELFPARAM, add parameters
@@ -56,11 +58,11 @@ noref entity _selftemp;
 #define SELFWRAP_SET(T, e, f) \
     (_selftemp = (e), _selftemp.__##T = ((f) ? T##_self : func_null), _selftemp.self##T = (f))
 #define SELFWRAP_GET(T, e) \
-    (0, (e).self##T)
+    (RVALUE, (e).self##T)
 #define _SELFWRAP_SET(T, e, f) \
     ((e).__##T = (f))
 #define _SELFWRAP_GET(T, e) \
-    (0, (e).__##T)
+    (RVALUE, (e).__##T)
 
 SELFWRAP(think, void, (), (entity this), (this))
 #define setthink(e, f) SELFWRAP_SET(think, e, f)
index e0605c93840599e8f928f359ce1f6b02cccd7016..884714e3596928e9ef334c5d0efd711d21e76cf4 100644 (file)
@@ -27,34 +27,89 @@ noref bool require_spawnfunc_prefix;
        #define _spawnfunc_check(fld) \
                if (fieldname == #fld) continue;
 
-       noref bool __spawnfunc_expecting;
+       noref int __spawnfunc_expecting;
        noref entity __spawnfunc_expect;
        noref bool __spawnfunc_unreachable_workaround = true;
 
+    .void(entity) __spawnfunc_constructor;
+    noref IntrusiveList g_spawn_queue;
+
+    #define SPAWNFUNC_INTERNAL_FIELDS(X) \
+        X(string, classname, "spawnfunc") \
+        X(string, target, string_null) \
+        X(string, target2, string_null) \
+        X(string, target3, string_null) \
+        X(string, target4, string_null) \
+        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) \
@@ -65,8 +120,15 @@ noref bool require_spawnfunc_prefix;
                                        LOG_WARNF(_("Entity field %s.%s (%s) is not whitelisted. If you believe this is an error, please file an issue."), #id, fieldname, value); \
                                } \
                                this.spawnfunc_checked = true; \
+                               if (this) { \
+                    /* not worldspawn, delay spawn */ \
+                    __spawnfunc_defer(this, __spawnfunc_##id); \
+                } else { \
+                    /* world might not be "worldspawn" */ \
+                    this.__spawnfunc_constructor = __spawnfunc_##id; \
+                } \
                        } \
-                       __spawnfunc_##id(this); \
+                       if (dospawn) { __spawnfunc_##id(this); } \
                        if (__spawnfunc_unreachable_workaround) return; \
                } \
                void __spawnfunc_##id(entity this)
index e2c5fd4f0048d90ccbbd719fdbcf12b310bb5fe8..6f511fcecfa1c9c1f2ef842ad5fe47ac44fbd7ef 100644 (file)
@@ -1,14 +1,5 @@
 #pragma once
 
-void __static_init() {}
-#define static_init() CALL_ACCUMULATED_FUNCTION(__static_init)
-void __static_init_late() {}
-#define static_init_late() CALL_ACCUMULATED_FUNCTION(__static_init_late)
-void __static_init_precache() {}
-#define static_init_precache() CALL_ACCUMULATED_FUNCTION(__static_init_precache)
-void __shutdown() {}
-#define shutdownhooks() CALL_ACCUMULATED_FUNCTION(__shutdown)
-
 #define GETTIME_REALTIME 1
 #ifdef MENUQC
 float(int tmr) _gettime = #67;
@@ -25,12 +16,34 @@ void profile(string s)
        LOG_TRACEF("[%f] %s", rt - g_starttime, s);
 }
 
-#define _STATIC_INIT(where, func) \
+#define _STATIC_INIT(func, where) \
        [[accumulate]] void _static_##func() { profile(#func); } \
        ACCUMULATE_FUNCTION(where, _static_##func) \
        void _static_##func()
 
-#define STATIC_INIT(func) _STATIC_INIT(__static_init,           func)
-#define STATIC_INIT_LATE(func) _STATIC_INIT(__static_init_late, func##_late)
-#define PRECACHE(func) _STATIC_INIT(__static_init_precache,     func##_precache)
-#define SHUTDOWN(func) _STATIC_INIT(__shutdown,                        func##_shutdown)
+/** before worldspawn */
+#define STATIC_INIT_EARLY(func) _STATIC_INIT(func##_0,    __static_init_0)
+#define static_init_early()     CALL_ACCUMULATED_FUNCTION(__static_init_0)
+void __static_init_0() {}
+
+/** during worldspawn */
+#define STATIC_INIT(func)       _STATIC_INIT(func##_1,    __static_init_1)
+#define static_init()           CALL_ACCUMULATED_FUNCTION(__static_init_1)
+void __static_init_1() {}
+
+/** directly after STATIC_INIT */
+#define STATIC_INIT_LATE(func)  _STATIC_INIT(func##_2,    __static_init_2)
+#define static_init_late()      CALL_ACCUMULATED_FUNCTION(__static_init_2)
+void __static_init_2() {}
+
+/** directly after STATIC_INIT_LATE */
+#define PRECACHE(func)          _STATIC_INIT(func##_3,    __static_init_3)
+#define static_init_precache()  CALL_ACCUMULATED_FUNCTION(__static_init_3)
+void __static_init_3() {}
+
+/* other map entities spawn now */
+
+/** before shutdown */
+#define SHUTDOWN(func)          _STATIC_INIT(func##_shutdown, __shutdown)
+#define shutdownhooks()         CALL_ACCUMULATED_FUNCTION(    __shutdown)
+void __shutdown() {}
index 4642f76403c3124b6cdc30fb9b3f923263394548..1100c474cb9eb49074cd85d08431fd0f0ba61012 100644 (file)
@@ -33,8 +33,8 @@ int g_magic_stats_hole = 0;
        void stats_get() {}
        #define STAT(...) EVAL_STAT(OVERLOAD(STAT, __VA_ARGS__))
        #define EVAL_STAT(...) __VA_ARGS__
-    #define STAT_1(id) STAT_2(id, NULL)
-       #define STAT_2(id, cl) (0, _STAT(id))
+    #define STAT_1(id) (RVALUE, _STAT(id))
+       #define STAT_2(id, cl) STAT_1(id)
 
        #define getstat_int(id) getstati(id, 0, 24)
        #define getstat_bool(id) boolean(getstati(id))
@@ -61,9 +61,14 @@ int g_magic_stats_hole = 0;
                }
        #define REGISTER_STAT_3(x, T, expr) REGISTER_STAT_2(x, T)
 #elif defined(SVQC)
+    /** Internal use only */
+    entity STATS;
        /** Add all registered stats, access with `STAT(ID, player)` or `.type stat = _STAT(ID); player.stat` */
        void stats_add() {}
-       #define STAT(id, cl) (cl)._STAT(id)
+       #define STAT(...) EVAL_STAT(OVERLOAD_(STAT, __VA_ARGS__))
+    #define EVAL_STAT(...) __VA_ARGS__
+    #define STAT_1(id) (RVALUE, STAT_2(id, STATS))
+       #define STAT_2(id, cl) (cl)._STAT(id)
 
        #define addstat_int(id, fld) addstat(id, AS_INT, fld)
        #define addstat_bool(id, fld) addstat(id, AS_INT, fld)
@@ -83,9 +88,10 @@ int g_magic_stats_hole = 0;
        const int AS_FLOAT = 8;
 
        .int __stat_null;
-       /** Prevent engine stats being sent */
-       STATIC_INIT(stats_clear)
+       STATIC_INIT(stats)
        {
+           STATS = new(stats);
+           // Prevent engine stats being sent
                int r = STATS_ENGINE_RESERVE;
                for (int i = 0, n = 256 - r; i < n; ++i) {
                        #define X(_, name, id) if (i == id) continue;
@@ -111,10 +117,11 @@ int g_magic_stats_hole = 0;
                        addstat_##T(STAT_##id.m_id, fld); \
                }
        void GlobalStats_update(entity this) {}
+    /** TODO: do we want the global copy to update? */
     #define REGISTER_STAT_3(id, T, expr) \
        REGISTER_STAT_2(id, T); \
        [[accumulate]] void GlobalStats_update(entity this) { STAT(id, this) = (expr); } \
-       STATIC_INIT(worldstat_##id) { entity this = NULL; STAT(id, this) = (expr); }
+       STATIC_INIT(worldstat_##id) { entity this = STATS; STAT(id, this) = (expr); }
 #else
        #define REGISTER_STAT_2(id, type)
     #define REGISTER_STAT_3(id, T, expr)
index b05a316c94a2a4d6b438420d000ce7242f506f19..0a3fb085c1d5fb988298073071745b829578cb3d 100644 (file)
@@ -256,6 +256,7 @@ int u8_strsize(string s)
        return l;
 }
 
+//List of Unicode spaces: https://www.cs.tut.fi/~jkorpela/chars/spaces.html
 ERASEABLE
 bool isInvisibleString(string s)
 {
@@ -272,8 +273,26 @@ bool isInvisibleString(string s)
                        case 192:          // charmap space
                                if (!utf8) break;
                                return false;
-                       case 160:          // space in unicode fonts
                        case 0xE000 + 192: // utf8 charmap space
+                       case 0x00A0: // NO-BREAK SPACE
+                       //case 0x1680: // OGHAM SPACE MARK
+                       case 0x180E: // MONGOLIAN VOWEL SEPARATOR
+                       case 0x2000: // EN QUAD
+                       case 0x2001: // EM QUAD
+                       case 0x2002: // EN SPACE
+                       case 0x2003: // EM SPACE
+                       case 0x2004: // THREE-PER-EM SPACE
+                       case 0x2005: // FOUR-PER-EM SPACE
+                       case 0x2006: // SIX-PER-EM SPACE
+                       case 0x2007: // FIGURE SPACE
+                       case 0x2008: // PUNCTUATION SPACE
+                       case 0x2009: // THIN SPACE
+                       case 0x200A: // HAIR SPACE
+                       case 0x200B: // ZERO WIDTH SPACE
+                       case 0x202F: // NARROW NO-BREAK SPACE
+                       case 0x205F: // MEDIUM MATHEMATICAL SPACE
+                       case 0x3000: // IDEOGRAPHIC SPACE
+                       case 0xFEFF: // ZERO WIDTH NO-BREAK SPACE
                                if (utf8) break;
                        default:
                                return false;
index 87a8d56892d69577c7d07d2cbe9f9ced6cc5162e..de7e01aa7a862d3f328d0c02a3fdd542e5238ad9 100644 (file)
@@ -10,6 +10,7 @@
 #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>
@@ -20,6 +21,7 @@
 #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>
index 2967c110ce9a5a58db86abcf00a996c27b53172b..67f6aae4db511fd7cb1b91cb1cdb7412c7f017ea 100644 (file)
@@ -10,6 +10,7 @@
 #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>
@@ -20,6 +21,7 @@
 #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>
index 7173ae5970721388811a5f639476540732851b54..9327a203416598b10e37229d7909f3e34f0eb86d 100644 (file)
@@ -202,9 +202,10 @@ void anticheat_report_to_eventlog(entity this) {
 }
 
 void anticheat_report_to_playerstats(entity this) {
-       PS_GR_P_ADDVAL(this, strcat(PLAYERSTATS_ANTICHEAT, "_time"), servertime - CS(this).anticheat_jointime);
+       PlayerStats_GameReport_Event_Player(this,
+               strcat(PLAYERSTATS_ANTICHEAT, "_time"), servertime - CS(this).anticheat_jointime);
 #define ANTICHEAT_REPORT_ONE(name, f, tmin, mi, ma) \
-       PS_GR_P_ADDVAL(this, strcat(PLAYERSTATS_ANTICHEAT, name), f)
+       PlayerStats_GameReport_Event_Player(this, strcat(PLAYERSTATS_ANTICHEAT, name), f)
        ANTICHEATS(ANTICHEAT_REPORT_ONE);
 #undef ANTICHEAT_REPORT_ONE
 }
index f585a9a2c33db3eafb7b72168b2b3d8a50de8afd..b71d4459d1387469f9c1732fa906dda15617f965 100644 (file)
@@ -88,7 +88,7 @@ float autocvar_g_balance_superweapons_time;
 float autocvar_g_balance_selfdamagepercent;
 bool autocvar_g_balance_teams;
 bool autocvar_g_balance_teams_prevent_imbalance;
-float autocvar_g_balance_teams_scorefactor;
+//float autocvar_g_balance_teams_scorefactor;
 float autocvar_g_ballistics_density_corpse;
 float autocvar_g_ballistics_density_player;
 float autocvar_g_ballistics_mindistance;
@@ -165,12 +165,7 @@ int autocvar_g_maxplayers;
 float autocvar_g_maxplayers_spectator_blocktime;
 float autocvar_g_maxpushtime;
 float autocvar_g_maxspeed;
-#define autocvar_g_instagib cvar("g_instagib")
-bool autocvar_g_instagib_damagedbycontents = true;
-bool autocvar_g_instagib_blaster_keepdamage = false;
-bool autocvar_g_instagib_blaster_keepforce = false;
-bool autocvar_g_instagib_mirrordamage;
-bool autocvar_g_instagib_friendlypush = true;
+bool autocvar_g_instagib;
 #define autocvar_g_mirrordamage cvar("g_mirrordamage")
 #define autocvar_g_mirrordamage_virtual cvar("g_mirrordamage_virtual")
 bool autocvar_g_mirrordamage_onlyweapons;
@@ -316,6 +311,7 @@ string autocvar_sv_jumpspeedcap_max;
 float autocvar_sv_jumpspeedcap_max_disable_on_ramps;
 string autocvar_sv_jumpspeedcap_min;
 float autocvar_sv_jumpvelocity;
+float autocvar_sv_jumpvelocity_crouch;
 bool autocvar_sv_logscores_bots;
 bool autocvar_sv_logscores_console;
 bool autocvar_sv_logscores_file;
@@ -416,7 +412,6 @@ float autocvar_g_monsters_armor_blockpercent;
 float autocvar_g_monsters_healthbars;
 bool autocvar_g_monsters_lineofsight = true;
 //bool autocvar_g_monsters_ignoretraces = true;
-#define autocvar_g_bloodloss cvar("g_bloodloss")
 bool autocvar_g_nades;
 bool autocvar_g_nades_override_dropweapon = true;
 vector autocvar_g_nades_throw_offset;
index f1d417d6bace6987d6f1a72984f6f77260b7db0f..d7b9a7ae0e7d6305ef86a5994b2b3e09f2edef80 100644 (file)
@@ -12,7 +12,9 @@
 #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"
@@ -218,9 +220,20 @@ void PutObserverInServer(entity this)
     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();
+               }
     }
 
     {
@@ -257,7 +270,7 @@ void PutObserverInServer(entity this)
        if (this.alivetime)
        {
                if (!warmup_stage)
-                       PS_GR_P_ADDVAL(this, PLAYERSTATS_ALIVETIME, time - this.alivetime);
+                       PlayerStats_GameReport_Event_Player(this, PLAYERSTATS_ALIVETIME, time - this.alivetime);
                this.alivetime = 0;
        }
 
@@ -268,7 +281,9 @@ void PutObserverInServer(entity this)
        if (mutator_returnvalue) {
            // mutator prevents resetting teams+score
        } else {
+               int oldteam = this.team;
                this.team = -1;  // move this as it is needed to log the player spectating in eventlog
+               MUTATOR_CALLHOOK(Player_ChangedTeam, this, oldteam, this.team);
         this.frags = FRAGS_SPECTATOR;
         PlayerScore_Clear(this);  // clear scores when needed
     }
@@ -522,7 +537,6 @@ void PutPlayerInServer(entity this)
                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;
@@ -544,6 +558,8 @@ void PutPlayerInServer(entity this)
                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);
 
@@ -857,19 +873,19 @@ Called when a client types 'kill' in the console
 .float clientkill_nexttime;
 void ClientKill_Now_TeamChange(entity this)
 {
-       if(CS(this).killindicator_teamchange == -1)
+       if(this.killindicator_teamchange == -1)
        {
                JoinBestTeam( this, false, true );
        }
-       else if(CS(this).killindicator_teamchange == -2)
+       else if(this.killindicator_teamchange == -2)
        {
                if(blockSpectators)
                        Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_SPECTATE_WARNING, autocvar_g_maxplayers_spectator_blocktime);
                PutObserverInServer(this);
        }
        else
-               SV_ChangeTeam(this, CS(this).killindicator_teamchange - 1);
-       CS(this).killindicator_teamchange = 0;
+               SV_ChangeTeam(this, this.killindicator_teamchange - 1);
+       this.killindicator_teamchange = 0;
 }
 
 void ClientKill_Now(entity this)
@@ -877,7 +893,7 @@ void ClientKill_Now(entity this)
        if(this.vehicle)
        {
            vehicles_exit(this.vehicle, VHEF_RELEASE);
-           if(!CS(this).killindicator_teamchange)
+           if(!this.killindicator_teamchange)
            {
             this.vehicle_health = -1;
             Damage(this, this, this, 1 , DEATH_KILL.m_id, this.origin, '0 0 0');
@@ -889,11 +905,13 @@ void ClientKill_Now(entity this)
 
        this.killindicator = NULL;
 
-       if(CS(this).killindicator_teamchange)
+       if(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
 }
@@ -952,7 +970,7 @@ void ClientKill_TeamChange (entity this, float targetteam) // 0 = don't change,
        return;
     killtime = M_ARGV(1, float);
 
-       CS(this).killindicator_teamchange = targetteam;
+       this.killindicator_teamchange = targetteam;
 
     if(!this.killindicator)
        {
@@ -1199,8 +1217,6 @@ void ClientConnect(entity this)
 
        CS(this).just_joined = true;  // stop spamming the eventlog with additional lines when the client connects
 
-       CS(this).netname_previous = strzone(this.netname);
-
        if(teamplay && IS_PLAYER(this))
                Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_JOIN_CONNECT_TEAM), this.netname);
        else
@@ -1267,6 +1283,8 @@ void ClientConnect(entity this)
                it.init_for_player(it, this);
        });
 
+       Handicap_Initialize(this);
+
        MUTATOR_CALLHOOK(ClientConnect, this);
 
        if (IS_REAL_CLIENT(this))
@@ -1628,8 +1646,8 @@ void player_regen(entity this)
                float mina, maxa, limith, limita;
                maxa = autocvar_g_balance_armor_rotstable;
                mina = autocvar_g_balance_armor_regenstable;
-               limith = autocvar_g_balance_health_limit;
-               limita = autocvar_g_balance_armor_limit;
+               limith = GetResourceLimit(this, RESOURCE_HEALTH);
+               limita = GetResourceLimit(this, RESOURCE_ARMOR);
 
                regen_health_rotstable = regen_health_rotstable * max_mod;
                regen_health_stable = regen_health_stable * max_mod;
@@ -1656,7 +1674,7 @@ void player_regen(entity this)
 
                maxf = autocvar_g_balance_fuel_rotstable;
                minf = autocvar_g_balance_fuel_regenstable;
-               limitf = autocvar_g_balance_fuel_limit;
+               limitf = GetResourceLimit(this, RESOURCE_FUEL);
 
                this.ammo_fuel = CalcRotRegen(this.ammo_fuel, minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, frametime * (time > this.pauseregen_finished) * ((this.items & ITEM_JetpackRegen.m_itemid) != 0), maxf, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, frametime * (time > this.pauserotfuel_finished), limitf);
        }
@@ -2474,19 +2492,22 @@ void PlayerPreThink (entity this)
                // WORKAROUND: only use dropclient in server frames (frametime set).
                // Never use it in cl_movement frames (frametime zero).
                checkSpectatorBlock(this);
-    }
+       }
 
        zoomstate_set = false;
 
        // Check for nameless players
-       if (isInvisibleString(this.netname)) {
-               this.netname = strzone(sprintf("Player#%d", this.playerid));
-               // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe?
-       }
-       if (this.netname != CS(this).netname_previous) {
-               if (autocvar_sv_eventlog) {
+       if (this.netname == "" || this.netname != CS(this).netname_previous)
+       {
+               bool assume_unchanged = (CS(this).netname_previous == "");
+               if (isInvisibleString(this.netname))
+               {
+                       this.netname = strzone(sprintf("Player#%d", this.playerid));
+                       assume_unchanged = false;
+                       // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe?
+               }
+               if (!assume_unchanged && autocvar_sv_eventlog)
                        GameLogEcho(strcat(":name:", ftos(this.playerid), ":", playername(this, false)));
-        }
                if (CS(this).netname_previous) strunzone(CS(this).netname_previous);
                CS(this).netname_previous = strzone(this.netname);
        }
@@ -2645,7 +2666,6 @@ void DrownPlayer(entity this)
                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!
index 9674872c0faa42bd6bc0c1a466204a0c166d9b08..af81535a7a231bbd66f34bc0612d40caaf567999 100644 (file)
@@ -73,7 +73,6 @@ CLASS(Client, Object)
 
     ATTRIB(Client, parm_idlesince, int, this.parm_idlesince);
     ATTRIB(Client, muted, bool, this.muted);
-    ATTRIB(Client, killindicator_teamchange, int, this.killindicator_teamchange);
     ATTRIB(Client, idlekick_lasttimeleft, float, this.idlekick_lasttimeleft);
     ATTRIB(Client, pm_frametime, float, this.pm_frametime);
     ATTRIB(Client, pressedkeys, int, this.pressedkeys);
@@ -224,8 +223,6 @@ METHOD(Client, m_unwind, bool(Client this))
     return false;
 }
 
-float c1, c2, c3, c4;
-
 void play_countdown(entity this, float finished, Sound samp);
 
 float CalcRotRegen(float current, float regenstable, float regenfactor, float regenlinear, float regenframetime, float rotstable, float rotfactor, float rotlinear, float rotframetime, float limit);
index a2c037c5d2d198573a8c5eb63eb7e388560df8bf..09a308ad5c64d6a1001fb31754d2a8d0721f89e0 100644 (file)
@@ -325,82 +325,90 @@ void ClientCommand_selectteam(entity caller, float request, float argc)
        {
                case CMD_REQUEST_COMMAND:
                {
-                       if (argv(1) != "")
+                       if (argv(1) == "")
                        {
-                               if (IS_CLIENT(caller))
+                               return;
+                       }
+                       if (!IS_CLIENT(caller))
+                       {
+                               return;
+                       }
+                       if (!teamplay)
+                       {
+                               sprint(caller, "^7selectteam can only be used in teamgames\n");
+                               return;
+                       }
+                       if (caller.team_forced > 0)
+                       {
+                               sprint(caller, "^7selectteam can not be used as your team is forced\n");
+                               return;
+                       }
+                       if (lockteams)
+                       {
+                               sprint(caller, "^7The game has already begun, you must wait until the next map to be able to join a team.\n");
+                               return;
+                       }
+                       float selection;
+                       switch (argv(1))
+                       {
+                               case "red":
                                {
-                                       if (teamplay)
-                                       {
-                                               if (caller.team_forced <= 0)
-                                               {
-                                                       if (!lockteams)
-                                                       {
-                                                               float selection;
-
-                                                               switch (argv(1))
-                                                               {
-                                                                       case "red": selection = NUM_TEAM_1;
-                                                                               break;
-                                                                       case "blue": selection = NUM_TEAM_2;
-                                                                               break;
-                                                                       case "yellow": selection = NUM_TEAM_3;
-                                                                               break;
-                                                                       case "pink": selection = NUM_TEAM_4;
-                                                                               break;
-                                                                       case "auto": selection = (-1);
-                                                                               break;
-
-                                                                       default: selection = 0;
-                                                                               break;
-                                                               }
-
-                                                               if (selection)
-                                                               {
-                                                                       if (caller.team == selection && selection != -1 && !IS_DEAD(caller))
-                                                                       {
-                                                                               sprint(caller, "^7You already are on that team.\n");
-                                                                       }
-                                                                       else if (CS(caller).wasplayer && autocvar_g_changeteam_banned)
-                                                                       {
-                                                                               sprint(caller, "^1You cannot change team, forbidden by the server.\n");
-                                                                       }
-                                                                       else
-                                                                       {
-                                                                               if (autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance)
-                                                                               {
-                                                                                       CheckAllowedTeams(caller);
-                                                                                       GetTeamCounts(caller);
-                                                                                       if (!TeamSmallerEqThanTeam(Team_TeamToNumber(selection), Team_TeamToNumber(caller.team), caller))
-                                                                                       {
-                                                                                               Send_Notification(NOTIF_ONE, caller, MSG_INFO, INFO_TEAMCHANGE_LARGERTEAM);
-                                                                                               return;
-                                                                                       }
-                                                                               }
-                                                                               ClientKill_TeamChange(caller, selection);
-                                                                       }
-                                                                       if(!IS_PLAYER(caller))
-                                                                               caller.team_selected = true; // avoids asking again for team selection on join
-                                                               }
-                                                       }
-                                                       else
-                                                       {
-                                                               sprint(caller, "^7The game has already begun, you must wait until the next map to be able to join a team.\n");
-                                                       }
-                                               }
-                                               else
-                                               {
-                                                       sprint(caller, "^7selectteam can not be used as your team is forced\n");
-                                               }
-                                       }
-                                       else
-                                       {
-                                               sprint(caller, "^7selectteam can only be used in teamgames\n");
-                                       }
+                                       selection = NUM_TEAM_1;
+                                       break;
+                               }
+                               case "blue":
+                               {
+                                       selection = NUM_TEAM_2;
+                                       break;
+                               }
+                               case "yellow":
+                               {
+                                       selection = NUM_TEAM_3;
+                                       break;
+                               }
+                               case "pink":
+                               {
+                                       selection = NUM_TEAM_4;
+                                       break;
                                }
+                               case "auto":
+                               {
+                                       selection = (-1);
+                                       break;
+                               }
+                               default:
+                               {
+                                       return;
+                               }
+                       }
+                       if (caller.team == selection && selection != -1 && !IS_DEAD(caller))
+                       {
+                               sprint(caller, "^7You already are on that team.\n");
+                               return;
+                       }
+                       if (CS(caller).wasplayer && autocvar_g_changeteam_banned)
+                       {
+                               sprint(caller, "^1You cannot change team, forbidden by the server.\n");
                                return;
                        }
+                       if ((selection != -1) && autocvar_g_balance_teams &&
+                               autocvar_g_balance_teams_prevent_imbalance)
+                       {
+                               CheckAllowedTeams(caller);
+                               GetTeamCounts(caller);
+                               if ((BIT(Team_TeamToNumber(selection) - 1) & FindBestTeams(caller, false)) == 0)
+                               {
+                                       Send_Notification(NOTIF_ONE, caller, MSG_INFO, INFO_TEAMCHANGE_LARGERTEAM);
+                                       return;
+                               }
+                       }
+                       ClientKill_TeamChange(caller, selection);
+                       if (!IS_PLAYER(caller))
+                       {
+                               caller.team_selected = true; // avoids asking again for team selection on join
+                       }
+                       return;
                }
-
                default:
                        sprint(caller, "Incorrect parameters for ^2selectteam^7\n");
                case CMD_REQUEST_USAGE:
index 2bb0f1365c3b71aa353be44d6bc7522438344293..f5569c08f2697d4baf879ff63dc659efaaa88e74 100644 (file)
@@ -1095,9 +1095,15 @@ void GameCommand_moveplayer(float request, float argc)
 
                                                                // If so, lets continue and finally move the player
                                                                client.team_forced = 0;
-                                                               MoveToTeam(client, team_id, 6);
-                                                               successful = strcat(successful, (successful ? ", " : ""), playername(client, false));
-                                                               LOG_INFO("Player ", ftos(GetFilteredNumber(t)), " (", playername(client, false), ") has been moved to the ", Team_ColoredFullName(team_id), "^7.");
+                                                               if (MoveToTeam(client, team_id, 6))
+                                                               {
+                                                                       successful = strcat(successful, (successful ? ", " : ""), playername(client, false));
+                                                                       LOG_INFO("Player ", ftos(GetFilteredNumber(t)), " (", playername(client, false), ") has been moved to the ", Team_ColoredFullName(team_id), "^7.");
+                                                               }
+                                                               else
+                                                               {
+                                                                       LOG_INFO("Unable to move player ", ftos(GetFilteredNumber(t)), " (", playername(client, false), ")");
+                                                               }
                                                                continue;
                                                        }
                                                        else
index 8cfed5bae4bb06e1c15e15ead247605aaa08481f..1a826c6f142daf0164d83a7a27f59b37c09d9820 100644 (file)
@@ -81,8 +81,9 @@ bool Nagger_SendEntity(entity this, entity to, float sendflags)
        {
                for (i = 1; i <= maxclients; i += 8)
                {
-                       for (f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
-                               if (!IS_REAL_CLIENT(e) || e.ready) f |= b;
+                       for (f = 0, e = edict_num(i), b = BIT(0); b < BIT(8); b <<= 1, e = nextent(e))
+                               if (!IS_REAL_CLIENT(e) || e.ready)
+                                       f |= b;
                        WriteByte(MSG_ENTITY, f);
                }
        }
@@ -429,7 +430,8 @@ void ReadyRestart_force()
        FOREACH_CLIENT(IS_PLAYER(it), {
                it.alivetime = 0;
                CS(it).killcount = 0;
-               PS_GR_P_ADDVAL(it, PLAYERSTATS_ALIVETIME, -PS_GR_P_ADDVAL(it, PLAYERSTATS_ALIVETIME, 0));
+               float val = PlayerStats_GameReport_Event_Player(it, PLAYERSTATS_ALIVETIME, 0);
+               PlayerStats_GameReport_Event_Player(it, PLAYERSTATS_ALIVETIME, -val);
        });
 
        restart_mapalreadyrestarted = false; // reset this var, needed when cvar sv_ready_restart_repeatable is in use
@@ -648,20 +650,11 @@ float VoteCommand_checkargs(float startpos, float argc)
 
 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;
 
@@ -677,14 +670,16 @@ int VoteCommand_parse(entity caller, string vote_command, string vote_list, floa
 
                        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; }
 
@@ -797,7 +792,6 @@ void VoteCommand_call(float request, entity caller, float argc, string vote_comm
                                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
@@ -815,10 +809,12 @@ void VoteCommand_call(float request, entity caller, float argc, string vote_comm
                                }
 
                                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
                        }
@@ -853,7 +849,8 @@ void VoteCommand_master(float request, entity caller, float argc, string vote_co
                                                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.");
@@ -863,13 +860,14 @@ void VoteCommand_master(float request, entity caller, float argc, string vote_co
                                                        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;
                                        }
@@ -885,13 +883,14 @@ void VoteCommand_master(float request, entity caller, float argc, string vote_co
                                                {
                                                        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;
                                        }
@@ -911,7 +910,6 @@ void VoteCommand_master(float request, entity caller, float argc, string vote_co
                                                {
                                                        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;
@@ -925,7 +923,8 @@ void VoteCommand_master(float request, entity caller, float argc, string vote_co
                                                        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
                                                }
@@ -971,7 +970,9 @@ void VoteCommand_no(float request, entity caller)  // CLIENT ONLY
                                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;
                }
@@ -1041,13 +1042,14 @@ void VoteCommand_yes(float request, entity caller)  // CLIENT ONLY
                        {
                                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;
                }
index 2e2301d0bdec150704ae2b6f297beddbda5870a2..d6c7bbb2e7f3662492d337e6da2e7798027d2714 100644 (file)
@@ -120,6 +120,10 @@ void target_give_init(entity this)
                        this.ammo_rockets += it.count * WEP_CVAR(devastator, ammo);
                        this.netname = cons(this.netname, "devastator");
                }
+               else if (it.classname == "weapon_railgun") {
+                       this.ammo_cells += it.count * WEP_CVAR_PRI(vortex, ammo); // WEAPONTODO
+                       this.netname = cons(this.netname, "vortex");
+               }
                else if (it.classname == "weapon_lightning") {
                        this.ammo_cells += it.count * WEP_CVAR_PRI(electro, ammo); // WEAPONTODO
                        this.netname = cons(this.netname, "electro");
index 4c3b1d221ea4ae73be7f0ff7cc7b7ed5d7b9000f..89582bb7a009f81e4e183604ebdd103894c610b8 100644 (file)
@@ -376,6 +376,8 @@ const float ACTIVE_TOGGLE   = 3;
 
 .float stat_respawn_time = _STAT(RESPAWN_TIME); // shows respawn time, and is negative when awaiting respawn
 
+.int killindicator_teamchange;
+
 void PlayerUseKey(entity this);
 
 USING(spawn_evalfunc_t, vector(entity this, entity player, entity spot, vector current));
index 8a0223804d473e8d1f3288f235f546bdb7dab84c..afe018c08bc0198c2240c99e44f7dea7e75295fd 100644 (file)
@@ -9,6 +9,7 @@
 #include "../common/state.qh"
 #include "../common/physics/player.qh"
 #include "../common/t_items.qh"
+#include "resources.qh"
 #include "../common/vehicles/all.qh"
 #include "../common/items/_mod.qh"
 #include "../common/mutators/mutator/waypoints/waypointsprites.qh"
@@ -53,8 +54,8 @@ void GiveFrags (entity attacker, entity targ, float f, int deathtype)
        {
                // regular frag
                GameRules_scoring_add(attacker, KILLS, 1);
-               if(targ.playerid)
-                       PS_GR_P_ADDVAL(attacker, sprintf("kills-%d", targ.playerid), 1);
+               if(!warmup_stage && targ.playerid)
+                       PlayerStats_GameReport_Event_Player(attacker, sprintf("kills-%d", targ.playerid), 1);
        }
 
        GameRules_scoring_add(targ, DEATHS, 1);
@@ -166,60 +167,42 @@ void Obituary_SpecialDeath(
        string s1, string s2, string s3,
        float f1, float f2, float f3)
 {
-       if(DEATH_ISSPECIAL(deathtype))
+       if(!DEATH_ISSPECIAL(deathtype))
        {
-               entity deathent = Deathtypes_from(deathtype - DT_FIRST);
-               if (!deathent) { backtrace("Obituary_SpecialDeath: Could not find deathtype entity!\n"); return; }
+               backtrace("Obituary_SpecialDeath called without a special deathtype?\n");
+               return;
+       }
 
-               if(g_cts && deathtype == DEATH_KILL.m_id)
-                       return; // TODO: somehow put this in CTS gamemode file!
+       entity deathent = Deathtypes_from(deathtype - DT_FIRST);
+       if (!deathent)
+       {
+               backtrace("Obituary_SpecialDeath: Could not find deathtype entity!\n");
+               return;
+       }
 
-               if(murder)
-               {
-                       if(deathent.death_msgmurder)
-                       {
-                               Send_Notification_WOCOVA(
-                                       NOTIF_ONE,
-                                       notif_target,
-                                       MSG_MULTI,
-                                       deathent.death_msgmurder,
-                                       s1, s2, s3, "",
-                                       f1, f2, f3, 0
-                               );
-                               Send_Notification_WOCOVA(
-                                       NOTIF_ALL_EXCEPT,
-                                       notif_target,
-                                       MSG_INFO,
-                                       deathent.death_msgmurder.nent_msginfo,
-                                       s1, s2, s3, "",
-                                       f1, f2, f3, 0
-                               );
-                       }
-               }
-               else
-               {
-                       if(deathent.death_msgself)
-                       {
-                               Send_Notification_WOCOVA(
-                                       NOTIF_ONE,
-                                       notif_target,
-                                       MSG_MULTI,
-                                       deathent.death_msgself,
-                                       s1, s2, s3, "",
-                                       f1, f2, f3, 0
-                               );
-                               Send_Notification_WOCOVA(
-                                       NOTIF_ALL_EXCEPT,
-                                       notif_target,
-                                       MSG_INFO,
-                                       deathent.death_msgself.nent_msginfo,
-                                       s1, s2, s3, "",
-                                       f1, f2, f3, 0
-                               );
-                       }
-               }
+       if(g_cts && deathtype == DEATH_KILL.m_id)
+               return; // TODO: somehow put this in CTS gamemode file!
+
+       Notification death_message = (murder) ? deathent.death_msgmurder : deathent.death_msgself;
+       if(death_message)
+       {
+               Send_Notification_WOCOVA(
+                       NOTIF_ONE,
+                       notif_target,
+                       MSG_MULTI,
+                       death_message,
+                       s1, s2, s3, "",
+                       f1, f2, f3, 0
+               );
+               Send_Notification_WOCOVA(
+                       NOTIF_ALL_EXCEPT,
+                       notif_target,
+                       MSG_INFO,
+                       death_message.nent_msginfo,
+                       s1, s2, s3, "",
+                       f1, f2, f3, 0
+               );
        }
-       else { backtrace("Obituary_SpecialDeath called without a special deathtype?\n"); return; }
 }
 
 float Obituary_WeaponDeath(
@@ -230,44 +213,43 @@ float Obituary_WeaponDeath(
        float f1, float f2)
 {
        Weapon death_weapon = DEATH_WEAPONOF(deathtype);
-       if (death_weapon != WEP_Null)
-       {
-               w_deathtype = deathtype;
-               Notification death_message = ((murder) ? death_weapon.wr_killmessage(death_weapon) : death_weapon.wr_suicidemessage(death_weapon));
-               w_deathtype = false;
+       if (death_weapon == WEP_Null)
+               return false;
 
-               if (death_message)
-               {
-                       Send_Notification_WOCOVA(
-                               NOTIF_ONE,
-                               notif_target,
-                               MSG_MULTI,
-                               death_message,
-                               s1, s2, s3, "",
-                               f1, f2, 0, 0
-                       );
-                       // send the info part to everyone
-                       Send_Notification_WOCOVA(
-                               NOTIF_ALL_EXCEPT,
-                               notif_target,
-                               MSG_INFO,
-                               death_message.nent_msginfo,
-                               s1, s2, s3, "",
-                               f1, f2, 0, 0
-                       );
-               }
-               else
-               {
-                       LOG_TRACEF(
-                               "Obituary_WeaponDeath(): ^1Deathtype ^7(%d)^1 has no notification for weapon %d!\n",
-                               deathtype,
-                               death_weapon
-                       );
-               }
+       w_deathtype = deathtype;
+       Notification death_message = ((murder) ? death_weapon.wr_killmessage(death_weapon) : death_weapon.wr_suicidemessage(death_weapon));
+       w_deathtype = false;
 
-               return true;
+       if (death_message)
+       {
+               Send_Notification_WOCOVA(
+                       NOTIF_ONE,
+                       notif_target,
+                       MSG_MULTI,
+                       death_message,
+                       s1, s2, s3, "",
+                       f1, f2, 0, 0
+               );
+               // send the info part to everyone
+               Send_Notification_WOCOVA(
+                       NOTIF_ALL_EXCEPT,
+                       notif_target,
+                       MSG_INFO,
+                       death_message.nent_msginfo,
+                       s1, s2, s3, "",
+                       f1, f2, 0, 0
+               );
        }
-       return false;
+       else
+       {
+               LOG_TRACEF(
+                       "Obituary_WeaponDeath(): ^1Deathtype ^7(%d)^1 has no notification for weapon %d!\n",
+                       deathtype,
+                       death_weapon
+               );
+       }
+
+       return true;
 }
 
 bool frag_centermessage_override(entity attacker, entity targ, int deathtype, int kill_count_to_attacker, int kill_count_to_target)
@@ -275,7 +257,7 @@ bool frag_centermessage_override(entity attacker, entity targ, int deathtype, in
        if(deathtype == DEATH_FIRE.m_id)
        {
                Send_Notification(NOTIF_ONE, attacker, MSG_CHOICE, CHOICE_FRAG_FIRE, targ.netname, kill_count_to_attacker, (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping));
-               Send_Notification(NOTIF_ONE, targ, MSG_CHOICE, CHOICE_FRAGGED_FIRE, attacker.netname, kill_count_to_target, attacker.health, attacker.armorvalue, (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping));
+               Send_Notification(NOTIF_ONE, targ, MSG_CHOICE, CHOICE_FRAGGED_FIRE, attacker.netname, kill_count_to_target, GetResourceAmount(attacker, RESOURCE_HEALTH), GetResourceAmount(attacker, RESOURCE_ARMOR), (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping));
                return true;
        }
 
@@ -378,11 +360,16 @@ void Obituary(entity attacker, entity inflictor, entity targ, int deathtype)
 
                        attacker.killsound += 1;
 
+                       // TODO: improve SPREE_ITEM and KILL_SPREE_LIST
+                       // these 2 macros are spread over multiple files
                        #define SPREE_ITEM(counta,countb,center,normal,gentle) \
                                case counta: \
                                { \
                                        Send_Notification(NOTIF_ONE, attacker, MSG_ANNCE, ANNCE_KILLSTREAK_##countb); \
-                                       PS_GR_P_ADDVAL(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##counta, 1); \
+                                       if (!warmup_stage)\
+                                       {\
+                                               PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##counta, 1); \
+                                       }\
                                        break; \
                                }
                        switch(CS(attacker).killcount)
@@ -392,12 +379,12 @@ void Obituary(entity attacker, entity inflictor, entity targ, int deathtype)
                        }
                        #undef SPREE_ITEM
 
-                       if(!checkrules_firstblood)
+                       if(!warmup_stage && !checkrules_firstblood)
                        {
                                checkrules_firstblood = true;
                                notif_firstblood = true; // modify the current messages so that they too show firstblood information
-                               PS_GR_P_ADDVAL(attacker, PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD, 1);
-                               PS_GR_P_ADDVAL(targ, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, 1);
+                               PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD, 1);
+                               PlayerStats_GameReport_Event_Player(targ, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, 1);
 
                                // tell spree_inf and spree_cen that this is a first-blood and first-victim event
                                kill_count_to_attacker = -1;
@@ -427,8 +414,8 @@ void Obituary(entity attacker, entity inflictor, entity targ, int deathtype)
                                        CHOICE_TYPEFRAGGED,
                                        attacker.netname,
                                        kill_count_to_target,
-                                       attacker.health,
-                                       attacker.armorvalue,
+                                       GetResourceAmount(attacker, RESOURCE_HEALTH),
+                                       GetResourceAmount(attacker, RESOURCE_ARMOR),
                                        (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
                                );
                        }
@@ -450,8 +437,8 @@ void Obituary(entity attacker, entity inflictor, entity targ, int deathtype)
                                        CHOICE_FRAGGED,
                                        attacker.netname,
                                        kill_count_to_target,
-                                       attacker.health,
-                                       attacker.armorvalue,
+                                       GetResourceAmount(attacker, RESOURCE_HEALTH),
+                                       GetResourceAmount(attacker, RESOURCE_ARMOR),
                                        (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
                                );
                        }
@@ -512,7 +499,10 @@ void Obituary(entity attacker, entity inflictor, entity targ, int deathtype)
                if(GameRules_scoring_add(targ, SCORE, 0) == -5)
                {
                        Send_Notification(NOTIF_ONE, targ, MSG_ANNCE, ANNCE_ACHIEVEMENT_BOTLIKE);
-                       PS_GR_P_ADDVAL(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1);
+                       if (!warmup_stage)
+                       {
+                               PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1);
+                       }
                }
        }
 
@@ -543,7 +533,7 @@ void Freeze (entity targ, float freeze_time, float frozen_type, float show_waypo
 
        STAT(FROZEN, targ) = frozen_type;
        targ.revive_progress = ((frozen_type == 3) ? 1 : 0);
-       targ.health = ((frozen_type == 3) ? targ_maxhealth : 1);
+       SetResourceAmount(targ, RESOURCE_HEALTH, ((frozen_type == 3) ? targ_maxhealth : 1));
        targ.revive_speed = freeze_time;
        if(targ.bot_attack)
                IL_REMOVE(g_bot_targets, targ);
@@ -588,7 +578,7 @@ void Unfreeze (entity targ)
 
        if(STAT(FROZEN, targ) && STAT(FROZEN, targ) != 3) // only reset health if target was frozen
        {
-               targ.health = ((IS_PLAYER(targ)) ? start_health : targ.max_health);
+               SetResourceAmount(targ, RESOURCE_HEALTH, ((IS_PLAYER(targ)) ? start_health : targ.max_health));
                targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
        }
 
@@ -646,9 +636,9 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, int d
                // These are ALWAYS lethal
                // No damage modification here
                // Instead, prepare the victim for his death...
-               targ.armorvalue = 0;
+               SetResourceAmount(targ, RESOURCE_ARMOR, 0);
                targ.spawnshieldtime = 0;
-               targ.health = 0.9; // this is < 1
+               SetResourceAmount(targ, RESOURCE_HEALTH, 0.9); // this is < 1
                targ.flags -= targ.flags & FL_GODMODE;
                damage = 100000;
        }
@@ -748,7 +738,7 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, int d
                        if(damage >= autocvar_g_frozen_revive_falldamage)
                        {
                                Unfreeze(targ);
-                               targ.health = autocvar_g_frozen_revive_falldamage_health;
+                               SetResourceAmount(targ, RESOURCE_HEALTH, autocvar_g_frozen_revive_falldamage_health);
                                Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3);
                                Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname);
                                Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
index 64aa03b5026d5b62f0e24df6fc731389a8b637e9..ce25dfa522772b08e3f1dfb41dcc2a536a2f4902 100644 (file)
@@ -521,7 +521,7 @@ void detect_maptype()
                o.y += random() * (world.maxs.y - world.mins.y);
                o.z += random() * (world.maxs.z - world.mins.z);
 
-               tracebox(o, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), o - '0 0 32768', MOVE_WORLDONLY, NULL);
+               tracebox(o, STAT(PL_MIN), STAT(PL_MAX), o - '0 0 32768', MOVE_WORLDONLY, NULL);
                if(trace_fraction == 1)
                        continue;
 
@@ -580,7 +580,15 @@ spawnfunc(__init_dedicated_server)
 
        e = new(info_player_deathmatch);  // safeguard against player joining
 
-       this.classname = "worldspawn"; // safeguard against various stuff ;)
+    // assign reflectively to avoid "assignment to world" warning
+    for (int i = 0, n = numentityfields(); i < n; ++i) {
+        string k = entityfieldname(i);
+        if (k == "classname") {
+            // safeguard against various stuff ;)
+            putentityfieldstring(i, this, "worldspawn");
+            break;
+        }
+    }
 
        // needs to be done so early because of the constants they create
        static_init();
@@ -597,6 +605,14 @@ void __init_dedicated_server_shutdown() {
        MapInfo_Shutdown();
 }
 
+STATIC_INIT_EARLY(maxclients)
+{
+       maxclients = 0;
+       for (entity head = nextent(NULL); head; head = nextent(head)) {
+               ++maxclients;
+       }
+}
+
 void Map_MarkAsRecent(string m);
 float world_already_spawned;
 void Nagger_Init();
@@ -680,12 +696,6 @@ spawnfunc(worldspawn)
 
        cvar_changes_init(); // do this very early now so it REALLY matches the server config
 
-       maxclients = 0;
-       for (entity head = nextent(NULL); head; head = nextent(head))
-       {
-               ++maxclients;
-       }
-
        // needs to be done so early because of the constants they create
        static_init();
 
@@ -936,6 +946,7 @@ spawnfunc(worldspawn)
        WinningConditionHelper(this); // set worldstatus
 
        world_initialized = 1;
+       __spawnfunc_spawn_all();
 }
 
 spawnfunc(light)
diff --git a/qcsrc/server/handicap.qc b/qcsrc/server/handicap.qc
new file mode 100644 (file)
index 0000000..d0dfc4d
--- /dev/null
@@ -0,0 +1,41 @@
+#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);
+}
diff --git a/qcsrc/server/handicap.qh b/qcsrc/server/handicap.qh
new file mode 100644 (file)
index 0000000..fa45a0e
--- /dev/null
@@ -0,0 +1,40 @@
+#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);
index 7bb8e922d5902a7f03ac9c67b0da27284eb17f12..ab837c8945b873c0982d13834693c4a1a0f1fe9e 100644 (file)
@@ -7,6 +7,7 @@
 #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"
@@ -199,14 +200,14 @@ string NearestLocation(vector p)
 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;
 }
@@ -525,6 +526,10 @@ void readplayerstartcvars()
        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");
 
@@ -643,6 +648,17 @@ void readplayerstartcvars()
                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)
@@ -711,6 +727,16 @@ void readplayerstartcvars()
        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);
@@ -933,24 +959,21 @@ void InitializeEntitiesRun()
 .float(entity) isEliminated;
 bool EliminatedPlayers_SendEntity(entity this, entity to, float sendflags)
 {
-       float i, f, b;
-       entity e;
-       WriteHeader(MSG_ENTITY, ENT_CLIENT_ELIMINATEDPLAYERS);
-       WriteByte(MSG_ENTITY, sendflags);
-
-       if(sendflags & 1)
-       {
-               for(i = 1; i <= maxclients; i += 8)
-               {
-                       for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
-                       {
-                               if(eliminatedPlayers.isEliminated(e))
-                                       f |= b;
+       Stream out = MSG_ENTITY;
+       WriteHeader(out, ENT_CLIENT_ELIMINATEDPLAYERS);
+       serialize(byte, out, sendflags);
+       if (sendflags & 1) {
+               for (int i = 1; i <= maxclients; i += 8) {
+                       int f = 0;
+                       entity e = edict_num(i);
+                       for (int b = 0; b < 8; ++b, e = nextent(e)) {
+                               if (eliminatedPlayers.isEliminated(e)) {
+                                       f |= BIT(b);
+                               }
                        }
-                       WriteByte(MSG_ENTITY, f);
+                       serialize(byte, out, f);
                }
        }
-
        return true;
 }
 
index 1c01a573797713c4fe39345da3f895e60717aad9..1dd3526dd48c435eaa00ade8bc1a0047b767079a 100644 (file)
@@ -188,6 +188,13 @@ float start_ammo_rockets;
 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;
index bc09d7a8cd9d92d3da6340807e8a09fd13b384cc..e0c4198cc7adbea3354f350544950a5cf8ac7397 100644 (file)
@@ -130,6 +130,31 @@ MUTATOR_HOOKABLE(MatchEnd, EV_NO_ARGS);
     /**/
 MUTATOR_HOOKABLE(CheckAllowedTeams, EV_CheckAllowedTeams);
 
+/** return true to manually override team counts */
+MUTATOR_HOOKABLE(GetTeamCounts, EV_NO_ARGS);
+
+/** allow overriding of team counts */
+#define EV_GetTeamCount(i, o) \
+    /** team to count                   */ i(float, MUTATOR_ARGV_0_float) \
+    /** player to ignore                */ i(entity, MUTATOR_ARGV_1_entity) \
+    /** number of players in a team     */ i(float, MUTATOR_ARGV_2_float) \
+    /**/                                   o(float, MUTATOR_ARGV_2_float) \
+    /** number of bots in a team        */ i(float, MUTATOR_ARGV_3_float) \
+    /**/                                   o(float, MUTATOR_ARGV_3_float) \
+    /** lowest scoring human in a team  */ i(entity, MUTATOR_ARGV_4_entity) \
+    /**/                                   o(entity, MUTATOR_ARGV_4_entity) \
+    /** lowest scoring bot in a team    */ i(entity, MUTATOR_ARGV_5_entity) \
+    /**/                                   o(entity, MUTATOR_ARGV_5_entity) \
+    /**/
+MUTATOR_HOOKABLE(GetTeamCount, EV_GetTeamCount);
+
+/** allows overriding best teams */
+#define EV_FindBestTeams(i, o) \
+    /** player checked   */ i(entity, MUTATOR_ARGV_0_entity) \
+    /** bitmask of teams */ o(float, MUTATOR_ARGV_1_float) \
+    /**/
+MUTATOR_HOOKABLE(FindBestTeams, EV_FindBestTeams);
+
 /** copies variables for spectating "spectatee" to "this" */
 #define EV_SpectateCopy(i, o) \
     /** spectatee   */ i(entity, MUTATOR_ARGV_0_entity) \
@@ -620,6 +645,38 @@ enum {
        MUT_ITEMTOUCH_PICKUP // return this flag to have the item "picked up" and taken even after mutator handled it
 };
 
+/** Called when the amount of entity resources changes. Can be used to override
+resource limit. */
+#define EV_GetResourceLimit(i, o) \
+       /** checked entity */ i(entity, MUTATOR_ARGV_0_entity) \
+       /** resource type */  i(int, MUTATOR_ARGV_1_int) \
+       /** limit */          i(float, MUTATOR_ARGV_2_float) \
+       /**/                  o(float, MUTATOR_ARGV_2_float) \
+       /**/
+MUTATOR_HOOKABLE(GetResourceLimit, EV_GetResourceLimit);
+
+/** Called when the amount of resource of an entity changes. See RESOURCE_*
+constants for resource types. Return true to forbid the change. */
+#define EV_SetResourceAmount(i, o) \
+       /** checked entity */ i(entity, MUTATOR_ARGV_0_entity) \
+       /** resource type */  i(int, MUTATOR_ARGV_1_int) \
+       /**/                  o(int, MUTATOR_ARGV_1_int) \
+       /** amount */         i(float, MUTATOR_ARGV_2_float) \
+       /**/                  o(float, MUTATOR_ARGV_2_float) \
+       /**/
+MUTATOR_HOOKABLE(SetResourceAmount, EV_SetResourceAmount);
+
+/** Called when entity is being given some resource. See RESOURCE_* constants
+for resource types. Return true to forbid giving. */
+#define EV_GiveResource(i, o) \
+       /** receiver */      i(entity, MUTATOR_ARGV_0_entity) \
+       /** resource type */ i(int, MUTATOR_ARGV_1_int) \
+       /**/                 o(int, MUTATOR_ARGV_1_int) \
+       /** amount */        i(float, MUTATOR_ARGV_2_float) \
+       /**/                 o(float, MUTATOR_ARGV_2_float) \
+       /**/
+MUTATOR_HOOKABLE(GiveResource, EV_GiveResource);
+
 /** called at when a player connect */
 #define EV_ClientConnect(i, o) \
     /** player */ i(entity, MUTATOR_ARGV_0_entity) \
@@ -728,6 +785,12 @@ MUTATOR_HOOKABLE(Race_FinalCheckpoint, EV_Race_FinalCheckpoint);
     /**/
 MUTATOR_HOOKABLE(ClientKill, EV_ClientKill);
 
+/** called when player is about to be killed during kill command or changing teams */
+#define EV_ClientKill_Now(i, o) \
+    /** player */        i(entity, MUTATOR_ARGV_0_entity) \
+    /**/
+MUTATOR_HOOKABLE(ClientKill_Now, EV_ClientKill_Now);
+
 #define EV_FixClientCvars(i, o) \
     /** player */        i(entity, MUTATOR_ARGV_0_entity) \
     /**/
@@ -808,6 +871,13 @@ MUTATOR_HOOKABLE(WantWeapon, EV_WantWeapon);
     /**/
 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) \
     /**/
@@ -875,7 +945,9 @@ MUTATOR_HOOKABLE(PrepareExplosionByDamage, EV_PrepareExplosionByDamage);
     /**/
 MUTATOR_HOOKABLE(MonsterModel, EV_MonsterModel);
 
-/**/
+/**
+ * Called before player changes their team. Return true to block team change.
+ */
 #define EV_Player_ChangeTeam(i, o) \
     /** player */         i(entity, MUTATOR_ARGV_0_entity) \
        /** current team */   i(float, MUTATOR_ARGV_1_float) \
@@ -883,6 +955,24 @@ MUTATOR_HOOKABLE(MonsterModel, EV_MonsterModel);
     /**/
 MUTATOR_HOOKABLE(Player_ChangeTeam, EV_Player_ChangeTeam);
 
+/**
+ * Called after player has changed their team.
+ */
+#define EV_Player_ChangedTeam(i, o) \
+    /** player */         i(entity, MUTATOR_ARGV_0_entity) \
+       /** old team */       i(float, MUTATOR_ARGV_1_float) \
+       /** current team */   i(float, MUTATOR_ARGV_2_float) \
+    /**/
+MUTATOR_HOOKABLE(Player_ChangedTeam, EV_Player_ChangedTeam);
+
+/**
+ * Called when player is about to be killed when changing teams. Return true to block killing.
+ */
+#define EV_Player_ChangeTeamKill(i, o) \
+    /** player */    i(entity, MUTATOR_ARGV_0_entity) \
+    /**/
+MUTATOR_HOOKABLE(Player_ChangeTeamKill, EV_Player_ChangeTeamKill);
+
 /**/
 #define EV_URI_GetCallback(i, o) \
        /** id */       i(float, MUTATOR_ARGV_0_float) \
index 159df8a7818edbf5348a6d9ab184fb1227dbaefe..919df49013dd5f53cc1c5c2f819cf11c2ee38d41 100644 (file)
@@ -266,7 +266,11 @@ MUTATOR_HOOKFUNCTION(ca, PlayerDies)
 
        ca_LastPlayerForTeam_Notify(frag_target);
        if (!allowed_to_spawn)
-               frag_target.respawn_flags =  RESPAWN_SILENT;
+       {
+               frag_target.respawn_flags = RESPAWN_SILENT;
+               // prevent unwanted sudden rejoin as spectator and move of spectator camera
+               frag_target.respawn_time = time + 2;
+       }
        if (!warmup_stage)
                eliminatedPlayers.SendFlags |= 1;
        if(IS_BOT_CLIENT(frag_target))
@@ -289,7 +293,7 @@ MUTATOR_HOOKFUNCTION(ca, MakePlayerObserver)
 
        if (!IS_DEAD(player))
                ca_LastPlayerForTeam_Notify(player);
-       if (CS(player).killindicator_teamchange == -2) // player wants to spectate
+       if (player.killindicator_teamchange == -2) // player wants to spectate
                player.caplayer = 0;
        if (player.caplayer)
                player.frags = FRAGS_LMS_LOSER;
index 44a877791b19673f539ac4dd065ffe5eaafa032f..26fc9e659204d7bf6305a5d2483bf1b2b4ba9819 100644 (file)
@@ -4,6 +4,7 @@
 #include "bot/api.qh"
 #include "cheats.qh"
 #include "g_damage.qh"
+#include "handicap.qh"
 #include "g_subs.qh"
 #include "miscfunctions.qh"
 #include "portals.qh"
@@ -317,9 +318,11 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
 
        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)
@@ -514,7 +517,7 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
 
                if(this.alivetime)
                {
-                       PS_GR_P_ADDVAL(this, PLAYERSTATS_ALIVETIME, time - this.alivetime);
+                       PlayerStats_GameReport_Event_Player(this, PLAYERSTATS_ALIVETIME, time - this.alivetime);
                        this.alivetime = 0;
                }
 
@@ -535,7 +538,7 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
                {
                        delete(this.killindicator);
                        this.killindicator = NULL;
-                       if(CS(this).killindicator_teamchange)
+                       if(this.killindicator_teamchange)
                                defer_ClientKill_Now_TeamChange = true;
 
                        if(this.classname == "body")
@@ -555,6 +558,7 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
                if(w != WEP_Null && accuracy_isgooddamage(attacker, this))
                        CS(attacker).accuracy.(accuracy_frags[w.m_id-1]) += 1;
 
+               this.respawn_time = 0;
                MUTATOR_CALLHOOK(PlayerDies, inflictor, attacker, this, deathtype, damage);
                damage = M_ARGV(4, float);
                excess = max(0, damage - take - save);
@@ -583,6 +587,9 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
                if(this.health >= 1 || !(IS_PLAYER(this) || this.classname == "body"))
                        return;
 
+               if (!this.respawn_time) // can be set in the mutator hook PlayerDies
+                       calculate_player_respawn_time(this);
+
                // when we get here, player actually dies
 
                Unfreeze(this); // remove any icy remains
@@ -618,9 +625,6 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
 
                STAT(MOVEVARS_SPECIALCOMMAND, this) = false; // sweet release
 
-               // when to allow respawn
-               calculate_player_respawn_time(this);
-
                this.death_time = time;
                if (random() < 0.5)
                        animdecide_setstate(this, this.anim_state | ANIMSTATE_DEAD1, true);
@@ -668,15 +672,19 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
        }
 }
 
-void MoveToTeam(entity client, int team_colour, int type)
+bool MoveToTeam(entity client, int team_colour, int type)
 {
        int lockteams_backup = lockteams;  // backup any team lock
        lockteams = 0;  // disable locked teams
        TeamchangeFrags(client);  // move the players frags
-       SetPlayerColors(client, team_colour - 1);  // set the players colour
+       if (!SetPlayerTeamSimple(client, team_colour))
+       {
+               return false;
+       }
        Damage(client, client, client, 100000, DEATH_AUTOTEAMCHANGE.m_id, client.origin, '0 0 0');  // kill the player
        lockteams = lockteams_backup;  // restore the team lock
        LogTeamchange(client.playerid, client.team, type);
+       return true;
 }
 
 /** print(), but only print if the server is not local */
index 1834bb865ffbb668801e525eb40b7e90f5afa341..dfa485e5f11fddad33799689e9d354fae640e572 100644 (file)
@@ -69,7 +69,12 @@ void calculate_player_respawn_time(entity this);
 
 void ClientKill_Now_TeamChange(entity this);
 
-void MoveToTeam(entity client, float team_colour, float type);
+/// \brief Moves player to the specified team.
+/// \param[in,out] client Client to move.
+/// \param[in] team_colour Color of the team.
+/// \param[in] type ???
+/// \return True on success, false otherwise.
+bool MoveToTeam(entity client, float team_colour, float type);
 
 void PlayerDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force);
 
diff --git a/qcsrc/server/resources.qc b/qcsrc/server/resources.qc
new file mode 100644 (file)
index 0000000..a2a1358
--- /dev/null
@@ -0,0 +1,191 @@
+#include "resources.qh"
+
+/// \file
+/// \brief Source file that contains implementation of the resource system.
+/// \author Lyberta
+/// \copyright GNU GPLv2 or any later version.
+
+#include "autocvars.qh"
+#include "miscfunctions.qh"
+
+float GetResourceLimit(entity e, int resource_type)
+{
+       float limit;
+       switch (resource_type)
+       {
+               case RESOURCE_HEALTH:
+               {
+                       limit = autocvar_g_balance_health_limit;
+                       break;
+               }
+               case RESOURCE_ARMOR:
+               {
+                       limit = autocvar_g_balance_armor_limit;
+                       break;
+               }
+               case RESOURCE_SHELLS:
+               {
+                       limit = g_pickup_shells_max;
+                       break;
+               }
+               case RESOURCE_BULLETS:
+               {
+                       limit = g_pickup_nails_max;
+                       break;
+               }
+               case RESOURCE_ROCKETS:
+               {
+                       limit = g_pickup_rockets_max;
+                       break;
+               }
+               case RESOURCE_CELLS:
+               {
+                       limit = g_pickup_cells_max;
+                       break;
+               }
+               case RESOURCE_PLASMA:
+               {
+                       limit = g_pickup_plasma_max;
+                       break;
+               }
+               case RESOURCE_FUEL:
+               {
+                       limit = autocvar_g_balance_fuel_limit;
+                       break;
+               }
+               default:
+               {
+                       error("GetResourceLimit: Invalid resource type.");
+                       return 0;
+               }
+       }
+       MUTATOR_CALLHOOK(GetResourceLimit, e, resource_type, limit);
+       limit = M_ARGV(2, float);
+       if (limit > RESOURCE_AMOUNT_HARD_LIMIT)
+       {
+               limit = RESOURCE_AMOUNT_HARD_LIMIT;
+       }
+       return limit;
+}
+
+float GetResourceAmount(entity e, int resource_type)
+{
+       .float resource_field = GetResourceField(resource_type);
+       return e.(resource_field);
+}
+
+void SetResourceAmount(entity e, int resource_type, float amount)
+{
+       bool forbid = MUTATOR_CALLHOOK(SetResourceAmount, e, resource_type, amount);
+       if (forbid)
+       {
+               return;
+       }
+       resource_type = M_ARGV(1, int);
+       amount = M_ARGV(2, float);
+       .float resource_field = GetResourceField(resource_type);
+       if (e.(resource_field) == amount)
+       {
+               return;
+       }
+       float max_amount = GetResourceLimit(e, resource_type);
+       if (amount > max_amount)
+       {
+               amount = max_amount;
+       }
+       e.(resource_field) = amount;
+}
+
+void GiveResource(entity receiver, int resource_type, float amount)
+{
+       if (amount == 0)
+       {
+               return;
+       }
+       bool forbid = MUTATOR_CALLHOOK(GiveResource, receiver, resource_type,
+               amount);
+       if (forbid)
+       {
+               return;
+       }
+       resource_type = M_ARGV(1, int);
+       amount = M_ARGV(2, float);
+       if (amount <= 0)
+       {
+               return;
+       }
+       SetResourceAmount(receiver, resource_type,
+               GetResourceAmount(receiver, resource_type) + amount);
+       switch (resource_type)
+       {
+               case RESOURCE_HEALTH:
+               {
+                       receiver.pauserothealth_finished =
+                               max(receiver.pauserothealth_finished, time +
+                               autocvar_g_balance_pause_health_rot);
+                       return;
+               }
+               case RESOURCE_ARMOR:
+               {
+                       receiver.pauserotarmor_finished =
+                               max(receiver.pauserotarmor_finished, time +
+                               autocvar_g_balance_pause_armor_rot);
+                       return;
+               }
+               case RESOURCE_FUEL:
+               {
+                       receiver.pauserotfuel_finished = max(receiver.pauserotfuel_finished,
+                               time + autocvar_g_balance_pause_fuel_rot);
+                       return;
+               }
+       }
+}
+
+void GiveResourceWithLimit(entity receiver, int resource_type, float amount,
+       float limit)
+{
+       if (amount == 0)
+       {
+               return;
+       }
+       float current_amount = GetResourceAmount(receiver, resource_type);
+       if (current_amount + amount > limit)
+       {
+               amount = limit - current_amount;
+       }
+       GiveResource(receiver, resource_type, amount);
+}
+
+int GetResourceType(.float resource_field)
+{
+       switch (resource_field)
+       {
+               case health: { return RESOURCE_HEALTH; }
+               case armorvalue: { return RESOURCE_ARMOR; }
+               case ammo_shells: { return RESOURCE_SHELLS; }
+               case ammo_nails: { return RESOURCE_BULLETS; }
+               case ammo_rockets: { return RESOURCE_ROCKETS; }
+               case ammo_cells: { return RESOURCE_CELLS; }
+               case ammo_plasma: { return RESOURCE_PLASMA; }
+               case ammo_fuel: { return RESOURCE_FUEL; }
+       }
+       error("GetResourceType: Invalid field.");
+       return 0;
+}
+
+.float GetResourceField(int resource_type)
+{
+       switch (resource_type)
+       {
+               case RESOURCE_HEALTH: { return health; }
+               case RESOURCE_ARMOR: { return armorvalue; }
+               case RESOURCE_SHELLS: { return ammo_shells; }
+               case RESOURCE_BULLETS: { return ammo_nails; }
+               case RESOURCE_ROCKETS: { return ammo_rockets; }
+               case RESOURCE_CELLS: { return ammo_cells; }
+               case RESOURCE_PLASMA: { return ammo_plasma; }
+               case RESOURCE_FUEL: { return ammo_fuel; }
+       }
+       error("GetResourceField: Invalid resource type.");
+       return health;
+}
diff --git a/qcsrc/server/resources.qh b/qcsrc/server/resources.qh
new file mode 100644 (file)
index 0000000..6ff3cea
--- /dev/null
@@ -0,0 +1,60 @@
+#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);
index 266f7734b660bae45bad90f29514a869ef182b46..32fe3c6aaa2dd178f3af6cff5d89606688e3209f 100644 (file)
@@ -54,7 +54,7 @@ vector ScoreField_Compare(entity t1, entity t2, .float field, float fieldflags,
 
 bool TeamScore_SendEntity(entity this, entity to, float sendflags)
 {
-       float i, p, longflags;
+       float i, longflags;
 
        WriteHeader(MSG_ENTITY, ENT_CLIENT_TEAMSCORES);
        int t = this.team - 1;
@@ -62,9 +62,9 @@ bool TeamScore_SendEntity(entity this, entity to, float sendflags)
        WriteByte(MSG_ENTITY, t);
 
        longflags = 0;
-       for(i = 0, p = 1; i < MAX_TEAMSCORE; ++i, p *= 2)
+       for(i = 0; i < MAX_TEAMSCORE; ++i)
                if(this.(teamscores(i)) > 127 || this.(teamscores(i)) <= -128)
-                       longflags |= p;
+                       longflags |= BIT(i);
 
 #if MAX_TEAMSCORE <= 8
        WriteByte(MSG_ENTITY, sendflags);
@@ -73,10 +73,10 @@ bool TeamScore_SendEntity(entity this, entity to, float sendflags)
        WriteShort(MSG_ENTITY, sendflags);
        WriteShort(MSG_ENTITY, longflags);
 #endif
-       for(i = 0, p = 1; i < MAX_TEAMSCORE; ++i, p *= 2)
-               if(sendflags & p)
+       for(i = 0; i < MAX_TEAMSCORE; ++i)
+               if(sendflags & BIT(i))
                {
-                       if(longflags & p)
+                       if(longflags & BIT(i))
                                WriteInt24_t(MSG_ENTITY, this.(teamscores(i)));
                        else
                                WriteChar(MSG_ENTITY, this.(teamscores(i)));
@@ -347,8 +347,10 @@ float PlayerScore_Add(entity player, PlayerScoreField scorefield, float score)
        if(scores_label(scorefield) != "")
                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);
+               PlayerStats_GameReport_Event_Player(s.owner, strcat(PLAYERSTATS_TOTAL, scores_label(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)
@@ -906,10 +908,10 @@ void PlayerScore_PlayerStats(entity p)
 {
        entity s = CS(p).scorekeeper;
        FOREACH(Scores, true, {
-               if(s.(scores(it)) != 0)
-                       if(scores_label(it) != "")
-                               PS_GR_P_ADDVAL(s.owner, strcat(PLAYERSTATS_SCOREBOARD, scores_label(it)), s.(scores(it)));
-    });
+               if(s.(scores(it)) != 0 && scores_label(it) != "")
+                       PlayerStats_GameReport_Event_Player(s.owner,
+                               strcat(PLAYERSTATS_SCOREBOARD, scores_label(it)), s.(scores(it)));
+       });
 }
 
 void PlayerScore_TeamStats()
@@ -922,9 +924,9 @@ void PlayerScore_TeamStats()
                if(!sk)
                        continue;
                for(i = 0; i < MAX_TEAMSCORE; ++i)
-                       if(sk.(teamscores(i)) != 0)
-                               if(teamscores_label(i) != "")
-                                       // the +1 is important here!
-                                       PS_GR_T_ADDVAL(t+1, strcat(PLAYERSTATS_SCOREBOARD, teamscores_label(i)), sk.(teamscores(i)));
+                       if(sk.(teamscores(i)) != 0 && teamscores_label(i) != "")
+                               // the +1 is important here!
+                               PlayerStats_GameReport_Event_Team(t+1,
+                                       strcat(PLAYERSTATS_SCOREBOARD, teamscores_label(i)), sk.(teamscores(i)));
        }
 }
index 2b539c999668bbcc5acca5304ef53100f7e37285..d46d95bd1c6114ee5138527c74cb726afc8a7dab 100644 (file)
@@ -5,6 +5,7 @@
 #include "client.qh"
 #include "scores.qh"
 #include <common/gamemodes/rules.qh>
+#include "teamplay.qh"
 
 int ScoreRules_teams;
 
index 1a5c8e5511420bbd00c066e37f9a641660f4ad69..97299ffeffa7e4a7c114ad53bf7885ca7585ed79 100644 (file)
@@ -88,7 +88,6 @@ void CreatureFrame_Liquids(entity this)
                        this.dmgtime = 0;
                }
                this.air_finished = time + 12;
-               this.dmg = 2;
        }
 }
 
@@ -247,168 +246,120 @@ void StartFrame()
 .string gametypefilter;
 .string cvarfilter;
 bool DoesQ3ARemoveThisEntity(entity this);
+
+/**
+ * Evaluate an expression of the form: [+ | -]? [var[op]val | [op]var | val | var] ...
+ * +: all must match. this is the default
+ * -: one must NOT match
+ *
+ * var>x
+ * var<x
+ * var>=x
+ * var<=x
+ * var==x
+ * var!=x
+ * var===x
+ * 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()
index 6f70f09beec2219624baeca92e2cd7deaa104fb4..7f86d19c01a47bc83cfd2870c0f0c170a0e93b99 100644 (file)
@@ -1 +1,3 @@
 #pragma once
+
+bool expr_evaluate(string s);
index e93a03201a9ddf4233b229126b69d15bca035de6..fd73969cb81a9d16cb88c55b02bee012cd0dfaa1 100644 (file)
@@ -43,8 +43,14 @@ void InitGameplayMode()
 
        // find out good world mins/maxs bounds, either the static bounds found by looking for solid, or the mapinfo specified bounds
        get_mi_min_max(1);
-       world.mins = mi_min;
-       world.maxs = mi_max;
+       // assign reflectively to avoid "assignment to world" warning
+       int done = 0; for (int i = 0, n = numentityfields(); i < n; ++i) {
+           string k = entityfieldname(i); vector v = (k == "mins") ? mi_min : (k == "maxs") ? mi_max : '0 0 0';
+           if (v) {
+            putentityfieldstring(i, world, sprintf("%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
@@ -160,58 +166,79 @@ void setcolor(entity this, int clr)
 #endif
 }
 
-void SetPlayerColors(entity pl, float _color)
+void SetPlayerColors(entity player, float _color)
 {
-       /*string s;
-       s = ftos(cl);
-       stuffcmd(pl, strcat("color ", s, " ", s, "\n")  );
-       pl.team = cl + 1;
-       //pl.clientcolors = pl.clientcolors - (pl.clientcolors & 15) + cl;
-       pl.clientcolors = 16*cl + cl;*/
-
-       float pants, shirt;
-       pants = _color & 0x0F;
-       shirt = _color & 0xF0;
-
-
-       if(teamplay) {
-               setcolor(pl, 16*pants + pants);
-       } else {
-               setcolor(pl, shirt + pants);
+       float pants = _color & 0x0F;
+       float shirt = _color & 0xF0;
+       if (teamplay)
+       {
+               setcolor(player, 16 * pants + pants);
+       }
+       else
+       {
+               setcolor(player, shirt + pants);
        }
 }
 
-void SetPlayerTeam(entity pl, float t, float s, float noprint)
+void KillPlayerForTeamChange(entity player)
 {
-       float _color;
-
-       if(t == 4)
-               _color = NUM_TEAM_4 - 1;
-       else if(t == 3)
-               _color = NUM_TEAM_3 - 1;
-       else if(t == 2)
-               _color = NUM_TEAM_2 - 1;
-       else
-               _color = NUM_TEAM_1 - 1;
-
-       SetPlayerColors(pl,_color);
-
-       if(t != s) {
-               LogTeamchange(pl.playerid, pl.team, 3);  // log manual team join
+       if (IS_DEAD(player))
+       {
+               return;
+       }
+       if (MUTATOR_CALLHOOK(Player_ChangeTeamKill, player) == true)
+       {
+               return;
+       }
+       Damage(player, player, player, 100000, DEATH_TEAMCHANGE.m_id, player.origin,
+               '0 0 0');
+}
 
-               if(!noprint)
-                       bprint(playername(pl, false), "^7 has changed from ", Team_NumberToColoredFullName(s), "^7 to ", Team_NumberToColoredFullName(t), "\n");
+bool SetPlayerTeamSimple(entity player, int team_num)
+{
+       if (player.team == team_num)
+       {
+               // This is important when players join the game and one of their color
+               // matches the team color while other doesn't. For example [BOT]Lion.
+               SetPlayerColors(player, team_num - 1);
+               return true;
        }
+       if (MUTATOR_CALLHOOK(Player_ChangeTeam, player, Team_TeamToNumber(
+               player.team), Team_TeamToNumber(team_num)) == true)
+       {
+               // Mutator has blocked team change.
+               return false;
+       }
+       int old_team = player.team;
+       SetPlayerColors(player, team_num - 1);
+       MUTATOR_CALLHOOK(Player_ChangedTeam, player, old_team, player.team);
+       return true;
+}
 
+bool SetPlayerTeam(entity player, int destination_team, int source_team,
+       bool no_print)
+{
+       int team_num = Team_NumberToTeam(destination_team);
+       if (!SetPlayerTeamSimple(player, team_num))
+       {
+               return false;
+       }
+       LogTeamchange(player.playerid, player.team, 3);  // log manual team join
+       if (no_print)
+       {
+               return true;
+       }
+       bprint(playername(player, false), "^7 has changed from ", Team_NumberToColoredFullName(source_team), "^7 to ", Team_NumberToColoredFullName(destination_team), "\n");
+       return true;
 }
 
 // set c1...c4 to show what teams are allowed
-void CheckAllowedTeams (entity for_whom)
+void CheckAllowedTeams(entity for_whom)
 {
        int teams_mask = 0;
 
        c1 = c2 = c3 = c4 = -1;
-       cb1 = cb2 = cb3 = cb4 = 0;
+       num_bots_team1 = num_bots_team2 = num_bots_team3 = num_bots_team4 = 0;
 
        string teament_name = string_null;
 
@@ -313,284 +340,570 @@ float PlayerValue(entity p)
 // teams that are allowed will now have their player counts stored in c1...c4
 void GetTeamCounts(entity ignore)
 {
-       float value, bvalue;
-       // now count how many players are on each team already
-
-       // FIXME: also find and memorize the lowest-scoring bot on each team (in case players must be shuffled around)
-       // also remember the lowest-scoring player
-
-       FOREACH_CLIENT(true, {
-               float t;
-               if(IS_PLAYER(it) || it.caplayer)
-                       t = it.team;
-               else if(it.team_forced > 0)
-                       t = it.team_forced; // reserve the spot
-               else
-                       continue;
-               if(it != ignore)// && it.netname != "")
+       if (MUTATOR_CALLHOOK(GetTeamCounts) == true)
+       {
+               if (c1 >= 0)
                {
+                       MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_1, ignore, c1,
+                               num_bots_team1, lowest_human_team1, lowest_bot_team1);
+                       c1 = M_ARGV(2, float);
+                       num_bots_team1 = M_ARGV(3, float);
+                       lowest_human_team1 = M_ARGV(4, entity);
+                       lowest_bot_team1 = M_ARGV(5, entity);
+               }
+               if (c2 >= 0)
+               {
+                       MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_2, ignore, c2,
+                               num_bots_team2, lowest_human_team2, lowest_bot_team2);
+                       c2 = M_ARGV(2, float);
+                       num_bots_team2 = M_ARGV(3, float);
+                       lowest_human_team2 = M_ARGV(4, entity);
+                       lowest_bot_team2 = M_ARGV(5, entity);
+               }
+               if (c3 >= 0)
+               {
+                       MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_3, ignore, c3,
+                               num_bots_team3, lowest_human_team3, lowest_bot_team3);
+                       c3 = M_ARGV(2, float);
+                       num_bots_team3 = M_ARGV(3, float);
+                       lowest_human_team3 = M_ARGV(4, entity);
+                       lowest_bot_team3 = M_ARGV(5, entity);
+               }
+               if (c4 >= 0)
+               {
+                       MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_4, ignore,
+                               c4, num_bots_team4, lowest_human_team4, lowest_bot_team4);
+                       c4 = M_ARGV(2, float);
+                       num_bots_team4 = M_ARGV(3, float);
+                       lowest_human_team4 = M_ARGV(4, entity);
+                       lowest_bot_team4 = M_ARGV(5, entity);
+               }
+       }
+       else
+       {
+               float value, bvalue;
+               // now count how many players are on each team already
+               float lowest_human_score1 = FLOAT_MAX;
+               float lowest_bot_score1 = FLOAT_MAX;
+               float lowest_human_score2 = FLOAT_MAX;
+               float lowest_bot_score2 = FLOAT_MAX;
+               float lowest_human_score3 = FLOAT_MAX;
+               float lowest_bot_score3 = FLOAT_MAX;
+               float lowest_human_score4 = FLOAT_MAX;
+               float lowest_bot_score4 = FLOAT_MAX;
+               FOREACH_CLIENT(true,
+               {
+                       float t;
+                       if (IS_PLAYER(it) || it.caplayer)
+                       {
+                               t = it.team;
+                       }
+                       else if (it.team_forced > 0)
+                       {
+                               t = it.team_forced; // reserve the spot
+                       }
+                       else
+                       {
+                               continue;
+                       }
+                       if (it == ignore)
+                       {
+                               continue;
+                       }
                        value = PlayerValue(it);
-                       if(IS_BOT_CLIENT(it))
+                       if (IS_BOT_CLIENT(it))
+                       {
                                bvalue = value;
+                       }
                        else
+                       {
                                bvalue = 0;
-                       if(t == NUM_TEAM_1)
+                       }
+                       if (value == 0)
                        {
-                               if(c1 >= 0)
-                               {
-                                       c1 = c1 + value;
-                                       cb1 = cb1 + bvalue;
-                               }
+                               continue;
                        }
-                       else if(t == NUM_TEAM_2)
+                       switch (t)
                        {
-                               if(c2 >= 0)
+                               case NUM_TEAM_1:
                                {
-                                       c2 = c2 + value;
-                                       cb2 = cb2 + bvalue;
+                                       if (c1 < 0)
+                                       {
+                                               break;
+                                       }
+                                       c1 += value;
+                                       num_bots_team1 += bvalue;
+                                       float temp_score = PlayerScore_Get(it, SP_SCORE);
+                                       if (!bvalue)
+                                       {
+                                               if (temp_score < lowest_human_score1)
+                                               {
+                                                       lowest_human_team1 = it;
+                                                       lowest_human_score1 = temp_score;
+                                               }
+                                               break;
+                                       }
+                                       if (temp_score < lowest_bot_score1)
+                                       {
+                                               lowest_bot_team1 = it;
+                                               lowest_bot_score1 = temp_score;
+                                       }
+                                       break;
                                }
-                       }
-                       else if(t == NUM_TEAM_3)
-                       {
-                               if(c3 >= 0)
+                               case NUM_TEAM_2:
                                {
-                                       c3 = c3 + value;
-                                       cb3 = cb3 + bvalue;
+                                       if (c2 < 0)
+                                       {
+                                               break;
+                                       }
+                                       c2 += value;
+                                       num_bots_team2 += bvalue;
+                                       float temp_score = PlayerScore_Get(it, SP_SCORE);
+                                       if (!bvalue)
+                                       {
+                                               if (temp_score < lowest_human_score2)
+                                               {
+                                                       lowest_human_team2 = it;
+                                                       lowest_human_score2 = temp_score;
+                                               }
+                                               break;
+                                       }
+                                       if (temp_score < lowest_bot_score2)
+                                       {
+                                               lowest_bot_team2 = it;
+                                               lowest_bot_score2 = temp_score;
+                                       }
+                                       break;
                                }
-                       }
-                       else if(t == NUM_TEAM_4)
-                       {
-                               if(c4 >= 0)
+                               case NUM_TEAM_3:
+                               {
+                                       if (c3 < 0)
+                                       {
+                                               break;
+                                       }
+                                       c3 += value;
+                                       num_bots_team3 += bvalue;
+                                       float temp_score = PlayerScore_Get(it, SP_SCORE);
+                                       if (!bvalue)
+                                       {
+                                               if (temp_score < lowest_human_score3)
+                                               {
+                                                       lowest_human_team3 = it;
+                                                       lowest_human_score3 = temp_score;
+                                               }
+                                               break;
+                                       }
+                                       if (temp_score < lowest_bot_score3)
+                                       {
+                                               lowest_bot_team3 = it;
+                                               lowest_bot_score3 = temp_score;
+                                       }
+                                       break;
+                               }
+                               case NUM_TEAM_4:
                                {
-                                       c4 = c4 + value;
-                                       cb4 = cb4 + bvalue;
+                                       if (c4 < 0)
+                                       {
+                                               break;
+                                       }
+                                       c4 += value;
+                                       num_bots_team4 += bvalue;
+                                       float temp_score = PlayerScore_Get(it, SP_SCORE);
+                                       if (!bvalue)
+                                       {
+                                               if (temp_score < lowest_human_score4)
+                                               {
+                                                       lowest_human_team4 = it;
+                                                       lowest_human_score4 = temp_score;
+                                               }
+                                               break;
+                                       }
+                                       if (temp_score < lowest_bot_score4)
+                                       {
+                                               lowest_bot_team4 = it;
+                                               lowest_bot_score4 = temp_score;
+                                       }
+                                       break;
                                }
                        }
-               }
-       });
+               });
+       }
 
        // if the player who has a forced team has not joined yet, reserve the spot
        if(autocvar_g_campaign)
        {
                switch(autocvar_g_campaign_forceteam)
                {
-                       case 1: if(c1 == cb1) ++c1; break;
-                       case 2: if(c2 == cb2) ++c2; break;
-                       case 3: if(c3 == cb3) ++c3; break;
-                       case 4: if(c4 == cb4) ++c4; break;
+                       case 1: if(c1 == num_bots_team1) ++c1; break;
+                       case 2: if(c2 == num_bots_team2) ++c2; break;
+                       case 3: if(c3 == num_bots_team3) ++c3; break;
+                       case 4: if(c4 == num_bots_team4) ++c4; break;
                }
        }
 }
 
-float TeamSmallerEqThanTeam(float ta, float tb, entity e)
+bool IsTeamSmallerThanTeam(int team_a, int team_b, entity player,
+       bool use_score)
 {
+       if (team_a == team_b)
+       {
+               return false;
+       }
        // we assume that CheckAllowedTeams and GetTeamCounts have already been called
-       float f;
-       float ca = -1, cb = -1, cba = 0, cbb = 0, sa = 0, sb = 0;
-
-       switch(ta)
+       int num_players_team_a = -1, num_players_team_b = -1;
+       int num_bots_team_a = 0, num_bots_team_b = 0;
+       float score_team_a = 0, score_team_b = 0;
+       switch (team_a)
        {
-               case 1: ca = c1; cba = cb1; sa = team1_score; break;
-               case 2: ca = c2; cba = cb2; sa = team2_score; break;
-               case 3: ca = c3; cba = cb3; sa = team3_score; break;
-               case 4: ca = c4; cba = cb4; sa = team4_score; break;
+               case 1:
+               {
+                       num_players_team_a = c1;
+                       num_bots_team_a = num_bots_team1;
+                       score_team_a = team1_score;
+                       break;
+               }
+               case 2:
+               {
+                       num_players_team_a = c2;
+                       num_bots_team_a = num_bots_team2;
+                       score_team_a = team2_score;
+                       break;
+               }
+               case 3:
+               {
+                       num_players_team_a = c3;
+                       num_bots_team_a = num_bots_team3;
+                       score_team_a = team3_score;
+                       break;
+               }
+               case 4:
+               {
+                       num_players_team_a = c4;
+                       num_bots_team_a = num_bots_team4;
+                       score_team_a = team4_score;
+                       break;
+               }
        }
-       switch(tb)
+       switch (team_b)
        {
-               case 1: cb = c1; cbb = cb1; sb = team1_score; break;
-               case 2: cb = c2; cbb = cb2; sb = team2_score; break;
-               case 3: cb = c3; cbb = cb3; sb = team3_score; break;
-               case 4: cb = c4; cbb = cb4; sb = team4_score; break;
+               case 1:
+               {
+                       num_players_team_b = c1;
+                       num_bots_team_b = num_bots_team1;
+                       score_team_b = team1_score;
+                       break;
+               }
+               case 2:
+               {
+                       num_players_team_b = c2;
+                       num_bots_team_b = num_bots_team2;
+                       score_team_b = team2_score;
+                       break;
+               }
+               case 3:
+               {
+                       num_players_team_b = c3;
+                       num_bots_team_b = num_bots_team3;
+                       score_team_b = team3_score;
+                       break;
+               }
+               case 4:
+               {
+                       num_players_team_b = c4;
+                       num_bots_team_b = num_bots_team4;
+                       score_team_b = team4_score;
+                       break;
+               }
        }
-
        // invalid
-       if(ca < 0 || cb < 0)
+       if (num_players_team_a < 0 || num_players_team_b < 0)
+       {
                return false;
-
-       // equal
-       if(ta == tb)
+       }
+       if (IS_REAL_CLIENT(player) && bots_would_leave)
+       {
+               num_players_team_a -= num_bots_team_a;
+               num_players_team_b -= num_bots_team_b;
+       }
+       if (!use_score)
+       {
+               return num_players_team_a < num_players_team_b;
+       }
+       if (num_players_team_a < num_players_team_b)
+       {
                return true;
+       }
+       if (num_players_team_a > num_players_team_b)
+       {
+               return false;
+       }
+       return score_team_a < score_team_b;
+}
 
-       if(IS_REAL_CLIENT(e))
+bool IsTeamEqualToTeam(int team_a, int team_b, entity player, bool use_score)
+{
+       if (team_a == team_b)
        {
-               if(bots_would_leave)
+               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)
+       {
+               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:
                {
-                       ca -= cba * 0.999;
-                       cb -= cbb * 0.999;
+                       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 (team_b)
+       {
+               case 1:
+               {
+                       num_players_team_b = c1;
+                       num_bots_team_b = num_bots_team1;
+                       score_team_b = team1_score;
+                       break;
+               }
+               case 2:
+               {
+                       num_players_team_b = c2;
+                       num_bots_team_b = num_bots_team2;
+                       score_team_b = team2_score;
+                       break;
+               }
+               case 3:
+               {
+                       num_players_team_b = c3;
+                       num_bots_team_b = num_bots_team3;
+                       score_team_b = team3_score;
+                       break;
+               }
+               case 4:
+               {
+                       num_players_team_b = c4;
+                       num_bots_team_b = num_bots_team4;
+                       score_team_b = team4_score;
+                       break;
+               }
+       }
+       // invalid
+       if (num_players_team_a < 0 || num_players_team_b < 0)
+               return false;
 
-       // keep teams alive (teams of size 0 always count as smaller, ignoring score)
-       if(ca < 1)
-               if(cb >= 1)
-                       return true;
-       if(ca >= 1)
-               if(cb < 1)
-                       return false;
-
-       // first, normalize
-       f = max(ca, cb, 1);
-       ca /= f;
-       cb /= f;
-       f = max(sa, sb, 1);
-       sa /= f;
-       sb /= f;
-
-       // the more we're at the end of the match, the more take scores into account
-       f = bound(0, game_completion_ratio * autocvar_g_balance_teams_scorefactor, 1);
-       ca += (sa - ca) * f;
-       cb += (sb - cb) * f;
+       if (IS_REAL_CLIENT(player) && bots_would_leave)
+       {
+               num_players_team_a -= num_bots_team_a;
+               num_players_team_b -= num_bots_team_b;
+       }
+       if (!use_score)
+       {
+               return num_players_team_a == num_players_team_b;
+       }
+       if (num_players_team_a != num_players_team_b)
+       {
+               return false;
+       }
+       return score_team_a == score_team_b;
+}
 
-       return ca <= cb;
+int FindBestTeams(entity player, bool use_score)
+{
+       if (MUTATOR_CALLHOOK(FindBestTeams, player) == true)
+       {
+               return M_ARGV(1, float);
+       }
+       int team_bits = 0;
+       int previous_team = 0;
+       if (c1 >= 0)
+       {
+               team_bits = BIT(0);
+               previous_team = 1;
+       }
+       if (c2 >= 0)
+       {
+               if (previous_team == 0)
+               {
+                       team_bits = BIT(1);
+                       previous_team = 2;
+               }
+               else if (IsTeamSmallerThanTeam(2, previous_team, player, use_score))
+               {
+                       team_bits = BIT(1);
+                       previous_team = 2;
+               }
+               else if (IsTeamEqualToTeam(2, previous_team, player, use_score))
+               {
+                       team_bits |= BIT(1);
+                       previous_team = 2;
+               }
+       }
+       if (c3 >= 0)
+       {
+               if (previous_team == 0)
+               {
+                       team_bits = BIT(2);
+                       previous_team = 3;
+               }
+               else if (IsTeamSmallerThanTeam(3, previous_team, player, use_score))
+               {
+                       team_bits = BIT(2);
+                       previous_team = 3;
+               }
+               else if (IsTeamEqualToTeam(3, previous_team, player, use_score))
+               {
+                       team_bits |= BIT(2);
+                       previous_team = 3;
+               }
+       }
+       if (c4 >= 0)
+       {
+               if (previous_team == 0)
+               {
+                       team_bits = BIT(3);
+               }
+               else if (IsTeamSmallerThanTeam(4, previous_team, player, use_score))
+               {
+                       team_bits = BIT(3);
+               }
+               else if (IsTeamEqualToTeam(4, previous_team, player, use_score))
+               {
+                       team_bits |= BIT(3);
+               }
+       }
+       return team_bits;
 }
 
 // returns # of smallest team (1, 2, 3, 4)
 // NOTE: Assumes CheckAllowedTeams has already been called!
-float FindSmallestTeam(entity pl, float ignore_pl)
+int FindSmallestTeam(entity player, float ignore_player)
 {
-       int totalteams = 0;
-       int t = 1; // initialize with a random team?
-       if(c4 >= 0) t = 4;
-       if(c3 >= 0) t = 3;
-       if(c2 >= 0) t = 2;
-       if(c1 >= 0) t = 1;
-
-       // find out what teams are available
-       //CheckAllowedTeams();
-
-       // make sure there are at least 2 teams to join
-       if(c1 >= 0)
-               totalteams = totalteams + 1;
-       if(c2 >= 0)
-               totalteams = totalteams + 1;
-       if(c3 >= 0)
-               totalteams = totalteams + 1;
-       if(c4 >= 0)
-               totalteams = totalteams + 1;
-
-       if((autocvar_bot_vs_human || pl.team_forced > 0) && totalteams == 1)
-               totalteams += 1;
-
-       if(totalteams <= 1)
+       // count how many players are in each team
+       if (ignore_player)
        {
-               if(autocvar_g_campaign && pl && IS_REAL_CLIENT(pl))
-                       return 1; // special case for campaign and player joining
-               else if(totalteams == 1) // single team
-                       LOG_TRACEF("Only 1 team available for %s, you may need to fix your map", MapInfo_Type_ToString(MapInfo_CurrentGametype()));
-               else // no teams, major no no
-                       error(sprintf("No teams available for %s\n", MapInfo_Type_ToString(MapInfo_CurrentGametype())));
+               GetTeamCounts(player);
        }
-
-       // count how many players are in each team
-       if(ignore_pl)
-               GetTeamCounts(pl);
        else
+       {
                GetTeamCounts(NULL);
-
+       }
+       int team_bits = FindBestTeams(player, true);
+       if (team_bits == 0)
+       {
+               error(sprintf("No teams available for %s\n", MapInfo_Type_ToString(MapInfo_CurrentGametype())));
+       }
        RandomSelection_Init();
-
-       if(TeamSmallerEqThanTeam(1, t, pl))
-               t = 1;
-       if(TeamSmallerEqThanTeam(2, t, pl))
-               t = 2;
-       if(TeamSmallerEqThanTeam(3, t, pl))
-               t = 3;
-       if(TeamSmallerEqThanTeam(4, t, pl))
-               t = 4;
-
-       // now t is the minimum, or A minimum!
-       if(t == 1 || TeamSmallerEqThanTeam(1, t, pl))
+       if ((team_bits & BIT(0)) != 0)
+       {
                RandomSelection_AddFloat(1, 1, 1);
-       if(t == 2 || TeamSmallerEqThanTeam(2, t, pl))
+       }
+       if ((team_bits & BIT(1)) != 0)
+       {
                RandomSelection_AddFloat(2, 1, 1);
-       if(t == 3 || TeamSmallerEqThanTeam(3, t, pl))
+       }
+       if ((team_bits & BIT(2)) != 0)
+       {
                RandomSelection_AddFloat(3, 1, 1);
-       if(t == 4 || TeamSmallerEqThanTeam(4, t, pl))
+       }
+       if ((team_bits & BIT(3)) != 0)
+       {
                RandomSelection_AddFloat(4, 1, 1);
-
+       }
        return RandomSelection_chosen_float;
 }
 
-int JoinBestTeam(entity this, bool only_return_best, bool forcebestteam)
+int JoinBestTeam(entity this, bool only_return_best, bool force_best_team)
 {
-       float smallest, selectedteam;
-
        // don't join a team if we're not playing a team game
-       if(!teamplay)
+       if (!teamplay)
+       {
                return 0;
+       }
 
        // find out what teams are available
        CheckAllowedTeams(this);
 
        // if we don't care what team he ends up on, put him on whatever team he entered as.
        // if he's not on a valid team, then let other code put him on the smallest team
-       if(!forcebestteam)
+       if (!force_best_team)
        {
+               int selected_team;
                if(     c1 >= 0 && this.team == NUM_TEAM_1)
-                       selectedteam = this.team;
+                       selected_team = this.team;
                else if(c2 >= 0 && this.team == NUM_TEAM_2)
-                       selectedteam = this.team;
+                       selected_team = this.team;
                else if(c3 >= 0 && this.team == NUM_TEAM_3)
-                       selectedteam = this.team;
+                       selected_team = this.team;
                else if(c4 >= 0 && this.team == NUM_TEAM_4)
-                       selectedteam = this.team;
+                       selected_team = this.team;
                else
-                       selectedteam = -1;
+                       selected_team = -1;
 
-               if(selectedteam > 0)
+               if (selected_team > 0)
                {
-                       if(!only_return_best)
+                       if (!only_return_best)
                        {
-                               SetPlayerColors(this, selectedteam - 1);
+                               SetPlayerTeamSimple(this, selected_team);
 
                                // when JoinBestTeam is called by client.qc/ClientKill_Now_TeamChange the players team is -1 and thus skipped
                                // when JoinBestTeam is called by client.qc/ClientConnect the player_id is 0 the log attempt is rejected
                                LogTeamchange(this.playerid, this.team, 99);
                        }
-                       return selectedteam;
+                       return selected_team;
                }
                // otherwise end up on the smallest team (handled below)
        }
 
-       smallest = FindSmallestTeam(this, true);
-
-       if(!only_return_best && !this.bot_forced_team)
+       int best_team = FindSmallestTeam(this, true);
+       if (only_return_best || this.bot_forced_team)
        {
-               TeamchangeFrags(this);
-               if(smallest == 1)
-               {
-                       SetPlayerColors(this, NUM_TEAM_1 - 1);
-               }
-               else if(smallest == 2)
-               {
-                       SetPlayerColors(this, NUM_TEAM_2 - 1);
-               }
-               else if(smallest == 3)
-               {
-                       SetPlayerColors(this, NUM_TEAM_3 - 1);
-               }
-               else if(smallest == 4)
-               {
-                       SetPlayerColors(this, NUM_TEAM_4 - 1);
-               }
-               else
-               {
-                       error("smallest team: invalid team\n");
-               }
-
-               LogTeamchange(this.playerid, this.team, 2); // log auto join
-
-               if(!IS_DEAD(this))
-                       Damage(this, this, this, 100000, DEATH_TEAMCHANGE.m_id, this.origin, '0 0 0');
+               return best_team;
        }
-
-       return smallest;
+       best_team = Team_NumberToTeam(best_team);
+       if (best_team == -1)
+       {
+               error("JoinBestTeam: invalid team\n");
+       }
+       int old_team = Team_TeamToNumber(this.team);
+       TeamchangeFrags(this);
+       SetPlayerTeamSimple(this, best_team);
+       LogTeamchange(this.playerid, this.team, 2); // log auto join
+       if (!IS_BOT_CLIENT(this))
+       {
+               AutoBalanceBots(old_team, Team_TeamToNumber(best_team));
+       }
+       KillPlayerForTeamChange(this);
+       return best_team;
 }
 
-//void() ctf_playerchanged;
 void SV_ChangeTeam(entity this, float _color)
 {
-       float scolor, dcolor, steam, dteam; //, dbotcount, scount, dcount;
+       float source_color, destination_color, source_team, destination_team;
 
        // in normal deathmatch we can just apply the color and we're done
        if(!teamplay)
@@ -606,38 +919,28 @@ void SV_ChangeTeam(entity this, float _color)
        if(!teamplay)
                return;
 
-       scolor = this.clientcolors & 0x0F;
-       dcolor = _color & 0x0F;
-
-       if(scolor == NUM_TEAM_1 - 1)
-               steam = 1;
-       else if(scolor == NUM_TEAM_2 - 1)
-               steam = 2;
-       else if(scolor == NUM_TEAM_3 - 1)
-               steam = 3;
-       else // if(scolor == NUM_TEAM_4 - 1)
-               steam = 4;
-       if(dcolor == NUM_TEAM_1 - 1)
-               dteam = 1;
-       else if(dcolor == NUM_TEAM_2 - 1)
-               dteam = 2;
-       else if(dcolor == NUM_TEAM_3 - 1)
-               dteam = 3;
-       else // if(dcolor == NUM_TEAM_4 - 1)
-               dteam = 4;
+       source_color = this.clientcolors & 0x0F;
+       destination_color = _color & 0x0F;
+
+       source_team = Team_TeamToNumber(source_color + 1);
+       destination_team = Team_TeamToNumber(destination_color + 1);
+
+       if (destination_team == -1)
+       {
+               return;
+       }
 
        CheckAllowedTeams(this);
 
-       if(dteam == 1 && c1 < 0) dteam = 4;
-       if(dteam == 4 && c4 < 0) dteam = 3;
-       if(dteam == 3 && c3 < 0) dteam = 2;
-       if(dteam == 2 && c2 < 0) dteam = 1;
+       if (destination_team == 1 && c1 < 0) destination_team = 4;
+       if (destination_team == 4 && c4 < 0) destination_team = 3;
+       if (destination_team == 3 && c3 < 0) destination_team = 2;
+       if (destination_team == 2 && c2 < 0) destination_team = 1;
 
        // not changing teams
-       if(scolor == dcolor)
+       if (source_color == destination_color)
        {
-               //bprint("same team change\n");
-               SetPlayerTeam(this, dteam, steam, true);
+               SetPlayerTeam(this, destination_team, source_team, true);
                return;
        }
 
@@ -647,169 +950,102 @@ void SV_ChangeTeam(entity this, float _color)
        }
 
        // autocvar_g_balance_teams_prevent_imbalance only makes sense if autocvar_g_balance_teams is on, as it makes the team selection dialog pointless
-       if(autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance)
+       if (autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance)
        {
                GetTeamCounts(this);
-               if(!TeamSmallerEqThanTeam(dteam, steam, this))
+               if ((BIT(destination_team - 1) & FindBestTeams(this, false)) == 0)
                {
                        Send_Notification(NOTIF_ONE, this, MSG_INFO, INFO_TEAMCHANGE_LARGERTEAM);
                        return;
                }
        }
-
-//     bprint("allow change teams from ", ftos(steam), " to ", ftos(dteam), "\n");
-
-       if(IS_PLAYER(this) && steam != dteam)
+       if(IS_PLAYER(this) && source_team != destination_team)
        {
                // reduce frags during a team change
                TeamchangeFrags(this);
        }
-
-       MUTATOR_CALLHOOK(Player_ChangeTeam, this, steam, dteam);
-
-       SetPlayerTeam(this, dteam, steam, !IS_CLIENT(this));
-
-       if(IS_PLAYER(this) && steam != dteam)
+       if (!SetPlayerTeam(this, destination_team, source_team, !IS_CLIENT(this)))
        {
-               // kill player when changing teams
-               if(!IS_DEAD(this))
-                       Damage(this, this, this, 100000, DEATH_TEAMCHANGE.m_id, this.origin, '0 0 0');
+               return;
        }
+       AutoBalanceBots(source_team, destination_team);
+       if (!IS_PLAYER(this) || (source_team == destination_team))
+       {
+               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)
+       if ((source_team == -1) || (destination_team == -1))
        {
-               smallestteam = 2;
-               smallestteam_count = c2;
-       }
-       if(c3 >= 0 && c3 < smallestteam_count)
-       {
-               smallestteam = 3;
-               smallestteam_count = c3;
-       }
-       if(c4 >= 0 && c4 < smallestteam_count)
-       {
-               smallestteam = 4;
-               smallestteam_count = c4;
+               return;
        }
-
-       if(!smallestteam)
+       if (!autocvar_g_balance_teams ||
+               !autocvar_g_balance_teams_prevent_imbalance)
        {
-               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);
 }
index 8d0ea9cb8ae6d846c326fd59b66732295bf7ee6e..1813db04d8d111387a6a1667c6033e9b2e890f6c 100644 (file)
@@ -3,10 +3,29 @@
 string cache_mutatormsg;
 string cache_lastmutatormsg;
 
-// client counts for each team
-//float c1, c2, c3, c4;
-// # of bots on those teams
-float cb1, cb2, cb3, cb4;
+// The following variables are used for balancing. They are not updated
+// automatically. You need to call CheckAllowedTeams and GetTeamCounts to get
+// proper values.
+
+// These four have 2 different states. If they are equal to -1, it means that
+// the player can't join the team. Zero or positive value means that player can
+// join the team and means the number of players on that team.
+float c1;
+float c2;
+float c3;
+float c4;
+float num_bots_team1; ///< Number of bots in the first team.
+float num_bots_team2; ///< Number of bots in the second team.
+float num_bots_team3; ///< Number of bots in the third team.
+float num_bots_team4; ///< Number of bots in the fourth team.
+entity lowest_human_team1; ///< Human with the lowest score in the first team.
+entity lowest_human_team2; ///< Human with the lowest score in the second team.
+entity lowest_human_team3; ///< Human with the lowest score in the third team.
+entity lowest_human_team4; ///< Human with the lowest score in the fourth team.
+entity lowest_bot_team1; ///< Bot with the lowest score in the first team.
+entity lowest_bot_team2; ///< Bot with the lowest score in the second team.
+entity lowest_bot_team3; ///< Bot with the lowest score in the third team.
+entity lowest_bot_team4; ///< Bot with the lowest score in the fourth team.
 
 int redowned, blueowned, yellowowned, pinkowned;
 
@@ -24,12 +43,30 @@ string GetClientVersionMessage(entity this);
 
 string getwelcomemessage(entity this);
 
-void SetPlayerColors(entity pl, float _color);
+void SetPlayerColors(entity player, float _color);
 
-void SetPlayerTeam(entity pl, float t, float s, float noprint);
+/// \brief Kills player as a result of team change.
+/// \param[in,out] player Player to kill.
+/// \return No return.
+void KillPlayerForTeamChange(entity player);
+
+/// \brief Sets the team of the player.
+/// \param[in,out] player Player to adjust.
+/// \param[in] team_num Team number to set. See TEAM_NUM constants.
+/// \return True if team switch was successful, false otherwise.
+bool SetPlayerTeamSimple(entity player, int team_num);
+
+/// \brief Sets the team of the player.
+/// \param[in,out] player Player to adjust.
+/// \param[in] destination_team Team to set.
+/// \param[in] source_team Previous team of the player.
+/// \param[in] no_print Whether to print this event to players' console.
+/// \return True if team switch was successful, false otherwise.
+bool SetPlayerTeam(entity player, int destination_team, int source_team,
+       bool no_print);
 
 // set c1...c4 to show what teams are allowed
-void CheckAllowedTeams (entity for_whom);
+void CheckAllowedTeams(entity for_whom);
 
 float PlayerValue(entity p);
 
@@ -37,16 +74,47 @@ float PlayerValue(entity p);
 // teams that are allowed will now have their player counts stored in c1...c4
 void GetTeamCounts(entity ignore);
 
-float TeamSmallerEqThanTeam(float ta, float tb, entity e);
+/// \brief Returns whether one team is smaller than the other.
+/// \param[in] team_a First team.
+/// \param[in] team_b Second team.
+/// \param[in] player Player to check.
+/// \param[in] use_score Whether to take into account team scores.
+/// \return True if first team is smaller than the second one, false otherwise.
+/// \note This function assumes that CheckAllowedTeams and GetTeamCounts have
+/// been called.
+bool IsTeamSmallerThanTeam(int team_a, int team_b, entity player,
+       bool use_score);
+
+/// \brief Returns whether one team is equal to the other.
+/// \param[in] team_a First team.
+/// \param[in] team_b Second team.
+/// \param[in] player Player to check.
+/// \param[in] use_score Whether to take into account team scores.
+/// \return True if first team is equal to the second one, false otherwise.
+/// \note This function assumes that CheckAllowedTeams and GetTeamCounts have
+/// been called.
+bool IsTeamEqualToTeam(int team_a, int team_b, entity player, bool use_score);
+
+/// \brief Returns the bitmask of the best teams for the player to join.
+/// \param[in] player Player to check.
+/// \param[in] use_score Whether to take into account team scores.
+/// \return Bitmask of the best teams for the player to join.
+/// \note This function assumes that CheckAllowedTeams and GetTeamCounts have
+/// been called.
+int FindBestTeams(entity player, bool use_score);
 
 // returns # of smallest team (1, 2, 3, 4)
 // NOTE: Assumes CheckAllowedTeams has already been called!
-float FindSmallestTeam(entity pl, float ignore_pl);
-
-int JoinBestTeam(entity this, bool only_return_best, bool forcebestteam);
+int FindSmallestTeam(entity player, float ignore_player);
 
-//void() ctf_playerchanged;
+int JoinBestTeam(entity this, bool only_return_best, bool force_best_team);
 
-void ShufflePlayerOutOfTeam (float source_team);
+/// \brief Auto balances bots in teams after the player has changed team.
+/// \param[in] source_team Previous team of the player (1, 2, 3, 4).
+/// \param[in] destination_team Current team of the player (1, 2, 3, 4).
+/// \return No return.
+/// \note This function assumes that CheckAllowedTeams and GetTeamCounts have
+/// been called.
+void AutoBalanceBots(int source_team, int destination_team);
 
 void setcolor(entity this, int clr);
index da5c7a56a7483839793114dd03e540615351e510..4a603c02256d244dd69fca54040a80b9f4faeed1 100644 (file)
@@ -41,7 +41,7 @@ const string STR_OBSERVER = "observer";
                } \
        } MACRO_END
 
-#define FOREACH_CLIENT(cond, body) FOREACH_CLIENTSLOT(IS_CLIENT(it) && (cond), body)
+#define FOREACH_CLIENT(cond, body) FOREACH_CLIENTSLOT(IS_CLIENT(it) && (cond), LAMBDA(body))
 
 // using the "inside out" version of knuth-fisher-yates shuffle
 // https://en.wikipedia.org/wiki/Fisher–Yates_shuffle
index 62b11ea45d62e47e3bc9c3d7244b05837e39c737..d5b78aa1cd064424b267ca3cfc2ce7c782714d09 100644 (file)
@@ -1,6 +1,7 @@
 #include "spawning.qh"
 
 #include "weaponsystem.qh"
+#include "../resources.qh"
 #include "../mutators/_mod.qh"
 #include <common/t_items.qh>
 #include <common/weapons/_all.qh>
@@ -101,16 +102,16 @@ void weapon_defaultspawnfunc(entity this, Weapon e)
                        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;
                }
        }
 
index 4b7d45b964157127f682e1de6048aa1bc3c358cd..52bf4a60788509039e72818a421099eb0c9aa907 100644 (file)
@@ -1,6 +1,7 @@
 #include "throwing.qh"
 
 #include "weaponsystem.qh"
+#include "../resources.qh"
 #include "../mutators/_mod.qh"
 #include <common/t_items.qh>
 #include "../g_damage.qh"
@@ -37,7 +38,7 @@ string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vecto
        float thisammo;
        string s;
        Weapon info = Weapons_from(wpn);
-       var .int ammotype = info.ammo_field;
+       int ammotype = info.ammo_type;
 
        entity wep = new(droppedweapon);
 
@@ -88,7 +89,7 @@ string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vecto
        wep.pickup_anyway = true; // these are ALWAYS pickable
 
        //wa = W_AmmoItemCode(wpn);
-       if(ammotype == ammo_none)
+       if(ammotype == RESOURCE_NONE)
        {
                return "";
        }
@@ -102,11 +103,10 @@ string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vecto
                        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)
                {
@@ -114,22 +114,23 @@ string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vecto
                        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);
@@ -155,7 +156,7 @@ bool W_IsWeaponThrowable(entity this, int w)
                // 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;
index f8949b15601919e1b863084a1e5653ab3f94d9c2..2f6fcb68d7213cbafc508343d377da932944ad0a 100644 (file)
@@ -5,6 +5,7 @@
 #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>
@@ -668,21 +669,22 @@ void W_DecreaseAmmo(Weapon wep, entity actor, float ammo_use, .entity weaponenti
                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);
        }
 }
 
@@ -702,16 +704,17 @@ void W_ReloadedAndReady(Weapon thiswep, entity actor, .entity weaponentity, int
        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;
 
@@ -754,9 +757,9 @@ void W_Reload(entity actor, .entity weaponentity, float sent_ammo_min, Sound sen
        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))
                        {
index e835fa67135e5145c0c8bd97ace8512c05492b4f..e3d1eb01368df1c714c55d7264eda99e89ff8bbd 100755 (executable)
@@ -74,6 +74,12 @@ function check() {
     done
 }
 
-check client
-check server
-check menu
+if [ ${#@} -eq 0 ]; then
+    check client
+    check server
+    check menu
+else
+    for var in ${@}; do
+        check ${var}
+    done
+fi
index 1c0eadcbe30b293c69a95134a2fc4e85a09c0e50..a8ac4a0e85a7533dbc78ff08ba450e8935322d9e 100755 (executable)
@@ -13,10 +13,14 @@ function hash() {
 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
index a538eafbb59c29b0ee036b4a1813626abb52c2f1..bfd9808fbb13e2029972cb4c36e36114e55a1f75 100644 (file)
@@ -2,6 +2,7 @@ models/ok_nade_counter/ok_nade_counter_01
 {
        dpnoshadow
        deformVertexes autosprite
+       nopicmip
        {
                map "models/ok_nade_counter/ok_nade_counter_01"
                blendfunc add
@@ -11,6 +12,7 @@ models/ok_nade_counter/ok_nade_counter_02
 {
        dpnoshadow
        deformVertexes autosprite
+       nopicmip
        {
                map "models/ok_nade_counter/ok_nade_counter_02"
                blendfunc add
@@ -20,6 +22,7 @@ models/ok_nade_counter/ok_nade_counter_03
 {
        dpnoshadow
        deformVertexes autosprite
+       nopicmip
        {
                map "models/ok_nade_counter/ok_nade_counter_03"
                blendfunc add
@@ -29,6 +32,7 @@ models/ok_nade_counter/ok_nade_counter_04
 {
        dpnoshadow
        deformVertexes autosprite
+       nopicmip
        {
                map "models/ok_nade_counter/ok_nade_counter_04"
                blendfunc add
@@ -38,6 +42,7 @@ models/ok_nade_counter/ok_nade_counter_05
 {
        dpnoshadow
        deformVertexes autosprite
+       nopicmip
        {
                map "models/ok_nade_counter/ok_nade_counter_05"
                blendfunc add
@@ -47,6 +52,7 @@ models/ok_nade_counter/ok_nade_counter_06
 {
        dpnoshadow
        deformVertexes autosprite
+       nopicmip
        {
                map "models/ok_nade_counter/ok_nade_counter_06"
                blendfunc add
@@ -56,6 +62,7 @@ models/ok_nade_counter/ok_nade_counter_07
 {
        dpnoshadow
        deformVertexes autosprite
+       nopicmip
        {
                map "models/ok_nade_counter/ok_nade_counter_07"
                blendfunc add
@@ -65,6 +72,7 @@ models/ok_nade_counter/ok_nade_counter_08
 {
        dpnoshadow
        deformVertexes autosprite
+       nopicmip
        {
                map "models/ok_nade_counter/ok_nade_counter_08"
                blendfunc add
@@ -74,6 +82,7 @@ models/ok_nade_counter/ok_nade_counter_09
 {
        dpnoshadow
        deformVertexes autosprite
+       nopicmip
        {
                map "models/ok_nade_counter/ok_nade_counter_09"
                blendfunc add