]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into martin-t/okc3
authorMartin Taibr <taibr.martin@gmail.com>
Fri, 2 Feb 2018 12:49:04 +0000 (13:49 +0100)
committerMartin Taibr <taibr.martin@gmail.com>
Fri, 2 Feb 2018 12:49:04 +0000 (13:49 +0100)
226 files changed:
.gitlab-ci.yml
defaultServer.cfg
defaultXonotic.cfg
minigames.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/hud/panel/physics.qc
qcsrc/client/hud/panel/scoreboard.qc
qcsrc/client/view.qc
qcsrc/client/view.qh
qcsrc/common/effects/qc/globalsound.qc
qcsrc/common/effects/qc/globalsound.qh
qcsrc/common/gamemodes/gamemode/nexball/nexball.qc
qcsrc/common/gamemodes/gamemode/nexball/sv_weapon.qc
qcsrc/common/gamemodes/gamemode/onslaught/sv_onslaught.qc
qcsrc/common/items/inventory.qh
qcsrc/common/items/item.qh
qcsrc/common/items/item/ammo.qc
qcsrc/common/items/item/ammo.qh
qcsrc/common/items/item/armor.qh
qcsrc/common/items/item/health.qh
qcsrc/common/items/item/jetpack.qc
qcsrc/common/items/item/jetpack.qh
qcsrc/common/items/item/pickup.qc
qcsrc/common/items/item/pickup.qh
qcsrc/common/items/item/powerup.qh
qcsrc/common/monsters/sv_monsters.qc
qcsrc/common/mutators/mutator/_mod.inc
qcsrc/common/mutators/mutator/_mod.qh
qcsrc/common/mutators/mutator/buffs/sv_buffs.qc
qcsrc/common/mutators/mutator/instagib/_mod.inc
qcsrc/common/mutators/mutator/instagib/items.qh
qcsrc/common/mutators/mutator/instagib/sv_instagib.qc
qcsrc/common/mutators/mutator/instagib/sv_instagib.qh
qcsrc/common/mutators/mutator/instagib/sv_items.qc [new file with mode: 0644]
qcsrc/common/mutators/mutator/kick_teamkiller/_mod.inc [new file with mode: 0644]
qcsrc/common/mutators/mutator/kick_teamkiller/_mod.qh [new file with mode: 0644]
qcsrc/common/mutators/mutator/kick_teamkiller/sv_kick_teamkiller.qc [new file with mode: 0644]
qcsrc/common/mutators/mutator/melee_only/sv_melee_only.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/overkill/sv_overkill.qh
qcsrc/common/mutators/mutator/random_items/_mod.inc [new file with mode: 0644]
qcsrc/common/mutators/mutator/random_items/_mod.qh [new file with mode: 0644]
qcsrc/common/mutators/mutator/random_items/sv_random_items.qc [new file with mode: 0644]
qcsrc/common/mutators/mutator/random_items/sv_random_items.qh [new file with mode: 0644]
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/notifications/all.inc
qcsrc/common/physics/movetypes/movetypes.qc
qcsrc/common/physics/player.qc
qcsrc/common/physics/player.qh
qcsrc/common/scores.qh
qcsrc/common/stats.qh
qcsrc/common/t_items.qc
qcsrc/common/t_items.qh
qcsrc/common/teams.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/func/door_rotating.qc
qcsrc/common/triggers/func/door_secret.qc
qcsrc/common/triggers/func/ladder.qc
qcsrc/common/triggers/misc/teleport_dest.qc
qcsrc/common/triggers/teleporters.qc
qcsrc/common/triggers/teleporters.qh
qcsrc/common/triggers/trigger/counter.qc
qcsrc/common/triggers/trigger/delay.qc
qcsrc/common/triggers/trigger/heal.qc
qcsrc/common/triggers/trigger/jumppads.qc
qcsrc/common/triggers/trigger/jumppads.qh
qcsrc/common/triggers/trigger/multi.qc
qcsrc/common/triggers/trigger/relay.qc
qcsrc/common/triggers/trigger/teleport.qc
qcsrc/common/triggers/trigger/viewloc.qc
qcsrc/common/triggers/trigger/viewloc.qh
qcsrc/common/triggers/triggers.qc
qcsrc/common/viewloc.qc
qcsrc/common/weapons/weapon.qh
qcsrc/common/weapons/weapon/arc.qc
qcsrc/common/weapons/weapon/arc.qh
qcsrc/common/weapons/weapon/blaster.qc
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.qc
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.qc
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.qc
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.qc
qcsrc/common/weapons/weapon/shockwave.qh
qcsrc/common/weapons/weapon/shotgun.qc
qcsrc/common/weapons/weapon/shotgun.qh
qcsrc/common/weapons/weapon/tuba.qc
qcsrc/common/weapons/weapon/tuba.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/ecs/systems/physics.qc
qcsrc/ecs/systems/sv_physics.qc
qcsrc/lib/spawnfunc.qh
qcsrc/lib/warpzone/common.qc
qcsrc/lib/warpzone/server.qc
qcsrc/menu/xonotic/dialog_multiplayer_join_serverinfo.qc
qcsrc/server/_mod.inc
qcsrc/server/_mod.qh
qcsrc/server/antilag.qc
qcsrc/server/antilag.qh
qcsrc/server/autocvars.qh
qcsrc/server/bot/api.qh
qcsrc/server/bot/default/aim.qc
qcsrc/server/bot/default/aim.qh
qcsrc/server/bot/default/bot.qc
qcsrc/server/bot/default/bot.qh
qcsrc/server/bot/default/havocbot/havocbot.qc
qcsrc/server/bot/default/havocbot/havocbot.qh
qcsrc/server/bot/default/havocbot/roles.qc
qcsrc/server/bot/default/navigation.qc
qcsrc/server/bot/default/navigation.qh
qcsrc/server/bot/default/waypoints.qc
qcsrc/server/bot/default/waypoints.qh
qcsrc/server/bot/null/bot_null.qc
qcsrc/server/client.qc
qcsrc/server/client.qh
qcsrc/server/command/sv_cmd.qc
qcsrc/server/compat/quake.qc
qcsrc/server/compat/quake2.qc
qcsrc/server/compat/quake3.qc
qcsrc/server/compat/wop.qc
qcsrc/server/defs.qh
qcsrc/server/g_damage.qc
qcsrc/server/g_subs.qc
qcsrc/server/g_world.qc
qcsrc/server/impulse.qc
qcsrc/server/items.qc [new file with mode: 0644]
qcsrc/server/items.qh [new file with mode: 0644]
qcsrc/server/miscfunctions.qc
qcsrc/server/mutators/events.qh
qcsrc/server/mutators/mutator/gamemode_assault.qc
qcsrc/server/mutators/mutator/gamemode_assault.qh
qcsrc/server/mutators/mutator/gamemode_ca.qc
qcsrc/server/mutators/mutator/gamemode_ctf.qc
qcsrc/server/mutators/mutator/gamemode_ctf.qh
qcsrc/server/mutators/mutator/gamemode_cts.qc
qcsrc/server/mutators/mutator/gamemode_domination.qc
qcsrc/server/mutators/mutator/gamemode_freezetag.qc
qcsrc/server/mutators/mutator/gamemode_keepaway.qc
qcsrc/server/mutators/mutator/gamemode_keyhunt.qc
qcsrc/server/mutators/mutator/gamemode_lms.qc
qcsrc/server/mutators/mutator/gamemode_race.qc
qcsrc/server/player.qc
qcsrc/server/scores.qc
qcsrc/server/scores.qh
qcsrc/server/scores_rules.qc
qcsrc/server/sv_main.qc
qcsrc/server/teamplay.qc
qcsrc/server/teamplay.qh
qcsrc/server/utils.qh
qcsrc/server/weapons/selection.qc
qcsrc/server/weapons/spawning.qc
qcsrc/server/weapons/throwing.qc
qcsrc/server/weapons/tracing.qc
qcsrc/server/weapons/weaponsystem.qh
randomitems-xonotic.cfg [new file with mode: 0644]

index 9305f527cd3abc9640d17bff880e021b854d7b17..bf43d891ded1096871f26547a3ce70d201afaee0 100644 (file)
@@ -29,7 +29,7 @@ test_sv_game:
     - wget -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints
     - wget -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache
     - make
-    - EXPECT=ed9be8d1b1a544f89bcdd7d36876fede
+    - EXPECT=29a3c5d84ed37810d674c2c176b21e04
     - HASH=$(${ENGINE} -noconfig -nohome +exec serverbench.cfg
       | tee /dev/stderr
       | grep '^:'
index e1487bfbb85cf7c7bfe46485de46ce72025507e0..6ca445642e88dba6d3203208caf6fadc699ea19b 100644 (file)
@@ -77,11 +77,14 @@ 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
 set sv_precacheitems 0
 set sv_spectator_speed_multiplier 1.5
+set sv_spectator_speed_multiplier_min 1
+set sv_spectator_speed_multiplier_max 5
 set sv_spectate 1 "if set to 1, new clients are allowed to spectate or observe the game, if set to 0 joining clients spawn as players immediately (no spectating)"
 set sv_defaultcharacter 0 "master switch, if set to 1 the further configuration for replacing all player models, skins and colors is taken from the sv_defaultplayermodel, sv_defaultplayerskin and sv_defaultplayercolors variables"
 set sv_defaultcharacterskin 0 "if set to 1 the further configuration for replacing all skins is taken from the sv_defaultplayerskin variables"
@@ -115,7 +118,8 @@ set bot_debug_goalstack 0 "Visualize the current path that each bot is following
 set bot_wander_enable 1 "Have bots wander around if they are unable to reach any useful goal. Disable only for debugging purposes."
 // general bot AI cvars
 set bot_ai_thinkinterval 0.05
-set bot_ai_strategyinterval 5 "How often a new objective is chosen"
+set bot_ai_strategyinterval 7 "How often a new objective is chosen"
+set bot_ai_strategyinterval_movingtarget 5.5 "How often a new objective is chosen when current objective can move"
 set bot_ai_enemydetectioninterval 2 "How often bots pick a new target"
 set bot_ai_enemydetectionradius 10000 "How far bots can see enemies"
 set bot_ai_dodgeupdateinterval 0.2 "How often scan for items to dodge. Currently not in use."
@@ -201,7 +205,6 @@ set g_casings 2 "specifies which casings (0: none, 1: only shotgun casings, 2: s
 set g_norecoil 0 "if set to 1 shooting weapons won't make you crosshair to move upwards (recoil)"
 set g_maplist_mostrecent "" "contains the name of the maps that were most recently played"
 set g_maplist_mostrecent_count 3       "number of most recent maps that are blocked from being played again"
-set g_maplist "" "the list of maps to be cycled among (is autogenerated if empty)"
 set g_maplist_index 0  "this is used internally for saving position in maplist cycle"
 set g_maplist_selectrandom 0   "if 1, a random map will be chosen as next map - DEPRECATED in favor of g_maplist_shuffle"
 set g_maplist_shuffle 1        "new randomization method: like selectrandom, but avoid playing the same maps in short succession. This works by taking out the first element and inserting it into g_maplist with a bias to the end of the list"
index eb6c72a5640408a2167b68bacd6ed45440ca5752..bac826e806f97192cd77e8413ec60cd2d76e2a03 100644 (file)
@@ -52,6 +52,9 @@ set _campaign_index ""
 set _campaign_name ""
 set _campaign_testrun 0 "To verify the campaign file, set this to 1, then start the first campaign level from the menu. If you end up in the menu again, it's good, if you get a QC crash, it's bad."
 
+// used by both server and menu to maintain the available list of maps
+seta g_maplist "" "the list of maps to be cycled among (is autogenerated if empty)"
+
 // we must change its default from 1.0 to 1 to be consistent with menuqc
 set slowmo 1
 
index 754eb0e67b4d57fd95922629fc2e29abd7ae003d..0cdd7a43113fb02ae640a61e7f9f7735953bfadb 100644 (file)
@@ -14,13 +14,5 @@ set sv_minigames_pong_ai_thinkspeed 0.1     "Seconds between AI actions"
 set sv_minigames_pong_ai_tolerance  0.33    "Distance of the ball relative to the paddle size"
 
 
-// Snake? Snake! SNAAAAKE!!
-set sv_minigames_snake_wrap 0 "Wrap around the edges of the screen instead of dying on touch"
-set sv_minigames_snake_delay_initial 0.7 "Initial delay between snake movement"
-set sv_minigames_snake_delay_multiplier 50 "Multiplier of incremental of movement speed (player_score / cvar)"
-set sv_minigames_snake_delay_min 0.1 "Minimum delay between snake movement (at fastest rate)"
-set sv_minigames_snake_lives 3
-
-
 // Bulldozer
 set sv_minigames_bulldozer_startlevel "level1"
index de8faa4ff1e01315b737f09350578b1665719bd1..af0fa9e68477e1b05e3366e6dddb57a1f3547677 100644 (file)
@@ -40,7 +40,9 @@ set g_instagib_ammo_convert_bullets 0 "convert bullet ammo packs to insta cell a
 set g_instagib_ammo_convert_cells 0 "convert normal cell ammo packs to insta cell ammo packs"
 set g_instagib_ammo_convert_rockets 0 "convert rocket ammo packs to insta cell ammo packs"
 set g_instagib_ammo_convert_shells 0 "convert shell ammo packs to insta cell ammo packs"
+set g_instagib_invisibility_time 30 "Time of invisibility powerup in seconds."
 set g_instagib_invis_alpha 0.15
+set g_instagib_speed_time 30 "Time of speed powerup in seconds."
 set g_instagib_speed_highspeed 1.5 "speed-multiplier that applies while you carry the invincibility powerup"
 set g_instagib_damagedbycontents 1 "allow damage from lava pits in instagib"
 set g_instagib_blaster_keepdamage 0 "allow secondary fire to hurt players"
@@ -477,3 +479,24 @@ set g_dynamic_handicap_scale 0.2 "The scale of the handicap. Larger values mean
 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."
+
+// ===============
+// kick teamkiller
+// ===============
+set g_kick_teamkiller_rate 0 "Limit for teamkills per minute before the client gets dropped. 0 means that the teamkillers don't get kicked automatically"
+set g_kick_teamkiller_lower_limit 5 "Minimum number of teamkills before the teamkill rate is considered"
+
+// =====================
+//  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"
+
+// ==============
+//  random items
+// ==============
+set g_random_items 0 "Whether to enable random items."
+set g_random_loot 0 "Whether to enable random loot."
+exec randomitems-xonotic.cfg
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 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 8e0d0f057695d8e237fbcbe2816b11298c6fce89..0360eb07e639795ae1b23709617ef05ca2e28caf 100644 (file)
@@ -96,6 +96,7 @@ string TranslateScoresLabel(string l)
                case "kd": return CTX(_("SCO^k/d"));
                case "kdr": return CTX(_("SCO^kdr"));
                case "kills": return CTX(_("SCO^kills"));
+               case "teamkills": return CTX(_("SCO^teamkills"));
                case "laps": return CTX(_("SCO^laps"));
                case "lives": return CTX(_("SCO^lives"));
                case "losses": return CTX(_("SCO^losses"));
@@ -312,6 +313,7 @@ void Cmd_Scoreboard_Help()
        LOG_INFO(_("^3deaths^7                   Number of deaths"));
        LOG_INFO(_("^3suicides^7                 Number of suicides"));
        LOG_INFO(_("^3frags^7                    kills - suicides"));
+       LOG_INFO(_("^3teamkills^7                Number of teamkills"));
        LOG_INFO(_("^3kd^7                       The kill-death ratio"));
        LOG_INFO(_("^3dmg^7                      The total damage done"));
        LOG_INFO(_("^3dmgtaken^7                 The total damage taken"));
@@ -360,15 +362,18 @@ void Cmd_Scoreboard_Help()
 "ping pl name |" \
 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
 " -teams,lms/deaths +ft,tdm/deaths" \
+" +tdm/sum" \
 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
+" +tdm,ft,dom,ons,as/teamkills"\
 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
-" +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
+" +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
 " +lms/lives +lms/rank" \
-" +kh/caps +kh/pushes +kh/destroyed" \
+" +kh/kckills +kh/losses +kh/caps" \
 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
 " +as/objectives +nb/faults +nb/goals" \
 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
+" +dom/ticks +dom/takes" \
 " -lms,rc,cts,inv,nb/score"
 
 void Cmd_Scoreboard_SetFields(int argc)
index 7a054606313f1e64d90b61105c2ae98683a909b9..6a294d5ad5d35322c4576e063a844bcc0da16a15 100644 (file)
@@ -12,6 +12,7 @@
 #include "mutators/events.qh"
 
 #include <common/animdecide.qh>
+#include <common/deathtypes/all.qh>
 #include <common/ent_cs.qh>
 #include <common/anim.qh>
 #include <common/constants.qh>
@@ -30,6 +31,7 @@
 #include <common/vehicles/all.qh>
 #include <common/weapons/_all.qh>
 #include <common/viewloc.qh>
+#include <common/triggers/trigger/viewloc.qh>
 #include <common/minigames/cl_minigames.qh>
 #include <common/minigames/cl_minigames_hud.qh>
 
@@ -910,7 +912,8 @@ vector crosshair_getcolor(entity this, float health_stat)
 
                case 2: // crosshair_color_by_health
                {
-                       float hp = health_stat;
+                       vector v = healtharmor_maxdamage(health_stat, STAT(ARMOR), armorblockpercent, DEATH_WEAPON.m_id);
+                       float hp = floor(v.x + 1);
 
                        //x = red
                        //y = green
@@ -976,7 +979,7 @@ void HUD_Crosshair(entity this)
        float f, i, j;
        vector v;
        if(!scoreboard_active && !camera_active && intermission != 2 && !STAT(GAME_STOPPED) &&
-               spectatee_status != -1 && !csqcplayer.viewloc && !MUTATOR_CALLHOOK(DrawCrosshair) &&
+               spectatee_status != -1 && (!csqcplayer.viewloc || (!spectatee_status && (csqcplayer.viewloc.spawnflags & VIEWLOC_FREEAIM))) && !MUTATOR_CALLHOOK(DrawCrosshair) &&
                !HUD_MinigameMenu_IsOpened() )
        {
                if (!autocvar_crosshair_enabled) // main toggle for crosshair rendering
@@ -1007,7 +1010,10 @@ void HUD_Crosshair(entity this)
                float shottype;
 
                // wcross_origin = '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
-               wcross_origin = project_3d_to_2d(view_origin + max_shot_distance * view_forward);
+               if(csqcplayer.viewloc && (csqcplayer.viewloc.spawnflags & VIEWLOC_FREEAIM))
+                       wcross_origin = viewloc_mousepos;
+               else
+                       wcross_origin = project_3d_to_2d(view_origin + max_shot_distance * view_forward);
                wcross_origin.z = 0;
                if(autocvar_crosshair_hittest)
                {
@@ -1477,6 +1483,19 @@ void HUD_Draw(entity this)
        HitSound();
 }
 
+void ViewLocation_Mouse()
+{
+       if(spectatee_status)
+               return; // don't draw it as spectator!
+
+       viewloc_mousepos += getmousepos() * autocvar_menu_mouse_speed;
+       viewloc_mousepos.x = bound(0, viewloc_mousepos.x, vid_conwidth);
+       viewloc_mousepos.y = bound(0, viewloc_mousepos.y, vid_conheight);
+
+       //float cursor_alpha = 1 - autocvar__menu_alpha;
+       //draw_cursor(viewloc_mousepos, '0.5 0.5 0', "/cursor_move", '1 1 1', cursor_alpha);
+}
+
 bool ov_enabled;
 float oldr_nearclip;
 float oldr_farclip_base;
@@ -1574,6 +1593,11 @@ void CSQC_UpdateView(entity this, float w, float h)
                button_zoom = false;
        }
 
+       // abused multiple places below
+       entity local_player = ((csqcplayer) ? csqcplayer : CSQCModel_server2csqc(player_localentnum - 1));
+       if(!local_player)
+               local_player = this; // fall back!
+
        // event chase camera
        if(autocvar_chase_active <= 0) // greater than 0 means it's enabled manually, and this code is skipped
        {
@@ -1625,10 +1649,6 @@ void CSQC_UpdateView(entity this, float w, float h)
                        }
                        eventchase_running = true;
 
-                       entity local_player = ((csqcplayer) ? csqcplayer : CSQCModel_server2csqc(player_localentnum - 1));
-                       if(!local_player)
-                               local_player = this; // fall back!
-
                        // make special vector since we can't use view_origin (It is one frame old as of this code, it gets set later with the results this code makes.)
                        vector current_view_origin = (csqcplayer ? csqcplayer.origin : pmove_org);
                        if (custom_eventchase)
@@ -2060,7 +2080,7 @@ void CSQC_UpdateView(entity this, float w, float h)
                // reticle_type is changed to the item we are zooming / aiming with, to decide which reticle to use
                // It must be a persisted float for fading out to work properly (you let go of the zoom button for
                // the view to go back to normal, so reticle_type would become 0 as we fade out)
-               if(spectatee_status || is_dead || hud != HUD_NORMAL)
+               if(spectatee_status || is_dead || hud != HUD_NORMAL || local_player.viewloc)
                {
                        // no zoom reticle while dead
                        reticle_type = 0;
@@ -2373,6 +2393,8 @@ void CSQC_UpdateView(entity this, float w, float h)
                HUD_Minigame_Mouse();
        else if(QuickMenu_IsOpened())
                QuickMenu_Mouse();
+       else if(local_player.viewloc && (local_player.viewloc.spawnflags & VIEWLOC_FREEAIM))
+               ViewLocation_Mouse(); // NOTE: doesn't use cursormode
        else
                HUD_Radar_Mouse();
 
index ac916a089568f55aa6f4e97e4ba4dc97f7517046..0a2c5c0c7066263f5a893538c5c26c152490f342 100644 (file)
@@ -5,3 +5,5 @@
 vector crosshair_getcolor(entity this, float health_stat);
 
 entity viewmodels[MAX_WEAPONSLOTS];
+
+vector viewloc_mousepos;
index edf995a0f6ccf453f64aa49ff175608aa3c6205f..874c64d5385755189e04766de0279a726cdbb70d 100644 (file)
@@ -11,8 +11,6 @@
        REGISTER_NET_TEMP(globalsound)
        REGISTER_NET_TEMP(playersound)
 
-       string GlobalSound_sample(string pair, float r);
-
        #ifdef SVQC
                /**
                 * @param from the source entity, its position is sent
index 1df0b1a42b1145eeeea104092fec1a98d5b5b25d..8c6dd86ddcd717571f32c53295f2e344083e5fb2 100644 (file)
@@ -119,6 +119,8 @@ void PrecachePlayerSounds(string f);
 //#endif
 entity GetVoiceMessage(string type);
 
+string GlobalSound_sample(string pair, float r);
+
 #ifdef SVQC
 
        void _GlobalSound(entity this, entity gs, entity ps, string sample, float chan, float vol, float voicetype, bool fake);
index 5a0ff2a2c22ade90150d22cad3fbcfdb8be07f3b..7eb8ecb4b0bd62a30004870d7c4a869ec87b85da 100644 (file)
@@ -909,7 +909,7 @@ MUTATOR_HOOKFUNCTION(nb, FilterItem)
 {
        entity item = M_ARGV(0, entity);
 
-       if(item.classname == "droppedweapon")
+       if(Item_IsLoot(item))
        if(item.weapon == WEP_NEXBALL.m_id)
                return true;
 
index 705ac6d541ac4d828e98adee8aa291f2fccb0515..3568f221b63fe53fbc440a282cf037bc14a59e6f 100644 (file)
@@ -2,7 +2,7 @@
 
 void W_Nexball_Attack(entity actor, .entity weaponentity, float t);
 void W_Nexball_Attack2(entity actor, .entity weaponentity);
-vector trigger_push_calculatevelocity(vector org, entity tgt, float ht);
+vector trigger_push_calculatevelocity(vector org, entity tgt, float ht, entity pushed_entity);
 
 METHOD(BallStealer, wr_think, void(BallStealer thiswep, entity actor, .entity weaponentity, int fire))
 {
@@ -147,7 +147,7 @@ void W_Nexball_Attack2(entity actor, .entity weaponentity)
        {
                entity _ball = actor.ballcarried;
                W_SetupShot(actor, weaponentity, false, 4, SND_NB_SHOOT1, CH_WEAPON_A, 0);
-               DropBall(_ball, w_shotorg, trigger_push_calculatevelocity(_ball.origin, _ball.enemy, 32));
+               DropBall(_ball, w_shotorg, trigger_push_calculatevelocity(_ball.origin, _ball.enemy, 32, _ball));
                setthink(_ball, W_Nexball_Think);
                _ball.nextthink = time;
                return;
index 0150de3925ee90072b040e2f5d132cd301a736ad..5a0e0975e528e7315c90e4ccf6e17199ac32e59e 100644 (file)
@@ -1488,7 +1488,7 @@ void havocbot_role_ons_offense(entity this)
        if(this.havocbot_attack_time>time)
                return;
 
-       if (this.bot_strategytime < time)
+       if (navigation_goalrating_timeout(this))
        {
                navigation_goalrating_start(this);
                havocbot_goalrating_enemyplayers(this, 20000, this.origin, 650);
@@ -1497,7 +1497,7 @@ void havocbot_role_ons_offense(entity this)
                havocbot_goalrating_ons_offenseitems(this, 10000, this.origin, 10000);
                navigation_goalrating_end(this);
 
-               this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+               navigation_goalrating_timeout_set(this);
        }
 }
 
index a022979a7065f7ee07c8cdfb0fc94c3ab9a0038a..8520075019b97c616107cabf94420ef9a27d58d6 100644 (file)
@@ -1,7 +1,6 @@
 #pragma once
 
 #include "all.qh"
-#include "item/pickup.qh"
 
 CLASS(Inventory, Object)
     /** Stores counts of items, the id being the index */
@@ -81,7 +80,7 @@ void Inventory_Write(Inventory data)
             if (!(minorBits & BIT(j))) { \
                 continue; \
             } \
-            const GameItem it = Items_from(Inventory_groups_minor * maj + j); \
+            const entity it = Items_from(Inventory_groups_minor * maj + j); \
             WriteByte(MSG_ENTITY, data.inv_items[it.m_id]); \
         } \
     } \
index c302ae40281d43699b335ab24a7d382b7d3d667e..b7fc933e8b5dbaf35dde8a37fca1762c51a4e08a 100644 (file)
@@ -1,9 +1,10 @@
 #pragma once
-#include <common/t_items.qh>
 
 #ifdef GAMEQC
+#include <common/models/all.qh>
 #include <common/sounds/all.qh>
 #include <common/sounds/all.inc>
+#include <common/stats.qh>
 #endif
 
 const int IT_UNLIMITED_WEAPON_AMMO             =  BIT(0); // when this bit is set, using a weapon does not reduce ammo. Checkpoints can give this powerup.
@@ -45,11 +46,30 @@ const int IT_PICKUPMASK                     = IT_UNLIMITED_AMMO | IT_JETPACK | IT_FU
 #ifdef SVQC
 .float  strength_finished = _STAT(STRENGTH_FINISHED);
 .float  invincible_finished = _STAT(INVINCIBLE_FINISHED);
+
+#define SPAWNFUNC_ITEM(name, item) \
+    spawnfunc(name) { StartItem(this, item); }
+
+#else
+
+#define SPAWNFUNC_ITEM(name, item)
+
 #endif
 
+enum
+{
+       ITEM_FLAG_NORMAL = BIT(0), ///< Item is usable during normal gameplay.
+       ITEM_FLAG_INSTAGIB = BIT(1), ///< Item is usable in instagib.
+       ITEM_FLAG_OVERKILL = BIT(2), ///< Item is usable in overkill.
+       ITEM_FLAG_MUTATORBLOCKED = BIT(3)
+};
+
 #define ITEM_HANDLE(signal, ...) __Item_Send_##signal(__VA_ARGS__)
 CLASS(GameItem, Object)
     ATTRIB(GameItem, m_id, int, 0);
+    /** the canonical spawnfunc name */
+    ATTRIB(GameItem, m_canonical_spawnfunc, string);
+    METHOD(GameItem, m_spawnfunc_hookreplace, GameItem(GameItem this, entity e)) { return this; }
     ATTRIB(GameItem, m_name, string);
     ATTRIB(GameItem, m_icon, string);
     ATTRIB(GameItem, m_color, vector, '1 1 1');
index d7e0dcc6872d0d55b9d7b00aad234920acf5cb45..3a13a1f81855c51c2eaeea4bea5adda6e10c07b3 100644 (file)
@@ -1 +1,23 @@
 #include "ammo.qh"
+
+#ifdef SVQC
+
+METHOD(Bullets, m_spawnfunc_hookreplace, GameItem(Bullets this, entity e))
+{
+       if (autocvar_sv_q3acompat_machineshotgunswap && !Item_IsLoot(e))
+       {
+               return ITEM_Shells;
+       }
+       return this;
+}
+
+METHOD(Shells, m_spawnfunc_hookreplace, GameItem(Shells this, entity e))
+{
+       if (autocvar_sv_q3acompat_machineshotgunswap && !Item_IsLoot(e))
+       {
+               return ITEM_Bullets;
+       }
+       return this;
+}
+
+#endif
index 20963928526a12ddfe94313004fffe946ca89b8c..1d5bd87baceb116b7e241f9955e4e12b8f6d3ee9 100644 (file)
@@ -1,6 +1,23 @@
 #pragma once
 
 #include "pickup.qh"
+#ifdef SVQC
+    #include <common/t_items.qh>
+#endif
+
+.int ammo_none;
+.int ammo_shells;
+.int ammo_nails;
+.int ammo_rockets;
+.int ammo_cells;
+#ifdef SVQC
+.int ammo_plasma = _STAT(PLASMA);
+.int ammo_fuel = _STAT(FUEL);
+#else
+.int ammo_plasma;
+.int ammo_fuel;
+#endif
+
 #ifdef SVQC
 PROPERTY(float, g_pickup_ammo_anyway);
 #endif
@@ -14,9 +31,6 @@ CLASS(Ammo, Pickup)
 #endif
 ENDCLASS(Ammo)
 
-#ifdef SVQC
-    #include <common/t_items.qh>
-#endif
 
 #ifdef GAMEQC
 MODEL(Bullets_ITEM, Item_Model("a_bullets.mdl"));
@@ -30,8 +44,14 @@ void ammo_bullets_init(entity item)
         item.ammo_nails = g_pickup_nails;
 }
 #endif
-REGISTER_ITEM(Bullets, Ammo) {
+
+CLASS(Bullets, Ammo)
+ENDCLASS(Bullets)
+
+REGISTER_ITEM(Bullets, Bullets) {
+    this.m_canonical_spawnfunc = "item_bullets";
 #ifdef GAMEQC
+       this.spawnflags = ITEM_FLAG_NORMAL;
     this.m_model    =   MDL_Bullets_ITEM;
 #endif
     this.netname    =   "bullets";
@@ -44,6 +64,8 @@ REGISTER_ITEM(Bullets, Ammo) {
 #endif
 }
 
+SPAWNFUNC_ITEM(item_bullets, ITEM_Bullets)
+
 #ifdef GAMEQC
 MODEL(Cells_ITEM, Item_Model("a_cells.md3"));
 #endif
@@ -57,7 +79,9 @@ void ammo_cells_init(entity item)
 }
 #endif
 REGISTER_ITEM(Cells, Ammo) {
+    this.m_canonical_spawnfunc = "item_cells";
 #ifdef GAMEQC
+       this.spawnflags = ITEM_FLAG_NORMAL;
     this.m_model    =   MDL_Cells_ITEM;
 #endif
     this.netname    =   "cells";
@@ -70,6 +94,8 @@ REGISTER_ITEM(Cells, Ammo) {
 #endif
 }
 
+SPAWNFUNC_ITEM(item_cells, ITEM_Cells)
+
 #ifdef GAMEQC
 MODEL(Plasma_ITEM, Item_Model("a_cells.md3"));
 #endif
@@ -83,7 +109,9 @@ void ammo_plasma_init(entity item)
 }
 #endif
 REGISTER_ITEM(Plasma, Ammo) {
+    this.m_canonical_spawnfunc = "item_plasma";
 #ifdef GAMEQC
+       this.spawnflags = ITEM_FLAG_NORMAL;
     this.m_model    =   MDL_Plasma_ITEM;
 #endif
     this.netname    =   "plasma";
@@ -96,6 +124,8 @@ REGISTER_ITEM(Plasma, Ammo) {
 #endif
 }
 
+SPAWNFUNC_ITEM(item_plasma, ITEM_Plasma)
+
 #ifdef GAMEQC
 MODEL(Rockets_ITEM, Item_Model("a_rockets.md3"));
 #endif
@@ -109,7 +139,9 @@ void ammo_rockets_init(entity item)
 }
 #endif
 REGISTER_ITEM(Rockets, Ammo) {
+    this.m_canonical_spawnfunc = "item_rockets";
 #ifdef GAMEQC
+       this.spawnflags = ITEM_FLAG_NORMAL;
     this.m_model    =   MDL_Rockets_ITEM;
 #endif
     this.netname    =   "rockets";
@@ -122,6 +154,8 @@ REGISTER_ITEM(Rockets, Ammo) {
 #endif
 }
 
+SPAWNFUNC_ITEM(item_rockets, ITEM_Rockets)
+
 #ifdef GAMEQC
 MODEL(Shells_ITEM, Item_Model("a_shells.md3"));
 #endif
@@ -134,8 +168,14 @@ void ammo_shells_init(entity item)
         item.ammo_shells = g_pickup_shells;
 }
 #endif
-REGISTER_ITEM(Shells, Ammo) {
+
+CLASS(Shells, Ammo)
+ENDCLASS(Shells)
+
+REGISTER_ITEM(Shells, Shells) {
+    this.m_canonical_spawnfunc = "item_shells";
 #ifdef GAMEQC
+       this.spawnflags = ITEM_FLAG_NORMAL;
     this.m_model    =   MDL_Shells_ITEM;
 #endif
     this.netname    =   "shells";
@@ -147,3 +187,5 @@ REGISTER_ITEM(Shells, Ammo) {
     this.m_iteminit =   ammo_shells_init;
 #endif
 }
+
+SPAWNFUNC_ITEM(item_shells, ITEM_Shells)
index 0258cf8811c7682e0922f3f6960ed6af2f9c7065..7f37c75aec002465260b1852810253e05c6a4b11 100644 (file)
@@ -32,7 +32,9 @@ void item_armorsmall_init(entity item)
 #endif
 
 REGISTER_ITEM(ArmorSmall, Armor) {
+    this.m_canonical_spawnfunc = "item_armor_small";
 #ifdef GAMEQC
+    this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_OVERKILL;
     this.m_model                =   MDL_ArmorSmall_ITEM;
     this.m_sound                =   SND_ArmorSmall;
 #endif
@@ -48,6 +50,8 @@ REGISTER_ITEM(ArmorSmall, Armor) {
 #endif
 }
 
+SPAWNFUNC_ITEM(item_armor_small, ITEM_ArmorSmall)
+
 #ifdef GAMEQC
 MODEL(ArmorMedium_ITEM, Item_Model("item_armor_medium.md3"));
 SOUND(ArmorMedium, Item_Sound("armor10"));
@@ -66,7 +70,9 @@ void item_armormedium_init(entity item)
 #endif
 
 REGISTER_ITEM(ArmorMedium, Armor) {
+    this.m_canonical_spawnfunc = "item_armor_medium";
 #ifdef GAMEQC
+    this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_OVERKILL;
     this.m_model                =   MDL_ArmorMedium_ITEM;
     this.m_sound                =   SND_ArmorMedium;
 #endif
@@ -82,6 +88,8 @@ REGISTER_ITEM(ArmorMedium, Armor) {
 #endif
 }
 
+SPAWNFUNC_ITEM(item_armor_medium, ITEM_ArmorMedium)
+
 #ifdef GAMEQC
 MODEL(ArmorBig_ITEM, Item_Model("item_armor_big.md3"));
 SOUND(ArmorBig, Item_Sound("armor17_5"));
@@ -100,7 +108,9 @@ void item_armorbig_init(entity item)
 #endif
 
 REGISTER_ITEM(ArmorBig, Armor) {
+    this.m_canonical_spawnfunc = "item_armor_big";
 #ifdef GAMEQC
+    this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_OVERKILL;
     this.m_model                =   MDL_ArmorBig_ITEM;
     this.m_sound                =   SND_ArmorBig;
 #endif
@@ -118,6 +128,8 @@ REGISTER_ITEM(ArmorBig, Armor) {
 #endif
 }
 
+SPAWNFUNC_ITEM(item_armor_big, ITEM_ArmorBig)
+
 #ifdef GAMEQC
 MODEL(ArmorMega_ITEM, Item_Model("item_armor_large.md3"));
 SOUND(ArmorMega, Item_Sound("armor25"));
@@ -136,7 +148,9 @@ void item_armormega_init(entity item)
 #endif
 
 REGISTER_ITEM(ArmorMega, Armor) {
+    this.m_canonical_spawnfunc = "item_armor_mega";
 #ifdef GAMEQC
+    this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_OVERKILL;
     this.m_model                =   MDL_ArmorMega_ITEM;
     this.m_sound                =   SND_ArmorMega;
 #endif
@@ -155,3 +169,5 @@ REGISTER_ITEM(ArmorMega, Armor) {
     this.m_iteminit             =   item_armormega_init;
 #endif
 }
+
+SPAWNFUNC_ITEM(item_armor_mega, ITEM_ArmorMega)
index cad5a376e753d3cd18cfb997261ed37141072881..da431086e18587448cf697c8127390fd166134f0 100644 (file)
@@ -32,7 +32,9 @@ void item_healthsmall_init(entity item)
 #endif
 
 REGISTER_ITEM(HealthSmall, Health) {
+    this.m_canonical_spawnfunc = "item_health_small";
 #ifdef GAMEQC
+       this.spawnflags = ITEM_FLAG_NORMAL;
     this.m_model                =   MDL_HealthSmall_ITEM;
     this.m_sound                =   SND_HealthSmall;
 #endif
@@ -48,6 +50,8 @@ REGISTER_ITEM(HealthSmall, Health) {
 #endif
 }
 
+SPAWNFUNC_ITEM(item_health_small, ITEM_HealthSmall)
+
 #ifdef GAMEQC
 MODEL(HealthMedium_ITEM, Item_Model("g_h25.md3"));
 SOUND(HealthMedium, Item_Sound("mediumhealth"));
@@ -66,7 +70,9 @@ void item_healthmedium_init(entity item)
 #endif
 
 REGISTER_ITEM(HealthMedium, Health) {
+    this.m_canonical_spawnfunc = "item_health_medium";
 #ifdef GAMEQC
+       this.spawnflags = ITEM_FLAG_NORMAL;
     this.m_model                =   MDL_HealthMedium_ITEM;
     this.m_sound                =   SND_HealthMedium;
 #endif
@@ -82,6 +88,8 @@ REGISTER_ITEM(HealthMedium, Health) {
 #endif
 }
 
+SPAWNFUNC_ITEM(item_health_medium, ITEM_HealthMedium)
+
 #ifdef GAMEQC
 MODEL(HealthBig_ITEM, Item_Model("g_h50.md3"));
 SOUND(HealthBig, Item_Sound("mediumhealth"));
@@ -100,7 +108,9 @@ void item_healthbig_init(entity item)
 #endif
 
 REGISTER_ITEM(HealthBig, Health) {
+    this.m_canonical_spawnfunc = "item_health_big";
 #ifdef GAMEQC
+       this.spawnflags = ITEM_FLAG_NORMAL;
     this.m_model                =   MDL_HealthBig_ITEM;
     this.m_sound                =   SND_HealthBig;
 #endif
@@ -118,6 +128,8 @@ REGISTER_ITEM(HealthBig, Health) {
 #endif
 }
 
+SPAWNFUNC_ITEM(item_health_big, ITEM_HealthBig)
+
 #ifdef GAMEQC
 MODEL(HealthMega_ITEM, Item_Model("g_h100.md3"));
 SOUND(HealthMega, Item_Sound("megahealth"));
@@ -136,7 +148,9 @@ void item_healthmega_init(entity item)
 #endif
 
 REGISTER_ITEM(HealthMega, Health) {
+    this.m_canonical_spawnfunc = "item_health_mega";
 #ifdef GAMEQC
+    this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_OVERKILL;
     this.m_model                =   MDL_HealthMega_ITEM;
     this.m_sound                =   SND_HealthMega;
 #endif
@@ -155,3 +169,5 @@ REGISTER_ITEM(HealthMega, Health) {
     this.m_iteminit             =   item_healthmega_init;
 #endif
 }
+
+SPAWNFUNC_ITEM(item_health_mega, ITEM_HealthMega)
index ec09d5c45d62d904c6e4a2ada5ec570ae3b5c4bc..11b9d924309d32e5e666c38827ae841dce043ae3 100644 (file)
@@ -1 +1,23 @@
 #include "jetpack.qh"
+
+#ifdef SVQC
+
+METHOD(Jetpack, m_spawnfunc_hookreplace, GameItem(Jetpack this, entity e))
+{
+       if(start_items & ITEM_Jetpack.m_itemid)
+       {
+               return ITEM_JetpackFuel;
+       }
+       return this;
+}
+
+METHOD(JetpackRegen, m_spawnfunc_hookreplace, GameItem(JetpackRegen this, entity e))
+{
+       if (start_items & ITEM_JetpackRegen.m_itemid)
+       {
+               return ITEM_JetpackFuel;
+       }
+       return this;
+}
+
+#endif
index a6d1c8dae89751694d03d16bc6f35238eceec0c5..284bf3d390fce1c7b62653a9570b9ef20a7dffe5 100644 (file)
@@ -23,8 +23,14 @@ void powerup_jetpack_init(entity item)
         item.ammo_fuel = g_pickup_fuel_jetpack;
 }
 #endif
+
+CLASS(Jetpack, Powerup)
+ENDCLASS(Jetpack)
+
 REGISTER_ITEM(Jetpack, Powerup) {
+    this.m_canonical_spawnfunc = "item_jetpack";
 #ifdef GAMEQC
+       this.spawnflags = ITEM_FLAG_NORMAL;
     this.m_model                =   MDL_Jetpack_ITEM;
     this.m_itemid               =   IT_JETPACK;
 #endif
@@ -41,6 +47,8 @@ REGISTER_ITEM(Jetpack, Powerup) {
 #endif
 }
 
+SPAWNFUNC_ITEM(item_jetpack, ITEM_Jetpack)
+
 #ifdef GAMEQC
 MODEL(JetpackFuel_ITEM, Item_Model("g_fuel.md3"));
 #endif
@@ -54,7 +62,9 @@ void ammo_fuel_init(entity item)
 }
 #endif
 REGISTER_ITEM(JetpackFuel, Ammo) {
+    this.m_canonical_spawnfunc = "item_fuel";
 #ifdef GAMEQC
+       this.spawnflags = ITEM_FLAG_NORMAL;
     this.m_model    =   MDL_JetpackFuel_ITEM;
 #endif
     this.netname    =   "fuel";
@@ -67,12 +77,19 @@ REGISTER_ITEM(JetpackFuel, Ammo) {
 #endif
 }
 
+SPAWNFUNC_ITEM(item_fuel, ITEM_JetpackFuel)
+
 #ifdef GAMEQC
 MODEL(JetpackRegen_ITEM, Item_Model("g_fuelregen.md3"));
 #endif
 
-REGISTER_ITEM(JetpackRegen, Powerup) {
+CLASS(JetpackRegen, Powerup)
+ENDCLASS(JetpackRegen)
+
+REGISTER_ITEM(JetpackRegen, JetpackRegen) {
+    this.m_canonical_spawnfunc = "item_fuel_regen";
 #ifdef GAMEQC
+       this.spawnflags = ITEM_FLAG_NORMAL;
     this.m_model                =   MDL_JetpackRegen_ITEM;
 #endif
     this.netname                =   "fuel_regen";
@@ -87,3 +104,5 @@ REGISTER_ITEM(JetpackRegen, Powerup) {
     this.m_pickupevalfunc       =   ammo_pickupevalfunc;
 #endif
 }
+
+SPAWNFUNC_ITEM(item_fuel_regen, ITEM_JetpackRegen)
index fc958709e87d66af6eb50b958ce71ab8ef8f11b2..b5944fc0a3def7d7235f09bfc66e6ad33f2fb11e 100644 (file)
@@ -1,7 +1,21 @@
 #include "pickup.qh"
+#include <common/items/inventory.qh>
 
 #ifdef SVQC
 bool ITEM_HANDLE(Pickup, entity this, entity item, entity player) {
     return this.giveTo(this, item, player);
 }
+
+METHOD(Pickup, giveTo, bool(Pickup this, entity item, entity player))
+{
+    TC(Pickup, this);
+    bool b = Item_GiveTo(item, player);
+    if (b) {
+        LOG_DEBUGF("entity %i picked up %s", player, this.m_name);
+        player.inventory.inv_items[this.m_id]++;
+        Inventory_update(player);
+    }
+    return b;
+}
+
 #endif
index 39cf78cc3016e3d3b4714bcf169c843892342f59..fb4bc28cd8ede336f7d6d656ca25edbdd4972e42 100644 (file)
@@ -17,15 +17,7 @@ PROPERTY(float, g_pickup_respawntimejitter_long)
 PROPERTY(float, g_pickup_respawntimejitter_powerup)
 #endif
 
-#include <common/items/inventory.qh>
 #include <common/items/item.qh>
-#include <common/t_items.qh>
-
-#ifdef GAMEQC
-#include <common/models/all.qh>
-#include <common/sounds/all.qh>
-#include <common/sounds/all.inc>
-#endif
 
 CLASS(Pickup, GameItem)
 #ifdef GAMEQC
@@ -52,17 +44,7 @@ CLASS(Pickup, GameItem)
     ATTRIB(Pickup, m_pickupanyway, float());
     ATTRIB(Pickup, m_iteminit, void(entity item));
     float Item_GiveTo(entity item, entity player);
-    METHOD(Pickup, giveTo, bool(Pickup this, entity item, entity player))
-    {
-        TC(Pickup, this);
-        bool b = Item_GiveTo(item, player);
-        if (b) {
-            LOG_DEBUGF("entity %i picked up %s", player, this.m_name);
-            player.inventory.inv_items[this.m_id]++;
-            Inventory_update(player);
-        }
-        return b;
-    }
+    METHOD(Pickup, giveTo, bool(Pickup this, entity item, entity player));
     bool ITEM_HANDLE(Pickup, Pickup this, entity item, entity player);
 #endif
 ENDCLASS(Pickup)
index 41b658c2fc5a25afe03eefcaeea7867e07f1a6de..fe47b63430ddd1726b774f080f56905a4dc7568c 100644 (file)
@@ -31,7 +31,9 @@ void powerup_strength_init(entity item)
 }
 #endif
 REGISTER_ITEM(Strength, Powerup) {
+    this.m_canonical_spawnfunc = "item_strength";
 #ifdef GAMEQC
+       this.spawnflags = ITEM_FLAG_NORMAL;
     this.m_model            =   MDL_Strength_ITEM;
     this.m_sound            =   SND_Strength;
     this.m_glow             =   true;
@@ -49,6 +51,8 @@ REGISTER_ITEM(Strength, Powerup) {
 #endif
 }
 
+SPAWNFUNC_ITEM(item_strength, ITEM_Strength)
+
 #ifdef GAMEQC
 MODEL(Shield_ITEM, Item_Model("g_invincible.md3"));
 SOUND(Shield, Item_Sound("powerup_shield"));
@@ -63,7 +67,9 @@ void powerup_shield_init(entity item)
 }
 #endif
 REGISTER_ITEM(Shield, Powerup) {
+    this.m_canonical_spawnfunc = "item_shield";
 #ifdef GAMEQC
+       this.spawnflags = ITEM_FLAG_NORMAL;
     this.m_model            =   MDL_Shield_ITEM;
     this.m_sound            =   SND_Shield;
     this.m_glow             =   true;
@@ -80,3 +86,6 @@ REGISTER_ITEM(Shield, Powerup) {
     this.m_iteminit         =   powerup_shield_init;
 #endif
 }
+
+SPAWNFUNC_ITEM(item_shield, ITEM_Shield)
+SPAWNFUNC_ITEM(item_invincible, ITEM_Shield)
index 80cd21b481b41474dcf860d2d42178c95428f228..15fba0134200deccbfacf77b8f70af222558ef4f 100644 (file)
@@ -35,7 +35,8 @@ void monster_dropitem(entity this, entity attacker)
                return;
 
        vector org = CENTER_OR_VIEWOFS(this);
-       entity e = new(droppedweapon); // use weapon handling to remove it on touch
+       entity e = spawn();
+       Item_SetLoot(e, true);
        e.spawnfunc_checked = true;
 
        e.monster_loot = this.monster_loot;
@@ -48,8 +49,6 @@ void monster_dropitem(entity this, entity attacker)
                e.noalign = true;
                StartItem(e, e.monster_loot);
                e.gravity = 1;
-               set_movetype(e, MOVETYPE_TOSS);
-               e.reset = SUB_Remove;
                setorigin(e, org);
                e.velocity = randomvec() * 175 + '0 0 325';
                e.item_spawnshieldtime = time + 0.7;
@@ -337,7 +336,12 @@ void Monster_Sound(entity this, .string samplefield, float sound_delay, bool del
        if(delaytoo)
        if(time < this.msound_delay)
                return; // too early
-       GlobalSound_string(this, this.(samplefield), chan, VOL_BASE, VOICETYPE_PLAYERSOUND);
+       string sample = this.(samplefield);
+       if (sample != "") sample = GlobalSound_sample(sample, random());
+       float myscale = ((this.scale) ? this.scale : 1); // safety net
+       float scale_inverse = 1 / myscale;
+       // TODO: change volume depending on size too?
+       sound7(this, chan, sample, VOL_BASE, ATTEN_NORM, scale_inverse * 100, 0);
 
        this.msound_delay = time + sound_delay;
 }
index eeb93ba5e83f7cb9252274e397c994bf9e93a73e..9d52fa20c44af25537d2b11bf68b04b2020cac0c 100644 (file)
@@ -15,6 +15,7 @@
 #include <common/mutators/mutator/instagib/_mod.inc>
 #include <common/mutators/mutator/invincibleproj/_mod.inc>
 #include <common/mutators/mutator/itemstime/_mod.inc>
+#include <common/mutators/mutator/kick_teamkiller/_mod.inc>
 #include <common/mutators/mutator/melee_only/_mod.inc>
 #include <common/mutators/mutator/midair/_mod.inc>
 #include <common/mutators/mutator/multijump/_mod.inc>
 #include <common/mutators/mutator/physical_items/_mod.inc>
 #include <common/mutators/mutator/pinata/_mod.inc>
 #include <common/mutators/mutator/random_gravity/_mod.inc>
+#include <common/mutators/mutator/random_items/_mod.inc>
 #include <common/mutators/mutator/rocketflying/_mod.inc>
 #include <common/mutators/mutator/rocketminsta/_mod.inc>
 #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 956c0d97536fbfeaddc4d4b8b1728ce147d09bc5..f9edf4c56c47868e01161b34793a5c05e33f64d9 100644 (file)
@@ -15,6 +15,7 @@
 #include <common/mutators/mutator/instagib/_mod.qh>
 #include <common/mutators/mutator/invincibleproj/_mod.qh>
 #include <common/mutators/mutator/itemstime/_mod.qh>
+#include <common/mutators/mutator/kick_teamkiller/_mod.qh>
 #include <common/mutators/mutator/melee_only/_mod.qh>
 #include <common/mutators/mutator/midair/_mod.qh>
 #include <common/mutators/mutator/multijump/_mod.qh>
 #include <common/mutators/mutator/physical_items/_mod.qh>
 #include <common/mutators/mutator/pinata/_mod.qh>
 #include <common/mutators/mutator/random_gravity/_mod.qh>
+#include <common/mutators/mutator/random_items/_mod.qh>
 #include <common/mutators/mutator/rocketflying/_mod.qh>
 #include <common/mutators/mutator/rocketminsta/_mod.qh>
 #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 e039a96b9501a9e99900884377b625fcab16861c..6994c81761ad8ac68f289516ea405eda1a75f7c4 100644 (file)
@@ -750,7 +750,7 @@ MUTATOR_HOOKFUNCTION(buffs, OnEntityPreSpawn, CBC_ORDER_LAST)
        switch(ent.classname)
        {
                case "item_strength":
-               case "item_invincible":
+               case "item_shield":
                {
                        entity e = spawn();
                        buff_SpawnReplacement(e, ent);
index 2195111f0f269e2903f767e3badf17644e0aa27d..55a67b1e38a95abcd62c87af38cd29425a308510 100644 (file)
@@ -1,5 +1,8 @@
 // generated file; do not modify
 #include <common/mutators/mutator/instagib/items.qc>
+#ifdef SVQC
+    #include <common/mutators/mutator/instagib/sv_items.qc>
+#endif
 #ifdef SVQC
     #include <common/mutators/mutator/instagib/sv_instagib.qc>
 #endif
index e80f36e1a087542eb7d3ee0c06f5c493c7902d0c..fe0070afcb1589bd8cbcd45af5e6486aa4632430 100644 (file)
@@ -23,11 +23,13 @@ void ammo_vaporizercells_init(entity item)
 }
 #endif
 REGISTER_ITEM(VaporizerCells, Ammo) {
+    this.m_canonical_spawnfunc = "item_vaporizer_cells";
 #ifdef GAMEQC
+    this.spawnflags = ITEM_FLAG_INSTAGIB | ITEM_FLAG_MUTATORBLOCKED;
     this.m_model                =   MDL_VaporizerCells_ITEM;
     this.m_sound                =   SND_VaporizerCells;
 #endif
-    this.netname                =   "minst_cells";
+    this.netname                =   "vaporizer_cells";
     this.m_name                 =   "Vaporizer Ammo";
     this.m_icon                 =   "ammo_supercells";
 #ifdef SVQC
@@ -39,17 +41,22 @@ REGISTER_ITEM(VaporizerCells, Ammo) {
 #endif
 }
 
+SPAWNFUNC_ITEM(item_vaporizer_cells, ITEM_VaporizerCells)
+SPAWNFUNC_ITEM(item_minst_cells, ITEM_VaporizerCells)
+
 #ifdef GAMEQC
 MODEL(ExtraLife_ITEM, Item_Model("g_h100.md3"));
 SOUND(ExtraLife, Item_Sound("megahealth"));
 #endif
 
 REGISTER_ITEM(ExtraLife, Powerup) {
+    this.m_canonical_spawnfunc = "item_extralife";
 #ifdef GAMEQC
+       this.spawnflags = ITEM_FLAG_INSTAGIB;
     this.m_model                =   MDL_ExtraLife_ITEM;
     this.m_sound                =   SND_ExtraLife;
 #endif
-    this.netname                =   "health_mega";
+    this.netname                =   "extralife";
     this.m_name                 =   "Extra life";
     this.m_icon                 =   "item_mega_health";
     this.m_color                =   '1 0 0';
@@ -58,40 +65,74 @@ REGISTER_ITEM(ExtraLife, Powerup) {
     this.m_itemid               =   IT_NAILS;
 }
 
+SPAWNFUNC_ITEM(item_extralife, ITEM_ExtraLife)
+
 #ifdef GAMEQC
 MODEL(Invisibility_ITEM, Item_Model("g_strength.md3"));
 SOUND(Invisibility, Item_Sound("powerup"));
 #endif
 
+#ifdef SVQC
+/// \brief Initializes the invisibility powerup.
+/// \param[in,out] item Item to initialize.
+/// \return No return.
+void powerup_invisibility_init(entity item);
+#endif
+
 REGISTER_ITEM(Invisibility, Powerup) {
+    this.m_canonical_spawnfunc = "item_invisibility";
 #ifdef GAMEQC
+       this.spawnflags = ITEM_FLAG_INSTAGIB | ITEM_FLAG_MUTATORBLOCKED;
     this.m_model            =   MDL_Invisibility_ITEM;
     this.m_sound            =   SND_Invisibility;
+    this.m_glow             =   true;
+    this.m_respawnsound     =   SND_STRENGTH_RESPAWN;
 #endif
-    this.netname            =   "strength";
+    this.netname            =   "invisibility";
     this.m_name             =   "Invisibility";
     this.m_icon             =   "strength";
     this.m_color            =   '0 0 1';
     this.m_waypoint         =   _("Invisibility");
     this.m_waypointblink    =   2;
     this.m_itemid           =   IT_STRENGTH;
+#ifdef SVQC
+    this.m_iteminit         =   powerup_invisibility_init;
+#endif
 }
 
+SPAWNFUNC_ITEM(item_invisibility, ITEM_Invisibility)
+
 #ifdef GAMEQC
 MODEL(Speed_ITEM, Item_Model("g_invincible.md3"));
 SOUND(Speed, Item_Sound("powerup_shield"));
 #endif
 
+#ifdef SVQC
+/// \brief Initializes the speed powerup.
+/// \param[in,out] item Item to initialize.
+/// \return No return.
+void powerup_speed_init(entity item);
+#endif
+
 REGISTER_ITEM(Speed, Powerup) {
+    this.m_canonical_spawnfunc = "item_speed";
 #ifdef GAMEQC
+       this.spawnflags = ITEM_FLAG_INSTAGIB | ITEM_FLAG_MUTATORBLOCKED;
     this.m_model            =   MDL_Speed_ITEM;
     this.m_sound            =   SND_Speed;
+    this.m_glow             =   true;
+    this.m_respawnsound     =   SND_SHIELD_RESPAWN;
 #endif
-    this.netname            =   "invincible";
+    this.netname            =   "speed";
     this.m_name             =   "Speed";
     this.m_icon             =   "shield";
     this.m_color            =   '1 0 1';
     this.m_waypoint         =   _("Speed");
     this.m_waypointblink    =   2;
     this.m_itemid           =   IT_INVINCIBLE;
+#ifdef SVQC
+    this.m_iteminit         =   powerup_speed_init;
+#endif
 }
+
+SPAWNFUNC_ITEM(item_speed, ITEM_Speed)
index 1561dc10db9a326acf932de70df86365e179bd4f..85bef1d49c4562a40b68dbd4e5f146551a523422 100644 (file)
@@ -17,17 +17,25 @@ float autocvar_g_instagib_speed_highspeed;
 
 #include <common/items/_mod.qh>
 
-REGISTER_MUTATOR(mutator_instagib, autocvar_g_instagib && !g_nexball);
-
-spawnfunc(item_minst_cells)
+REGISTER_MUTATOR(mutator_instagib, autocvar_g_instagib && !g_nexball)
 {
-       if (!g_instagib) { delete(this); return; }
-       StartItem(this, ITEM_VaporizerCells);
+       MUTATOR_ONADD
+       {
+               ITEM_VaporizerCells.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED;
+               ITEM_Invisibility.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED;
+               ITEM_Speed.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED;
+       }
+       MUTATOR_ONROLLBACK_OR_REMOVE
+       {
+               ITEM_VaporizerCells.spawnflags |= ITEM_FLAG_MUTATORBLOCKED;
+               ITEM_Invisibility.spawnflags |= ITEM_FLAG_MUTATORBLOCKED;
+               ITEM_Speed.spawnflags |= ITEM_FLAG_MUTATORBLOCKED;
+       }
 }
 
 void instagib_invisibility(entity this)
 {
-       this.strength_finished = autocvar_g_balance_powerup_strength_time;
+       this.strength_finished = autocvar_g_instagib_invisibility_time;
        StartItem(this, ITEM_Invisibility);
 }
 
@@ -38,7 +46,7 @@ void instagib_extralife(entity this)
 
 void instagib_speed(entity this)
 {
-       this.invincible_finished = autocvar_g_balance_powerup_invincible_time;
+       this.invincible_finished = autocvar_g_instagib_speed_time;
        StartItem(this, ITEM_Speed);
 }
 
@@ -170,6 +178,11 @@ MUTATOR_HOOKFUNCTION(mutator_instagib, MakePlayerObserver)
        instagib_stop_countdown(player);
 }
 
+MUTATOR_HOOKFUNCTION(mutator_instagib, ForbidRandomStartWeapons)
+{
+       return true;
+}
+
 MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerSpawn)
 {
        entity player = M_ARGV(0, entity);
@@ -377,13 +390,13 @@ MUTATOR_HOOKFUNCTION(mutator_instagib, SetWeaponArena)
 
 void replace_with_insta_cells(entity item)
 {
-       entity e = spawn();
+       entity e = new(item_vaporizer_cells);
        setorigin(e, item.origin);
-       e.noalign = item.noalign;
+       e.noalign = Item_ShouldKeepPosition(item);
        e.cnt = item.cnt;
        e.team = item.team;
        e.spawnfunc_checked = true;
-       spawnfunc_item_minst_cells(e);
+       spawnfunc_item_vaporizer_cells(e);
 }
 
 MUTATOR_HOOKFUNCTION(mutator_instagib, FilterItem)
@@ -423,7 +436,7 @@ MUTATOR_HOOKFUNCTION(mutator_instagib, FilterItem)
                return true;
        }
 
-       if(item.weapon == WEP_VAPORIZER.m_id && item.classname == "droppedweapon")
+       if(item.weapon == WEP_VAPORIZER.m_id && Item_IsLoot(item))
        {
                SetResourceAmount(item, RESOURCE_CELLS, autocvar_g_instagib_ammo_drop);
                return false;
@@ -439,7 +452,7 @@ MUTATOR_HOOKFUNCTION(mutator_instagib, FilterItem)
                return false;
 
        float cells = GetResourceAmount(item, RESOURCE_CELLS);
-       if(cells > autocvar_g_instagib_ammo_drop && item.classname != "item_minst_cells")
+       if(cells > autocvar_g_instagib_ammo_drop && item.classname != "item_vaporizer_cells")
                SetResourceAmount(item, RESOURCE_CELLS, autocvar_g_instagib_ammo_drop);
 
        if(cells && !item.weapon)
@@ -508,18 +521,27 @@ MUTATOR_HOOKFUNCTION(mutator_instagib, OnEntityPreSpawn)
        if (!autocvar_g_powerups) { return; }
        entity ent = M_ARGV(0, entity);
        // Can't use .itemdef here
-       if (!(ent.classname == "item_strength" || ent.classname == "item_invincible" || ent.classname == "item_health_mega"))
+       if (!(ent.classname == "item_strength" || ent.classname == "item_shield" || ent.classname == "item_health_mega"))
                return;
 
        entity e = spawn();
 
        float r = random();
        if (r < 0.3)
+       {
+               e.classname = "item_invisibility";
                setthink(e, instagib_invisibility);
+       }
        else if (r < 0.6)
+       {
+               e.classname = "item_extralife";
                setthink(e, instagib_extralife);
+       }
        else
+       {
+               e.classname = "item_speed";
                setthink(e, instagib_speed);
+       }
 
        e.nextthink = time + 0.1;
        e.spawnflags = ent.spawnflags;
index 4c6d20b1293c7f317ab89d8c5f61ac89a2088ea1..9020b93124777851f80a80eb1e2dc4b0edab75fa 100644 (file)
@@ -3,3 +3,7 @@
 #include "items.qh"
 
 float autocvar_g_instagib_invis_alpha;
+
+void instagib_invisibility(entity this);
+void instagib_extralife(entity this);
+void instagib_speed(entity this);
diff --git a/qcsrc/common/mutators/mutator/instagib/sv_items.qc b/qcsrc/common/mutators/mutator/instagib/sv_items.qc
new file mode 100644 (file)
index 0000000..ffd9bfb
--- /dev/null
@@ -0,0 +1,23 @@
+#include "items.qh"
+
+/// \brief Time of ivisibility powerup in seconds.
+float autocvar_g_instagib_invisibility_time;
+/// \brief Time of speed powerup in seconds.
+float autocvar_g_instagib_speed_time;
+
+void powerup_invisibility_init(entity item)
+{
+       if(!item.strength_finished)
+       {
+               item.strength_finished = autocvar_g_instagib_invisibility_time;
+       }
+}
+
+
+void powerup_speed_init(entity item)
+{
+       if(!item.invincible_finished)
+       {
+               item.invincible_finished = autocvar_g_instagib_speed_time;
+       }
+}
diff --git a/qcsrc/common/mutators/mutator/kick_teamkiller/_mod.inc b/qcsrc/common/mutators/mutator/kick_teamkiller/_mod.inc
new file mode 100644 (file)
index 0000000..a374a0e
--- /dev/null
@@ -0,0 +1,4 @@
+// generated file; do not modify
+#ifdef SVQC
+    #include <common/mutators/mutator/kick_teamkiller/sv_kick_teamkiller.qc>
+#endif
diff --git a/qcsrc/common/mutators/mutator/kick_teamkiller/_mod.qh b/qcsrc/common/mutators/mutator/kick_teamkiller/_mod.qh
new file mode 100644 (file)
index 0000000..98fb481
--- /dev/null
@@ -0,0 +1 @@
+// generated file; do not modify
diff --git a/qcsrc/common/mutators/mutator/kick_teamkiller/sv_kick_teamkiller.qc b/qcsrc/common/mutators/mutator/kick_teamkiller/sv_kick_teamkiller.qc
new file mode 100644 (file)
index 0000000..a3b028f
--- /dev/null
@@ -0,0 +1,33 @@
+
+float autocvar_g_kick_teamkiller_rate;
+float autocvar_g_kick_teamkiller_lower_limit;
+
+REGISTER_MUTATOR(kick_teamkiller, (autocvar_g_kick_teamkiller_rate > 0));
+
+MUTATOR_HOOKFUNCTION(kick_teamkiller, PlayerDies)
+{
+       if (!teamplay)
+       {
+               return;
+       }
+       if (warmup_stage)
+       {
+               return;
+       }
+       entity attacker = M_ARGV(1, entity);
+       if (!IS_REAL_CLIENT(attacker))
+       {
+               return;
+       }
+
+       int teamkills = PlayerScore_Get(attacker, SP_TEAMKILLS);
+       // use the players actual playtime
+       float playtime = time - CS(attacker).startplaytime;
+       // rate is in teamkills/minutes, playtime in seconds
+       if (teamkills >= autocvar_g_kick_teamkiller_lower_limit && 
+           teamkills >= autocvar_g_kick_teamkiller_rate*playtime/60.0)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_KICK_TEAMKILL, attacker.netname);
+               dropclient(attacker);
+       }
+}
index ac06a8f7747160259588667e74a20ba33878da9e..d6796fc0590e0581be335989330a384934bee600 100644 (file)
@@ -15,6 +15,11 @@ MUTATOR_HOOKFUNCTION(melee_only, SetWeaponArena)
        M_ARGV(0, string) = "off";
 }
 
+MUTATOR_HOOKFUNCTION(melee_only, ForbidRandomStartWeapons)
+{
+       return true;
+}
+
 MUTATOR_HOOKFUNCTION(melee_only, ForbidThrowCurrentWeapon)
 {
        return true;
index 92e16b48d22e04bc65a49f33b12dbdacbf17eee3..3dc7e4e0e3e050c4eb7e72d838945492aa552750 100644 (file)
@@ -1273,30 +1273,27 @@ MUTATOR_HOOKFUNCTION(nades, PlayerPreThink)
                }
        }
 
-       float n = 0;
+       int n = 0;
        entity o = NULL;
        if(player.freezetag_frozen_timeout > 0 && time >= player.freezetag_frozen_timeout)
                n = -1;
-       else
+       else if(STAT(FROZEN, player) == 3)
        {
                vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
                n = 0;
                FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
-                       if(!IS_DEAD(it))
-                       if(STAT(FROZEN, it) == 0)
-                       if(SAME_TEAM(it, player))
+                       if(!IS_DEAD(it) && STAT(FROZEN, it) == 0 && SAME_TEAM(it, player))
                        if(boxesoverlap(player.absmin - revive_extra_size, player.absmax + revive_extra_size, it.absmin, it.absmax))
                        {
                                if(!o)
                                        o = it;
-                               if(STAT(FROZEN, player) == 1)
-                                       it.reviving = true;
+                               it.reviving = true;
                                ++n;
                        }
                });
        }
 
-       if(n && STAT(FROZEN, player) == 3) // OK, there is at least one teammate reviving us
+       if(n > 0 && 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);
                SetResourceAmount(player, RESOURCE_HEALTH, max(1, player.revive_progress * start_health));
index af364995a1e92bc13c42b287babb5e3471938441..ec2593215a09b5187abd522608e07dc6f978e141 100644 (file)
@@ -1,5 +1,7 @@
 #include "sv_new_toys.qh"
 
+#include "../random_items/sv_random_items.qh"
+
 /*
 
 CORE    laser   vortex     lg      rl      cry     gl      elec    hagar   fireb   hook
@@ -193,6 +195,11 @@ MUTATOR_HOOKFUNCTION(nt, SetStartItems)
 
 MUTATOR_HOOKFUNCTION(nt, SetWeaponreplace)
 {
+       if (autocvar_g_random_items)
+       {
+               // Do not replace weapons when random items are enabled.
+               return;
+       }
        entity wep = M_ARGV(0, entity);
        entity wepinfo = M_ARGV(1, entity);
        string ret_string = M_ARGV(2, string);
index 886740aedcea031e9d31632b17c3a47fb7937fa4..eb14a3159efd0f9f9e4e6fbdcc46cecb6718cb0e 100644 (file)
@@ -290,6 +290,11 @@ MUTATOR_HOOKFUNCTION(nix, PlayerPreThink)
                NIX_GiveCurrentWeapon(player);
 }
 
+MUTATOR_HOOKFUNCTION(nix, ForbidRandomStartWeapons)
+{
+       return true;
+}
+
 MUTATOR_HOOKFUNCTION(nix, PlayerSpawn)
 {
        entity player = M_ARGV(0, entity);
index 41327956f383b062deb21356e896170cb57c82f9..f88e52a9dfea6ab2d4efd9f8d61581ac28683b44 100644 (file)
@@ -10,8 +10,6 @@ MUTATOR_HOOKFUNCTION(hmg_nadesupport, Nade_Damage)
        M_ARGV(3, float) /* damage */ = (M_ARGV(0, entity)).max_health * 0.1;
 }
 
-spawnfunc(weapon_hmg) { weapon_defaultspawnfunc(this, WEP_HMG); }
-
 void W_HeavyMachineGun_Attack_Auto(Weapon thiswep, entity actor, .entity weaponentity, int fire)
 {
        if (!PHYS_INPUT_BUTTON_ATCK(actor))
index 07b31ea3cd107c6e9af918b749b17d2ac4c0673f..99c8093970e4bc6ef7acd2c69bbc516b8093a244 100644 (file)
@@ -1,6 +1,9 @@
 #pragma once
 
+#include <common/weapons/all.qh>
+
 CLASS(HeavyMachineGun, Weapon)
+/* spawnfunc */ ATTRIB(HeavyMachineGun, m_canonical_spawnfunc, string, "weapon_hmg");
 /* 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);
@@ -40,3 +43,5 @@ CLASS(HeavyMachineGun, Weapon)
 
 ENDCLASS(HeavyMachineGun)
 REGISTER_WEAPON(HMG, hmg, NEW(HeavyMachineGun));
+
+SPAWNFUNC_WEAPON(weapon_hmg, WEP_HMG)
index f901d11593d2951ccf0e542002be2d4230886f85..b38e0ee52ce78ab021f179fabe8e42ca64d04a2e 100644 (file)
@@ -1,7 +1,6 @@
 #include "rpc.qh"
 
 #ifdef SVQC
-spawnfunc(weapon_rpc) { weapon_defaultspawnfunc(this, WEP_RPC); }
 
 void W_RocketPropelledChainsaw_Explode(entity this, entity directhitentity)
 {
index 560f7f4fa4313cdb68a2b69f1dc0306a696b96e0..78d5de51ae4621249a2c23f55fbefdd5a5df468b 100644 (file)
@@ -1,6 +1,9 @@
 #pragma once
 
+#include <common/weapons/all.qh>
+
 CLASS(RocketPropelledChainsaw, Weapon)
+/* spawnfunc */ ATTRIB(RocketPropelledChainsaw, m_canonical_spawnfunc, string, "weapon_rpc");
 /* 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);
@@ -45,3 +48,5 @@ CLASS(RocketPropelledChainsaw, Weapon)
 
 ENDCLASS(RocketPropelledChainsaw)
 REGISTER_WEAPON(RPC, rpc, NEW(RocketPropelledChainsaw));
+
+SPAWNFUNC_WEAPON(weapon_rpc, WEP_RPC)
index b47e587511643e3076e172835052c16787d3079d..5611042b0b3100f1bb8cb0dcd2e06e546fa9720a 100644 (file)
@@ -9,34 +9,52 @@ bool autocvar_g_overkill_powerups_replace;
 
 bool autocvar_g_overkill_itemwaypoints = true;
 
-bool autocvar_g_overkill_filter_healthmega;
-bool autocvar_g_overkill_filter_armormedium;
-bool autocvar_g_overkill_filter_armorbig;
-bool autocvar_g_overkill_filter_armormega;
-
-.float ok_item;
-
 .Weapon ok_lastwep[MAX_WEAPONSLOTS];
 
-void ok_Initialize();
-
 REGISTER_MUTATOR(ok, expr_evaluate(autocvar_g_overkill) && !cvar("g_instagib") && !g_nexball && cvar_string("g_mod_balance") == "Overkill")
 {
        MUTATOR_ONADD
        {
-               ok_Initialize();
+               precache_all_playermodels("models/ok_player/*.dpm");
+
+               if (autocvar_g_overkill_filter_healthmega)
+               {
+                       ITEM_HealthMega.spawnflags |= ITEM_FLAG_MUTATORBLOCKED;
+               }
+               if (autocvar_g_overkill_filter_armormedium)
+               {
+                       ITEM_ArmorMedium.spawnflags |= ITEM_FLAG_MUTATORBLOCKED;
+               }
+               if (autocvar_g_overkill_filter_armorbig)
+               {
+                       ITEM_ArmorBig.spawnflags |= ITEM_FLAG_MUTATORBLOCKED;
+               }
+               if (autocvar_g_overkill_filter_armormega)
+               {
+                       ITEM_ArmorMega.spawnflags |= ITEM_FLAG_MUTATORBLOCKED;
+               }
+
+               WEP_RPC.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
+               WEP_HMG.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
+
+               WEP_SHOTGUN.mdl = "ok_shotgun";
+               WEP_MACHINEGUN.mdl = "ok_mg";
+               WEP_VORTEX.mdl = "ok_sniper";
        }
 
        MUTATOR_ONREMOVE
        {
+               ITEM_HealthMega.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED;
+               ITEM_ArmorMedium.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED;
+               ITEM_ArmorBig.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED;
+               ITEM_ArmorMega.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED;
+
                WEP_RPC.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
                WEP_HMG.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
        }
 }
 
 void W_Blaster_Attack(entity, .entity, float, float, float, float, float, float, float, float, float, float);
-spawnfunc(weapon_hmg);
-spawnfunc(weapon_rpc);
 
 MUTATOR_HOOKFUNCTION(ok, Damage_Calculate, CBC_ORDER_LAST)
 {
@@ -61,20 +79,10 @@ MUTATOR_HOOKFUNCTION(ok, Damage_Calculate, CBC_ORDER_LAST)
 
 void ok_DropItem(entity this, entity targ)
 {
-       entity e = new(droppedweapon); // hax
+       entity e = spawn();
        e.ok_item = true;
-       e.noalign = true;
-       e.pickup_anyway = true;
-       e.spawnfunc_checked = true;
-       spawnfunc_item_armor_small(e);
-       if (!wasfreed(e)) { // might have been blocked by a mutator
-        set_movetype(e, MOVETYPE_TOSS);
-        e.gravity = 1;
-        e.reset = SUB_Remove;
-        setorigin(e, this.origin + '0 0 32');
-        e.velocity = '0 0 200' + normalize(targ.origin - this.origin) * 500;
-        SUB_SetFade(e, time + 5, 1);
-       }
+       Item_InitializeLoot(e, "item_armor_small", this.origin + '0 0 32',
+               '0 0 200' + normalize(targ.origin - this.origin) * 500, 5);
 }
 
 MUTATOR_HOOKFUNCTION(ok, PlayerDies)
@@ -159,6 +167,11 @@ MUTATOR_HOOKFUNCTION(ok, PlayerPreThink)
        PHYS_INPUT_BUTTON_ATCK2(player) = false;
 }
 
+MUTATOR_HOOKFUNCTION(ok, ForbidRandomStartWeapons)
+{
+       return true;
+}
+
 MUTATOR_HOOKFUNCTION(ok, PlayerWeaponSelect)
 {
        entity player = M_ARGV(0, entity);
@@ -181,51 +194,6 @@ MUTATOR_HOOKFUNCTION(ok, PlayerWeaponSelect)
        }
 }
 
-void self_spawnfunc_weapon_hmg(entity this) { spawnfunc_weapon_hmg(this); }
-void self_spawnfunc_weapon_rpc(entity this) { spawnfunc_weapon_rpc(this); }
-
-MUTATOR_HOOKFUNCTION(ok, OnEntityPreSpawn)
-{
-       entity ent = M_ARGV(0, entity);
-
-       if(autocvar_g_powerups)
-       if(autocvar_g_overkill_powerups_replace)
-       {
-               if(ent.classname == "item_strength")
-               {
-                       entity wep = new(weapon_hmg);
-                       setorigin(wep, ent.origin);
-                       setmodel(wep, MDL_OK_HMG);
-                       wep.ok_item = true;
-                       wep.noalign = ent.noalign;
-                       wep.cnt = ent.cnt;
-                       wep.team = ent.team;
-                       wep.respawntime = g_pickup_respawntime_superweapon;
-                       wep.pickup_anyway = true;
-                       wep.spawnfunc_checked = true;
-                       setthink(wep, self_spawnfunc_weapon_hmg);
-                       wep.nextthink = time + 0.1;
-                       return true;
-               }
-               else if(ent.classname == "item_invincible")
-               {
-                       entity wep = new(weapon_rpc);
-                       setorigin(wep, ent.origin);
-                       setmodel(wep, MDL_OK_RPC);
-                       wep.ok_item = true;
-                       wep.noalign = ent.noalign;
-                       wep.cnt = ent.cnt;
-                       wep.team = ent.team;
-                       wep.respawntime = g_pickup_respawntime_superweapon;
-                       wep.pickup_anyway = true;
-                       wep.spawnfunc_checked = true;
-                       setthink(wep, self_spawnfunc_weapon_rpc);
-                       wep.nextthink = time + 0.1;
-                       return true;
-               }
-       }
-}
-
 bool ok_HandleItemWaypoints(entity e)
 {
        if(!autocvar_g_overkill_itemwaypoints)
@@ -258,17 +226,42 @@ MUTATOR_HOOKFUNCTION(ok, FilterItem)
 {
        entity item = M_ARGV(0, entity);
 
-       if(item.ok_item)
+       if (item.ok_item)
+       {
                return false;
-
-       switch(item.itemdef)
+       }
+       if (!autocvar_g_powerups || !autocvar_g_overkill_powerups_replace)
        {
-               case ITEM_HealthMega: return autocvar_g_overkill_filter_healthmega;
-               case ITEM_ArmorMedium: return autocvar_g_overkill_filter_armormedium;
-               case ITEM_ArmorBig: return autocvar_g_overkill_filter_armorbig;
-               case ITEM_ArmorMega: return autocvar_g_overkill_filter_armormega;
+               return true;
+       }
+       if (item.classname == "item_strength")
+       {
+               entity wep = new(weapon_hmg);
+               setorigin(wep, item.origin);
+               wep.ok_item = true;
+               wep.noalign = Item_ShouldKeepPosition(item);
+               wep.cnt = item.cnt;
+               wep.team = item.team;
+               wep.respawntime = g_pickup_respawntime_superweapon;
+               wep.pickup_anyway = true;
+               wep.spawnfunc_checked = true;
+               Item_Initialize(wep, "weapon_hmg");
+               return true;
+       }
+       else if (item.classname == "item_shield")
+       {
+               entity wep = new(weapon_rpc);
+               setorigin(wep, item.origin);
+               wep.ok_item = true;
+               wep.noalign = Item_ShouldKeepPosition(item);
+               wep.cnt = item.cnt;
+               wep.team = item.team;
+               wep.respawntime = g_pickup_respawntime_superweapon;
+               wep.pickup_anyway = true;
+               wep.spawnfunc_checked = true;
+               Item_Initialize(wep, "weapon_rpc");
+               return true;
        }
-
        return true;
 }
 
@@ -304,15 +297,3 @@ MUTATOR_HOOKFUNCTION(ok, SetModname)
        M_ARGV(0, string) = "Overkill";
        return true;
 }
-
-void ok_Initialize()
-{
-       precache_all_playermodels("models/ok_player/*.dpm");
-
-       WEP_RPC.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
-       WEP_HMG.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
-
-       WEP_SHOTGUN.mdl = "ok_shotgun";
-       WEP_MACHINEGUN.mdl = "ok_mg";
-       WEP_VORTEX.mdl = "ok_sniper";
-}
index 6f70f09beec2219624baeca92e2cd7deaa104fb4..72324e6dbb01b375b00010231235a03d7a35e649 100644 (file)
@@ -1 +1,8 @@
 #pragma once
+
+bool autocvar_g_overkill_filter_healthmega;
+bool autocvar_g_overkill_filter_armormedium;
+bool autocvar_g_overkill_filter_armorbig;
+bool autocvar_g_overkill_filter_armormega;
+
+.float ok_item;
diff --git a/qcsrc/common/mutators/mutator/random_items/_mod.inc b/qcsrc/common/mutators/mutator/random_items/_mod.inc
new file mode 100644 (file)
index 0000000..191ea09
--- /dev/null
@@ -0,0 +1,4 @@
+// generated file; do not modify
+#ifdef SVQC
+    #include <common/mutators/mutator/random_items/sv_random_items.qc>
+#endif
diff --git a/qcsrc/common/mutators/mutator/random_items/_mod.qh b/qcsrc/common/mutators/mutator/random_items/_mod.qh
new file mode 100644 (file)
index 0000000..928d174
--- /dev/null
@@ -0,0 +1,4 @@
+// generated file; do not modify
+#ifdef SVQC
+    #include <common/mutators/mutator/random_items/sv_random_items.qh>
+#endif
diff --git a/qcsrc/common/mutators/mutator/random_items/sv_random_items.qc b/qcsrc/common/mutators/mutator/random_items/sv_random_items.qc
new file mode 100644 (file)
index 0000000..3d30574
--- /dev/null
@@ -0,0 +1,438 @@
+#include "sv_random_items.qh"
+
+/// \file
+/// \brief Source file that contains implementation of the random items mutator.
+/// \author Lyberta
+/// \copyright GNU GPLv2 or any later version.
+
+//============================ Constants ======================================
+
+enum
+{
+       RANDOM_ITEM_TYPE_HEALTH = 1,
+       RANDOM_ITEM_TYPE_ARMOR,
+       RANDOM_ITEM_TYPE_RESOURCE,
+       RANDOM_ITEM_TYPE_WEAPON,
+       RANDOM_ITEM_TYPE_POWERUP
+};
+
+//======================= Global variables ====================================
+
+// Replace cvars
+
+/// \brief Classnames to replace %s with.
+/// string autocvar_g_random_items_replace_%s;
+
+// Map probability cvars
+
+/// \brief Probability of random %s spawning in the map.
+/// float autocvar_g_random_items_%s_probability;
+
+/// \brief Probability of random %s spawning in the map during overkill.
+/// float autocvar_g_random_items_overkill_%s_probability;
+
+// Loot
+
+bool autocvar_g_random_loot; ///< Whether to enable random loot.
+
+float autocvar_g_random_loot_min; ///< Minimum amount of loot items.
+float autocvar_g_random_loot_max; ///< Maximum amount of loot items.
+float autocvar_g_random_loot_time; ///< Amount of time the loot will stay.
+float autocvar_g_random_loot_spread; ///< How far can loot be thrown.
+
+// Loot probability cvars
+
+/// \brief Probability of random %s spawning as loot.
+/// float autocvar_g_random_loot_%s_probability;
+
+/// \brief Probability of random %s spawning as loot during overkill.
+/// float autocvar_g_random_loot_overkill_%s_probability;
+
+/// \brief Holds whether random item is spawning. Used to prevent infinite
+/// recursion.
+bool random_items_is_spawning = false;
+
+//====================== Forward declarations =================================
+
+/// \brief Returns a random classname of the item with specific property.
+/// \param[in] prefix Prefix of the cvars that hold probabilities.
+/// \return Random classname of the item.
+string RandomItems_GetRandomItemClassNameWithProperty(string prefix,
+       .bool item_property);
+
+//=========================== Public API ======================================
+
+string RandomItems_GetRandomItemClassName(string prefix)
+{
+       if (autocvar_g_instagib)
+       {
+               return RandomItems_GetRandomInstagibItemClassName(prefix);
+       }
+       if (expr_evaluate(autocvar_g_overkill))
+       {
+               return RandomItems_GetRandomOverkillItemClassName(prefix);
+       }
+       return RandomItems_GetRandomVanillaItemClassName(prefix);
+}
+
+string RandomItems_GetRandomVanillaItemClassName(string prefix)
+{
+       RandomSelection_Init();
+       string cvar_name = sprintf("g_%s_health_probability", prefix);
+       if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+       {
+               LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+       }
+       else
+       {
+               RandomSelection_AddFloat(RANDOM_ITEM_TYPE_HEALTH, cvar(cvar_name), 1);
+       }
+       cvar_name = sprintf("g_%s_armor_probability", prefix);
+       if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+       {
+               LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+       }
+       else
+       {
+               RandomSelection_AddFloat(RANDOM_ITEM_TYPE_ARMOR, cvar(cvar_name), 1);
+       }
+       cvar_name = sprintf("g_%s_resource_probability", prefix);
+       if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+       {
+               LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+       }
+       else
+       {
+               RandomSelection_AddFloat(RANDOM_ITEM_TYPE_RESOURCE, cvar(cvar_name), 1);
+       }
+       cvar_name = sprintf("g_%s_weapon_probability", prefix);
+       if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+       {
+               LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+       }
+       else
+       {
+               RandomSelection_AddFloat(RANDOM_ITEM_TYPE_WEAPON, cvar(cvar_name), 1);
+       }
+       cvar_name = sprintf("g_%s_powerup_probability", prefix);
+       if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+       {
+               LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+       }
+       else
+       {
+               RandomSelection_AddFloat(RANDOM_ITEM_TYPE_POWERUP, cvar(cvar_name), 1);
+       }
+       int item_type = RandomSelection_chosen_float;
+       switch (item_type)
+       {
+               case RANDOM_ITEM_TYPE_HEALTH:
+               {
+                       return RandomItems_GetRandomItemClassNameWithProperty(prefix,
+                               instanceOfHealth);
+               }
+               case RANDOM_ITEM_TYPE_ARMOR:
+               {
+                       return RandomItems_GetRandomItemClassNameWithProperty(prefix,
+                               instanceOfArmor);
+               }
+               case RANDOM_ITEM_TYPE_RESOURCE:
+               {
+                       return RandomItems_GetRandomItemClassNameWithProperty(prefix,
+                               instanceOfAmmo);
+               }
+               case RANDOM_ITEM_TYPE_WEAPON:
+               {
+                       RandomSelection_Init();
+                       FOREACH(Weapons, it != WEP_Null &&
+                               !(it.spawnflags & WEP_FLAG_MUTATORBLOCKED),
+                       {
+                               cvar_name = sprintf("g_%s_%s_probability", prefix,
+                                       it.m_canonical_spawnfunc);
+                               if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+                               {
+                                       LOG_WARNF("Random items: cvar %s doesn't exist.",
+                                               cvar_name);
+                                       continue;
+                               }
+                               RandomSelection_AddString(it.m_canonical_spawnfunc,
+                                       cvar(cvar_name), 1);
+                       });
+                       return RandomSelection_chosen_string;
+               }
+               case RANDOM_ITEM_TYPE_POWERUP:
+               {
+                       return RandomItems_GetRandomItemClassNameWithProperty(prefix,
+                               instanceOfPowerup);
+               }
+       }
+       return "";
+}
+
+string RandomItems_GetRandomInstagibItemClassName(string prefix)
+{
+       RandomSelection_Init();
+       FOREACH(Items, it.spawnflags & ITEM_FLAG_INSTAGIB,
+       {
+               string cvar_name = sprintf("g_%s_%s_probability", prefix,
+                       it.m_canonical_spawnfunc);
+               if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+               {
+                       LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+                       continue;
+               }
+               RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1);
+       });
+       return RandomSelection_chosen_string;
+}
+
+string RandomItems_GetRandomOverkillItemClassName(string prefix)
+{
+       RandomSelection_Init();
+       FOREACH(Items, (it.spawnflags & ITEM_FLAG_OVERKILL) &&
+               !(it.spawnflags & ITEM_FLAG_MUTATORBLOCKED),
+       {
+               string cvar_name = sprintf("g_%s_overkill_%s_probability", prefix,
+                       it.m_canonical_spawnfunc);
+               if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+               {
+                       LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+                       continue;
+               }
+               RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1);
+       });
+       string cvar_name = sprintf("g_%s_overkill_weapon_hmg_probability", prefix);
+       if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+       {
+               LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+       }
+       else
+       {
+               RandomSelection_AddString("weapon_hmg", cvar(cvar_name), 1);
+       }
+       cvar_name = sprintf("g_%s_overkill_weapon_rpc_probability", prefix);
+       if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+       {
+               LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+       }
+       else
+       {
+               RandomSelection_AddString("weapon_rpc", cvar(cvar_name), 1);
+       }
+       return RandomSelection_chosen_string;
+}
+
+//========================= Free functions ====================================
+
+/// \brief Returns list of classnames to replace a map item with.
+/// \param[in] item Item to inspect.
+/// \return List of classnames to replace a map item with.
+string RandomItems_GetItemReplacementClassNames(entity item)
+{
+       string cvar_name = sprintf("g_random_items_replace_%s", item.classname);
+       if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+       {
+               LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+               return "";
+       }
+       return cvar_string(cvar_name);
+}
+
+string RandomItems_GetRandomItemClassNameWithProperty(string prefix,
+       .bool item_property)
+{
+       RandomSelection_Init();
+       FOREACH(Items, it.item_property && (it.spawnflags & ITEM_FLAG_NORMAL),
+       {
+               string cvar_name = sprintf("g_%s_%s_probability", prefix,
+                       it.m_canonical_spawnfunc);
+               if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+               {
+                       LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+                       continue;
+               }
+               RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1);
+       });
+       return RandomSelection_chosen_string;
+}
+
+/// \brief Replaces a map item.
+/// \param[in] item Item to replace.
+/// \return Spawned item on success, NULL otherwise.
+entity RandomItems_ReplaceMapItem(entity item)
+{
+       //PrintToChatAll(strcat("Replacing ", item.classname));
+       string new_classnames = RandomItems_GetItemReplacementClassNames(item);
+       if (new_classnames == "")
+       {
+               return NULL;
+       }
+       string new_classname;
+       if (new_classnames == "random")
+       {
+               new_classname = RandomItems_GetRandomItemClassName("random_items");
+               if (new_classname == "")
+               {
+                       return NULL;
+               }
+       }
+       else
+       {
+               int num_new_classnames = tokenize_console(new_classnames);
+               if (num_new_classnames == 1)
+               {
+                       new_classname = new_classnames;
+               }
+               else
+               {
+                       int classname_index = floor(random() * num_new_classnames);
+                       new_classname = argv(classname_index);
+               }
+       }
+       //PrintToChatAll(strcat("Replacing with ", new_classname));
+       if (new_classname == item.classname)
+       {
+               return NULL;
+       }
+       random_items_is_spawning = true;
+       entity new_item;
+       if (!expr_evaluate(autocvar_g_overkill))
+       {
+               new_item = Item_Create(strzone(new_classname), item.origin,
+                       Item_ShouldKeepPosition(item));
+               random_items_is_spawning = false;
+               if (new_item == NULL)
+               {
+                       return NULL;
+               }
+       }
+       else
+       {
+               new_item = spawn();
+               new_item.classname = strzone(new_classname);
+               new_item.spawnfunc_checked = true;
+               new_item.noalign = Item_ShouldKeepPosition(item);
+               new_item.ok_item = true;
+               Item_Initialize(new_item, new_classname);
+               random_items_is_spawning = false;
+               if (wasfreed(new_item))
+               {
+                       return NULL;
+               }
+               setorigin(new_item, item.origin);
+       }
+       if (item.team)
+       {
+               new_item.team = item.team;
+       }
+       return new_item;
+}
+
+/// \brief Spawns a random loot item.
+/// \param[in] position Position of the item.
+/// \return No return.
+void RandomItems_SpawnLootItem(vector position)
+{
+       string class_name = RandomItems_GetRandomItemClassName("random_loot");
+       if (class_name == "")
+       {
+               return;
+       }
+       vector spread = '0 0 0';
+       spread.z = autocvar_g_random_loot_spread / 2;
+       spread += randomvec() * autocvar_g_random_loot_spread;
+       random_items_is_spawning = true;
+       if (!expr_evaluate(autocvar_g_overkill))
+       {
+               Item_CreateLoot(class_name, position, spread,
+                       autocvar_g_random_loot_time);
+       }
+       else
+       {
+               entity item = spawn();
+               item.ok_item = true;
+               item.classname = class_name;
+               Item_InitializeLoot(item, class_name, position, spread,
+                       autocvar_g_random_loot_time);
+       }
+       random_items_is_spawning = false;
+}
+
+//============================= Hooks ========================================
+
+REGISTER_MUTATOR(random_items, (autocvar_g_random_items ||
+       autocvar_g_random_loot));
+
+MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsString)
+{
+       M_ARGV(0, string) = strcat(M_ARGV(0, string), ":random_items");
+}
+
+MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsPrettyString)
+{
+       M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Random items");
+}
+
+/// \brief Hook that is called when an item is about to spawn.
+MUTATOR_HOOKFUNCTION(random_items, FilterItem, CBC_ORDER_LAST)
+{
+       //PrintToChatAll("FilterItem");
+       if (!autocvar_g_random_items)
+       {
+               return false;
+       }
+       if (random_items_is_spawning == true)
+       {
+               return false;
+       }
+       entity item = M_ARGV(0, entity);
+       if (Item_IsLoot(item))
+       {
+               return false;
+       }
+       if (RandomItems_ReplaceMapItem(item) == NULL)
+       {
+               return false;
+       }
+       return true;
+}
+
+/// \brief Hook that is called after the player has touched an item.
+MUTATOR_HOOKFUNCTION(random_items, ItemTouched, CBC_ORDER_LAST)
+{
+       //PrintToChatAll("ItemTouched");
+       if (!autocvar_g_random_items)
+       {
+               return;
+       }
+       entity item = M_ARGV(0, entity);
+       if (Item_IsLoot(item))
+       {
+               return;
+       }
+       entity new_item = RandomItems_ReplaceMapItem(item);
+       if (new_item == NULL)
+       {
+               return;
+       }
+       Item_ScheduleRespawn(new_item);
+       delete(item);
+}
+
+/// \brief Hook which is called when the player dies.
+MUTATOR_HOOKFUNCTION(random_items, PlayerDies)
+{
+       //PrintToChatAll("PlayerDies");
+       if (!autocvar_g_random_loot)
+       {
+               return;
+       }
+       entity victim = M_ARGV(2, entity);
+       vector loot_position = victim.origin + '0 0 32';
+       int num_loot_items = floor(autocvar_g_random_loot_min + random() *
+               autocvar_g_random_loot_max);
+       for (int item_index = 0; item_index < num_loot_items; ++item_index)
+       {
+               RandomItems_SpawnLootItem(loot_position);
+       }
+}
diff --git a/qcsrc/common/mutators/mutator/random_items/sv_random_items.qh b/qcsrc/common/mutators/mutator/random_items/sv_random_items.qh
new file mode 100644 (file)
index 0000000..c9b4dbb
--- /dev/null
@@ -0,0 +1,32 @@
+#pragma once
+
+/// \file
+/// \brief Header file that describes the random items mutator.
+/// \author Lyberta
+/// \copyright GNU GPLv2 or any later version.
+
+bool autocvar_g_random_items; ///< Whether to enable random items.
+
+/// \brief Returns a random classname of the item.
+/// \param[in] prefix Prefix of the cvars that hold probabilities.
+/// \return Random classname of the item.
+/// \note This function will automatically detect gamemode and use cvars from
+/// that gamemode.
+string RandomItems_GetRandomItemClassName(string prefix);
+
+/// \brief Returns a random classname of the vanilla item.
+/// \param[in] prefix Prefix of the cvars that hold probabilities.
+/// \return Random classname of the vanilla item.
+/// \note This includes mutator items that don't change gameplay a lot such as
+/// jetpack and new toys.
+string RandomItems_GetRandomVanillaItemClassName(string prefix);
+
+/// \brief Returns a random classname of the instagib item.
+/// \param[in] prefix Prefix of the cvars that hold probabilities.
+/// \return Random classname of the instagib item.
+string RandomItems_GetRandomInstagibItemClassName(string prefix);
+
+/// \brief Returns a random classname of the overkill item.
+/// \param[in] prefix Prefix of the cvars that hold probabilities.
+/// \return Random classname of the overkill item.
+string RandomItems_GetRandomOverkillItemClassName(string prefix);
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 3b5492ac2d0adab12235d3512c13177e179b412b..b4c6146bb6662535e983df75a4e73962e6289636 100644 (file)
     MSG_INFO_NOTIF(QUIT_DISCONNECT,                         N_CHATCON,  1, 0, "s1", "",         "",             _("^BG%s^F3 disconnected"), "")
     MSG_INFO_NOTIF(QUIT_KICK_IDLING,                        N_CHATCON,  1, 0, "s1", "",         "",             _("^BG%s^F3 was kicked for idling"), "")
     MSG_INFO_NOTIF(QUIT_KICK_SPECTATING,                    N_CONSOLE,  0, 0, "", "",           "",             _("^F2You were kicked from the server because you are a spectator and spectators aren't allowed at the moment."), "")
+    MSG_INFO_NOTIF(QUIT_KICK_TEAMKILL,                      N_CHATCON,  1, 0, "s1", "",         "",             _("^BG%s^F3 was kicked for excessive teamkilling"), "")
     MSG_INFO_NOTIF(QUIT_SPECTATE,                           N_CONSOLE,  1, 0, "s1", "",         "",             _("^BG%s^F3 is now spectating"), "")
 
     MSG_INFO_NOTIF(RACE_ABANDONED,                          N_CONSOLE,  1, 0, "s1", "",                                                                     "",                         _("^BG%s^BG has abandoned the race"), "")
index 995c65b4d322061a7f00d0b5f0899a4480af02a4..3fe2808583b94254d580b3c3960b2610c9192e6f 100644 (file)
@@ -544,7 +544,6 @@ void _Movetype_Physics_ClientFrame(entity this, float movedt)
                        _Movetype_CheckWater(this);
                        this.origin = this.origin + movedt * this.velocity;
                        this.angles = this.angles + movedt * this.avelocity;
-                       _Movetype_LinkEdict(this, false);
                        break;
                case MOVETYPE_STEP:
                        _Movetype_Physics_Step(this, movedt);
@@ -563,6 +562,12 @@ void _Movetype_Physics_ClientFrame(entity this, float movedt)
                case MOVETYPE_PHYSICS:
                        break;
        }
+
+       //_Movetype_CheckVelocity(this);
+
+       _Movetype_LinkEdict(this, true);
+
+       //_Movetype_CheckVelocity(this);
 }
 
 void Movetype_Physics_NoMatchTicrate(entity this, float movedt, bool isclient)  // to be run every move frame
index 20e2d4cc32f16d0b736e421d0a1172262492ff7c..7d3cab007b508fa6dafceb84a14585d0fd68f327 100644 (file)
@@ -21,7 +21,7 @@ float Physics_ClientOption(entity this, string option, float defaultval)
                if(cvar_type(s) & CVAR_TYPEFLAG_EXISTS)
                        return cvar(s);
        }
-       if(autocvar_g_physics_clientselect && autocvar_g_physics_clientselect_default)
+       if(autocvar_g_physics_clientselect && autocvar_g_physics_clientselect_default && autocvar_g_physics_clientselect_default != "")
        {
                string s = strcat("g_physics_", autocvar_g_physics_clientselect_default, "_", option);
                if(cvar_type(s) & CVAR_TYPEFLAG_EXISTS)
@@ -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 ae59381e5c11394ce93abcf441cd5c453be4d8a6..0ebea585f80e61071cedd24e4c8e2d22abdec445 100644 (file)
@@ -82,6 +82,7 @@ bool IsFlying(entity a);
 
 #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)
index 646638a80ca704778f8628513a6225021c154566..476d0dbbaa612445241bd1450c48642a7f78f347 100644 (file)
@@ -34,6 +34,7 @@ REGISTER_SP(DMGTAKEN);
 REGISTER_SP(KILLS);
 REGISTER_SP(DEATHS);
 REGISTER_SP(SUICIDES);
+REGISTER_SP(TEAMKILLS);
 REGISTER_SP(FRAGS);
 
 REGISTER_SP(ELO);
index 41a0e7eb974169d9919e694da085f1c17102051e..daa1d3c8b7bd205640288198c94a0eb7f010dede 100644 (file)
@@ -303,6 +303,7 @@ REGISTER_STAT(SLICK_APPLYGRAVITY, bool, autocvar_sv_slick_applygravity)
 
 #ifdef SVQC
 #include "physics/movetypes/movetypes.qh"
+float warmup_limit;
 #endif
 
 REGISTER_STAT(MOVEVARS_AIRACCEL_QW_STRETCHFACTOR, float)
@@ -346,6 +347,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 50ae0c336c51393ee61d7a14e70875588fd1d6e3..e13df065a477b5f0f95a7370c484c5968a444ba4 100644 (file)
@@ -37,6 +37,7 @@ REGISTER_NET_LINKED(ENT_CLIENT_ITEM)
 #ifdef CSQC
 bool autocvar_cl_ghost_items_vehicle = true;
 .vector item_glowmod;
+.bool item_simple; // probably not really needed, but better safe than sorry
 void Item_SetAlpha(entity this)
 {
        bool veh_hud = (hud && autocvar_cl_ghost_items_vehicle);
@@ -96,14 +97,16 @@ void ItemDraw(entity this)
     {
         if(this.ItemStatus & ITS_ANIMATE1)
         {
-            this.angles += this.avelocity * frametime;
+               if(!this.item_simple)
+               this.angles += this.avelocity * frametime;
             float fade_in = bound(0, time - this.onground_time, 1);
             setorigin(this, this.oldorigin + fade_in * ('0 0 10' + '0 0 8' * sin((time - this.onground_time) * 2)));
         }
 
         if(this.ItemStatus & ITS_ANIMATE2)
         {
-            this.angles += this.avelocity * frametime;
+               if(!this.item_simple)
+               this.angles += this.avelocity * frametime;
             float fade_in = bound(0, time - this.onground_time, 1);
             setorigin(this, this.oldorigin + fade_in * ('0 0 8' + '0 0 4' * sin((time - this.onground_time) * 3)));
         }
@@ -112,19 +115,6 @@ void ItemDraw(entity this)
     Item_SetAlpha(this);
 }
 
-void ItemDrawSimple(entity this)
-{
-    if(this.gravity)
-    {
-        Movetype_Physics_MatchServer(this, false);
-
-        if(IS_ONGROUND(this))
-            this.gravity = 0;
-    }
-
-    Item_SetAlpha(this);
-}
-
 void Item_PreDraw(entity this)
 {
        if(warpzone_warpzones_exist)
@@ -228,11 +218,12 @@ NET_HANDLE(ENT_CLIENT_ITEM, bool isnew)
 
         this.mdl = "";
         string _fn = ReadString();
+        this.item_simple = false; // reset it!
 
         if(autocvar_cl_simple_items && (this.ItemStatus & ITS_ALLOWSI))
         {
             string _fn2 = substring(_fn, 0 , strlen(_fn) -4);
-            this.draw = ItemDrawSimple;
+            this.item_simple = true;
 
             if(fexists(strcat(_fn2, autocvar_cl_simpleitems_postfix, ".md3")))
                 this.mdl = strzone(strcat(_fn2, autocvar_cl_simpleitems_postfix, ".md3"));
@@ -244,12 +235,12 @@ NET_HANDLE(ENT_CLIENT_ITEM, bool isnew)
                 this.mdl = strzone(strcat(_fn2, autocvar_cl_simpleitems_postfix, ".mdl"));
             else
             {
-                this.draw = ItemDraw;
+                this.item_simple = false;
                 LOG_TRACE("Simple item requested for ", _fn, " but no model exists for it");
             }
         }
 
-        if(this.draw != ItemDrawSimple)
+        if(!this.item_simple)
             this.mdl = strzone(_fn);
 
 
@@ -533,7 +524,7 @@ void Item_Respawn (entity this)
 
 void Item_RespawnCountdown (entity this)
 {
-       if(this.count >= ITEM_RESPAWN_TICKS)
+       if(this.item_respawncounter >= ITEM_RESPAWN_TICKS)
        {
                if(this.waypointsprite_attached)
                        WaypointSprite_Kill(this.waypointsprite_attached);
@@ -542,8 +533,8 @@ void Item_RespawnCountdown (entity this)
        else
        {
                this.nextthink = time + 1;
-               this.count += 1;
-               if(this.count == 1)
+               this.item_respawncounter += 1;
+               if(this.item_respawncounter == 1)
                {
                        do {
                                {
@@ -584,7 +575,7 @@ void Item_RespawnCountdown (entity this)
                        });
 
                        WaypointSprite_Ping(this.waypointsprite_attached);
-                       //WaypointSprite_UpdateHealth(this.waypointsprite_attached, this.count);
+                       //WaypointSprite_UpdateHealth(this.waypointsprite_attached, this.item_respawncounter);
                }
        }
 }
@@ -607,7 +598,7 @@ void Item_ScheduleRespawnIn(entity e, float t)
                setthink(e, Item_RespawnCountdown);
                e.nextthink = time + max(0, t - ITEM_RESPAWN_TICKS);
                e.scheduledrespawntime = e.nextthink + ITEM_RESPAWN_TICKS;
-               e.count = 0;
+               e.item_respawncounter = 0;
                if(Item_ItemsTime_Allow(e.itemdef) || (e.weapons & WEPSET_SUPERWEAPONS))
                {
                        t = Item_ItemsTime_UpdateTime(e, e.scheduledrespawntime);
@@ -877,9 +868,8 @@ float Item_GiveTo(entity item, entity player)
 
 void Item_Touch(entity this, entity toucher)
 {
-
        // remove the item if it's currnetly in a NODROP brush or hits a NOIMPACT surface (such as sky)
-       if (this.classname == "droppedweapon")
+       if (Item_IsLoot(this))
        {
                if (ITEM_TOUCH_NEEDKILL())
                {
@@ -904,17 +894,16 @@ void Item_Touch(entity this, entity toucher)
 
        toucher = M_ARGV(1, entity);
 
-       if (this.classname == "droppedweapon")
+       if (Item_IsExpiring(this))
        {
                this.strength_finished = max(0, this.strength_finished - time);
                this.invincible_finished = max(0, this.invincible_finished - time);
                this.superweapons_finished = max(0, this.superweapons_finished - time);
        }
-       entity it = this.itemdef;
-       bool gave = ITEM_HANDLE(Pickup, it, this, toucher);
+       bool gave = ITEM_HANDLE(Pickup, this.itemdef, this, toucher);
        if (!gave)
        {
-               if (this.classname == "droppedweapon")
+               if (Item_IsExpiring(this))
                {
                        // undo what we did above
                        this.strength_finished += time;
@@ -931,30 +920,40 @@ LABEL(pickup)
        Send_Effect(EFFECT_ITEM_PICKUP, CENTER_OR_VIEWOFS(this), '0 0 0', 1);
        _sound (toucher, (this.itemdef.instanceOfPowerup ? CH_TRIGGER_SINGLE : CH_TRIGGER), (this.item_pickupsound ? this.item_pickupsound : Sound_fixpath(this.item_pickupsound_ent)), VOL_BASE, ATTEN_NORM);
 
-       if (this.classname == "droppedweapon")
+       MUTATOR_CALLHOOK(ItemTouched, this, toucher);
+       if (wasfreed(this))
+       {
+               return;
+       }
+
+       if (Item_IsLoot(this))
+       {
                delete(this);
-       else if (this.spawnshieldtime)
+               return;
+       }
+       if (!this.spawnshieldtime)
        {
-               entity e;
-               if(this.team)
+               return;
+       }
+       entity e;
+       if (this.team)
+       {
+               RandomSelection_Init();
+               IL_EACH(g_items, it.team == this.team,
                {
-                       RandomSelection_Init();
-                       IL_EACH(g_items, it.team == this.team,
+                       if (it.itemdef) // is a registered item
                        {
-                               if(it.itemdef) // is a registered item
-                               {
-                                       Item_Show(it, -1);
-                                       it.scheduledrespawntime = 0;
-                                       RandomSelection_AddEnt(it, it.cnt, 0);
-                               }
-                       });
-                       e = RandomSelection_chosen_ent;
-                       Item_Show(e, 1); // reset its state so it is visible (extra sendflags doesn't matter, this happens anyway)
-               }
-               else
-                       e = this;
-               Item_ScheduleRespawn(e);
+                               Item_Show(it, -1);
+                               it.scheduledrespawntime = 0;
+                               RandomSelection_AddEnt(it, it.cnt, 0);
+                       }
+               });
+               e = RandomSelection_chosen_ent;
+               Item_Show(e, 1); // reset its state so it is visible (extra sendflags doesn't matter, this happens anyway)
        }
+       else
+               e = this;
+       Item_ScheduleRespawn(e);
 }
 
 void Item_Reset(entity this)
@@ -962,16 +961,19 @@ void Item_Reset(entity this)
        Item_Show(this, !this.state);
        setorigin(this, this.origin);
 
-       if (this.classname != "droppedweapon")
+       if (Item_IsLoot(this))
        {
-               setthink(this, Item_Think);
-               this.nextthink = time;
-
-               if (this.waypointsprite_attached)
-                       WaypointSprite_Kill(this.waypointsprite_attached);
-
-               if (this.itemdef.instanceOfPowerup || (this.weapons & WEPSET_SUPERWEAPONS)) // do not spawn powerups initially!
-                       Item_ScheduleInitialRespawn(this);
+               return;
+       }
+       setthink(this, Item_Think);
+       this.nextthink = time;
+       if (this.waypointsprite_attached)
+       {
+               WaypointSprite_Kill(this.waypointsprite_attached);
+       }
+       if (this.itemdef.instanceOfPowerup || (this.weapons & WEPSET_SUPERWEAPONS)) // do not spawn powerups initially!
+       {
+               Item_ScheduleInitialRespawn(this);
        }
 }
 
@@ -1115,8 +1117,6 @@ float ammo_pickupevalfunc(entity player, entity item)
        return rating;
 }
 
-.int item_group;
-.int item_group_count;
 float healtharmor_pickupevalfunc(entity player, entity item)
 {
        float c = 0;
@@ -1203,11 +1203,9 @@ void _StartItem(entity this, entity def, float defaultrespawntime, float default
                return;
        }
 
-       // is it a dropped weapon?
-       if (this.classname == "droppedweapon")
+       if (Item_IsLoot(this))
        {
                this.reset = SUB_Remove;
-               // it's a dropped weapon
                set_movetype(this, MOVETYPE_TOSS);
 
                // Savage: remove thrown items after a certain period of time ("garbage collection")
@@ -1217,7 +1215,7 @@ void _StartItem(entity this, entity def, float defaultrespawntime, float default
                this.takedamage = DAMAGE_YES;
                this.event_damage = Item_Damage;
 
-               if(this.strength_finished || this.invincible_finished || this.superweapons_finished)
+               if (Item_IsExpiring(this))
                {
                        // if item is worthless after a timer, have it expire then
                        this.nextthink = max(this.strength_finished, this.invincible_finished, this.superweapons_finished);
@@ -1332,7 +1330,7 @@ void _StartItem(entity this, entity def, float defaultrespawntime, float default
 
        if(def.instanceOfWeaponPickup)
        {
-               if (this.classname != "droppedweapon") // if dropped, colormap is already set up nicely
+               if (!Item_IsLoot(this)) // if dropped, colormap is already set up nicely
                        this.colormap = 1024; // color shirt=0 pants=0 grey
                else
                        this.gravity = 1;
@@ -1368,6 +1366,13 @@ void _StartItem(entity this, entity def, float defaultrespawntime, float default
 
 void StartItem(entity this, GameItem def)
 {
+    def = def.m_spawnfunc_hookreplace(def, this);
+    if (def.spawnflags & ITEM_FLAG_MUTATORBLOCKED)
+    {
+        delete(this);
+        return;
+    }
+    this.classname = def.m_canonical_spawnfunc;
     _StartItem(
        this,
        this.itemdef = def,
@@ -1424,114 +1429,9 @@ void setItemGroupCount()
        }
 }
 
-spawnfunc(item_rockets)
-{
-    StartItem(this, ITEM_Rockets);
-}
-
-spawnfunc(item_bullets)
-{
-       if(!weaponswapping && autocvar_sv_q3acompat_machineshotgunswap &&
-          (this.classname != "droppedweapon"))
-       {
-               weaponswapping = true;
-               spawnfunc_item_shells(this);
-               weaponswapping = false;
-               return;
-       }
-
-    StartItem(this, ITEM_Bullets);
-}
-
-spawnfunc(item_cells)
-{
-       StartItem(this, ITEM_Cells);
-}
-
-spawnfunc(item_plasma)
-{
-       StartItem(this, ITEM_Plasma);
-}
-
-spawnfunc(item_shells)
-{
-       if(!weaponswapping && autocvar_sv_q3acompat_machineshotgunswap &&
-          (this.classname != "droppedweapon"))
-       {
-               weaponswapping = true;
-               spawnfunc_item_bullets(this);
-               weaponswapping = false;
-               return;
-       }
-
-       StartItem(this, ITEM_Shells);
-}
-
-spawnfunc(item_armor_small)
-{
-       StartItem(this, ITEM_ArmorSmall);
-}
-
-spawnfunc(item_armor_medium)
-{
-       StartItem(this, ITEM_ArmorMedium);
-}
-
-spawnfunc(item_armor_big)
-{
-       StartItem(this, ITEM_ArmorBig);
-}
-
-spawnfunc(item_armor_mega)
-{
-       StartItem(this, ITEM_ArmorMega);
-}
-
-spawnfunc(item_health_small)
-{
-       StartItem(this, ITEM_HealthSmall);
-}
-
-spawnfunc(item_health_medium)
-{
-    StartItem(this, ITEM_HealthMedium);
-}
-
-spawnfunc(item_health_big)
-{
-       StartItem(this, ITEM_HealthBig);
-}
-
-spawnfunc(item_health_mega)
-{
-    StartItem(this, ITEM_HealthMega);
-}
-
-// support old misnamed entities
-spawnfunc(item_armor1) { spawnfunc_item_armor_small(this); }  // FIXME: in Quake this is green armor, in Xonotic maps it is an armor shard
-spawnfunc(item_armor25) { spawnfunc_item_armor_mega(this); }
-spawnfunc(item_armor_large) { spawnfunc_item_armor_mega(this); }
-spawnfunc(item_health1) { spawnfunc_item_health_small(this); }
-spawnfunc(item_health25) { spawnfunc_item_health_medium(this); }
-spawnfunc(item_health_large) { spawnfunc_item_health_big(this); }
-spawnfunc(item_health100) { spawnfunc_item_health_mega(this); }
-
-spawnfunc(item_strength)
-{
-       StartItem(this, ITEM_Strength);
-}
-
-spawnfunc(item_invincible)
-{
-       StartItem(this, ITEM_Shield);
-}
-
-// compatibility:
-spawnfunc(item_quad) { this.classname = "item_strength";spawnfunc_item_strength(this);}
-
 void target_items_use(entity this, entity actor, entity trigger)
 {
-       if(actor.classname == "droppedweapon")
+       if(Item_IsLoot(actor))
        {
                EXACTTRIGGER_TOUCH(this, trigger);
                delete(actor);
@@ -1546,7 +1446,7 @@ void target_items_use(entity this, entity actor, entity trigger)
                EXACTTRIGGER_TOUCH(this, trigger);
        }
 
-       IL_EACH(g_items, it.enemy == actor && it.classname == "droppedweapon",
+       IL_EACH(g_items, it.enemy == actor && Item_IsLoot(it),
        {
                delete(it);
        });
@@ -1668,31 +1568,6 @@ spawnfunc(target_items)
        }
 }
 
-spawnfunc(item_fuel)
-{
-       StartItem(this, ITEM_JetpackFuel);
-}
-
-spawnfunc(item_fuel_regen)
-{
-       if(start_items & ITEM_JetpackRegen.m_itemid)
-       {
-               spawnfunc_item_fuel(this);
-               return;
-       }
-       StartItem(this, ITEM_JetpackRegen);
-}
-
-spawnfunc(item_jetpack)
-{
-       if(start_items & ITEM_Jetpack.m_itemid)
-       {
-               spawnfunc_item_fuel(this);
-               return;
-       }
-       StartItem(this, ITEM_Jetpack);
-}
-
 float GiveWeapon(entity e, float wpn, float op, float val)
 {
        WepSet v0, v1;
index 80385971ab34d40a1ea17dbcc42e73714042aa9a..d2f44c61daa8e6544f44f47f4f92a61c54907218 100644 (file)
@@ -1,9 +1,5 @@
 #pragma once
 
-#ifdef SVQC
-#include <server/defs.qh>
-#endif
-
 const int AMMO_COUNT = 4; // amount of ammo types to show in the inventory panel
 
 // item networking
@@ -30,6 +26,8 @@ const int ISF_SIZE                            = BIT(7);
 
 #ifdef SVQC
 void StartItem(entity this, entity a);
+.int item_group;
+.int item_group_count;
 #endif
 
 #ifdef CSQC
@@ -48,21 +46,13 @@ string autocvar_cl_simpleitems_postfix = "_simple";
 .vector colormod;
 
 void ItemDraw(entity this);
-void ItemDrawSimple(entity this);
 
 #endif
 #ifdef SVQC
-spawnfunc(item_strength);
-spawnfunc(item_invincible);
-spawnfunc(item_armor_small);
-spawnfunc(item_shells);
-spawnfunc(item_bullets);
-spawnfunc(item_rockets);
 
 float autocvar_sv_simple_items;
 bool ItemSend(entity this, entity to, int sf);
 
-
 bool have_pickup_item(entity this);
 
 const float ITEM_RESPAWN_TICKS = 10;
@@ -73,6 +63,8 @@ const float ITEM_RESPAWN_TICKS = 10;
 .float max_armorvalue;
 .float pickup_anyway;
 
+.float item_respawncounter;
+
 void Item_Show (entity e, float mode);
 
 void Item_Respawn (entity this);
index 1f8a603dd01744b1dde6a4f13f98b5b32bb61d03..57d644c0448549e13bff834f7cb6fd289d983cc3 100644 (file)
@@ -125,6 +125,42 @@ float Team_ColorToTeam(string team_color)
        return -1;
 }
 
+/// \brief Returns whether team is valid.
+/// \param[in] team_ Team to check.
+/// \return True if team is valid, false otherwise.
+bool Team_IsValidTeam(int team_)
+{
+       switch (team_)
+       {
+               case NUM_TEAM_1:
+               case NUM_TEAM_2:
+               case NUM_TEAM_3:
+               case NUM_TEAM_4:
+               {
+                       return true;
+               }
+       }
+       return false;
+}
+
+/// \brief Returns whether team number is valid.
+/// \param[in] number Team number to check.
+/// \return True if team number is valid, false otherwise.
+bool Team_IsValidNumber(int number)
+{
+       switch (number)
+       {
+               case 1:
+               case 2:
+               case 3:
+               case 4:
+               {
+                       return true;
+               }
+       }
+       return false;
+}
+
 float Team_NumberToTeam(float number)
 {
        switch(number)
index 6e0c2b3a3119fd690aa4bc54ef3f2a2009883a47..84e5581c8d7e521d7e6aa7e61f2a9fb4508a4d67 100644 (file)
@@ -67,7 +67,8 @@ void LaunchDebris (entity this, string debrisname, vector force)
        dbr.velocity_x = this.debrisvelocity.x + this.debrisvelocityjitter.x * crandom();
        dbr.velocity_y = this.debrisvelocity.y + this.debrisvelocityjitter.y * crandom();
        dbr.velocity_z = this.debrisvelocity.z + this.debrisvelocityjitter.z * crandom();
-       this.velocity = this.velocity + force * this.debrisdamageforcescale;
+       dbr.velocity = dbr.velocity + force * this.debrisdamageforcescale;
+       dbr.angles = this.angles;
        dbr.avelocity_x = random()*this.debrisavelocityjitter.x;
        dbr.avelocity_y = random()*this.debrisavelocityjitter.y;
        dbr.avelocity_z = random()*this.debrisavelocityjitter.z;
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 392ab3e5f5f46b883793a34745656298176606b5..bb15503c7f801686a3a1ee3af473de786923fc3c 100644 (file)
@@ -765,6 +765,7 @@ spawnfunc(func_door)
 
        if (this.health)
        {
+               //this.canteamdamage = true; // TODO
                this.takedamage = DAMAGE_YES;
                this.event_damage = door_damage;
        }
index 2c72dc9cf07c1b249d91648683f0e3f4396017a9..c61a02686650eb56ae3a138d3cace85fed2a99b0 100644 (file)
@@ -109,6 +109,7 @@ spawnfunc(func_door_rotating)
 
        if (this.health)
        {
+               //this.canteamdamage = true; // TODO
                this.takedamage = DAMAGE_YES;
                this.event_damage = door_damage;
        }
index 0bad196abf3a57a79a4fbac2637170f35321bdf6..6f2d101ef9a914b61bae21278a6c5fa1c386b6a5 100644 (file)
@@ -228,6 +228,7 @@ spawnfunc(func_door_secret)
 
        if (this.spawnflags & SECRET_YES_SHOOT)
        {
+               //this.canteamdamage = true; // TODO
                this.health = 10000;
                this.takedamage = DAMAGE_YES;
                this.event_damage = fd_secret_damage;
index f4a7ffb02cae0d36338a447950d7fb62c5a69486..92f361a145c098c56efa037cc3b9ede05b3a9a4f 100644 (file)
@@ -42,9 +42,71 @@ void func_ladder_link(entity this)
 void func_ladder_init(entity this)
 {
        settouch(this, func_ladder_touch);
-
        trigger_init(this);
        func_ladder_link(this);
+
+       if(min(this.absmax.x - this.absmin.x, this.absmax.y - this.absmin.y) > 100)
+               return;
+
+       entity tracetest_ent = spawn();
+       setsize(tracetest_ent, PL_MIN_CONST, PL_MAX_CONST);
+       tracetest_ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
+
+       vector top_min = (this.absmin + this.absmax) / 2;
+       top_min.z = this.absmax.z;
+       vector top_max = top_min;
+       top_max.z += PL_MAX_CONST.z - PL_MIN_CONST.z;
+       tracebox(top_max + jumpstepheightvec, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, tracetest_ent);
+       if(trace_startsolid)
+       {
+               tracebox(top_max + stepheightvec, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, tracetest_ent);
+               if(trace_startsolid)
+               {
+                       tracebox(top_max, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, tracetest_ent);
+                       if(trace_startsolid)
+                       {
+                               if(this.absmax.x - this.absmin.x > PL_MAX_CONST.x - PL_MIN_CONST.x
+                                       && this.absmax.y - this.absmin.y < this.absmax.x - this.absmin.x)
+                               {
+                                       // move top on one side
+                                       top_max.y = top_min.y = this.absmin.y + (PL_MAX_CONST.y - PL_MIN_CONST.y) * 0.75;
+                               }
+                               else if(this.absmax.y - this.absmin.y > PL_MAX_CONST.y - PL_MIN_CONST.y
+                                       && this.absmax.x - this.absmin.x < this.absmax.y - this.absmin.y)
+                               {
+                                       // move top on one side
+                                       top_max.x = top_min.x = this.absmin.x + (PL_MAX_CONST.x - PL_MIN_CONST.x) * 0.75;
+                               }
+                               tracebox(top_max, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, tracetest_ent);
+                               if(trace_startsolid)
+                               {
+                                       if(this.absmax.x - this.absmin.x > PL_MAX_CONST.x - PL_MIN_CONST.x
+                                               && this.absmax.y - this.absmin.y < this.absmax.x - this.absmin.x)
+                                       {
+                                               // alternatively on the other side
+                                               top_max.y = top_min.y = this.absmax.y - (PL_MAX_CONST.y - PL_MIN_CONST.y) * 0.75;
+                                       }
+                                       else if(this.absmax.y - this.absmin.y > PL_MAX_CONST.y - PL_MIN_CONST.y
+                                               && this.absmax.x - this.absmin.x < this.absmax.y - this.absmin.y)
+                                       {
+                                               // alternatively on the other side
+                                               top_max.x = top_min.x = this.absmax.x - (PL_MAX_CONST.x - PL_MIN_CONST.x) * 0.75;
+                                       }
+                                       tracebox(top_max, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, tracetest_ent);
+                               }
+                       }
+               }
+       }
+       if(trace_startsolid || trace_endpos.z < this.absmax.z)
+       {
+               delete(tracetest_ent);
+               return;
+       }
+
+       this.bot_pickup = true; // allow bots to make use of this ladder
+       float cost = waypoint_getlinearcost(trace_endpos.z - this.absmin.z);
+       top_min = trace_endpos;
+       waypoint_spawnforteleporter_boxes(this, WAYPOINTFLAG_LADDER, this.absmin, this.absmax, top_min, top_min, cost);
 }
 
 spawnfunc(func_ladder)
index fc3cec863a26920d0547df1143e75af283631f88..3f5203806964b64988426406eef59eda544ec899 100644 (file)
@@ -55,11 +55,6 @@ spawnfunc(misc_teleporter_dest)
        spawnfunc_info_teleport_destination(this);
 }
 
-spawnfunc(target_teleporter)
-{
-       spawnfunc_info_teleport_destination(this);
-}
-
 #elif defined(CSQC)
 
 void teleport_dest_remove(entity this)
index 8e9936b2ea2980586e44844ccb66b27f92801670..5aedf30214ea16f1939743fc021391c9d5df4d46 100644 (file)
@@ -165,6 +165,7 @@ void TeleportPlayer(entity teleporter, entity player, vector to, vector to_angle
                }
 
                player.lastteleporttime = time;
+               player.lastteleport_origin = from;
        }
 #endif
 }
@@ -228,14 +229,21 @@ entity Simple_TeleportPlayer(entity teleporter, entity player)
 
 void teleport_findtarget(entity this)
 {
+       bool istrigger = (this.solid == SOLID_TRIGGER);
+
        int n = 0;
-       entity e;
-       for(e = NULL; (e = find(e, targetname, this.target)); )
+       for(entity e = NULL; (e = find(e, targetname, this.target)); )
        {
                ++n;
 #ifdef SVQC
                if(e.move_movetype == MOVETYPE_NONE)
-                       waypoint_spawnforteleporter(this, e.origin, 0);
+               {
+                       entity tracetest_ent = spawn();
+                       setsize(tracetest_ent, PL_MIN_CONST, PL_MAX_CONST);
+                       tracetest_ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
+                       waypoint_spawnforteleporter(this, e.origin, 0, tracetest_ent);
+                       delete(tracetest_ent);
+               }
                if(e.classname != "info_teleport_destination")
                        LOG_INFO("^3MAPPER ERROR: teleporter does target an invalid teleport destination entity. Angles will not work.");
 #endif
@@ -250,7 +258,7 @@ void teleport_findtarget(entity this)
        else if(n == 1)
        {
                // exactly one dest - bots love that
-               this.enemy = find(e, targetname, this.target);
+               this.enemy = find(NULL, targetname, this.target);
        }
        else
        {
@@ -259,9 +267,11 @@ void teleport_findtarget(entity this)
        }
 
        // now enable touch
-       settouch(this, Teleport_Touch);
+       if(istrigger)
+               settouch(this, Teleport_Touch);
 #ifdef SVQC
-       trigger_teleport_link(this);
+       if(istrigger)
+               trigger_teleport_link(this);
 #endif
 }
 
@@ -301,7 +311,5 @@ void WarpZone_PostTeleportPlayer_Callback(entity pl)
        #ifdef SVQC
                pl.oldvelocity = pl.velocity;
        #endif
-               // reset teleport time tracking too (or multijump can cause insane speeds)
-               pl.lastteleporttime = time;
        }
 }
index d7faaeff80bd7d76f6e1595e04d2cfef37dda429..6f5b2b595d8c09a110839cb4aeb3ca3a3ef11f42 100644 (file)
@@ -67,5 +67,4 @@ void WarpZone_PostTeleportPlayer_Callback(entity pl);
 
 #ifdef CSQC
 .entity realowner;
-.float lastteleporttime;
 #endif
index 8246aed7c329a67c8eaa10cf3f4822186ba62cfc..87c046b0dba724fc877ad318b9b95e1be69fb8e1 100644 (file)
@@ -1,45 +1,61 @@
 #include "counter.qh"
 #ifdef SVQC
+void counter_reset(entity this);
+
 void counter_use(entity this, entity actor, entity trigger)
 {
        this.count -= 1;
        if (this.count < 0)
                return;
 
+       bool doactivate = (this.spawnflags & 4);
+
        if (this.count == 0)
        {
-               if(IS_PLAYER(actor) && (this.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
+               if(IS_PLAYER(actor) && !(this.spawnflags & SPAWNFLAG_NOMESSAGE))
                        Send_Notification(NOTIF_ONE, actor, MSG_CENTER, CENTER_SEQUENCE_COMPLETED);
 
-               this.enemy = actor;
-               multi_trigger(this);
+               doactivate = true;
+
+               if(this.respawntime)
+               {
+                       setthink(this, counter_reset);
+                       this.nextthink = time + this.respawntime;
+               }
        }
        else
        {
-               if(IS_PLAYER(actor) && (this.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
-               if(this.count >= 4)
-                       Send_Notification(NOTIF_ONE, actor, MSG_CENTER, CENTER_SEQUENCE_COUNTER);
-               else
-                       Send_Notification(NOTIF_ONE, actor, MSG_CENTER, CENTER_SEQUENCE_COUNTER_FEWMORE, this.count);
+               if(IS_PLAYER(actor) && !(this.spawnflags & SPAWNFLAG_NOMESSAGE))
+               {
+                       if(this.count >= 4)
+                               Send_Notification(NOTIF_ONE, actor, MSG_CENTER, CENTER_SEQUENCE_COUNTER);
+                       else
+                               Send_Notification(NOTIF_ONE, actor, MSG_CENTER, CENTER_SEQUENCE_COUNTER_FEWMORE, this.count);
+               }
        }
+
+       if(doactivate)
+               SUB_UseTargets(this, actor, trigger);
 }
 
 void counter_reset(entity this)
 {
+       setthink(this, func_null);
+       this.nextthink = 0;
        this.count = this.cnt;
-       multi_reset(this);
 }
 
 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
 Acts as an intermediary for an action that takes multiple inputs.
 
-If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
+If nomessage is not set, it will print "1 more.. " etc when triggered and "sequence complete" when finished.
+
+If respawntime is set, it will re-enable itself after the time once the sequence has been completed
 
 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
 */
 spawnfunc(trigger_counter)
 {
-       this.wait = -1;
        if (!this.count)
                this.count = 2;
        this.cnt = this.count;
index dc1a781f81a0c81933fae9d47f63baf53506555a..2cd4cfd1338ac0809f3f9ff451a65ee3f5a59f8d 100644 (file)
@@ -1,13 +1,22 @@
 #include "delay.qh"
 #ifdef SVQC
+void delay_delayeduse(entity this)
+{
+       SUB_UseTargets(this, this.enemy, this.goalentity);
+       this.enemy = this.goalentity = NULL;
+}
+
 void delay_use(entity this, entity actor, entity trigger)
 {
-   setthink(this, SUB_UseTargets_self);
-   this.nextthink = time + this.wait;
+       this.enemy = actor;
+       this.goalentity = trigger;
+       setthink(this, delay_delayeduse);
+       this.nextthink = time + this.wait;
 }
 
 void delay_reset(entity this)
 {
+       this.enemy = this.goalentity = NULL;
        setthink(this, func_null);
        this.nextthink = 0;
 }
index e7b309062848d0fe673b8d797f4039512f292372..f2345e8f59397aba32c0802dd24e6b218a3c255a 100644 (file)
@@ -13,31 +13,55 @@ void trigger_heal_touch(entity this, entity toucher)
                if (!IS_DEAD(toucher))
                if (toucher.triggerhealtime < time)
                {
-                       EXACTTRIGGER_TOUCH(this, toucher);
-                       toucher.triggerhealtime = time + 1;
+                       bool is_trigger = !boolean(!this.nottargeted && this.targetname != "");
+                       if(is_trigger)
+                               EXACTTRIGGER_TOUCH(this, toucher);
+                       if(this.delay > 0)
+                               toucher.triggerhealtime = time + this.delay;
 
+                       bool playthesound = (this.spawnflags & 4);
                        if (toucher.health < this.max_health)
                        {
+                               playthesound = true;
                                toucher.health = min(toucher.health + this.health, this.max_health);
                                toucher.pauserothealth_finished = max(toucher.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
-                               _sound (toucher, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM);
                        }
+
+                       if(playthesound)
+                               _sound (toucher, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM);
                }
        }
 }
 
-spawnfunc(trigger_heal)
+void trigger_heal_use(entity this, entity actor, entity trigger)
 {
-       this.active = ACTIVE_ACTIVE;
+       trigger_heal_touch(this, actor);
+}
 
-       EXACTTRIGGER_INIT;
-       settouch(this, trigger_heal_touch);
-       if (!this.health)
+void trigger_heal_init(entity this)
+{
+       this.active = ACTIVE_ACTIVE;
+       if(!this.delay)
+               this.delay = 1;
+       if(!this.health)
                this.health = 10;
-       if (!this.max_health)
-               this.max_health = 200; //Max health topoff for field
+       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);
 }
+
+spawnfunc(trigger_heal)
+{
+       EXACTTRIGGER_INIT;
+       settouch(this, trigger_heal_touch);
+       trigger_heal_init(this);
+}
+
+spawnfunc(target_heal)
+{
+       this.use = trigger_heal_use;
+       trigger_heal_init(this);
+}
 #endif
index df965074587315eebbc4781704aba2f7386410f9..9e40cfd40178224fc9c48efe76cece7de982253a 100644 (file)
@@ -25,22 +25,20 @@ REGISTER_NET_LINKED(ENT_CLIENT_TARGET_PUSH)
          tgt - target entity (can be either a point or a model entity; if it is
                the latter, its midpoint is used)
          ht  - jump height, measured from the higher one of org and tgt's midpoint
+         pushed_entity - object that is to be pushed
 
        Returns: velocity for the jump
-       the global trigger_push_calculatevelocity_flighttime is set to the total
-       jump time
  */
-
-vector trigger_push_calculatevelocity(vector org, entity tgt, float ht)
+vector trigger_push_calculatevelocity(vector org, entity tgt, float ht, entity pushed_entity)
 {
        float grav, sdist, zdist, vs, vz, jumpheight;
        vector sdir, torg;
 
        torg = tgt.origin + (tgt.mins + tgt.maxs) * 0.5;
 
-       grav = PHYS_GRAVITY(other);
-       if(PHYS_ENTGRAVITY(other))
-               grav *= PHYS_ENTGRAVITY(other);
+       grav = PHYS_GRAVITY(NULL);
+       if(pushed_entity && PHYS_ENTGRAVITY(pushed_entity))
+               grav *= PHYS_ENTGRAVITY(pushed_entity);
 
        zdist = torg.z - org.z;
        sdist = vlen(torg - org - zdist * '0 0 1');
@@ -88,6 +86,7 @@ vector trigger_push_calculatevelocity(vector org, entity tgt, float ht)
        if(zdist == 0)
                solution_x = solution.y; // solution_x is 0 in this case, so don't use it, but rather use solution_y (which will be sqrt(0.5 * jumpheight / grav), actually)
 
+       float flighttime;
        if(zdist < 0)
        {
                // down-jump
@@ -96,14 +95,14 @@ vector trigger_push_calculatevelocity(vector org, entity tgt, float ht)
                        // almost straight line type
                        // jump apex is before the jump
                        // we must take the larger one
-                       trigger_push_calculatevelocity_flighttime = solution.y;
+                       flighttime = solution.y;
                }
                else
                {
                        // regular jump
                        // jump apex is during the jump
                        // we must take the larger one too
-                       trigger_push_calculatevelocity_flighttime = solution.y;
+                       flighttime = solution.y;
                }
        }
        else
@@ -114,17 +113,17 @@ vector trigger_push_calculatevelocity(vector org, entity tgt, float ht)
                        // almost straight line type
                        // jump apex is after the jump
                        // we must take the smaller one
-                       trigger_push_calculatevelocity_flighttime = solution.x;
+                       flighttime = solution.x;
                }
                else
                {
                        // regular jump
                        // jump apex is during the jump
                        // we must take the larger one
-                       trigger_push_calculatevelocity_flighttime = solution.y;
+                       flighttime = solution.y;
                }
        }
-       vs = sdist / trigger_push_calculatevelocity_flighttime;
+       vs = sdist / flighttime;
 
        // finally calculate the velocity
        return sdir * vs + '0 0 1' * vz;
@@ -137,7 +136,7 @@ bool jumppad_push(entity this, entity targ)
 
        if(this.enemy)
        {
-               targ.velocity = trigger_push_calculatevelocity(targ.origin, this.enemy, this.height);
+               targ.velocity = trigger_push_calculatevelocity(targ.origin, this.enemy, this.height, targ);
        }
        else if(this.target && this.target != "")
        {
@@ -150,7 +149,7 @@ bool jumppad_push(entity this, entity targ)
                        else
                                RandomSelection_AddEnt(e, 1, 1);
                }
-               targ.velocity = trigger_push_calculatevelocity(targ.origin, RandomSelection_chosen_ent, this.height);
+               targ.velocity = trigger_push_calculatevelocity(targ.origin, RandomSelection_chosen_ent, this.height, targ);
        }
        else
        {
@@ -208,13 +207,16 @@ bool jumppad_push(entity this, entity targ)
                                        centerprint(targ, this.message);
                        }
                        else
+                       {
                                targ.lastteleporttime = time;
+                               targ.lastteleport_origin = targ.origin;
+                       }
 
                        if (!IS_DEAD(targ))
                                animdecide_setaction(targ, ANIMACTION_JUMP, true);
                }
                else
-                       targ.jumppadcount = true;
+                       targ.jumppadcount = 1;
 
                // reset tracking of who pushed you into a hazard (for kill credit)
                targ.pushltime = 0;
@@ -272,38 +274,152 @@ void trigger_push_touch(entity this, entity toucher)
 #ifdef SVQC
 void trigger_push_link(entity this);
 void trigger_push_updatelink(entity this);
+bool trigger_push_testorigin(entity tracetest_ent, entity targ, entity jp, vector org)
+{
+       setorigin(tracetest_ent, org);
+       tracetoss(tracetest_ent, tracetest_ent);
+       if(trace_startsolid)
+               return false;
+
+       if (!jp.height)
+       {
+               // since tracetoss starting from jumppad's origin often fails when target
+               // is very close to real destination, start it directly from target's
+               // origin instead
+               vector ofs = '0 0 0';
+               if (vdist(vec2(tracetest_ent.velocity), <, autocvar_sv_maxspeed))
+                       ofs = stepheightvec;
+
+               tracetest_ent.velocity.z = 0;
+               setorigin(tracetest_ent, targ.origin + ofs);
+               tracetoss(tracetest_ent, tracetest_ent);
+               if (trace_startsolid && ofs.z)
+               {
+                       setorigin(tracetest_ent, targ.origin + ofs / 2);
+                       tracetoss(tracetest_ent, tracetest_ent);
+                       if (trace_startsolid && ofs.z)
+                       {
+                               setorigin(tracetest_ent, targ.origin);
+                               tracetoss(tracetest_ent, tracetest_ent);
+                               if (trace_startsolid)
+                                       return false;
+                       }
+               }
+       }
+       tracebox(trace_endpos, tracetest_ent.mins, tracetest_ent.maxs, trace_endpos - eZ * 1500, true, tracetest_ent);
+       return true;
+}
 #endif
-void trigger_push_findtarget(entity this)
+
+/// if (item != NULL) returns true if the item can be reached by using this jumppad, false otherwise
+/// if (item == NULL) tests jumppad's trajectory and eventually spawns waypoints for it (return value doesn't matter)
+bool trigger_push_test(entity this, entity item)
 {
        // first calculate a typical start point for the jump
        vector org = (this.absmin + this.absmax) * 0.5;
-       org.z = this.absmax.z - PL_MIN_CONST.z;
+       org.z = this.absmax.z - PL_MIN_CONST.z - 10;
 
        if (this.target)
        {
                int n = 0;
+#ifdef SVQC
+               vector vel = '0 0 0';
+#endif
                for(entity t = NULL; (t = find(t, targetname, this.target)); )
                {
                        ++n;
 #ifdef SVQC
+                       if(t.move_movetype != MOVETYPE_NONE)
+                               continue;
+
                        entity e = spawn();
-                       setorigin(e, org);
                        setsize(e, PL_MIN_CONST, PL_MAX_CONST);
-                       e.velocity = trigger_push_calculatevelocity(org, t, this.height);
-                       tracetoss(e, e);
-                       if(e.move_movetype == MOVETYPE_NONE)
-                               waypoint_spawnforteleporter(this, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity));
+                       e.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
+                       e.velocity = trigger_push_calculatevelocity(org, t, this.height, e);
+
+                       if(item)
+                       {
+                               setorigin(e, org);
+                               tracetoss(e, e);
+                               bool r = (trace_ent == item);
+                               delete(e);
+                               return r;
+                       }
+
+                       vel = e.velocity;
+                       vector best_target = '0 0 0';
+                       vector best_org = '0 0 0';
+                       vector best_vel = '0 0 0';
+                       bool valid_best_target = false;
+                       if (trigger_push_testorigin(e, t, this, org))
+                       {
+                               best_target = trace_endpos;
+                               best_org = org;
+                               best_vel = e.velocity;
+                               valid_best_target = true;
+                       }
+
+                       vector new_org;
+                       vector dist = t.origin - org;
+                       if (dist.x || dist.y) // if not perfectly vertical
+                       {
+                               // test trajectory with different starting points, sometimes the trajectory
+                               // starting from the jumppad origin can't reach the real destination
+                               // and destination waypoint ends up near the jumppad itself
+                               vector flatdir = normalize(dist - eZ * dist.z);
+                               vector ofs = flatdir * 0.5 * min(fabs(this.absmax.x - this.absmin.x), fabs(this.absmax.y - this.absmin.y));
+                               new_org = org + ofs;
+                               e.velocity = trigger_push_calculatevelocity(new_org, t, this.height, e);
+                               vel = e.velocity;
+                               if (vdist(vec2(e.velocity), <, autocvar_sv_maxspeed))
+                                       e.velocity = autocvar_sv_maxspeed * flatdir;
+                               if (trigger_push_testorigin(e, t, this, new_org) && (!valid_best_target || trace_endpos.z > best_target.z + 50))
+                               {
+                                       best_target = trace_endpos;
+                                       best_org = new_org;
+                                       best_vel = vel;
+                                       valid_best_target = true;
+                               }
+                               new_org = org - ofs;
+                               e.velocity = trigger_push_calculatevelocity(new_org, t, this.height, e);
+                               vel = e.velocity;
+                               if (vdist(vec2(e.velocity), <, autocvar_sv_maxspeed))
+                                       e.velocity = autocvar_sv_maxspeed * flatdir;
+                               if (trigger_push_testorigin(e, t, this, new_org) && (!valid_best_target || trace_endpos.z > best_target.z + 50))
+                               {
+                                       best_target = trace_endpos;
+                                       best_org = new_org;
+                                       best_vel = vel;
+                                       valid_best_target = true;
+                               }
+                       }
+
+                       if (valid_best_target)
+                       {
+                               if (!(boxesoverlap(this.absmin, this.absmax + eZ * 50, best_target + PL_MIN_CONST, best_target + PL_MAX_CONST)))
+                               {
+                                       float velxy = vlen(vec2(best_vel));
+                                       float cost = vlen(vec2(t.origin - best_org)) / velxy;
+                                       if(velxy < autocvar_sv_maxspeed)
+                                               velxy = autocvar_sv_maxspeed;
+                                       cost += vlen(vec2(best_target - t.origin)) / velxy;
+                                       waypoint_spawnforteleporter(this, best_target, cost, e);
+                               }
+                       }
                        delete(e);
 #endif
                }
 
+               if(item)
+                       return false;
+
                if(!n)
                {
                        // no dest!
 #ifdef SVQC
                        objerror (this, "Jumppad with nonexistant target");
 #endif
-                       return;
+                       return false;
                }
                else if(n == 1)
                {
@@ -320,16 +436,30 @@ void trigger_push_findtarget(entity this)
        else
        {
                entity e = spawn();
-               setorigin(e, org);
                setsize(e, PL_MIN_CONST, PL_MAX_CONST);
+               e.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
+               setorigin(e, org);
                e.velocity = this.movedir;
                tracetoss(e, e);
-               waypoint_spawnforteleporter(this, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity));
+               if(item)
+               {
+                       bool r = (trace_ent == item);
+                       delete(e);
+                       return r;
+               }
+               if (!(boxesoverlap(this.absmin, this.absmax + eZ * 50, trace_endpos + PL_MIN_CONST, trace_endpos + PL_MAX_CONST)))
+                       waypoint_spawnforteleporter(this, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity), e);
                delete(e);
        }
 
        defer(this, 0.1, trigger_push_updatelink);
 #endif
+       return true;
+}
+
+void trigger_push_findtarget(entity this)
+{
+       trigger_push_test(this, NULL);
 }
 
 #ifdef SVQC
@@ -394,6 +524,8 @@ spawnfunc(trigger_push)
 
        trigger_push_link(this); // link it now
 
+       IL_PUSH(g_jumppads, this);
+
        // this must be called to spawn the teleport waypoints for bots
        InitializeEntity(this, trigger_push_findtarget, INITPRIO_FINDTARGET);
 }
index 76d244da55b2d0b8e814b1d3574b53f293c01ff6..50ed0a343c8c86b7cc53e12274ddac7261bead75 100644 (file)
@@ -1,5 +1,8 @@
 #pragma once
 
+IntrusiveList g_jumppads;
+STATIC_INIT(g_jumppads) { g_jumppads = IL_NEW(); }
+
 const float PUSH_ONCE          = 1;
 const float PUSH_SILENT                = 2;
 
@@ -11,11 +14,10 @@ const int NUM_JUMPPADSUSED = 3;
 .float jumppadcount;
 .entity jumppadsused[NUM_JUMPPADSUSED];
 
-float trigger_push_calculatevelocity_flighttime;
-
 #ifdef SVQC
 void SUB_UseTargets(entity this, entity actor, entity trigger);
 void trigger_push_use(entity this, entity actor, entity trigger);
+bool trigger_push_testorigin(entity tracetest_ent, entity targ, entity jp, vector org);
 #endif
 
 /*
@@ -26,17 +28,16 @@ void trigger_push_use(entity this, entity actor, entity trigger);
          tgt - target entity (can be either a point or a model entity; if it is
                the latter, its midpoint is used)
          ht  - jump height, measured from the higher one of org and tgt's midpoint
+         pushed_entity - object that is to be pushed
 
        Returns: velocity for the jump
-       the global trigger_push_calculatevelocity_flighttime is set to the total
-       jump time
  */
-
-vector trigger_push_calculatevelocity(vector org, entity tgt, float ht);
+vector trigger_push_calculatevelocity(vector org, entity tgt, float ht, entity pushed_entity);
 
 void trigger_push_touch(entity this, entity toucher);
 
 .vector dest;
+bool trigger_push_test(entity this, entity item);
 void trigger_push_findtarget(entity this);
 
 /*
index 5e8c641be542735efe020f155f2d7ff556f7dcbc..2f32e50fa50b92d8396a72293df0022c07413420 100644 (file)
@@ -104,6 +104,9 @@ void multi_eventdamage(entity this, entity inflictor, entity attacker, float dam
        if(this.spawnflags & DOOR_NOSPLASH)
                if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
                        return;
+       if(this.team)
+               if(((this.spawnflags & 4) == 0) == (this.team != attacker.team))
+                       return;
        this.health = this.health - damage;
        if (this.health <= 0)
        {
@@ -169,6 +172,7 @@ spawnfunc(trigger_multiple)
        {
                if (this.spawnflags & SPAWNFLAG_NOTOUCH)
                        objerror (this, "health and notouch don't make sense\n");
+               this.canteamdamage = true;
                this.max_health = this.health;
                this.event_damage = multi_eventdamage;
                this.takedamage = DAMAGE_YES;
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 5f545f01418488f4375c229715a06040e3fb2c87..0330ce8d8cc46b1e9065ff29065ff51f233dadfd 100644 (file)
@@ -12,52 +12,83 @@ void trigger_teleport_use(entity this, entity actor, entity trigger)
 }
 #endif
 
-void Teleport_Touch(entity this, entity toucher)
+bool Teleport_Active(entity this, entity player)
 {
        if (this.active != ACTIVE_ACTIVE)
-               return;
+               return false;
 
 #ifdef SVQC
-       if (!toucher.teleportable)
-               return;
+       if (!player.teleportable)
+               return false;
 
-       if(toucher.vehicle)
-       if(!toucher.vehicle.teleportable)
-               return;
+       if(player.vehicle)
+       if(!player.vehicle.teleportable)
+               return false;
 
-       if(IS_TURRET(toucher))
-               return;
+       if(IS_TURRET(player))
+               return false;
 #elif defined(CSQC)
-       if(!IS_PLAYER(toucher))
-               return;
+       if(!IS_PLAYER(player))
+               return false;
 #endif
 
-       if(IS_DEAD(toucher))
-               return;
+       if(IS_DEAD(player))
+               return false;
 
        if(this.team)
-               if(((this.spawnflags & 4) == 0) == (DIFF_TEAM(this, toucher)))
-                       return;
+               if(((this.spawnflags & 4) == 0) == (DIFF_TEAM(this, player)))
+                       return false;
 
-       EXACTTRIGGER_TOUCH(this, toucher);
+       return true;
+}
+
+void Teleport_Touch(entity this, entity toucher)
+{
+       entity player = toucher;
+
+       if(!Teleport_Active(this, player))
+               return;
+
+       EXACTTRIGGER_TOUCH(this, player);
 
 #ifdef SVQC
-       if(IS_PLAYER(toucher))
-               RemoveGrapplingHooks(toucher);
+       if(IS_PLAYER(player))
+               RemoveGrapplingHooks(player);
 #endif
 
        entity e;
-       e = Simple_TeleportPlayer(this, toucher);
+       e = Simple_TeleportPlayer(this, player);
 
 #ifdef SVQC
        string s = this.target; this.target = string_null;
-       SUB_UseTargets(this, toucher, toucher); // TODO: should we be using toucher for trigger too?
+       SUB_UseTargets(this, player, player); // TODO: should we be using toucher for trigger too?
        if (!this.target) this.target = s;
 
-       SUB_UseTargets(e, toucher, toucher);
+       SUB_UseTargets(e, player, player);
 #endif
 }
 
+#ifdef SVQC
+void target_teleport_use(entity this, entity actor, entity trigger)
+{
+       entity player = actor;
+
+       if(!Teleport_Active(this, player))
+               return;
+
+       if(IS_PLAYER(player))
+               RemoveGrapplingHooks(player);
+
+       entity e = Simple_TeleportPlayer(this, player);
+
+       string s = this.target; this.target = string_null;
+       SUB_UseTargets(this, player, player); // TODO: should we be using toucher for trigger too?
+       if (!this.target) this.target = s;
+
+       SUB_UseTargets(e, player, player);
+}
+#endif
+
 #ifdef SVQC
 float trigger_teleport_send(entity this, entity to, float sf)
 {
@@ -101,6 +132,25 @@ spawnfunc(trigger_teleport)
 
        IL_PUSH(g_teleporters, this);
 }
+
+spawnfunc(target_teleporter)
+{
+       if(this.target == "")
+       {
+               // actually a destination!
+               spawnfunc_info_teleport_destination(this);
+               return;
+       }
+
+       this.active = ACTIVE_ACTIVE;
+
+       this.use = target_teleport_use;
+
+       if(this.noise != "")
+               FOREACH_WORD(this.noise, true, precache_sound(it));
+
+       InitializeEntity(this, teleport_findtarget, INITPRIO_FINDTARGET);
+}
 #elif defined(CSQC)
 NET_HANDLE(ENT_CLIENT_TRIGGER_TELEPORT, bool isnew)
 {
index ffc04a0782bf8d0a5b59c00876adf0b8319dc078..41394ac4318909a62f685c869fbf28970919c523 100644 (file)
@@ -13,13 +13,37 @@ REGISTER_NET_LINKED(ENT_CLIENT_VIEWLOC_TRIGGER)
 
 void viewloc_think(entity this)
 {
-       entity e;
-
        // we abuse this method, rather than using normal .touch, because touch isn't reliable with multiple clients inside the same trigger, and can't "untouch" entities
 
        // set myself as current viewloc where possible
+#if 1
+       FOREACH_CLIENT(it.viewloc == this,
+       {
+               it.viewloc = NULL;
+       });
+#else
+       entity e;
        for(e = NULL; (e = findentity(e, viewloc, this)); )
                e.viewloc = NULL;
+#endif
+
+#if 1
+       FOREACH_CLIENT(!it.viewloc && IS_PLAYER(it),
+       {
+               vector emin = it.absmin;
+               vector emax = it.absmax;
+               if(this.solid == SOLID_BSP)
+               {
+                       emin -= '1 1 1';
+                       emax += '1 1 1';
+               }
+               if(boxesoverlap(emin, emax, this.absmin, this.absmax)) // quick
+               {
+                       if(WarpZoneLib_BoxTouchesBrush(emin, emax, this, it)) // accurate
+                               it.viewloc = this;
+               }
+       });
+#else
 
                for(e = findradius((this.absmin + this.absmax) * 0.5, vlen(this.absmax - this.absmin) * 0.5 + 1); e; e = e.chain)
                        if(!e.viewloc)
@@ -37,6 +61,7 @@ void viewloc_think(entity this)
                                                if(WarpZoneLib_BoxTouchesBrush(emin, emax, this, e)) // accurate
                                                        e.viewloc = this;
                                }
+#endif
 
        this.nextthink = time;
 }
@@ -46,6 +71,8 @@ bool trigger_viewloc_send(entity this, entity to, int sf)
        // CSQC doesn't need to know our origin (yet), as we're only available for referencing
        WriteHeader(MSG_ENTITY, ENT_CLIENT_VIEWLOC_TRIGGER);
 
+       WriteByte(MSG_ENTITY, this.spawnflags);
+
        WriteEntity(MSG_ENTITY, this.enemy);
        WriteEntity(MSG_ENTITY, this.goalentity);
 
@@ -143,6 +170,8 @@ void trigger_viewloc_updatelink(entity this)
 
 NET_HANDLE(ENT_CLIENT_VIEWLOC_TRIGGER, bool isnew)
 {
+       this.spawnflags = ReadByte();
+
        float point1 = ReadShort();
        float point2 = ReadShort();
 
index 167fc218f7707c5f00b2f70d612d654f21ee1999..69c6c821ed7f23cde5c765a3f8bb989f1c2de2c8 100644 (file)
@@ -2,6 +2,10 @@
 
 .entity viewloc;
 
+const int VIEWLOC_NOSIDESCROLL = BIT(0); // NOTE: currently unimplemented
+const int VIEWLOC_FREEAIM = BIT(1);
+const int VIEWLOC_FREEMOVE = BIT(2);
+
 #ifdef CSQC
 .entity goalentity;
 .entity enemy;
index 2a76d80c41a7a7f41675d9a06826b2e236683d28..f6cb013d9e2df3d4ad8b4170ac8299148576a6c4 100644 (file)
@@ -293,8 +293,3 @@ void SUB_UseTargets_Ex(entity this, entity actor, entity trigger, bool preventRe
 
 void SUB_UseTargets(entity this, entity actor, entity trigger) { SUB_UseTargets_Ex(this, actor, trigger, false); }
 void SUB_UseTargets_PreventReuse(entity this, entity actor, entity trigger) { SUB_UseTargets_Ex(this, actor, trigger, true); }
-
-void SUB_UseTargets_self(entity this)
-{
-       SUB_UseTargets(this, NULL, NULL);
-}
index d6d7d9facfb2edc3f7f32c39138119723a47f5b0..e4e5ba991bec4fafa3dfc4bfbe278d69f4e2214c 100644 (file)
@@ -19,35 +19,39 @@ void viewloc_PlayerPhysics(entity this)
 
                vector old_movement = PHYS_CS(this).movement;
                PHYS_CS(this).movement_x = old_movement_y;
-               PHYS_CS(this).movement_y = 0;
-
-               if(PHYS_CS(this).movement_x < 0)
-                       PHYS_CS(this).movement_x = -PHYS_CS(this).movement_x;
+               if((this.viewloc.spawnflags & VIEWLOC_FREEMOVE) && !(time < this.ladder_time))
+                       PHYS_CS(this).movement_y = old_movement_x;
+               else
+                       PHYS_CS(this).movement_y = 0;
 
                vector level_start, level_end;
                level_start = this.viewloc.enemy.origin;
                level_end = this.viewloc.goalentity.origin;
-               vector forward, backward;
-               forward = vectoangles(normalize(level_end - level_start));
-               backward = vectoangles(normalize(level_start - level_end));
+               vector forward = vectoangles(normalize(level_end - level_start));
+               vector backward = vectoangles(normalize(level_start - level_end));
 
-               if(PHYS_CS(this).movement_x < 0) // left
-                       this.angles_y = backward_y;
-               if(PHYS_CS(this).movement_x > 0) // right
-                       this.angles_y = forward_y;
+               if((this.viewloc.spawnflags & VIEWLOC_FREEMOVE) && this.angles_y < 0 && !(time < this.ladder_time))
+                       PHYS_CS(this).movement_y = -PHYS_CS(this).movement_y;
 
-               if(old_movement_x > 0)
-#ifdef CSQC
-                       input_angles_x =
-#endif
-                       this.v_angle_x = this.angles_x = -50;
-               else if(old_movement_x < 0)
-#ifdef CSQC
-                       input_angles_x =
-#endif
-                       this.v_angle_x = this.angles_x = 50;
+               if(this.viewloc.spawnflags & VIEWLOC_FREEAIM)
+               {
+                       if(this.angles_y > 0)
+                               PHYS_CS(this).movement_x = -PHYS_CS(this).movement_x;
+               }
+               else
+               {
+                       if(PHYS_CS(this).movement_x < 0)
+                               PHYS_CS(this).movement_x = -PHYS_CS(this).movement_x;
+
+                       if(PHYS_CS(this).movement_x < 0) // left
+                               this.angles_y = backward.y;
+                       if(PHYS_CS(this).movement_x > 0) // right
+                               this.angles_y = forward.y;
+               }
 
                //if(!PHYS_INPUT_BUTTON_CROUCH(this) && !IS_DUCKED(this))
+               if(!(this.viewloc.spawnflags & VIEWLOC_FREEMOVE))
+               {
 #ifdef SVQC
                        //PHYS_INPUT_BUTTON_CROUCH(this) = (old_movement_x < 0);
                        if (old_movement.x < 0)
@@ -60,6 +64,7 @@ void viewloc_PlayerPhysics(entity this)
                        }
                        //else { input_buttons &= ~16; this.flags &= ~FL_DUCKED; }
 #endif
+               }
        }
 }
 
@@ -77,11 +82,20 @@ void viewloc_SetTags(entity this)
                this.viewloc = findfloat(NULL, entnum, this.tag_networkviewloc);
 }
 
+vector CursorToWorldCoord(vector mpos)
+{
+       vector wnear = cs_unproject(vec2(mpos)); // determine the world coordinate for the mouse cursor upon the near clip plane
+       vector wfar = cs_unproject(vec3(mpos.x, mpos.y, max_shot_distance)); // determine the world coordinate for the mouse cursor upon the far clip plane, with an outrageously large value as a workaround for dp.
+       traceline(wnear, wfar, MOVE_NOMONSTERS, NULL);
+       return trace_endpos;
+}
+
 vector old_camera_angle = '0 0 0';
 bool autocvar_cam_snap_close;
 bool autocvar_cam_track;
 bool autocvar_cam_snap_hard;
 bool autocvar_cam_snap_unlock;
+bool autocvar_cam_useangle = true;
 void viewloc_SetViewLocation()
 {
        entity view = CSQCModel_server2csqc(player_localentnum - 1);
@@ -90,23 +104,19 @@ void viewloc_SetViewLocation()
        if(view.viewloc && !wasfreed(view.viewloc) && view.viewloc.enemy && view.viewloc.goalentity)
        {
                bool have_sidescroll = (view.viewloc.enemy != view.viewloc.goalentity);
-               vector position_a, position_b, camera_position, camera_angle = '0 0 0', forward, backward;
-               //vector scratch;
+               vector position_a = view.viewloc.enemy.origin;
+               vector position_b = view.viewloc.goalentity.origin;
+               vector camera_angle = '0 0 0';
+               vector camera_position;
 
-               position_a = view.viewloc.enemy.origin;
-               position_b = view.viewloc.goalentity.origin;
-
-#if 0
                /*TODO: have the camera only move when a player moves too much from the center of the camera
-                * basically the player can move around in a "box" in the center of th screen with out changing the camera position or angles
-               */
-               if (cvar("cam_box")) {
-                       camera_position = vec_bounds_in(view.origin, position_a, position_b);
-               }
-               else
-#endif
-                       camera_position = vec_bounds_in(view.origin, position_a, position_b);
+                * basically the player would move around in a small "box" in the center of the screen with out changing the camera position or angles */
+               camera_position = vec_bounds_in(view.origin, position_a, position_b);
 
+               // use camera's angle when possible
+               if (autocvar_cam_useangle) {
+                       camera_angle = view.viewloc.enemy.movedir;
+               }
 
                // a tracking camera follows the player when it leaves the world box
                if (autocvar_cam_track || !have_sidescroll) {
@@ -114,32 +124,32 @@ void viewloc_SetViewLocation()
                }
 
                // hard snap changes the angle as soon as it crosses over the nearest 90 degree mark
-               if (autocvar_cam_snap_hard){
+               if (autocvar_cam_snap_hard) {
                        camera_angle = angle_snap_vec(aim_vec(camera_position, view.origin), 90);
                }
 
                // tries to avoid snapping unless it *really* needs to
-               if (autocvar_cam_snap_close){
-
+               if (autocvar_cam_snap_close) {
                        // like hard snap, but don't snap angles yet.
                        camera_angle = aim_vec(camera_position, view.origin);
 
                        /* if the difference between the old and new angle is 60 degrees or more, switch angles.
                         * NOTE: bug/feature: this will use non-snaped angles for one frame.
-                        * doing this resualts in less code, faster code, and a smoother transisition between angles.
+                        * doing this results in less code, faster code, and a smoother transisition between angles.
                         */
-                       float camera_angle_diff = max(camera_angle_y, old_camera_angle_y) - min(camera_angle_y, old_camera_angle_y);
+                       float camera_angle_diff = max(camera_angle.y, old_camera_angle.y) - min(camera_angle.y, old_camera_angle.y);
 
-                       if ( camera_angle_diff >= 60)
-                               old_camera_angle_y = angle_snap_f(camera_angle_y, 90);
-                       else
-                               camera_angle_y = old_camera_angle_y;
+                       if (60 <= camera_angle_diff) { // use new angles
+                               old_camera_angle.y = angle_snap_f(camera_angle.y, 90);
+                       } else { // use old angles
+                               camera_angle.y = old_camera_angle.y;
+                       }
                }
 
                //unlocking this allows the camera to look up and down. this also allows a top-down view.
                if (!autocvar_cam_snap_unlock) {
-                       camera_angle_x = 0;
-                       camera_angle_z = 0;
+                       camera_angle.x = 0;
+                       camera_angle.z = 0;
                }
 
 #if 0
@@ -153,17 +163,38 @@ void viewloc_SetViewLocation()
                setproperty(VF_ORIGIN, camera_position);
                setproperty(VF_ANGLES, camera_angle);
 
-               if(have_sidescroll)
-               {
-                       forward = vectoangles(normalize(vec_to_min(position_b, position_a) - vec_to_max(position_b, position_a)));
-                       backward = vectoangles(normalize(vec_to_max(position_b, position_a) - vec_to_min(position_b, position_a)));
-
-                       if(input_movevalues_y < 0) // left
-                               view.angles_y = backward.y;
-                       if(input_movevalues_y > 0) // favour right
-                               view.angles_y = forward.y;
-
-                       setproperty(VF_CL_VIEWANGLES, view.angles);
+               if(spectatee_status)
+                       return; // if spectating, don't replace angles or inputs!
+
+               if (have_sidescroll) {
+                       vector view_angle = view.angles;
+                       if (!(view.viewloc.spawnflags & VIEWLOC_FREEAIM)) {
+                               vector avatar_facing_dir;
+                               // get the player's forward-facing direction, based on positions a and b
+                               if (0 == input_movevalues.y) {
+                                       avatar_facing_dir = view_angle; // default to the previous values
+                               } else if (0 > input_movevalues.y) { // left is forward
+                                       avatar_facing_dir = vectoangles(normalize(vec_to_max(position_b, position_a) - vec_to_min(position_b, position_a)));
+                               } else { // right is forward
+                                       avatar_facing_dir = vectoangles(normalize(vec_to_min(position_b, position_a) - vec_to_max(position_b, position_a)));
+                               }
+                               view_angle.y = avatar_facing_dir.y; // snap avatar to look on along the correct axis
+
+                               // if (0 == input_movevalues.x) look straight ahead
+                               if (!(view.viewloc.spawnflags & VIEWLOC_FREEMOVE)) {
+                                       if (0 > input_movevalues.x) { // look up
+                                               view_angle.x = 50;
+                                       } else if (0 < input_movevalues.x) { // look down
+                                               view_angle.x = -50;
+                                       }
+                               }
+                       } else {
+                               vector mpos = CursorToWorldCoord(viewloc_mousepos);
+                               mpos.x = view.origin.x; // replace the cursor's x position with the player's
+                               view_angle = aim_vec(view.origin + view.view_ofs, mpos); // get new angles
+                       }
+                       view.angles_y = view_angle.y;
+                       setproperty(VF_CL_VIEWANGLES, view_angle);
                }
        }
 }
index 71d7c3db39f464b975e9258764fa08676984eae5..b5f542928f3fc4d869999c8b379a414143eb2a8e 100644 (file)
@@ -37,27 +37,13 @@ const int WS_INUSE  = 3;
 /** idle frame */
 const int WS_READY  = 4;
 
-#ifdef SVQC
-.int ammo_shells;
-.int ammo_nails;
-.int ammo_rockets;
-.int ammo_cells;
-.int ammo_plasma = _STAT(PLASMA);
-.int ammo_fuel = _STAT(FUEL);
-.int ammo_none;
-#else
-.int ammo_shells;
-.int ammo_nails;
-.int ammo_rockets;
-.int ammo_cells;
-.int ammo_plasma;
-.int ammo_fuel;
-.int ammo_none;
-#endif
-
 /** fields which are explicitly/manually set are marked with "M", fields set automatically are marked with "A" */
 CLASS(Weapon, Object)
        ATTRIB(Weapon, m_id, int, 0);
+       /** the canonical spawnfunc name */
+    ATTRIB(Weapon, m_canonical_spawnfunc, string);
+    /** control what happens when this weapon is spawned */
+    METHOD(Weapon, m_spawnfunc_hookreplace, Weapon(Weapon this, entity e)) { return this; }
     /** A: WEPSET_id : WEPSET_... */
     ATTRIB(Weapon, weapons, WepSet, '0 0 0');
     /** M: ammotype  : main ammo type */
@@ -137,6 +123,18 @@ CLASS(Weapon, Object)
        }
 ENDCLASS(Weapon)
 
+#ifdef SVQC
+
+void weapon_defaultspawnfunc(entity this, Weapon e);
+#define SPAWNFUNC_WEAPON(name, weapon) \
+    spawnfunc(name) { weapon_defaultspawnfunc(this, weapon); }
+
+#else
+
+#define SPAWNFUNC_WEAPON(name, weapon)
+
+#endif
+
 #include <common/items/_mod.qh>
 CLASS(WeaponPickup, Pickup)
     ATTRIB(WeaponPickup, m_weapon, Weapon);
index 5b982219e7cc6708dd0ba994b0e5d2501be34797..23e3dbcb2bc8d235e2921f45ab8803570bd0ea35 100644 (file)
@@ -1,7 +1,6 @@
 #include "arc.qh"
 
 #ifdef SVQC
-spawnfunc(weapon_arc) { weapon_defaultspawnfunc(this, WEP_ARC); }
 
 bool W_Arc_Beam_Send(entity this, entity to, int sf)
 {
index abd9933116b2d86af3506acd6013b3648f6796f9..4ec2d4edc98a1a59053f8a7ac8346c9f5a7bd52e 100644 (file)
@@ -1,6 +1,7 @@
 #pragma once
 
 CLASS(Arc, Weapon)
+/* spawnfunc */ ATTRIB(Arc, m_canonical_spawnfunc, string, "weapon_arc");
 /* ammotype  */ ATTRIB(Arc, ammo_type, int, RESOURCE_CELLS);
 /* impulse   */ ATTRIB(Arc, impulse, int, 3);
 /* flags     */ ATTRIB(Arc, spawnflags, int, WEP_TYPE_HITSCAN);
@@ -74,6 +75,7 @@ CLASS(Arc, Weapon)
 ENDCLASS(Arc)
 REGISTER_WEAPON(ARC, arc, NEW(Arc));
 
+SPAWNFUNC_WEAPON(weapon_arc, WEP_ARC)
 
 #ifdef GAMEQC
 const float ARC_MAX_SEGMENTS = 20;
index ac1540cd51fc860038f1f04072b55c75a40e5656..2189c1db209d1d1d68b57949bdd8a5103183bb17 100644 (file)
@@ -1,8 +1,6 @@
 #include "blaster.qh"
 
 #ifdef SVQC
-spawnfunc(weapon_blaster) { weapon_defaultspawnfunc(this, WEP_BLASTER); }
-spawnfunc(weapon_laser) { spawnfunc_weapon_blaster(this); }
 
 void W_Blaster_Touch(entity this, entity toucher)
 {
index 972dcd003f23118654a332337c16904e93ca190d..0a0e7c17d2f57c21cdbee6d3735f0576c566377e 100644 (file)
@@ -1,6 +1,7 @@
 #pragma once
 
 CLASS(Blaster, Weapon)
+/* spawnfunc */ ATTRIB(Blaster, m_canonical_spawnfunc, string, "weapon_blaster");
 /* 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);
@@ -44,10 +45,14 @@ CLASS(Blaster, Weapon)
 ENDCLASS(Blaster)
 REGISTER_WEAPON(BLASTER, blaster, NEW(Blaster));
 
+SPAWNFUNC_WEAPON(weapon_blaster, WEP_BLASTER)
+SPAWNFUNC_WEAPON(weapon_laser, WEP_BLASTER)
+
 #ifdef SVQC
 .float blaster_damage;
 .float blaster_edgedamage;
 .float blaster_radius;
 .float blaster_force;
 .float blaster_lifetime;
+
 #endif
index 2e2cb519af644ad83fa9133dc6f711227542b72f..246452fe62ff9cce932ec72a698033ef5d8839de 100644 (file)
@@ -1,7 +1,6 @@
 #include "crylink.qh"
 
 #ifdef SVQC
-spawnfunc(weapon_crylink) { weapon_defaultspawnfunc(this, WEP_CRYLINK); }
 
 void W_Crylink_CheckLinks(entity e)
 {
index e686cfa94cf87cbaf962ba52ecf77fb52a69e520..e48bf5fb3481e8db22c4698662997a648adc3db0 100644 (file)
@@ -1,9 +1,10 @@
 #pragma once
 
 CLASS(Crylink, Weapon)
+/* spawnfunc */ ATTRIB(Crylink, m_canonical_spawnfunc, string, "weapon_crylink");
 /* 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);
+/* flags     */ ATTRIB(Crylink, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH | WEP_FLAG_CANCLIMB);
 /* rating    */ ATTRIB(Crylink, bot_pickupbasevalue, float, 6000);
 /* color     */ ATTRIB(Crylink, wpcolor, vector, '1 0.5 1');
 /* modelname */ ATTRIB(Crylink, mdl, string, "crylink");
@@ -59,6 +60,8 @@ CLASS(Crylink, Weapon)
 ENDCLASS(Crylink)
 REGISTER_WEAPON(CRYLINK, crylink, NEW(Crylink));
 
+SPAWNFUNC_WEAPON(weapon_crylink, WEP_CRYLINK)
+
 #ifdef SVQC
 .float gravity;
 .float crylink_waitrelease;
index f4ef7ad32c08b320d139348908b42e7c95cb54bb..46db8358cef76b423c43e5b218e391d6ff876a9a 100644 (file)
@@ -1,8 +1,6 @@
 #include "devastator.qh"
 
 #ifdef SVQC
-spawnfunc(weapon_devastator) { weapon_defaultspawnfunc(this, WEP_DEVASTATOR); }
-spawnfunc(weapon_rocketlauncher) { spawnfunc_weapon_devastator(this); }
 
 .entity lastrocket;
 
index 9c419751173120e17ba80911ea9aca37e9b30026..0e8d8b2fbc8b271102ac17363b1435a957db1047 100644 (file)
@@ -1,6 +1,7 @@
 #pragma once
 
 CLASS(Devastator, Weapon)
+/* spawnfunc */ ATTRIB(Devastator, m_canonical_spawnfunc, string, "weapon_devastator");
 /* 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);
@@ -62,6 +63,9 @@ CLASS(Devastator, Weapon)
 ENDCLASS(Devastator)
 REGISTER_WEAPON(DEVASTATOR, devastator, NEW(Devastator));
 
+SPAWNFUNC_WEAPON(weapon_devastator, WEP_DEVASTATOR)
+SPAWNFUNC_WEAPON(weapon_rocketlauncher, WEP_DEVASTATOR)
+
 #ifdef SVQC
 .float rl_release;
 .float rl_detonate_later;
index 8b3946e46e43d77b673b7023c89b3ab4c5afbf6c..05f306174dae307cd024d8518f77a20b39c30155 100644 (file)
@@ -1,7 +1,6 @@
 #include "electro.qh"
 
 #ifdef SVQC
-spawnfunc(weapon_electro) { weapon_defaultspawnfunc(this, WEP_ELECTRO); }
 
 void W_Electro_TriggerCombo(vector org, float rad, entity own)
 {
@@ -282,7 +281,7 @@ void W_Electro_Orb_Touch(entity this, entity toucher)
        PROJECTILE_TOUCH(this, toucher);
        if(toucher.takedamage == DAMAGE_AIM)
                { if(WEP_CVAR_SEC(electro, touchexplode)) { W_Electro_Explode(this, toucher); } }
-       else
+       else if(toucher.owner != this.owner && toucher.classname != this.classname) // don't stick to player's other projectiles!
        {
                //UpdateCSQCProjectile(this);
                spamsound(this, CH_SHOTS, SND_ELECTRO_BOUNCE, VOL_BASE, ATTEN_NORM);
index 07c967c49bee20b1bb4d31b2f6c87801e3dac863..4018e5926c78ac03364173ce19883119b59d1503 100644 (file)
@@ -1,6 +1,7 @@
 #pragma once
 
 CLASS(Electro, Weapon)
+/* spawnfunc */ ATTRIB(Electro, m_canonical_spawnfunc, string, "weapon_electro");
 /* 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);
@@ -66,6 +67,7 @@ CLASS(Electro, Weapon)
 ENDCLASS(Electro)
 REGISTER_WEAPON(ELECTRO, electro, NEW(Electro));
 
+SPAWNFUNC_WEAPON(weapon_electro, WEP_ELECTRO)
 
 #ifdef SVQC
 .float electro_count;
index 3f9cd4c4e18f6d731d2d7db54764b983ceb8ec0e..3f6830f33c271516446592883fed8ff4e08a6c06 100644 (file)
@@ -1,7 +1,6 @@
 #include "fireball.qh"
 
 #ifdef SVQC
-spawnfunc(weapon_fireball) { weapon_defaultspawnfunc(this, WEP_FIREBALL); }
 
 void W_Fireball_Explode(entity this, entity directhitentity)
 {
index e973d28845cee8ad52c995be9992a5ffd2da3667..4302c9e7973467d5843917f708cc805260db2cce 100644 (file)
@@ -1,6 +1,7 @@
 #pragma once
 
 CLASS(Fireball, Weapon)
+/* spawnfunc */ ATTRIB(Fireball, m_canonical_spawnfunc, string, "weapon_fireball");
 /* 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);
@@ -53,6 +54,8 @@ CLASS(Fireball, Weapon)
 ENDCLASS(Fireball)
 REGISTER_WEAPON(FIREBALL, fireball, NEW(Fireball));
 
+SPAWNFUNC_WEAPON(weapon_fireball, WEP_FIREBALL)
+
 #ifdef SVQC
 .float bot_primary_fireballmooth; // whatever a mooth is
 .vector fireball_impactvec;
index be95d5dde2a3650367840715bf865565d1d69256..ff2e74539ce83faf9acb719b3faf4c26cd376bc7 100644 (file)
@@ -1,7 +1,6 @@
 #include "hagar.qh"
 
 #ifdef SVQC
-spawnfunc(weapon_hagar) { weapon_defaultspawnfunc(this, WEP_HAGAR); }
 
 // NO bounce protection, as bounces are limited!
 
index 24c700cc84d9412bf5362bd6d827656cda909d0f..924326fb3a0a65e211ef2a0eba363444e5f1b771 100644 (file)
@@ -1,6 +1,7 @@
 #pragma once
 
 CLASS(Hagar, Weapon)
+/* spawnfunc */ ATTRIB(Hagar, m_canonical_spawnfunc, string, "weapon_hagar");
 /* 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);
@@ -56,3 +57,5 @@ CLASS(Hagar, Weapon)
 
 ENDCLASS(Hagar)
 REGISTER_WEAPON(HAGAR, hagar, NEW(Hagar));
+
+SPAWNFUNC_WEAPON(weapon_hagar, WEP_HAGAR)
index 2fb16d6e35f0dd9a1c60ffc96a4034c22ff86596..ae6c9a66372f4637ec28cfacb5890a82987f0e33 100644 (file)
@@ -1,7 +1,6 @@
 #include "hlac.qh"
 
 #ifdef SVQC
-spawnfunc(weapon_hlac) { weapon_defaultspawnfunc(this, WEP_HLAC); }
 
 void W_HLAC_Touch(entity this, entity toucher)
 {
index 4664e54d96776a7b02bc3ccdb7d34aa91ebc392c..d2bd427c2397bb90a1a69acb5c8be17382e39da0 100644 (file)
@@ -1,6 +1,7 @@
 #pragma once
 
 CLASS(HLAC, Weapon)
+/* spawnfunc */ ATTRIB(HLAC, m_canonical_spawnfunc, string, "weapon_hlac");
 /* 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);
@@ -48,3 +49,5 @@ CLASS(HLAC, Weapon)
 
 ENDCLASS(HLAC)
 REGISTER_WEAPON(HLAC, hlac, NEW(HLAC));
+
+SPAWNFUNC_WEAPON(weapon_hlac, WEP_HLAC)
index d92e0caa8538b4c9ec4ccc5c0bc79a3fef41e7f4..13b5661fecae03d87bbe6de17346c2b4a5e549f2 100644 (file)
@@ -2,8 +2,6 @@
 
 #ifdef SVQC
 
-spawnfunc(weapon_hook) { weapon_defaultspawnfunc(this, WEP_HOOK); }
-
 void W_Hook_ExplodeThink(entity this)
 {
        float dt, dmg_remaining_next, f;
@@ -320,8 +318,8 @@ void Draw_GrapplingHook(entity this)
                {
                        default:
                        case NET_ENT_CLIENT_HOOK:
-                               if(autocvar_chase_active > 0)
-                                       a = csqcplayer.origin;
+                               if(autocvar_chase_active)
+                                       a = csqcplayer.origin + csqcplayer.view_ofs;
                                else
                                        a = view_origin + view_forward * vs.x + view_right * -vs.y + view_up * vs.z;
                                b = this.origin;
index 4988323fda187803dab6218b45002d4bede7bf90..31424d421b14d68009a52e061c9e6dbf252d40e7 100644 (file)
@@ -1,6 +1,7 @@
 #pragma once
 
 CLASS(Hook, Weapon)
+/* spawnfunc */ ATTRIB(Hook, m_canonical_spawnfunc, string, "weapon_hook");
 /* 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);
@@ -49,6 +50,8 @@ CLASS(Hook, Weapon)
 ENDCLASS(Hook)
 REGISTER_WEAPON(HOOK, hook, NEW(Hook));
 
+SPAWNFUNC_WEAPON(weapon_hook, WEP_HOOK)
+
 CLASS(OffhandHook, OffhandWeapon)
 #ifdef SVQC
     METHOD(OffhandHook, offhand_think, void(OffhandHook this, entity actor, bool key_pressed))
index 8d96c4b1013ca875a0d581408a335877c83c160a..80567c2955383932015ee9bb60eb6c5efac9738f 100644 (file)
@@ -2,17 +2,14 @@
 
 #ifdef SVQC
 
-spawnfunc(weapon_machinegun)
+METHOD(MachineGun, m_spawnfunc_hookreplace, Weapon(MachineGun this, entity e))
 {
-       if(autocvar_sv_q3acompat_machineshotgunswap)
-       if(this.classname != "droppedweapon")
+       if (autocvar_sv_q3acompat_machineshotgunswap && !Item_IsLoot(e))
        {
-               weapon_defaultspawnfunc(this, WEP_SHOCKWAVE);
-               return;
+               return WEP_SHOCKWAVE;
        }
-       weapon_defaultspawnfunc(this, WEP_MACHINEGUN);
+       return this;
 }
-spawnfunc(weapon_uzi) { spawnfunc_weapon_machinegun(this); }
 
 void W_MachineGun_MuzzleFlash_Think(entity this)
 {
index 2f0974971ecc80112eb285e589801f88a074dc71..a7ede47a2105125d60a9a07778130c633720ce8e 100644 (file)
@@ -1,6 +1,7 @@
 #pragma once
 
 CLASS(MachineGun, Weapon)
+/* spawnfunc */ ATTRIB(MachineGun, m_canonical_spawnfunc, string, "weapon_machinegun");
 /* 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);
@@ -54,3 +55,6 @@ CLASS(MachineGun, Weapon)
 
 ENDCLASS(MachineGun)
 REGISTER_WEAPON(MACHINEGUN, machinegun, NEW(MachineGun));
+
+SPAWNFUNC_WEAPON(weapon_machinegun, WEP_MACHINEGUN)
+SPAWNFUNC_WEAPON(weapon_uzi, WEP_MACHINEGUN)
index 99b00a814f20ffbdb4d3d62507ba6ccff96f2e80..575b76d72e12f4fbf9000331b3ed56a32cafd338 100644 (file)
@@ -1,7 +1,6 @@
 #include "minelayer.qh"
 
 #ifdef SVQC
-spawnfunc(weapon_minelayer) { weapon_defaultspawnfunc(this, WEP_MINE_LAYER); }
 
 void W_MineLayer_Stick(entity this, entity to)
 {
index f113e6439e3bea5f42d569e36d86482ff9b9ffe2..f804aaf44e9c637520a33a2673b47bed06aba853 100644 (file)
@@ -1,6 +1,7 @@
 #pragma once
 
 CLASS(MineLayer, Weapon)
+/* spawnfunc */ ATTRIB(MineLayer, m_canonical_spawnfunc, string, "weapon_minelayer");
 /* 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);
@@ -53,6 +54,8 @@ CLASS(MineLayer, Weapon)
 ENDCLASS(MineLayer)
 REGISTER_WEAPON(MINE_LAYER, minelayer, NEW(MineLayer));
 
+SPAWNFUNC_WEAPON(weapon_minelayer, WEP_MINE_LAYER)
+
 #ifdef SVQC
 void W_MineLayer_Think(entity this);
 .float minelayer_detonate, mine_explodeanyway;
index 6a3d2e1250a63294e400a4a283e850fef651eb6e..6ada37cd675fd92db33ab02e849df8fcc364968a 100644 (file)
@@ -2,9 +2,6 @@
 
 #ifdef SVQC
 
-spawnfunc(weapon_mortar) { weapon_defaultspawnfunc(this, WEP_MORTAR); }
-spawnfunc(weapon_grenadelauncher) { spawnfunc_weapon_mortar(this); }
-
 void W_Mortar_Grenade_Explode(entity this, entity directhitentity)
 {
        if(directhitentity.takedamage == DAMAGE_AIM)
index 4fc5ec9ad2995644c9a72c1f3d268be874f39edf..2161d468bf6df5f18c497aee3c2ef484d7c01665 100644 (file)
@@ -1,9 +1,10 @@
 #pragma once
 
 CLASS(Mortar, Weapon)
+/* spawnfunc */ ATTRIB(Mortar, m_canonical_spawnfunc, string, "weapon_mortar");
 /* 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);
+/* flags     */ ATTRIB(Mortar, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH | WEP_FLAG_NODUAL);
 /* rating    */ ATTRIB(Mortar, bot_pickupbasevalue, float, 7000);
 /* color     */ ATTRIB(Mortar, wpcolor, vector, '1 0 0');
 /* modelname */ ATTRIB(Mortar, mdl, string, "gl");
@@ -53,6 +54,8 @@ CLASS(Mortar, Weapon)
 ENDCLASS(Mortar)
 REGISTER_WEAPON(MORTAR, mortar, NEW(Mortar));
 
+SPAWNFUNC_WEAPON(weapon_mortar, WEP_MORTAR)
+SPAWNFUNC_WEAPON(weapon_grenadelauncher, WEP_MORTAR)
 
 #ifdef SVQC
 .float gl_detonate_later;
index b4dab73cc951c2d8e93ba90c3024da215b799e00..a134c03ab5cce4368ca971fce13701a3ed1de8d8 100644 (file)
@@ -3,8 +3,6 @@
 #ifdef SVQC
 #include <common/triggers/trigger/jumppads.qh>
 
-spawnfunc(weapon_porto) { weapon_defaultspawnfunc(this, WEP_PORTO); }
-
 REGISTER_MUTATOR(porto_ticker, true);
 MUTATOR_HOOKFUNCTION(porto_ticker, SV_StartFrame) {
        FOREACH_CLIENT(IS_PLAYER(it), it.porto_forbidden = max(0, it.porto_forbidden - 1));
@@ -47,7 +45,7 @@ void W_Porto_Fail(entity this, float failhard)
                {
                        this.flags = FL_ITEM;
                        IL_PUSH(g_items, this);
-                       this.velocity = trigger_push_calculatevelocity(this.origin, this.realowner, 128);
+                       this.velocity = trigger_push_calculatevelocity(this.origin, this.realowner, 128, this);
                        tracetoss(this, this);
                        if(vdist(trace_endpos - this.realowner.origin, <, 128))
                        {
index b46e479aa9f383292498a75e9672cda2013786b4..93b3a6e9f7da4d73251da4dd731ab1810390227c 100644 (file)
@@ -1,6 +1,7 @@
 #pragma once
 
 CLASS(PortoLaunch, Weapon)
+/* spawnfunc */ ATTRIB(PortoLaunch, m_canonical_spawnfunc, string, "weapon_porto");
 /* 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);
@@ -35,6 +36,8 @@ CLASS(PortoLaunch, Weapon)
 ENDCLASS(PortoLaunch)
 REGISTER_WEAPON(PORTO, porto, NEW(PortoLaunch));
 
+SPAWNFUNC_WEAPON(weapon_porto, WEP_PORTO)
+
 #ifdef SVQC
 .entity porto_current;
 .vector porto_v_angle; // holds "held" view angles
index 54784251281c0b488c4867123984548e060ae028..0e49171122634db1b0aaecce386dd1ccc984914d 100644 (file)
@@ -1,9 +1,6 @@
 #include "rifle.qh"
 
 #ifdef SVQC
-spawnfunc(weapon_rifle) { weapon_defaultspawnfunc(this, WEP_RIFLE); }
-spawnfunc(weapon_campingrifle) { spawnfunc_weapon_rifle(this); }
-spawnfunc(weapon_sniperrifle) { spawnfunc_weapon_rifle(this); }
 
 void W_Rifle_FireBullet(Weapon thiswep, .entity weaponentity, float pSpread, float pDamage, float pForce, float pSolidPenetration, float pAmmo, int deathtype, float pTracer, float pShots, Sound pSound, entity actor)
 {
index b1c01b86ff861238a106b1d03a2b8dad77d8e55a..87bc4d7ec1d856424cde3776886176591166ef57 100644 (file)
@@ -1,9 +1,10 @@
 #pragma once
 
 CLASS(Rifle, Weapon)
+/* spawnfunc */ ATTRIB(Rifle, m_canonical_spawnfunc, string, "weapon_rifle");
 /* 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);
+/* flags     */ ATTRIB(Rifle, spawnflags, int, WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_PENETRATEWALLS | WEP_FLAG_NODUAL);
 /* rating    */ ATTRIB(Rifle, bot_pickupbasevalue, float, 7000);
 /* color     */ ATTRIB(Rifle, wpcolor, vector, '0.5 1 0');
 /* modelname */ ATTRIB(Rifle, mdl, string, "campingrifle");
@@ -47,6 +48,9 @@ CLASS(Rifle, Weapon)
 ENDCLASS(Rifle)
 REGISTER_WEAPON(RIFLE, rifle, NEW(Rifle));
 
+SPAWNFUNC_WEAPON(weapon_rifle, WEP_RIFLE)
+SPAWNFUNC_WEAPON(weapon_campingrifle, WEP_RIFLE)
+SPAWNFUNC_WEAPON(weapon_sniperrifle, WEP_RIFLE)
 
 #ifdef SVQC
 .float rifle_accumulator;
index b2df4f0efb54c53e5b951f9c874e3ce651b4b25e..5e3faeeab94a347424dda81601187a329f229f53 100644 (file)
@@ -1,7 +1,6 @@
 #include "seeker.qh"
 
 #ifdef SVQC
-spawnfunc(weapon_seeker) { weapon_defaultspawnfunc(this, WEP_SEEKER); }
 
 // ============================
 // Begin: Missile functions, these are general functions to be manipulated by other code
index 443d0843d01b3e1fea2bba4ba9d11a4cc182ba4d..e4e9fd535248f4b1f58bfe71e96866d9ac2f9306 100644 (file)
@@ -1,6 +1,7 @@
 #pragma once
 
 CLASS(Seeker, Weapon)
+/* spawnfunc */ ATTRIB(Seeker, m_canonical_spawnfunc, string, "weapon_seeker");
 /* 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);
@@ -82,6 +83,8 @@ CLASS(Seeker, Weapon)
 ENDCLASS(Seeker)
 REGISTER_WEAPON(SEEKER, seeker, NEW(Seeker));
 
+SPAWNFUNC_WEAPON(weapon_seeker, WEP_SEEKER)
+
 #ifdef SVQC
 .entity tag_target, wps_tag_tracker;
 .float tag_time;
index bc9e94767a45177e35b3dd788f283f3beeeb7c00..0ca4d528fe62ea550776a9d6ee1c9ad56dc6a526 100644 (file)
@@ -3,16 +3,14 @@
 REGISTER_NET_TEMP(TE_CSQC_SHOCKWAVEPARTICLE)
 
 #ifdef SVQC
-spawnfunc(weapon_shockwave)
+METHOD(Shockwave, m_spawnfunc_hookreplace, Weapon(Shockwave this, entity e))
 {
        //if(autocvar_sv_q3acompat_machineshockwaveswap) // WEAPONTODO
-       if(autocvar_sv_q3acompat_machineshotgunswap)
-       if(this.classname != "droppedweapon")
+       if (autocvar_sv_q3acompat_machineshotgunswap && !Item_IsLoot(e))
        {
-               weapon_defaultspawnfunc(this, WEP_MACHINEGUN);
-               return;
+               return WEP_MACHINEGUN;
        }
-       weapon_defaultspawnfunc(this, WEP_SHOCKWAVE);
+       return this;
 }
 
 const float MAX_SHOCKWAVE_HITS = 10;
@@ -318,13 +316,7 @@ void W_Shockwave_Attack(entity actor, .entity weaponentity)
        if(autocvar_g_antilag == 0 || noantilag)
                lag = 0; // only do hitscan, but no antilag
        if(lag)
-       {
-               FOREACH_CLIENT(IS_PLAYER(it) && it != actor, antilag_takeback(it, CS(it), time - lag));
-               IL_EACH(g_monsters, it != actor,
-               {
-                       antilag_takeback(it, it, time - lag);
-               });
-       }
+               antilag_takeback_all(actor, lag);
 
        while(head)
        {
@@ -598,13 +590,7 @@ void W_Shockwave_Attack(entity actor, .entity weaponentity)
        }
 
        if(lag)
-       {
-               FOREACH_CLIENT(IS_PLAYER(it) && it != actor, antilag_restore(it, CS(it)));
-               IL_EACH(g_monsters, it != actor,
-               {
-                       antilag_restore(it, it);
-               });
-       }
+               antilag_restore_all(actor);
 }
 
 METHOD(Shockwave, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
index 89685376dad9a6fd52b6b61f966fdbe2d35ba7ac..ade2e9a85c46a08984c3adb698f80254323021a2 100644 (file)
@@ -1,6 +1,7 @@
 #pragma once
 
 CLASS(Shockwave, Weapon)
+/* spawnfunc */ ATTRIB(Shockwave, m_canonical_spawnfunc, string, "weapon_shockwave");
 /* 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);
@@ -74,6 +75,7 @@ CLASS(Shockwave, Weapon)
 ENDCLASS(Shockwave)
 REGISTER_WEAPON(SHOCKWAVE, shockwave, NEW(Shockwave));
 
+SPAWNFUNC_WEAPON(weapon_shockwave, WEP_SHOCKWAVE)
 
 #ifdef CSQC
 void Net_ReadShockwaveParticle();
index e163df4bc61d1958c0ff203bbd8937005abaddef..9ffef64287ca8cfe7841bebc53c46a0232de05aa 100644 (file)
@@ -1,7 +1,6 @@
 #include "shotgun.qh"
 
 #ifdef SVQC
-spawnfunc(weapon_shotgun) { weapon_defaultspawnfunc(this, WEP_SHOTGUN); }
 
 void W_Shotgun_Attack(Weapon thiswep, entity actor, .entity weaponentity, float isprimary)
 {
index cd646a768f456aa2c74c9bdf8dda6f909ef8b305..e40b1d8a1f7f8aa07914fd2a4afabc325cff901d 100644 (file)
@@ -1,6 +1,7 @@
 #pragma once
 
 CLASS(Shotgun, Weapon)
+/* spawnfunc */ ATTRIB(Shotgun, m_canonical_spawnfunc, string, "weapon_shotgun");
 /* 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);
@@ -52,3 +53,5 @@ CLASS(Shotgun, Weapon)
 
 ENDCLASS(Shotgun)
 REGISTER_WEAPON(SHOTGUN, shotgun, NEW(Shotgun));
+
+SPAWNFUNC_WEAPON(weapon_shotgun, WEP_SHOTGUN)
index a5ae87c36b392256205cc47dd79aa879c628ae7a..37c36964a2299655452f9cffb2e9397181aaaabe 100644 (file)
@@ -10,8 +10,6 @@
 .float tuba_lastnotes_cnt; // over
 .vector tuba_lastnotes[MAX_TUBANOTES];
 
-spawnfunc(weapon_tuba) { weapon_defaultspawnfunc(this, WEP_TUBA); }
-
 bool W_Tuba_HasPlayed(entity pl, .entity weaponentity, string melody, int instrument, bool ignorepitch, float mintempo, float maxtempo)
 {
        float i, j, mmin, mmax, nolength;
index 714a2b8b5853566a8175049433744f69dcdfc4c8..ffa1dd6e2dd5589e5623611655bce63a87bf4042 100644 (file)
@@ -1,6 +1,7 @@
 #pragma once
 
 CLASS(Tuba, Weapon)
+/* spawnfunc */ ATTRIB(Tuba, m_canonical_spawnfunc, string, "weapon_tuba");
 /* impulse   */ ATTRIB(Tuba, impulse, int, 1);
 /* flags     */ ATTRIB(Tuba, spawnflags, int, WEP_FLAG_HIDDEN | WEP_TYPE_SPLASH | WEP_FLAG_NODUAL);
 /* rating    */ ATTRIB(Tuba, bot_pickupbasevalue, float, 2000);
@@ -40,6 +41,8 @@ CLASS(Tuba, Weapon)
 ENDCLASS(Tuba)
 REGISTER_WEAPON(TUBA, tuba, NEW(Tuba));
 
+SPAWNFUNC_WEAPON(weapon_tuba, WEP_TUBA)
+
 #ifdef CSQC
 entityclass(Tuba);
 class(Tuba) .int note;
index 73822b4c50fcbecad3a12f3189253ee4df03054c..4a9475a9ccebc212ebfc9ff36016c546865a1aef 100644 (file)
@@ -105,8 +105,6 @@ NET_HANDLE(TE_CSQC_VAPORBEAMPARTICLE, bool isNew)
 #endif
 
 #ifdef SVQC
-spawnfunc(weapon_vaporizer) { weapon_defaultspawnfunc(this, WEP_VAPORIZER); }
-spawnfunc(weapon_minstanex) { spawnfunc_weapon_vaporizer(this); }
 
 void W_RocketMinsta_Explosion(entity actor, vector loc)
 {
index 0c5c19200a437d67a063ffe58fd43785557bcafc..ea9f8dd2ba60db6a8452b088e424e5ed0d976046 100644 (file)
@@ -1,6 +1,7 @@
 #pragma once
 
 CLASS(Vaporizer, Weapon)
+/* spawnfunc */ ATTRIB(Vaporizer, m_canonical_spawnfunc, string, "weapon_vaporizer");
 /* 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);
@@ -50,6 +51,8 @@ CLASS(Vaporizer, Weapon)
 ENDCLASS(Vaporizer)
 REGISTER_WEAPON(VAPORIZER, vaporizer, NEW(Vaporizer));
 
+SPAWNFUNC_WEAPON(weapon_vaporizer, WEP_VAPORIZER)
+SPAWNFUNC_WEAPON(weapon_minstanex, WEP_VAPORIZER)
 
 #ifdef SVQC
 .float vaporizer_lasthit;
index 202780e29afc4d982400df3cd2ee3ad98fa43f5b..60557cbf8d42211602f985a6ba32da1c22551fa1 100644 (file)
@@ -71,8 +71,6 @@ NET_HANDLE(TE_CSQC_VORTEXBEAMPARTICLE, bool isNew)
 #endif
 
 #ifdef SVQC
-spawnfunc(weapon_vortex) { weapon_defaultspawnfunc(this, WEP_VORTEX); }
-spawnfunc(weapon_nex) { spawnfunc_weapon_vortex(this); }
 
 REGISTER_MUTATOR(vortex_charge, true);
 
index 5a41b90d8efede1e64457ac4001c09c86ea9b7b1..59152e2759f93b144585d9b8fcc6f394e6751627 100644 (file)
@@ -1,6 +1,7 @@
 #pragma once
 
 CLASS(Vortex, Weapon)
+/* spawnfunc */ ATTRIB(Vortex, m_canonical_spawnfunc, string, "weapon_vortex");
 /* 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);
@@ -59,8 +60,9 @@ CLASS(Vortex, Weapon)
 ENDCLASS(Vortex)
 REGISTER_WEAPON(VORTEX, vortex, NEW(Vortex));
 
+SPAWNFUNC_WEAPON(weapon_vortex, WEP_VORTEX)
+SPAWNFUNC_WEAPON(weapon_nex, WEP_VORTEX)
 
 #ifdef SVQC
-
 .float vortex_lasthit;
 #endif
index e6c2f7ccd8617e0087b19d0224d5e0760174cf3f..ec97bb2a15734f9bbc7c6f3b700e752a1527f8dc 100644 (file)
@@ -108,6 +108,7 @@ void sys_phys_update(entity this, float dt)
                this.com_phys_water = true;
                sys_phys_simulate(this, dt);
                this.com_phys_water = false;
+               this.jumppadcount = 0;
        } else if (time < this.ladder_time) {
                this.com_phys_friction = PHYS_FRICTION(this);
                this.com_phys_vel_max = PHYS_MAXSPEED(this) * maxspeed_mod;
index 6731bdf9433b7e228481237974e79a020334ddfa..6677d1782fe0c17720c8ef7d4a6f45c092419ce9 100644 (file)
@@ -65,7 +65,7 @@ void sys_phys_spectator_control(entity this)
                            || CS(this).impulse == 18
                            || (CS(this).impulse >= 200 && CS(this).impulse <= 209)
                           ) {
-                               this.spectatorspeed = bound(1, this.spectatorspeed + 0.5, 5);
+                               this.spectatorspeed = bound(autocvar_sv_spectator_speed_multiplier_min, this.spectatorspeed + 0.5, autocvar_sv_spectator_speed_multiplier_max);
                        } else if (CS(this).impulse == 11) {
                                this.spectatorspeed = maxspeed_mod;
                        } else if (CS(this).impulse == 12
@@ -73,7 +73,7 @@ void sys_phys_spectator_control(entity this)
                            || CS(this).impulse == 19
                            || (CS(this).impulse >= 220 && CS(this).impulse <= 229)
                                  ) {
-                               this.spectatorspeed = bound(1, this.spectatorspeed - 0.5, 5);
+                               this.spectatorspeed = bound(autocvar_sv_spectator_speed_multiplier_min, this.spectatorspeed - 0.5, autocvar_sv_spectator_speed_multiplier_max);
                        } else if (CS(this).impulse >= 1 && CS(this).impulse <= 9) {
                                this.spectatorspeed = 1 + 0.5 * (CS(this).impulse - 1);
                        }
@@ -86,7 +86,7 @@ void sys_phys_fixspeed(entity this, float maxspeed_mod)
 {
        float spd = max(PHYS_MAXSPEED(this), PHYS_MAXAIRSPEED(this)) * maxspeed_mod;
        if (this.speed != spd) {
-               this.speed = spd;
+               this.speed = spd; // TODO: send this as a stat and set the below cvars on the client?
                string temps = ftos(spd);
                stuffcmd(this, strcat("cl_forwardspeed ", temps, "\n"));
                stuffcmd(this, strcat("cl_backspeed ", temps, "\n"));
index 884714e3596928e9ef334c5d0efd711d21e76cf4..09f430407ef8ea26ba38dcb577047f5cdea9bc52 100644 (file)
@@ -169,6 +169,8 @@ noref bool require_spawnfunc_prefix;
                FIELD_SCALAR(fld, cvarfilter) \
                FIELD_SCALAR(fld, debrisdamageforcescale) \
                FIELD_SCALAR(fld, debrisfadetime) \
+               FIELD_SCALAR(fld, debrismovetype) \
+               FIELD_SCALAR(fld, debrisskin) \
                FIELD_SCALAR(fld, debristimejitter) \
                FIELD_SCALAR(fld, debristime) \
                FIELD_SCALAR(fld, debris) \
@@ -253,6 +255,9 @@ noref bool require_spawnfunc_prefix;
                FIELD_VEC(fld, absmin) \
                FIELD_VEC(fld, angles) \
                FIELD_VEC(fld, avelocity) \
+               FIELD_VEC(fld, debrisavelocityjitter) \
+               FIELD_VEC(fld, debrisvelocity) \
+               FIELD_VEC(fld, debrisvelocityjitter) \
                FIELD_VEC(fld, color) \
                FIELD_VEC(fld, mangle) \
                FIELD_VEC(fld, maxs) \
index 65b1a7f6416485d2777926c3c86e1aae006a9a02..82d3a50719356083ae96fbb18d691dfc2b9f83df 100644 (file)
@@ -819,12 +819,12 @@ bool WarpZoneLib_MoveOutOfSolid(entity e)
        vector m1 = e.maxs;
        e.mins = '0 0 0';
        e.maxs = '0 0 0';
-       WarpZoneLib_MoveOutOfSolid_Expand(e, eX * m0.x); e.mins.x = m0.x;
-       WarpZoneLib_MoveOutOfSolid_Expand(e, eX * m1.x); e.maxs.x = m1.x;
-       WarpZoneLib_MoveOutOfSolid_Expand(e, eY * m0.y); e.mins.y = m0.y;
-       WarpZoneLib_MoveOutOfSolid_Expand(e, eY * m1.y); e.maxs.y = m1.y;
-       WarpZoneLib_MoveOutOfSolid_Expand(e, eZ * m0.z); e.mins.z = m0.z;
-       WarpZoneLib_MoveOutOfSolid_Expand(e, eZ * m1.z); e.maxs.z = m1.z;
+       WarpZoneLib_MoveOutOfSolid_Expand(e, eX * m0.x); e.mins_x = m0.x;
+       WarpZoneLib_MoveOutOfSolid_Expand(e, eX * m1.x); e.maxs_x = m1.x;
+       WarpZoneLib_MoveOutOfSolid_Expand(e, eY * m0.y); e.mins_y = m0.y;
+       WarpZoneLib_MoveOutOfSolid_Expand(e, eY * m1.y); e.maxs_y = m1.y;
+       WarpZoneLib_MoveOutOfSolid_Expand(e, eZ * m0.z); e.mins_z = m0.z;
+       WarpZoneLib_MoveOutOfSolid_Expand(e, eZ * m1.z); e.maxs_z = m1.z;
        setorigin(e, e.origin);
 
        tracebox(e.origin, e.mins, e.maxs, e.origin, MOVE_WORLDONLY, e);
index 6db6122352511d9640ac75baf603103a4e96ce65..936d075da8f69e2b3057d965dc35029b57b1cf23 100644 (file)
 
 void WarpZone_TeleportPlayer(entity teleporter, entity player, vector to, vector to_angles, vector to_velocity)
 {
+#ifdef SVQC
+       player.lastteleport_origin = player.origin;
+       player.lastteleporttime = time;
+#endif
        setorigin(player, to); // NOTE: this also aborts the move, when this is called by touch
 #ifdef SVQC
        player.oldorigin = to; // for DP's unsticking
index 2f37eb7e2b665c260d6e5174797eaaec3d58cda4..baeb9a51bcbf127d14f8dc89acb36b6814ab5901 100644 (file)
@@ -198,6 +198,7 @@ void XonoticServerInfoDialog_loadServerInfo(entity me, float i)
                        break;
        }
        me.encryptLabel.setText(me.encryptLabel, me.currentServerEncrypt);
+       setZonedTooltip(me.encryptLabel, _("Use the `crypto_aeslevel` cvar to change your preferences"), string_null);
 
        s = crypto_getidfp(me.currentServerCName);
        if (!s) { s = _("N/A"); }
index de7e01aa7a862d3f328d0c02a3fdd542e5238ad9..569301c5d65c369b60deb900f44be3bf1447c5bf 100644 (file)
@@ -14,6 +14,7 @@
 #include <server/impulse.qc>
 #include <server/ipban.qc>
 #include <server/item_key.qc>
+#include <server/items.qc>
 #include <server/mapvoting.qc>
 #include <server/matrix.qc>
 #include <server/miscfunctions.qc>
index 67f6aae4db511fd7cb1b91cb1cdb7412c7f017ea..2013fd6bb5db5c521737cf0985fc65c13a43846e 100644 (file)
@@ -14,6 +14,7 @@
 #include <server/impulse.qh>
 #include <server/ipban.qh>
 #include <server/item_key.qh>
+#include <server/items.qh>
 #include <server/mapvoting.qh>
 #include <server/matrix.qh>
 #include <server/miscfunctions.qh>
index 46c871876108005a0a69978681454bf1a9446f09..4062f7f660df2d15dfb01ea48bdf822a81599995 100644 (file)
@@ -119,3 +119,30 @@ void antilag_clear(entity e, entity store)
        }
        store.antilag_index = ANTILAG_MAX_ORIGINS - 1; // next one is 0
 }
+
+// TODO: use a single intrusive list across all antilagged entities
+void antilag_takeback_all(entity ignore, float lag)
+{
+       FOREACH_CLIENT(IS_PLAYER(it) && it != ignore, antilag_takeback(it, CS(it), time - lag));
+       IL_EACH(g_monsters, it != ignore,
+       {
+               antilag_takeback(it, it, time - lag);
+       });
+       IL_EACH(g_projectiles, it != ignore && it.classname == "nade",
+       {
+               antilag_takeback(it, it, time - lag);
+       });
+}
+
+void antilag_restore_all(entity ignore)
+{
+       FOREACH_CLIENT(IS_PLAYER(it) && it != ignore, antilag_restore(it, CS(it)));
+       IL_EACH(g_monsters, it != ignore,
+       {
+               antilag_restore(it, it);
+       });
+       IL_EACH(g_projectiles, it != ignore && it.classname == "nade",
+       {
+               antilag_restore(it, it);
+       });
+}
index 6cf392eed5ae1e8bd2bc125b3a19aa5b9e70d6dd..d57762ccd3899ef8588a560229b39e25ac89210e 100644 (file)
@@ -6,6 +6,9 @@ void antilag_takeback(entity e, entity store, float t);
 void antilag_restore(entity e, entity store);
 void antilag_clear(entity e, entity store);
 
+void antilag_takeback_all(entity ignore, float lag);
+void antilag_restore_all(entity ignore);
+
 .float antilag_debug;
 
 #define ANTILAG_LATENCY(e) min(0.4, CS(e).ping * 0.001)
index 4303aa9764ea3d9164380fe49023c410824786bf..80da3f403ac97c025274b109d1ede8f5c8c8ea83 100644 (file)
@@ -6,6 +6,7 @@ int autocvar__campaign_index;
 string autocvar__campaign_name;
 bool autocvar__sv_init;
 float autocvar_bot_ai_strategyinterval;
+float autocvar_bot_ai_strategyinterval_movingtarget;
 #define autocvar_bot_number cvar("bot_number")
 int autocvar_bot_vs_human;
 int autocvar_captureleadlimit_override;
@@ -311,6 +312,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;
@@ -328,6 +330,8 @@ bool autocvar_sv_ready_restart_repeatable;
 bool autocvar_sv_servermodelsonly;
 int autocvar_sv_spectate;
 float autocvar_sv_spectator_speed_multiplier;
+float autocvar_sv_spectator_speed_multiplier_min = 1;
+float autocvar_sv_spectator_speed_multiplier_max = 5;
 bool autocvar_sv_status_privacy;
 float autocvar_sv_stepheight;
 float autocvar_sv_strengthsound_antispam_refire_threshold;
index f33cc4f2646502447f0bcf60fd4c96c10d0d5738..51ac148fc13c42c1185866cd87d6430165f61b8e 100644 (file)
@@ -2,15 +2,17 @@
 
 #include <server/defs.qh>
 #include <common/weapons/_all.qh>
+#include <common/physics/player.qh>
 
 const int WAYPOINTFLAG_GENERATED = BIT(23);
 const int WAYPOINTFLAG_ITEM = BIT(22);
-const int WAYPOINTFLAG_TELEPORT = BIT(21);
+const int WAYPOINTFLAG_TELEPORT = BIT(21); // teleports, warpzones and jumppads
 const int WAYPOINTFLAG_NORELINK = BIT(20);
 const int WAYPOINTFLAG_PERSONAL = BIT(19);
 const int WAYPOINTFLAG_PROTECTED = BIT(18);  // Useless WP detection never kills these.
 const int WAYPOINTFLAG_USEFUL = BIT(17);  // Useless WP detection temporary flag.
 const int WAYPOINTFLAG_DEAD_END = BIT(16);  // Useless WP detection temporary flag.
+const int WAYPOINTFLAG_LADDER = BIT(15);
 
 entity kh_worldkeylist;
 .entity kh_worldkeynext;
@@ -21,6 +23,7 @@ float bot_weapons_far[Weapons_MAX];
 float bot_weapons_mid[Weapons_MAX];
 float skill;
 
+.float bot_tracewalk_time;
 .float bot_attack;
 .float bot_dodgerating;
 .float bot_dodge;
@@ -28,11 +31,13 @@ float skill;
 .float bot_moveskill; // moving technique
 .float bot_pickup;
 .float(entity player, entity item) bot_pickupevalfunc;
-.float bot_strategytime;
 .string cleanname;
 .float havocbot_role_timeout;
+.void(entity this) havocbot_role;
+.void(entity this) havocbot_previous_role;
 .float isbot; // true if this client is actually a bot
 .float lastteleporttime;
+.vector lastteleport_origin;
 .float navigation_hasgoals;
 .float nearestwaypointtimeout;
 .entity nearestwaypoint;
@@ -70,10 +75,16 @@ void havocbot_goalrating_enemyplayers(entity this, float ratingscale, vector org
 void havocbot_goalrating_items(entity this, float ratingscale, vector org, float sradius);
 void havocbot_goalrating_waypoints(entity this, float ratingscale, vector org, float sradius);
 
+bool havocbot_goalrating_item_pickable_check_players(entity this, vector org, entity item, vector item_org);
+
 vector havocbot_middlepoint;
 float havocbot_middlepoint_radius;
 vector havocbot_symmetryaxis_equation;
 
+.float goalentity_lock_timeout;
+.float ignoregoaltime;
+.entity ignoregoal;
+
 .entity bot_basewaypoint;
 .bool navigation_dynamicgoal;
 void navigation_dynamicgoal_init(entity this, bool initially_static);
@@ -82,21 +93,30 @@ void navigation_dynamicgoal_unset(entity this);
 entity navigation_findnearestwaypoint(entity ent, float walkfromwp);
 void navigation_goalrating_end(entity this);
 void navigation_goalrating_start(entity this);
+void navigation_goalrating_timeout_set(entity this);
+void navigation_goalrating_timeout_force(entity this);
+void navigation_goalrating_timeout_expire(entity this, float seconds);
+bool navigation_goalrating_timeout(entity this);
+bool navigation_goalrating_timeout_can_be_anticipated(entity this);
 void navigation_markroutes(entity this, entity fixed_source_waypoint);
 void navigation_markroutes_inverted(entity fixed_source_waypoint);
 void navigation_routerating(entity this, entity e, float f, float rangebias);
 
-bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float movemode);
+vector get_closer_dest(entity ent, vector org);
 
-void waypoint_remove(entity e);
+void set_tracewalk_dest(entity ent, vector org, bool fix_player_dest);
+vector set_tracewalk_dest_2(entity ent, vector org);
+bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float end_height, float movemode);
+
+void waypoint_remove_fromeditor(entity pl);
+void waypoint_remove(entity wp);
 void waypoint_saveall();
 void waypoint_schedulerelinkall();
 void waypoint_schedulerelink(entity wp);
 void waypoint_spawnforitem(entity e);
 void waypoint_spawnforitem_force(entity e, vector org);
-void waypoint_spawnforteleporter(entity e, vector destination, float timetaken);
-void waypoint_spawnforteleporter_v(entity e, vector org, vector destination, float timetaken);
+void waypoint_spawnforteleporter(entity e, vector destination, float timetaken, entity tracetest_ent);
+void waypoint_spawnforteleporter_wz(entity e, vector org, vector destination, float timetaken, vector down_dir, entity tracetest_ent);
+void waypoint_spawn_fromeditor(entity pl);
 entity waypoint_spawn(vector m1, vector m2, float f);
-
-.entity goalcurrent;
-void navigation_clearroute(entity this);
+void waypoint_unreachable(entity pl);
index 8f2abb3f824b5745448a5fb4c16ab21545e5451b..5ad1295f990f0b3d16be6b989e3263e4b17b3466 100644 (file)
@@ -132,9 +132,8 @@ bool bot_shouldattack(entity this, entity targ)
                if(targ.team==0)
                        return false;
        }
-       else if(bot_ignore_bots)
-               if(IS_BOT_CLIENT(targ))
-                       return false;
+       else if (autocvar_bot_ignore_bots && IS_BOT_CLIENT(targ))
+               return false;
 
        if (!targ.takedamage)
                return false;
@@ -174,7 +173,7 @@ void bot_lagfunc(entity this, float t, float f1, float f2, entity e1, vector v1,
                this.bot_canfire = 1;
 }
 
-float bot_aimdir(entity this, vector v, float maxfiredeviation)
+void bot_aimdir(entity this, vector v, float maxfiredeviation)
 {
        float dist, delta_t, blend;
        vector desiredang, diffang;
@@ -184,6 +183,9 @@ float bot_aimdir(entity this, vector v, float maxfiredeviation)
        this.v_angle_y = this.v_angle.y - floor(this.v_angle.y / 360) * 360;
        this.v_angle_z = 0;
 
+       // invalid aim dir (can happen when bot overlaps target)
+       if(!v) return;
+
        // get the desired angles to aim at
        //dprint(" at:", vtos(v));
        v = normalize(v);
@@ -314,7 +316,7 @@ float bot_aimdir(entity this, vector v, float maxfiredeviation)
        //dprint(ftos(maxfiredeviation),"\n");
        //dprint(" diff:", vtos(diffang), "\n");
 
-       return this.bot_canfire && (time < this.bot_firetimer);
+       //return this.bot_canfire && (time < this.bot_firetimer);
 }
 
 vector bot_shotlead(vector targorigin, vector targvelocity, float shotspeed, float shotdelay)
@@ -325,7 +327,7 @@ vector bot_shotlead(vector targorigin, vector targvelocity, float shotspeed, flo
 
 bool bot_aim(entity this, .entity weaponentity, float shotspeed, float shotspeedupward, float maxshottime, bool applygravity)
 {
-       float f, r, hf, distanceratio;
+       float r, hf, distanceratio;
        vector v;
        /*
        eprint(this);
@@ -367,11 +369,11 @@ bool bot_aim(entity this, .entity weaponentity, float shotspeed, float shotspeed
                        return false;
                }
 
-               f = bot_aimdir(this, findtrajectory_velocity - shotspeedupward * '0 0 1', r);
+               bot_aimdir(this, findtrajectory_velocity - shotspeedupward * '0 0 1', r);
        }
        else
        {
-               f = bot_aimdir(this, v - shotorg, r);
+               bot_aimdir(this, v - shotorg, r);
                //dprint("AIM: ");dprint(vtos(this.bot_aimtargorigin));dprint(" + ");dprint(vtos(this.bot_aimtargvelocity));dprint(" * ");dprint(ftos(this.bot_aimlatency + vlen(this.bot_aimtargorigin - shotorg) / shotspeed));dprint(" = ");dprint(vtos(v));dprint(" : aimdir = ");dprint(vtos(normalize(v - shotorg)));dprint(" : ");dprint(vtos(shotdir));dprint("\n");
                //traceline(shotorg, shotorg + shotdir * 10000, false, this);
                //if (trace_ent.takedamage)
index e7c60758aec2eca3c46fe3c588a8cce4e635551e..b8b35f1c203a08968e3b82e7029bbfd17109e6dd 100644 (file)
@@ -90,7 +90,7 @@ void lag_update(entity this);
 void bot_lagfunc(entity this, float t, float f1, float f2, entity e1, vector v1, vector v2, vector v3, vector v4);
 
 float bot_shouldattack(entity this, entity targ);
-float bot_aimdir(entity this, vector v, float maxfiredeviation);
+void bot_aimdir(entity this, vector v, float maxfiredeviation);
 bool bot_aim(entity this, .entity weaponentity, float shotspeed, float shotspeedupward, float maxshottime, bool applygravity);
 float findtrajectorywithleading(vector org, vector m1, vector m2, entity targ, float shotspeed, float shotspeedupward, float maxtime, float shotdelay, entity ignore);
 
index f8ddd0c637576d81efb058e330c11922d9438a56..a605fc0689e73259ba266cd41047ca573bb252ad 100644 (file)
@@ -133,7 +133,7 @@ void bot_think(entity this)
                if (this.deadflag == DEAD_DEAD)
                {
                        PHYS_INPUT_BUTTON_JUMP(this) = true; // press jump to respawn
-                       this.bot_strategytime = 0;
+                       navigation_goalrating_timeout_force(this);
                }
        }
        else if(this.aistatus & AI_STATUS_STUCK)
@@ -438,7 +438,7 @@ void bot_clientconnect(entity this)
        else if(this.bot_forced_team==4)
                this.team = NUM_TEAM_4;
        else
-               JoinBestTeam(this, false, true);
+               JoinBestTeam(this, true);
 
        havocbot_setupbot(this);
 }
@@ -579,9 +579,8 @@ void autoskill(float factor)
 void bot_calculate_stepheightvec()
 {
        stepheightvec = autocvar_sv_stepheight * '0 0 1';
-       jumpstepheightvec = stepheightvec +
-               ((autocvar_sv_jumpvelocity * autocvar_sv_jumpvelocity) / (2 * autocvar_sv_gravity)) * '0 0 0.85';
-               // 0.75 factor is for safety to make the jumps easy
+       jumpheight_vec = (autocvar_sv_jumpvelocity ** 2) / (2 * autocvar_sv_gravity) * '0 0 1';
+       jumpstepheightvec = stepheightvec + jumpheight_vec * 0.85; // reduce it a bit to make the jumps easy
 }
 
 float bot_fixcount()
@@ -598,16 +597,6 @@ float bot_fixcount()
                        ++realplayers;
                });
        }
-       if(currentbots == -1)
-       {
-               currentbots = 0;
-               // human players joining early may cause weird issues (bots appearing on
-               // the scoreboard as spectators) when switching map with the gotomap
-               // command, as it doesn't remove bots of the previous match, and with
-               // minplayers > 1, so ignore human players in the first bot frame
-               // TODO maybe find a cleaner solution
-               activerealplayers = 0;
-       }
 
        int bots;
        // add/remove bots if needed to make sure there are at least
@@ -690,15 +679,52 @@ void bot_clear(entity this)
 
 void bot_serverframe()
 {
+       if (intermission_running && currentbots > 0)
+       {
+               // after the end of the match all bots stay unless all human players disconnect
+               int realplayers = 0;
+               FOREACH_CLIENT(IS_REAL_CLIENT(it), { ++realplayers; });
+               if (!realplayers)
+               {
+                       FOREACH_CLIENT(IS_BOT_CLIENT(it), { dropclient(it); });
+                       currentbots = 0;
+               }
+               return;
+       }
+
        if (game_stopped)
                return;
 
-       if (time < 2)
+       // Added 0.5 to avoid possible addition + immediate removal of bots that would make them appear as
+       // spectators in the scoreboard and never go away. This issue happens at time 2 if map is changed
+       // with the gotomap command, minplayers is > 1 and human clients join as players very soon
+       // either intentionally or automatically (sv_spectate 0)
+       if (time < 2.5)
        {
                currentbots = -1;
                return;
        }
 
+       if (currentbots == -1)
+       {
+               // count bots already in the server from the previous match
+               currentbots = 0;
+               FOREACH_CLIENT(IS_BOT_CLIENT(it), { ++currentbots; });
+       }
+
+       if(autocvar_skill != skill)
+       {
+               float wpcost_update = false;
+               if(skill >= autocvar_bot_ai_bunnyhop_skilloffset && autocvar_skill < autocvar_bot_ai_bunnyhop_skilloffset)
+                       wpcost_update = true;
+               if(skill < autocvar_bot_ai_bunnyhop_skilloffset && autocvar_skill >= autocvar_bot_ai_bunnyhop_skilloffset)
+                       wpcost_update = true;
+
+               skill = autocvar_skill;
+               if (wpcost_update)
+                       waypoint_updatecost_foralllinks();
+       }
+
        bot_calculate_stepheightvec();
        bot_navigation_movemode = ((autocvar_bot_navigation_ignoreplayers) ? MOVE_NOMONSTERS : MOVE_NORMAL);
 
@@ -717,8 +743,6 @@ void bot_serverframe()
                        botframe_nextthink = time + 10;
        }
 
-       bot_ignore_bots = autocvar_bot_ignore_bots;
-
        if(botframe_spawnedwaypoints)
        {
                if(autocvar_waypoint_benchmark)
@@ -747,8 +771,7 @@ void bot_serverframe()
        {
                botframe_spawnedwaypoints = true;
                waypoint_loadall();
-               if(!waypoint_load_links())
-                       waypoint_schedulerelinkall();
+               waypoint_load_links();
        }
 
        if (bot_list)
@@ -758,11 +781,26 @@ void bot_serverframe()
                //  frame, which causes choppy framerates)
                if (bot_strategytoken_taken)
                {
+                       // give goal token to the first bot without goals; if all bots don't have
+                       // any goal (or are dead/frozen) simply give it to the next one
                        bot_strategytoken_taken = false;
-                       if (bot_strategytoken)
-                               bot_strategytoken = bot_strategytoken.nextbot;
-                       if (!bot_strategytoken)
-                               bot_strategytoken = bot_list;
+                       entity bot_strategytoken_save = bot_strategytoken;
+                       while (true)
+                       {
+                               if (bot_strategytoken)
+                                       bot_strategytoken = bot_strategytoken.nextbot;
+                               if (!bot_strategytoken)
+                                       bot_strategytoken = bot_list;
+
+                               if (!(IS_DEAD(bot_strategytoken) || STAT(FROZEN, bot_strategytoken))
+                                       && !bot_strategytoken.goalcurrent)
+                                       break;
+
+                               if (!bot_strategytoken_save) // break loop if all the bots are dead or frozen
+                                       break;
+                               if (bot_strategytoken == bot_strategytoken_save)
+                                       bot_strategytoken_save = NULL; // looped through all the bots
+                       }
                }
 
                if (botframe_nextdangertime < time)
index b72fad9bd600248601794130a2cf0f505c110991..ca567181bb13fd29331a6dd4c10a37d65f2b2c56 100644 (file)
@@ -76,7 +76,12 @@ float botframe_spawnedwaypoints;
 float botframe_nextthink;
 float botframe_nextdangertime;
 float bot_cvar_nextthink;
-float bot_ignore_bots; // let bots not attack other bots (only works in non-teamplay)
+
+int _content_type;
+#define IN_LAVA(pos) (_content_type = pointcontents(pos), (_content_type == CONTENT_LAVA || _content_type == CONTENT_SLIME))
+#define IN_LIQUID(pos) (_content_type = pointcontents(pos), (_content_type == CONTENT_WATER || _content_type == CONTENT_LAVA || _content_type == CONTENT_SLIME))
+#define SUBMERGED(pos) IN_LIQUID(pos + autocvar_sv_player_viewoffset)
+#define WETFEET(pos) IN_LIQUID(pos + eZ * (m1.z + 1))
 
 /*
  * Functions
index 46acf8828774324f3790596ce465576f6c6c7b77..2a0d0c8503f005c17d2156fd2871ebf216ac932d 100644 (file)
@@ -33,8 +33,7 @@ void havocbot_ai(entity this)
        if(bot_execute_commands(this))
                return;
 
-       if (bot_strategytoken == this)
-       if (!bot_strategytoken_taken)
+       if (bot_strategytoken == this && !bot_strategytoken_taken)
        {
                if(this.havocbot_blockhead)
                {
@@ -46,7 +45,6 @@ void havocbot_ai(entity this)
                                this.havocbot_role(this); // little too far down the rabbit hole
                }
 
-               // TODO: tracewalk() should take care of this job (better path finding under water)
                // if we don't have a goal and we're under water look for a waypoint near the "shore" and push it
                if(!(IS_DEAD(this) || STAT(FROZEN, this)))
                if(!this.goalcurrent)
@@ -86,7 +84,11 @@ void havocbot_ai(entity this)
        }
 
        if(IS_DEAD(this) || STAT(FROZEN, this))
+       {
+               if (this.goalcurrent)
+                       navigation_clearroute(this);
                return;
+       }
 
        havocbot_chooseenemy(this);
 
@@ -139,11 +141,23 @@ void havocbot_ai(entity this)
                this.aistatus |= AI_STATUS_ROAMING;
                this.aistatus &= ~AI_STATUS_ATTACKING;
 
-               vector now,v,next;//,heading;
+               vector now, next;
                float aimdistance,skillblend,distanceblend,blend;
-               next = now = ( (this.goalcurrent.absmin + this.goalcurrent.absmax) * 0.5) - (this.origin + this.view_ofs);
+
+               vector v = get_closer_dest(this.goalcurrent, this.origin);
+               if(this.goalcurrent.wpisbox)
+               {
+                       // avoid a glitch when bot is teleported but teleport waypoint isn't removed yet
+                       if(this.goalstack02 && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT
+                       && this.lastteleporttime > 0 && time - this.lastteleporttime < 0.15)
+                               v = (this.goalstack02.absmin + this.goalstack02.absmax) * 0.5;
+                       // aim to teleport origin if bot is inside teleport waypoint but hasn't touched the real teleport yet
+                       else if(boxesoverlap(this.goalcurrent.absmin, this.goalcurrent.absmax, this.origin, this.origin))
+                               v = this.goalcurrent.origin;
+               }
+               next = now = v - (this.origin + this.view_ofs);
                aimdistance = vlen(now);
-               //heading = this.velocity;
+
                //dprint(this.goalstack01.classname,etos(this.goalstack01),"\n");
                if(
                        this.goalstack01 != this && this.goalstack01 && !wasfreed(this.goalstack01) && ((this.aistatus & AI_STATUS_RUNNING) == 0) &&
@@ -275,7 +289,6 @@ void havocbot_bunnyhop(entity this, vector dir)
        float bunnyhopdistance;
        vector deviation;
        float maxspeed;
-       vector gco, gno;
 
        // Don't jump when attacking
        if(this.aistatus & AI_STATUS_ATTACKING)
@@ -308,12 +321,12 @@ void havocbot_bunnyhop(entity this, vector dir)
                this.bot_timelastseengoal = 0;
        }
 
-       gco = (this.goalcurrent.absmin + this.goalcurrent.absmax) * 0.5;
+       vector gco = get_closer_dest(this.goalcurrent, this.origin);
        bunnyhopdistance = vlen(this.origin - gco);
 
        // Run only to visible goals
        if(IS_ONGROUND(this))
-       if(vlen(this.velocity - eZ * this.velocity.z) >= autocvar_sv_maxspeed) // if -really- running
+       if(vdist(vec2(this.velocity), >=, autocvar_sv_maxspeed)) // if -really- running
        if(checkpvs(this.origin + this.view_ofs, this.goalcurrent))
        {
                        this.bot_lastseengoal = this.goalcurrent;
@@ -345,7 +358,7 @@ void havocbot_bunnyhop(entity this, vector dir)
                                        if(fabs(gco.z - this.origin.z) < this.maxs.z - this.mins.z)
                                        if(this.goalstack01 && !wasfreed(this.goalstack01))
                                        {
-                                               gno = (this.goalstack01.absmin + this.goalstack01.absmax) * 0.5;
+                                               vector gno = (this.goalstack01.absmin + this.goalstack01.absmax) * 0.5;
                                                deviation = vectoangles(gno - this.origin) - vectoangles(gco - this.origin);
                                                while (deviation.y < -180) deviation.y = deviation.y + 360;
                                                while (deviation.y > 180) deviation.y = deviation.y - 360;
@@ -415,21 +428,67 @@ void havocbot_bunnyhop(entity this, vector dir)
 #endif
 }
 
-.entity goalcurrent_prev;
-.float goalcurrent_distance;
-.float goalcurrent_distance_time;
+// return true when bot isn't getting closer to the current goal
+bool havocbot_checkgoaldistance(entity this, vector gco)
+{
+       float curr_dist_z = max(20, fabs(this.origin.z - gco.z));
+       float curr_dist_2d = max(20, vlen(vec2(this.origin - gco)));
+       float distance_time = this.goalcurrent_distance_time;
+       if(distance_time < 0)
+               distance_time = -distance_time;
+       if(curr_dist_z >= this.goalcurrent_distance_z && curr_dist_2d >= this.goalcurrent_distance_2d)
+       {
+               if(!distance_time)
+                       this.goalcurrent_distance_time = time;
+               else if (time - distance_time > 0.5)
+                       return true;
+       }
+       else
+       {
+               // reduce it a little bit so it works even with very small approaches to the goal
+               this.goalcurrent_distance_z = max(20, curr_dist_z - 10);
+               this.goalcurrent_distance_2d = max(20, curr_dist_2d - 10);
+               this.goalcurrent_distance_time = 0;
+       }
+       return false;
+}
+
+entity havocbot_select_an_item_of_group(entity this, int gr)
+{
+       entity selected = NULL;
+       float selected_dist2 = 0;
+       // select farthest item of this group from bot's position
+       IL_EACH(g_items, it.item_group == gr && it.solid,
+       {
+               float dist2 = vlen2(this.origin - it.origin);
+               if (dist2 < 600 ** 2 && dist2 > selected_dist2)
+               {
+                       selected = it;
+                       selected_dist2 = vlen2(this.origin - selected.origin);
+               }
+       });
+
+       if (!selected)
+               return NULL;
+
+       set_tracewalk_dest(selected, this.origin, false);
+       if (!tracewalk(this, this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this),
+               tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode))
+       {
+               return NULL;
+       }
+
+       return selected;
+}
+
 void havocbot_movetogoal(entity this)
 {
-       vector destorg;
        vector diff;
        vector dir;
        vector flatdir;
-       vector m1;
-       vector m2;
        vector evadeobstacle;
        vector evadelava;
        float maxspeed;
-       vector gco;
        //float dist;
        vector dodge;
        //if (this.goalentity)
@@ -437,8 +496,8 @@ void havocbot_movetogoal(entity this)
        CS(this).movement = '0 0 0';
        maxspeed = autocvar_sv_maxspeed;
 
+       PHYS_INPUT_BUTTON_JETPACK(this) = false;
        // Jetpack navigation
-       if(this.goalcurrent)
        if(this.navigation_jetpack_goal)
        if(this.goalcurrent==this.navigation_jetpack_goal)
        if(this.ammo_fuel)
@@ -465,18 +524,14 @@ void havocbot_movetogoal(entity this)
                if(this.aistatus & AI_STATUS_JETPACK_LANDING)
                {
                        // Calculate brake distance in xy
-                       float db, v, d;
-                       vector dxy;
-
-                       dxy = this.origin - ( ( this.goalcurrent.absmin + this.goalcurrent.absmax ) * 0.5 ); dxy.z = 0;
-                       d = vlen(dxy);
-                       v = vlen(this.velocity -  this.velocity.z * '0 0 1');
-                       db = ((v ** 2) / (autocvar_g_jetpack_acceleration_side * 2)) + 100;
-               //      dprint("distance ", ftos(ceil(d)), " velocity ", ftos(ceil(v)), " brake at ", ftos(ceil(db)), "\n");
+                       float d = vlen(vec2(this.origin - (this.goalcurrent.absmin + this.goalcurrent.absmax) * 0.5));
+                       float v = vlen(vec2(this.velocity));
+                       float db = ((v ** 2) / (autocvar_g_jetpack_acceleration_side * 2)) + 100;
+                       //LOG_INFOF("distance %d, velocity %d, brake at %d ", ceil(d), ceil(v), ceil(db));
                        if(d < db || d < 500)
                        {
                                // Brake
-                               if(fabs(this.velocity.x)>maxspeed*0.3)
+                               if(v > maxspeed * 0.3)
                                {
                                        CS(this).movement_x = dir * v_forward * -maxspeed;
                                        return;
@@ -497,7 +552,7 @@ void havocbot_movetogoal(entity this)
                }
 
                // Flying
-               PHYS_INPUT_BUTTON_HOOK(this) = true;
+               PHYS_INPUT_BUTTON_JETPACK(this) = true;
                if(this.navigation_jetpack_point.z - STAT(PL_MAX, this).z + STAT(PL_MIN, this).z < this.origin.z)
                {
                        CS(this).movement_x = dir * v_forward * maxspeed;
@@ -509,17 +564,25 @@ void havocbot_movetogoal(entity this)
        // Handling of jump pads
        if(this.jumppadcount)
        {
-               // If got stuck on the jump pad try to reach the farthest visible waypoint
-               // but with some randomness so it can try out different paths
-               if(this.aistatus & AI_STATUS_OUT_JUMPPAD)
+               if(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
+               {
+                       this.aistatus |= AI_STATUS_OUT_JUMPPAD;
+                       navigation_poptouchedgoals(this);
+                       return;
+               }
+               else if(this.aistatus & AI_STATUS_OUT_JUMPPAD)
                {
-                       if(fabs(this.velocity.z)<50)
+                       // If got stuck on the jump pad try to reach the farthest visible waypoint
+                       // but with some randomness so it can try out different paths
+                       if(!this.goalcurrent)
                        {
                                entity newgoal = NULL;
-                               if (vdist(this.origin - this.goalcurrent.origin, <, 150))
-                                       this.aistatus &= ~AI_STATUS_OUT_JUMPPAD;
-                               else IL_EACH(g_waypoints, vdist(it.origin - this.origin, <=, 1000),
+                               IL_EACH(g_waypoints, vdist(it.origin - this.origin, <=, 1000),
                                {
+                                       if(it.wpflags & WAYPOINTFLAG_TELEPORT)
+                                       if(it.origin.z < this.origin.z - 100 && vdist(vec2(it.origin - this.origin), <, 100))
+                                               continue;
+
                                        traceline(this.origin + this.view_ofs, ((it.absmin + it.absmax) * 0.5), true, this);
 
                                        if(trace_fraction < 1)
@@ -541,11 +604,32 @@ void havocbot_movetogoal(entity this)
                                }
                        }
                        else
-                               return;
+                       {
+                               if (this.goalcurrent.bot_pickup)
+                               {
+                                       entity jumppad_wp = this.goalcurrent_prev;
+                                       navigation_poptouchedgoals(this);
+                                       if(!this.goalcurrent && jumppad_wp.wp00)
+                                       {
+                                               // head to the jumppad destination once bot reaches the goal item
+                                               navigation_pushroute(this, jumppad_wp.wp00);
+                                       }
+                               }
+                               vector gco = (this.goalcurrent.absmin + this.goalcurrent.absmax) * 0.5;
+                               if (this.origin.z > gco.z && vdist(vec2(this.velocity), <, autocvar_sv_maxspeed))
+                                       this.aistatus &= ~AI_STATUS_OUT_JUMPPAD;
+                               else if(havocbot_checkgoaldistance(this, gco))
+                               {
+                                       navigation_clearroute(this);
+                                       navigation_goalrating_timeout_force(this);
+                               }
+                               else
+                                       return;
+                       }
                }
                else
                {
-                       if(time - this.lastteleporttime > 0.3 && this.velocity.z > 0)
+                       if(time - this.lastteleporttime > 0.2 && this.velocity.z > 0)
                        {
                                vector velxy = this.velocity; velxy_z = 0;
                                if(vdist(velxy, <, autocvar_sv_maxspeed * 0.2))
@@ -565,9 +649,12 @@ void havocbot_movetogoal(entity this)
                this.aistatus &= ~AI_STATUS_OUT_JUMPPAD;
 
        // If there is a trigger_hurt right below try to use the jetpack or make a rocketjump
-       if(skill>6)
-       if (!(IS_ONGROUND(this)))
+       if (skill > 6 && !(IS_ONGROUND(this)))
        {
+               #define ROCKETJUMP_DAMAGE() WEP_CVAR(devastator, damage) * 0.8 \
+                       * ((this.strength_finished > time) ? autocvar_g_balance_powerup_strength_selfdamage : 1) \
+                       * ((this.invincible_finished > time) ? autocvar_g_balance_powerup_invincible_takedamage : 1)
+
                tracebox(this.origin, this.mins, this.maxs, this.origin + '0 0 -65536', MOVE_NOMONSTERS, this);
                if(tracebox_hits_trigger_hurt(this.origin, this.mins, this.maxs, trace_endpos ))
                if(this.items & IT_JETPACK)
@@ -576,12 +663,10 @@ void havocbot_movetogoal(entity this)
                        if(tracebox_hits_trigger_hurt(this.origin, this.mins, this.maxs, trace_endpos + '0 0 1' ))
                        {
                                if(this.velocity.z<0)
-                               {
-                                       PHYS_INPUT_BUTTON_HOOK(this) = true;
-                               }
+                                       PHYS_INPUT_BUTTON_JETPACK(this) = true;
                        }
                        else
-                               PHYS_INPUT_BUTTON_HOOK(this) = true;
+                               PHYS_INPUT_BUTTON_JETPACK(this) = true;
 
                        // If there is no goal try to move forward
 
@@ -610,7 +695,7 @@ void havocbot_movetogoal(entity this)
 
                        return;
                }
-               else if(this.health > WEP_CVAR(devastator, damage) * 0.5 * ((this.strength_finished < time) ? autocvar_g_balance_powerup_strength_selfdamage : 1))
+               else if(this.health + this.armorvalue > ROCKETJUMP_DAMAGE())
                {
                        if(this.velocity.z < 0)
                        {
@@ -653,8 +738,7 @@ void havocbot_movetogoal(entity this)
        }
 
        // If we are under water with no goals, swim up
-       if(this.waterlevel)
-       if(this.goalcurrent==NULL)
+       if(this.waterlevel && !this.goalcurrent)
        {
                dir = '0 0 0';
                if(this.waterlevel>WATERLEVEL_SWIMMING)
@@ -675,12 +759,17 @@ void havocbot_movetogoal(entity this)
 
 
        bool locked_goal = false;
-       if(this.goalentity && wasfreed(this.goalentity))
+       if((this.goalentity && wasfreed(this.goalentity))
+               || (this.goalcurrent == this.goalentity && this.goalentity.tag_entity))
        {
                navigation_clearroute(this);
-               this.bot_strategytime = 0;
+               navigation_goalrating_timeout_force(this);
                return;
        }
+       else if(this.goalentity.tag_entity)
+       {
+               navigation_goalrating_timeout_expire(this, 2);
+       }
        else if(this.goalentity.bot_pickup)
        {
                if(this.goalentity.bot_pickup_respawning)
@@ -689,28 +778,91 @@ void havocbot_movetogoal(entity this)
                                this.goalentity.bot_pickup_respawning = false;
                        else if(time < this.goalentity.scheduledrespawntime - 10) // item already taken (by someone else)
                        {
-                               this.goalentity.bot_pickup_respawning = false;
-                               navigation_clearroute(this);
-                               this.bot_strategytime = 0;
-                               return;
+                               if(checkpvs(this.origin, this.goalentity))
+                               {
+                                       this.goalentity.bot_pickup_respawning = false;
+                                       navigation_goalrating_timeout_expire(this, random());
+                               }
+                               locked_goal = true; // wait for item to respawn
                        }
                        else if(this.goalentity == this.goalcurrent)
                                locked_goal = true; // wait for item to respawn
                }
-               else if(!this.goalentity.solid)
+               else if(!this.goalentity.solid && !boxesoverlap(this.goalentity.absmin, this.goalentity.absmax, this.absmin, this.absmax))
                {
-                       navigation_clearroute(this);
-                       this.bot_strategytime = 0;
-                       return;
+                       if(checkpvs(this.origin, this.goalentity))
+                       {
+                               navigation_goalrating_timeout_expire(this, random());
+                       }
+               }
+       }
+       if (this.goalcurrent == this.goalentity && this.goalentity_lock_timeout > time)
+               locked_goal = true;
+
+       navigation_shortenpath(this);
+
+       if (IS_MOVABLE(this.goalcurrent))
+       {
+               if (IS_DEAD(this.goalcurrent))
+               {
+                       if (checkpvs(this.origin + this.view_ofs, this.goalcurrent))
+                       {
+                               navigation_goalrating_timeout_force(this);
+                               return;
+                       }
+               }
+               else if (this.bot_tracewalk_time < time)
+               {
+                       set_tracewalk_dest(this.goalcurrent, this.origin, true);
+                       if (!(trace_ent == this || tracewalk(this, this.origin, this.mins, this.maxs,
+                               tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode)))
+                       {
+                               navigation_goalrating_timeout_force(this);
+                               return;
+                       }
+                       this.bot_tracewalk_time = max(time, this.bot_tracewalk_time) + 0.25;
                }
        }
        if(!locked_goal)
-               navigation_poptouchedgoals(this);
+       {
+               // optimize path finding by anticipating goalrating when bot is near a waypoint;
+               // in this case path finding can start directly from a waypoint instead of
+               // looking for all the reachable waypoints up to a certain distance
+               if (navigation_poptouchedgoals(this))
+               {
+                       if (this.goalcurrent)
+                       {
+                               if (IS_MOVABLE(this.goalcurrent) && IS_DEAD(this.goalcurrent))
+                               {
+                                       // remove even if not visible
+                                       navigation_goalrating_timeout_force(this);
+                                       return;
+                               }
+                               else if (navigation_goalrating_timeout_can_be_anticipated(this))
+                                       navigation_goalrating_timeout_force(this);
+                       }
+                       else
+                       {
+                               entity old_goal = this.goalcurrent_prev;
+                               if (old_goal.item_group && this.item_group != old_goal.item_group)
+                               {
+                                       // Avoid multiple costly calls of path finding code that selects one of the closest
+                                       // item of the group by telling the bot to head directly to the farthest item.
+                                       // Next time we let the bot select a goal as usual which can be another item
+                                       // of this group (the closest one) and so on
+                                       this.item_group = old_goal.item_group;
+                                       entity new_goal = havocbot_select_an_item_of_group(this, old_goal.item_group);
+                                       if (new_goal)
+                                               navigation_pushroute(this, new_goal);
+                               }
+                       }
+               }
+       }
 
        // if ran out of goals try to use an alternative goal or get a new strategy asap
        if(this.goalcurrent == NULL)
        {
-               this.bot_strategytime = 0;
+               navigation_goalrating_timeout_force(this);
                return;
        }
 
@@ -718,23 +870,37 @@ void havocbot_movetogoal(entity this)
        if(autocvar_bot_debug_goalstack)
                debuggoalstack(this);
 
-       m1 = this.goalcurrent.origin + this.goalcurrent.mins;
-       m2 = this.goalcurrent.origin + this.goalcurrent.maxs;
-       destorg = this.origin;
-       destorg.x = bound(m1_x, destorg.x, m2_x);
-       destorg.y = bound(m1_y, destorg.y, m2_y);
-       destorg.z = bound(m1_z, destorg.z, m2_z);
+       bool bunnyhop_forbidden = false;
+       vector destorg = get_closer_dest(this.goalcurrent, this.origin);
+
+       // in case bot ends up inside the teleport waypoint without touching
+       // the teleport itself, head to the teleport origin
+       if(this.goalcurrent.wpisbox && boxesoverlap(this.goalcurrent.absmin, this.goalcurrent.absmax, this.origin + eZ * this.mins.z, this.origin + eZ * this.maxs.z))
+       {
+               bunnyhop_forbidden = true;
+               destorg = this.goalcurrent.origin;
+               if(destorg.z > this.origin.z)
+                       PHYS_INPUT_BUTTON_JUMP(this) = true;
+       }
+
        diff = destorg - this.origin;
-       //dist = vlen(diff);
+
+       if (fabs(diff.x) < 10 && fabs(diff.y) < 10
+               && this.goalcurrent == this.goalentity && time < this.goalentity_lock_timeout)
+       {
+               destorg = this.origin;
+               diff.x = 0;
+               diff.y = 0;
+       }
+
        dir = normalize(diff);
        flatdir = diff;flatdir.z = 0;
        flatdir = normalize(flatdir);
-       gco = (this.goalcurrent.absmin + this.goalcurrent.absmax) * 0.5;
 
        //if (this.bot_dodgevector_time < time)
        {
-       //      this.bot_dodgevector_time = time + cvar("bot_ai_dodgeupdateinterval");
-       //      this.bot_dodgevector_jumpbutton = 1;
+               //this.bot_dodgevector_time = time + cvar("bot_ai_dodgeupdateinterval");
+               //this.bot_dodgevector_jumpbutton = 1;
                evadeobstacle = '0 0 0';
                evadelava = '0 0 0';
 
@@ -744,18 +910,20 @@ void havocbot_movetogoal(entity this)
                {
                        if(this.waterlevel>WATERLEVEL_SWIMMING)
                        {
-                       //      flatdir_z = 1;
-                               this.aistatus |= AI_STATUS_OUT_WATER;
+                               if(!this.goalcurrent)
+                                       this.aistatus |= AI_STATUS_OUT_WATER;
+                               else if(destorg.z > this.origin.z)
+                                       PHYS_INPUT_BUTTON_JUMP(this) = true;
                        }
                        else
                        {
-                               if(this.velocity.z >= 0 && !(this.watertype == CONTENT_WATER && gco.z < this.origin.z) &&
+                               dir = flatdir;
+                               if(this.velocity.z >= 0 && !(this.watertype == CONTENT_WATER && destorg.z < this.origin.z) &&
                                        ( !(this.waterlevel == WATERLEVEL_WETFEET && this.watertype == CONTENT_WATER) || this.aistatus & AI_STATUS_OUT_WATER))
                                        PHYS_INPUT_BUTTON_JUMP(this) = true;
                                else
                                        PHYS_INPUT_BUTTON_JUMP(this) = false;
                        }
-                       dir = normalize(flatdir);
                }
                else
                {
@@ -766,62 +934,91 @@ void havocbot_movetogoal(entity this)
 
                        // jump if going toward an obstacle that doesn't look like stairs we
                        // can walk up directly
-                       offset = (vdist(this.velocity, >, 32) ? this.velocity * 0.2 : v_forward * 32);
-                       tracebox(this.origin, this.mins, this.maxs, this.origin + offset, false, this);
+                       vector deviation = '0 0 0';
+                       if (this.velocity)
+                       {
+                               deviation = vectoangles(diff) - vectoangles(this.velocity);
+                               while (deviation.y < -180) deviation.y += 360;
+                               while (deviation.y > 180) deviation.y -= 360;
+                       }
+                       vector flat_diff = vec2(diff);
+                       offset = max(32, vlen(vec2(this.velocity)) * cos(deviation.y * DEG2RAD) * 0.2) * flatdir;
+                       vector actual_destorg = this.origin + offset;
+                       if (!this.goalstack01 || this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
+                       {
+                               if (vlen2(flat_diff) < vlen2(offset))
+                               {
+                                       actual_destorg.x = destorg.x;
+                                       actual_destorg.y = destorg.y;
+                               }
+                       }
+                       else if (vdist(flat_diff, <, 32) && diff.z < -16) // destination is under the bot
+                       {
+                               actual_destorg.x = destorg.x;
+                               actual_destorg.y = destorg.y;
+                       }
+                       else if (vlen2(flat_diff) < vlen2(offset))
+                       {
+                               vector next_goal_org = (this.goalstack01.absmin + this.goalstack01.absmax) * 0.5;
+                               vector next_dir = normalize(vec2(next_goal_org - destorg));
+                               float next_dist = vlen(vec2(this.origin + offset - destorg));
+                               actual_destorg = vec2(destorg) + next_dist * next_dir;
+                               actual_destorg.z = this.origin.z;
+                       }
+
+                       tracebox(this.origin, this.mins, this.maxs, actual_destorg, false, this);
                        if (trace_fraction < 1)
                        if (trace_plane_normal.z < 0.7)
                        {
                                s = trace_fraction;
-                               tracebox(this.origin + stepheightvec, this.mins, this.maxs, this.origin + offset + stepheightvec, false, this);
+                               tracebox(this.origin + stepheightvec, this.mins, this.maxs, actual_destorg + stepheightvec, false, this);
                                if (trace_fraction < s + 0.01)
                                if (trace_plane_normal.z < 0.7)
                                {
                                        s = trace_fraction;
-                                       tracebox(this.origin + jumpstepheightvec, this.mins, this.maxs, this.origin + offset + jumpstepheightvec, false, this);
+                                       tracebox(this.origin + jumpstepheightvec, this.mins, this.maxs, actual_destorg + jumpstepheightvec, false, this);
                                        if (trace_fraction > s)
                                                PHYS_INPUT_BUTTON_JUMP(this) = true;
                                }
                        }
 
                        // if bot for some reason doesn't get close to the current goal find another one
-                       if(!IS_PLAYER(this.goalcurrent) && !(this.goalcurrent.bot_pickup_respawning && this.goalcurrent_distance < 50))
+                       if(!this.jumppadcount && !IS_PLAYER(this.goalcurrent))
+                       if(!(locked_goal && this.goalcurrent_distance_z < 50 && this.goalcurrent_distance_2d < 50))
+                       if(havocbot_checkgoaldistance(this, destorg))
                        {
-                               float curr_dist = vlen(this.origin - this.goalcurrent.origin);
-                               if(this.goalcurrent != this.goalcurrent_prev)
-                               {
-                                       this.goalcurrent_prev = this.goalcurrent;
-                                       this.goalcurrent_distance = curr_dist;
-                                       this.goalcurrent_distance_time = 0;
-                               }
-                               else if(curr_dist > this.goalcurrent_distance)
+                               if(this.goalcurrent_distance_time < 0) // can't get close for the second time
                                {
-                                       if(!this.goalcurrent_distance_time)
-                                               this.goalcurrent_distance_time = time;
-                                       else if (time - this.goalcurrent_distance_time > 0.5)
-                                       {
-                                               this.goalcurrent_prev = NULL;
-                                               navigation_clearroute(this);
-                                               this.bot_strategytime = 0;
-                                               return;
-                                       }
+                                       navigation_clearroute(this);
+                                       navigation_goalrating_timeout_force(this);
+                                       return;
                                }
-                               else
+
+                               set_tracewalk_dest(this.goalcurrent, this.origin, false);
+                               if (!tracewalk(this, this.origin, this.mins, this.maxs,
+                                       tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode))
                                {
-                                       // reduce it a little bit so it works even with very small approaches to the goal
-                                       this.goalcurrent_distance = max(20, curr_dist - 15);
-                                       this.goalcurrent_distance_time = 0;
+                                       navigation_clearroute(this);
+                                       navigation_goalrating_timeout_force(this);
+                                       return;
                                }
+
+                               // give bot only another chance to prevent bot getting stuck
+                               // in case it thinks it can walk but actually can't
+                               this.goalcurrent_distance_z = FLOAT_MAX;
+                               this.goalcurrent_distance_2d = FLOAT_MAX;
+                               this.goalcurrent_distance_time = -time; // mark second try
                        }
 
                        // Check for water/slime/lava and dangerous edges
                        // (only when the bot is on the ground or jumping intentionally)
 
+                       offset = (vdist(this.velocity, >, 32) ? this.velocity * 0.2 : v_forward * 32);
                        vector dst_ahead = this.origin + this.view_ofs + offset;
                        vector dst_down = dst_ahead - '0 0 3000';
                        traceline(this.origin + this.view_ofs, dst_ahead, true, NULL);
 
                        bool unreachable = false;
-                       bool ignorehazards = false;
                        s = CONTENT_SOLID;
                        if(trace_fraction == 1 && this.jumppadcount == 0 && !this.goalcurrent.wphardwired )
                        if((IS_ONGROUND(this)) || (this.aistatus & AI_STATUS_RUNNING) || (this.aistatus & AI_STATUS_ROAMING) || PHYS_INPUT_BUTTON_JUMP(this))
@@ -835,16 +1032,7 @@ void havocbot_movetogoal(entity this)
                                        s = pointcontents(trace_endpos + '0 0 1');
                                        if (s != CONTENT_SOLID)
                                        if (s == CONTENT_LAVA || s == CONTENT_SLIME)
-                                       {
                                                evadelava = normalize(this.velocity) * -1;
-                                               if(this.waterlevel >= WATERLEVEL_WETFEET && (this.watertype == CONTENT_LAVA || this.watertype == CONTENT_SLIME))
-                                                       ignorehazards = true;
-                                       }
-                                       else if (s == CONTENT_WATER)
-                                       {
-                                               if(this.waterlevel >= WATERLEVEL_WETFEET && this.watertype == CONTENT_WATER)
-                                                       ignorehazards = true;
-                                       }
                                        else if (s == CONTENT_SKY)
                                                evadeobstacle = normalize(this.velocity) * -1;
                                        else if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
@@ -854,7 +1042,7 @@ void havocbot_movetogoal(entity this)
                                                tracebox(dst_ahead, this.mins, this.maxs, dst_down, true, this);
                                                if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
                                                {
-                                                       if (gco.z > this.origin.z + jumpstepheightvec.z)
+                                                       if (destorg.z > this.origin.z + jumpstepheightvec.z)
                                                        {
                                                                // the goal is probably on an upper platform, assume bot can't get there
                                                                unreachable = true;
@@ -873,15 +1061,16 @@ void havocbot_movetogoal(entity this)
 
                        if(evadeobstacle || evadelava || (s == CONTENT_WATER))
                        {
-                               if(!ignorehazards)
-                                       this.aistatus |= AI_STATUS_DANGER_AHEAD;
+                               this.aistatus |= AI_STATUS_DANGER_AHEAD;
                                if(IS_PLAYER(this.goalcurrent))
                                        unreachable = true;
                        }
                        if(unreachable)
                        {
                                navigation_clearroute(this);
-                               this.bot_strategytime = 0;
+                               navigation_goalrating_timeout_force(this);
+                               this.ignoregoal = this.goalcurrent;
+                               this.ignoregoaltime = time + autocvar_bot_ai_ignoregoal_timeout;
                        }
                }
 
@@ -923,8 +1112,8 @@ void havocbot_movetogoal(entity this)
                havocbot_keyboard_movement(this, destorg);
 
        // Bunnyhop!
-//     if(this.aistatus & AI_STATUS_ROAMING)
-       if(this.goalcurrent)
+       //if(this.aistatus & AI_STATUS_ROAMING)
+       if(!bunnyhop_forbidden && this.goalcurrent)
        if(skill+this.bot_moveskill >= autocvar_bot_ai_bunnyhop_skilloffset)
                havocbot_bunnyhop(this, dir);
 
@@ -1265,7 +1454,8 @@ float havocbot_moveto(entity this, vector pos)
                        debuggoalstack(this);
 
                // Heading
-               vector dir = ( ( this.goalcurrent.absmin + this.goalcurrent.absmax ) * 0.5 ) - (this.origin + this.view_ofs);
+               vector dir = get_closer_dest(this.goalcurrent, this.origin);
+               dir = dir - (this.origin + this.view_ofs);
                dir.z = 0;
                bot_aimdir(this, dir, -1);
 
index 88ba407a2d68aca2dd10f96bbe028c79de4987a9..2f987f674ec8719503137e05452abe5e7701fd1e 100644 (file)
@@ -23,6 +23,7 @@
 .float havocbot_stickenemy;
 .float havocbot_role_timeout;
 
+.float bot_tracewalk_time;
 .entity ignoregoal;
 .entity bot_lastseengoal;
 .entity havocbot_personal_waypoint;
index 94aed9c96e1219333a5c792572b7a33e74a9a0cd..e469436014e1e030cc7dd1fc3bad16e61ad9fb11 100644 (file)
@@ -2,6 +2,7 @@
 
 #include <server/defs.qh>
 #include <server/miscfunctions.qh>
+#include <server/items.qh>
 #include "havocbot.qh"
 
 #include "../cvars.qh"
@@ -9,6 +10,8 @@
 #include "../bot.qh"
 #include "../navigation.qh"
 
+.float bot_ratingscale;
+.float bot_ratingscale_time;
 .float max_armorvalue;
 .float havocbot_role_timeout;
 
@@ -44,15 +47,81 @@ void havocbot_goalrating_waypoints(entity this, float ratingscale, vector org, f
        }
 };
 
+bool havocbot_goalrating_item_can_be_left_to_teammate(entity this, entity player, entity item)
+{
+       if (item.health && player.health <= this.health) {return true;}
+       if (item.armorvalue && player.armorvalue <= this.armorvalue) {return true;}
+       if (item.weapons && !(player.weapons & item.weapons)) {return true;}
+       if (item.ammo_shells && player.ammo_shells <= this.ammo_shells) {return true;}
+       if (item.ammo_nails && player.ammo_nails <= this.ammo_nails) {return true;}
+       if (item.ammo_rockets && player.ammo_rockets <= this.ammo_rockets) {return true;}
+       if (item.ammo_cells && player.ammo_cells <= this.ammo_cells) {return true;}
+       if (item.ammo_plasma && player.ammo_plasma <= this.ammo_plasma) {return true;}
+       if (item.itemdef.instanceOfPowerup) {return true;}
+
+       return false;
+};
+
+bool havocbot_goalrating_item_pickable_check_players(entity this, vector org, entity item, vector item_org)
+{
+       if(!teamplay)
+               return true;
+
+       // actually these variables hold the squared distances in order to optimize code
+       float friend_distance = FLOAT_MAX;
+       float enemy_distance = FLOAT_MAX;
+       float dist;
+
+       FOREACH_CLIENT(IS_PLAYER(it) && it != this && !IS_DEAD(it),
+       {
+               if (it.team == this.team)
+               {
+                       if (!IS_REAL_CLIENT(it))
+                               continue;
+
+                       dist = vlen2(it.origin - item_org);
+                       if(dist > friend_distance)
+                               continue;
+
+                       if(havocbot_goalrating_item_can_be_left_to_teammate(this, it, item))
+                       {
+                               friend_distance = dist;
+                               continue;
+                       }
+               }
+               else
+               {
+                       // If enemy only track distances
+                       // TODO: track only if visible ?
+                       dist = vlen2(it.origin - item_org);
+                       if(dist < enemy_distance)
+                               enemy_distance = dist;
+               }
+       });
+
+       // Rate the item only if no one needs it, or if an enemy is closer to it
+       dist = vlen2(item_org - org);
+       if ((enemy_distance < friend_distance && dist < enemy_distance) ||
+               (friend_distance > autocvar_bot_ai_friends_aware_pickup_radius ** 2) ||
+               (dist < friend_distance && dist < 200 ** 2))
+               return true;
+       return false;
+};
+
 void havocbot_goalrating_items(entity this, float ratingscale, vector org, float sradius)
 {
-       float rating, discard, friend_distance, enemy_distance;
+       float rating;
        vector o;
        ratingscale = ratingscale * 0.0001; // items are rated around 10000 already
 
        IL_EACH(g_items, it.bot_pickup,
        {
-               rating = 0;
+               // ignore if bot already rated this item with a higher ratingscale
+               // NOTE: this code assumes each bot rates items in a different frame
+               if(it.bot_ratingscale_time == time && ratingscale < it.bot_ratingscale)
+                       continue;
+               it.bot_ratingscale_time = time;
+               it.bot_ratingscale = ratingscale;
 
                if(!it.solid)
                {
@@ -81,14 +150,15 @@ void havocbot_goalrating_items(entity this, float ratingscale, vector org, float
                        continue;
 
                // Check if the item can be picked up safely
-               if(it.classname == "droppedweapon")
+               if(Item_IsLoot(it))
                {
                        if(!IS_ONGROUND(it))
                                continue;
                        traceline(o, o + '0 0 -1500', true, NULL);
 
-                       if(Mod_Q1BSP_SuperContentsFromNativeContents(pointcontents(trace_endpos + '0 0 1')) & DPCONTENTS_LIQUIDSMASK)
+                       if(IN_LAVA(trace_endpos + '0 0 1'))
                                continue;
+
                        // this tracebox_hits_trigger_hurt call isn't needed:
                        // dropped weapons are removed as soon as they fall on a trigger_hurt
                        // and can't be rated while they are in the air
@@ -97,61 +167,14 @@ void havocbot_goalrating_items(entity this, float ratingscale, vector org, float
                }
                else
                {
-                       // Ignore items under water
-                       // TODO: can't .waterlevel be used here?
-                       if(Mod_Q1BSP_SuperContentsFromNativeContents(pointcontents(it.origin + ((it.mins + it.maxs) * 0.5))) & DPCONTENTS_LIQUIDSMASK)
+                       if(IN_LAVA(it.origin + (it.mins + it.maxs) * 0.5))
                                continue;
                }
 
-               if(teamplay)
-               {
-                       friend_distance = 10000; enemy_distance = 10000;
-                       discard = false;
-
-                       entity picker = it;
-                       FOREACH_CLIENT(IS_PLAYER(it) && it != this && !IS_DEAD(it),
-                       {
-                               if ( it.team == this.team )
-                               {
-                                       if ( !IS_REAL_CLIENT(it) || discard )
-                                               continue;
-
-                                       if( vdist(it.origin - o, >, friend_distance) )
-                                               continue;
-
-                                       friend_distance = vlen(it.origin - o); // distance between player and item
-                                       discard = true;
-
-                                       if (picker.health && it.health > this.health) continue;
-                                       if (picker.armorvalue && it.armorvalue > this.armorvalue) continue;
-
-                                       if (picker.weapons && (picker.weapons & ~it.weapons)) continue;
-
-                                       if (picker.ammo_shells && it.ammo_shells > this.ammo_shells) continue;
-                                       if (picker.ammo_nails && it.ammo_nails > this.ammo_nails) continue;
-                                       if (picker.ammo_rockets && it.ammo_rockets > this.ammo_rockets) continue;
-                                       if (picker.ammo_cells && it.ammo_cells > this.ammo_cells) continue;
-                                       if (picker.ammo_plasma && it.ammo_plasma > this.ammo_plasma) continue;
-
-                                       discard = false;
-                               }
-                               else
-                               {
-                                       // If enemy only track distances
-                                       // TODO: track only if visible ?
-                                       if( vdist(it.origin - o, <, enemy_distance) )
-                                               enemy_distance = vlen(it.origin - o); // distance between player and item
-                               }
-                       });
-
-                       // Rate the item only if no one needs it, or if an enemy is closer to it
-                       if ( (enemy_distance < friend_distance && vdist(o - org, <, enemy_distance)) ||
-                               (friend_distance > autocvar_bot_ai_friends_aware_pickup_radius ) || !discard )
-                               rating = it.bot_pickupevalfunc(this, it);
-               }
-               else
-                       rating = it.bot_pickupevalfunc(this, it);
+               if(!havocbot_goalrating_item_pickable_check_players(this, org, it, o))
+                       continue;
 
+               rating = it.bot_pickupevalfunc(this, it);
                if(rating > 0)
                        navigation_routerating(this, it, rating * ratingscale, 2000);
        });
@@ -174,6 +197,8 @@ void havocbot_goalrating_enemyplayers(entity this, float ratingscale, vector org
                // TODO: Merge this logic with the bot_shouldattack function
                if(vdist(it.origin - org, <, 100) || vdist(it.origin - org, >, sradius))
                        continue;
+               if(vdist(vec2(it.velocity), >, autocvar_sv_maxspeed * 2))
+                       continue;
 
                // rate only visible enemies
                /*
@@ -188,6 +213,8 @@ void havocbot_goalrating_enemyplayers(entity this, float ratingscale, vector org
                {
                        if (time < this.strength_finished - 1) t += 0.5;
                        if (time < it.strength_finished - 1) t -= 0.5;
+                       if (time < this.invincible_finished - 1) t += 0.2;
+                       if (time < it.invincible_finished - 1) t -= 0.4;
                }
                t += max(0, 8 - skill) * 0.05; // less skilled bots attack more mindlessly
                ratingscale *= t;
@@ -203,17 +230,15 @@ void havocbot_role_generic(entity this)
        if(IS_DEAD(this))
                return;
 
-       if (this.bot_strategytime < time)
+       if (navigation_goalrating_timeout(this))
        {
-               this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
                navigation_goalrating_start(this);
                havocbot_goalrating_items(this, 10000, this.origin, 10000);
                havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
                havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
                navigation_goalrating_end(this);
 
-               if(IS_PLAYER(this.goalentity))
-                       this.bot_strategytime = time + min(2, autocvar_bot_ai_strategyinterval);
+               navigation_goalrating_timeout_set(this);
        }
 }
 
@@ -225,7 +250,7 @@ void havocbot_chooserole_generic(entity this)
 void havocbot_chooserole(entity this)
 {
        LOG_TRACE("choosing a role...");
-       this.bot_strategytime = 0;
+       navigation_goalrating_timeout_force(this);
        if(!MUTATOR_CALLHOOK(HavocBot_ChooseRole, this))
                havocbot_chooserole_generic(this);
 }
index d0061b90068efbcade910b2332e20c5aff9198b5..7b80a1a6f27cfd790fb59f325f8eda9404dfbcf9 100644 (file)
 
 .float speed;
 
+void navigation_goalrating_timeout_set(entity this)
+{
+       if(IS_MOVABLE(this.goalentity))
+               this.bot_strategytime = time + autocvar_bot_ai_strategyinterval_movingtarget;
+       else
+               this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+}
+
+// use this when current goal must be discarded immediately
+void navigation_goalrating_timeout_force(entity this)
+{
+       navigation_goalrating_timeout_expire(this, 0);
+}
+
+// use this when current goal can be kept for a short while to increase the chance
+// of bot touching a waypoint, which helps to find a new goal more efficiently
+void navigation_goalrating_timeout_expire(entity this, float seconds)
+{
+       if (seconds <= 0)
+               this.bot_strategytime = 0;
+       else if (this.bot_strategytime > time + seconds)
+               this.bot_strategytime = time + seconds;
+}
+
+bool navigation_goalrating_timeout(entity this)
+{
+       return this.bot_strategytime < time;
+}
+
+#define MAX_CHASE_DISTANCE 700
+bool navigation_goalrating_timeout_can_be_anticipated(entity this)
+{
+       if(time > this.bot_strategytime - (IS_MOVABLE(this.goalentity) ? 3 : 2))
+               return true;
+
+       if (this.goalentity.bot_pickup && time > this.bot_strategytime - 5)
+       {
+               vector gco = (this.goalentity.absmin + this.goalentity.absmax) * 0.5;
+               if(!havocbot_goalrating_item_pickable_check_players(this, this.origin, this.goalentity, gco))
+               {
+                       this.ignoregoal = this.goalentity;
+                       this.ignoregoaltime = time + autocvar_bot_ai_ignoregoal_timeout;
+                       return true;
+               }
+       }
+       return false;
+}
+
 void navigation_dynamicgoal_init(entity this, bool initially_static)
 {
        this.navigation_dynamicgoal = true;
@@ -30,6 +78,8 @@ void navigation_dynamicgoal_init(entity this, bool initially_static)
 void navigation_dynamicgoal_set(entity this)
 {
        this.nearestwaypointtimeout = time;
+       if (this.nearestwaypoint)
+               this.nearestwaypointtimeout += 2;
 }
 
 void navigation_dynamicgoal_unset(entity this)
@@ -39,49 +89,185 @@ void navigation_dynamicgoal_unset(entity this)
        this.nearestwaypointtimeout = -1;
 }
 
-// rough simulation of walking from one point to another to test if a path
-// can be traveled, used for waypoint linking and havocbot
+// returns point of ent closer to org
+vector get_closer_dest(entity ent, vector org)
+{
+       vector dest = '0 0 0';
+       if ((ent.classname != "waypoint") || ent.wpisbox)
+       {
+               vector wm1 = ent.origin + ent.mins;
+               vector wm2 = ent.origin + ent.maxs;
+               dest.x = bound(wm1.x, org.x, wm2.x);
+               dest.y = bound(wm1.y, org.y, wm2.y);
+               dest.z = bound(wm1.z, org.z, wm2.z);
+       }
+       else
+               dest = ent.origin;
+       return dest;
+}
 
-bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float movemode)
+void set_tracewalk_dest(entity ent, vector org, bool fix_player_dest)
 {
-       vector org;
-       vector move;
-       vector dir;
-       float dist;
-       float totaldist;
-       float stepdist;
-       float ignorehazards;
-       float swimming;
-       entity tw_ladder = NULL;
+       if ((ent.classname != "waypoint") || ent.wpisbox)
+       {
+               vector wm1 = ent.origin + ent.mins;
+               vector wm2 = ent.origin + ent.maxs;
+               if (IS_PLAYER(ent) || IS_MONSTER(ent))
+               {
+                       // move destination point out of player bbox otherwise tracebox always fails
+                       // (if bot_navigation_ignoreplayers is false)
+                       wm1 += vec2(PL_MIN_CONST) + '-1 -1 0';
+                       wm2 += vec2(PL_MAX_CONST) + '1 1 0';
+               }
+               // set destination point to x and y coords of ent that are closer to org
+               // z coord is set to ent's min height
+               tracewalk_dest.x = bound(wm1.x, org.x, wm2.x);
+               tracewalk_dest.y = bound(wm1.y, org.y, wm2.y);
+               tracewalk_dest.z = wm1.z;
+               tracewalk_dest_height = wm2.z - wm1.z; // destination height
+       }
+       else
+       {
+               tracewalk_dest = ent.origin;
+               tracewalk_dest_height = 0;
+       }
+       if (fix_player_dest && IS_PLAYER(ent) && !IS_ONGROUND(ent))
+       {
+               // snap player to the ground
+               if (org.x == tracewalk_dest.x && org.y == tracewalk_dest.y)
+               {
+                       // bot is right under the player
+                       tracebox(ent.origin, ent.mins, ent.maxs, ent.origin - '0 0 700', MOVE_NORMAL, ent);
+                       tracewalk_dest = trace_endpos;
+                       tracewalk_dest_height = 0;
+               }
+               else
+               {
+                       tracebox(tracewalk_dest, ent.mins, ent.maxs, tracewalk_dest - '0 0 700', MOVE_NORMAL, ent);
+                       if (!trace_startsolid && tracewalk_dest.z - trace_endpos.z > 0)
+                       {
+                               tracewalk_dest_height = tracewalk_dest.z - trace_endpos.z;
+                               tracewalk_dest.z = trace_endpos.z;
+                       }
+               }
+       }
+}
+
+// returns point of ent closer to org
+vector set_tracewalk_dest_2(entity ent, vector org)
+{
+       vector closer_dest = '0 0 0';
+       if ((ent.classname != "waypoint") || ent.wpisbox)
+       {
+               vector wm1 = ent.origin + ent.mins;
+               vector wm2 = ent.origin + ent.maxs;
+               closer_dest.x = bound(wm1.x, org.x, wm2.x);
+               closer_dest.y = bound(wm1.y, org.y, wm2.y);
+               closer_dest.z = bound(wm1.z, org.z, wm2.z);
+               // set destination point to x and y coords of ent that are closer to org
+               // z coord is set to ent's min height
+               tracewalk_dest.x = closer_dest.x;
+               tracewalk_dest.y = closer_dest.y;
+               tracewalk_dest.z = wm1.z;
+               tracewalk_dest_height = wm2.z - wm1.z; // destination height
+       }
+       else
+       {
+               closer_dest = ent.origin;
+               tracewalk_dest = closer_dest;
+               tracewalk_dest_height = 0;
+       }
+       return closer_dest;
+}
+
+bool navigation_check_submerged_state(entity ent, vector pos)
+{
+       bool submerged;
+       if(IS_PLAYER(ent))
+               submerged = (ent.waterlevel == WATERLEVEL_SUBMERGED);
+       else if(ent.nav_submerged_state != SUBMERGED_UNDEFINED)
+               submerged = (ent.nav_submerged_state == SUBMERGED_YES);
+       else
+       {
+               submerged = SUBMERGED(pos);
+               // NOTE: SUBMERGED check of box waypoint origin may fail even if origin
+               //  is actually submerged because often they are inside some solid.
+               //  That's why submerged state is saved now that we know current pos is
+               //  not stuck in solid (previous tracewalk call to this pos was successfully)
+               if(!ent.navigation_dynamicgoal)
+                       ent.nav_submerged_state = (submerged) ? SUBMERGED_YES : SUBMERGED_NO;
+       }
+       return submerged;
+}
+
+bool navigation_checkladders(entity e, vector org, vector m1, vector m2, vector end, vector end2, int movemode)
+{
+       IL_EACH(g_ladders, it.classname == "func_ladder",
+       {
+               if(it.bot_pickup)
+               if(boxesoverlap(org + m1 + '-1 -1 -1', org + m2 + '1 1 1', it.absmin, it.absmax))
+               if(boxesoverlap(end, end2, it.absmin + vec2(m1) + '-1 -1 0', it.absmax + vec2(m2) + '1 1 0'))
+               {
+                       vector top = org;
+                       top.z = it.absmax.z + (PL_MAX_CONST.z - PL_MIN_CONST.z);
+                       tracebox(org, m1, m2, top, movemode, e);
+                       if(trace_fraction == 1)
+                               return true;
+               }
+       });
+       return false;
+}
+
+vector resurface_limited(vector org, float lim, vector m1)
+{
+       if (WETFEET(org + eZ * (lim - org.z)))
+               org.z = lim;
+       else
+       {
+               float RES_min_h = org.z;
+               float RES_max_h = lim;
+               do {
+                       org.z = 0.5 * (RES_min_h + RES_max_h);
+                       if(WETFEET(org))
+                               RES_min_h = org.z;
+                       else
+                               RES_max_h = org.z;
+               } while (RES_max_h - RES_min_h >= 1);
+               org.z = RES_min_h;
+       }
+       return org;
+}
+#define RESURFACE_LIMITED(org, lim) org = resurface_limited(org, lim, m1)
+
+#define NAV_WALK 0
+#define NAV_SWIM_ONWATER 1
+#define NAV_SWIM_UNDERWATER 2
 
+// rough simulation of walking from one point to another to test if a path
+// can be traveled, used for waypoint linking and havocbot
+// if end_height is > 0 destination is any point in the vertical segment [end, end + end_height * eZ]
+bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float end_height, float movemode)
+{
        if(autocvar_bot_debug_tracewalk)
        {
                debugresetnodes();
                debugnode(e, start);
        }
 
-       move = end - start;
-       move.z = 0;
-       org = start;
-       dist = totaldist = vlen(move);
-       dir = normalize(move);
-       stepdist = 32;
-       ignorehazards = false;
-       swimming = false;
+       vector org = start;
+       vector flatdir = end - start;
+       flatdir.z = 0;
+       float flatdist = vlen(flatdir);
+       flatdir = normalize(flatdir);
+       float stepdist = 32;
+       bool ignorehazards = false;
+       int nav_action;
 
        // Analyze starting point
        traceline(start, start, MOVE_NORMAL, e);
        if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA))
                ignorehazards = true;
-       else
-       {
-               traceline( start, start + '0 0 -65536', MOVE_NORMAL, e);
-               if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA))
-               {
-                       ignorehazards = true;
-                       swimming = true;
-               }
-       }
+
        tracebox(start, m1, m2, start, MOVE_NOMONSTERS, e);
        if (trace_startsolid)
        {
@@ -93,78 +279,309 @@ bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float m
                return false;
        }
 
+       vector end2 = end;
+       if(end_height)
+               end2.z += end_height;
+
+       vector fixed_end = end;
+       vector move;
+
+       if (flatdist > 0 && WETFEET(org))
+       {
+               if (SUBMERGED(org))
+                       nav_action = NAV_SWIM_UNDERWATER;
+               else
+               {
+                       // tracebox down by player's height
+                       // useful to know if water level is so low that bot can still walk
+                       tracebox(org, m1, m2, org - eZ * (m2.z - m1.z), movemode, e);
+                       if (SUBMERGED(trace_endpos))
+                       {
+                               org = trace_endpos;
+                               nav_action = NAV_SWIM_UNDERWATER;
+                       }
+                       else
+                               nav_action = NAV_WALK;
+               }
+       }
+       else
+               nav_action =  NAV_WALK;
+
        // Movement loop
-       move = end - org;
-       for (;;)
+       while (true)
        {
-               if (boxesoverlap(end, end, org + m1 + '-1 -1 -1', org + m2 + '1 1 1'))
+               if (flatdist <= 0)
                {
-                       // Succeeded
-                       if(autocvar_bot_debug_tracewalk)
-                               debugnodestatus(org, DEBUG_NODE_SUCCESS);
+                       bool success = true;
+                       if (org.z > end2.z + 1)
+                       {
+                               tracebox(org, m1, m2, end2, movemode, e);
+                               org = trace_endpos;
+                               if (org.z > end2.z + 1)
+                                       success = false;
+                       }
+                       else if (org.z < end.z - 1)
+                       {
+                               tracebox(org, m1, m2, org - jumpheight_vec, movemode, e);
+                               if (SUBMERGED(trace_endpos))
+                               {
+                                       vector v = trace_endpos;
+                                       tracebox(v, m1, m2, end, movemode, e);
+                                       if(trace_endpos.z >= end.z - 1)
+                                       {
+                                               RESURFACE_LIMITED(v, trace_endpos.z);
+                                               trace_endpos = v;
+                                       }
+                               }
+                               else if (trace_endpos.z > org.z - jumpheight_vec.z)
+                                       tracebox(trace_endpos, m1, m2, trace_endpos + jumpheight_vec, movemode, e);
+                               org = trace_endpos;
+                               if (org.z < end.z - 1)
+                                       success = false;
+                       }
 
-                       //print("tracewalk: ", vtos(start), " can reach ", vtos(end), "\n");
-                       return true;
+                       if (success)
+                       {
+                               // Succeeded
+                               if(autocvar_bot_debug_tracewalk)
+                               {
+                                       debugnode(e, org);
+                                       debugnodestatus(org, DEBUG_NODE_SUCCESS);
+                               }
+
+                               //print("tracewalk: ", vtos(start), " can reach ", vtos(end), "\n");
+                               return true;
+                       }
                }
+
                if(autocvar_bot_debug_tracewalk)
                        debugnode(e, org);
 
-               if (dist <= 0)
+               if (flatdist <= 0)
                        break;
-               if (stepdist > dist)
-                       stepdist = dist;
-               dist = dist - stepdist;
-               traceline(org, org, MOVE_NORMAL, e);
-               if (!ignorehazards)
-               {
-                       if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA))
-                       {
-                               // hazards blocking path
-                               if(autocvar_bot_debug_tracewalk)
-                                       debugnodestatus(org, DEBUG_NODE_FAIL);
 
-                               //print("tracewalk: ", vtos(start), " hits a hazard when trying to reach ", vtos(end), "\n");
-                               return false;
+               if (stepdist > flatdist)
+                       stepdist = flatdist;
+               if(nav_action == NAV_SWIM_UNDERWATER || (nav_action == NAV_SWIM_ONWATER && org.z > end2.z))
+               {
+                       // can't use movement direction here to calculate move because of
+                       // precision errors especially when direction has a high enough z value
+                       //water_dir = normalize(water_end - org);
+                       //move = org + water_dir * stepdist;
+                       fixed_end.z = bound(end.z, org.z, end2.z);
+                       if (stepdist == flatdist) {
+                               move = fixed_end;
+                               flatdist = 0;
+                       } else {
+                               move = org + (fixed_end - org) * (stepdist / flatdist);
+                               flatdist = vlen(vec2(fixed_end - move));
                        }
                }
-               if (trace_dpstartcontents & DPCONTENTS_LIQUIDSMASK)
+               else // horiz. direction
                {
-                       move = normalize(end - org);
-                       tracebox(org, m1, m2, org + move * stepdist, movemode, e);
+                       flatdist -= stepdist;
+                       move = org + flatdir * stepdist;
+               }
 
-                       if(autocvar_bot_debug_tracewalk)
-                               debugnode(e, trace_endpos);
+               if(nav_action == NAV_SWIM_ONWATER)
+               {
+                       tracebox(org, m1, m2, move, movemode, e); // swim
 
+                       // hit something
                        if (trace_fraction < 1)
                        {
-                               swimming = true;
-                               org = trace_endpos + normalize(org - trace_endpos) * stepdist;
-                               for (; org.z < end.z + e.maxs.z; org.z += stepdist)
+                               // stepswim
+                               tracebox(org + stepheightvec, m1, m2, move + stepheightvec, movemode, e);
+
+                               if (trace_fraction < 1 || trace_startsolid) // can't jump obstacle out of water
                                {
-                                       if(autocvar_bot_debug_tracewalk)
-                                               debugnode(e, org);
+                                       org = trace_endpos;
+                                       if(navigation_checkladders(e, org, m1, m2, end, end2, movemode))
+                                       {
+                                               if(autocvar_bot_debug_tracewalk)
+                                               {
+                                                       debugnode(e, org);
+                                                       debugnodestatus(org, DEBUG_NODE_SUCCESS);
+                                               }
 
-                                       if(pointcontents(org) == CONTENT_EMPTY)
-                                               break;
-                               }
+                                               //print("tracewalk: ", vtos(start), " can reach ", vtos(end), "\n");
+                                               return true;
+                                       }
 
-                               if(pointcontents(org + '0 0 1') != CONTENT_EMPTY)
-                               {
                                        if(autocvar_bot_debug_tracewalk)
                                                debugnodestatus(org, DEBUG_NODE_FAIL);
 
                                        return false;
-                                       //print("tracewalk: ", vtos(start), " failed under water\n");
+                                       //print("tracewalk: ", vtos(start), " hit something when trying to reach ", vtos(end), "\n");
+                               }
+
+                               //succesful stepswim
+
+                               if (flatdist <= 0)
+                               {
+                                       org = trace_endpos;
+                                       continue;
                                }
-                               continue;
 
+                               if (org.z <= move.z) // going horiz.
+                               {
+                                       tracebox(trace_endpos, m1, m2, move, movemode, e);
+                                       org = trace_endpos;
+                                       nav_action = NAV_WALK;
+                                       continue;
+                               }
                        }
-                       else
+
+                       if (org.z <= move.z) // going horiz.
+                       {
                                org = trace_endpos;
+                               nav_action = NAV_SWIM_ONWATER;
+                       }
+                       else // going down
+                       {
+                               org = trace_endpos;
+                               if (SUBMERGED(org))
+                                       nav_action = NAV_SWIM_UNDERWATER;
+                               else
+                                       nav_action = NAV_SWIM_ONWATER;
+                       }
                }
-               else
+               else if(nav_action == NAV_SWIM_UNDERWATER)
+               {
+                       if (move.z >= org.z) // swimming upwards or horiz.
+                       {
+                               tracebox(org, m1, m2, move, movemode, e); // swim
+
+                               bool stepswum = false;
+
+                               // hit something
+                               if (trace_fraction < 1)
+                               {
+                                       // stepswim
+                                       vector stepswim_move = move + stepheightvec;
+                                       if (flatdist > 0 && stepswim_move.z > end2.z + stepheightvec.z) // don't allow stepswim to go higher than destination
+                                               stepswim_move.z = end2.z;
+
+                                       tracebox(org + stepheightvec, m1, m2, stepswim_move, movemode, e);
+
+                                       // hit something
+                                       if (trace_startsolid)
+                                       {
+                                               if(autocvar_bot_debug_tracewalk)
+                                                       debugnodestatus(org, DEBUG_NODE_FAIL);
+
+                                               //print("tracewalk: ", vtos(start), " hit something when trying to reach ", vtos(end), "\n");
+                                               return false;
+                                       }
+
+                                       if (trace_fraction < 1)
+                                       {
+                                               float org_z_prev = org.z;
+                                               RESURFACE_LIMITED(org, end2.z);
+                                               if(org.z == org_z_prev)
+                                               {
+                                                       if(autocvar_bot_debug_tracewalk)
+                                                               debugnodestatus(org, DEBUG_NODE_FAIL);
+
+                                                       //print("tracewalk: ", vtos(start), " can't reach ", vtos(end), "\n");
+                                                       return false;
+                                               }
+                                               if(SUBMERGED(org))
+                                                       nav_action = NAV_SWIM_UNDERWATER;
+                                               else
+                                                       nav_action = NAV_SWIM_ONWATER;
+
+                                               // we didn't advance horiz. in this step, flatdist decrease should be reverted
+                                               // but we can't do it properly right now... apply this workaround instead
+                                               if (flatdist <= 0)
+                                                       flatdist = 1;
+
+                                               continue;
+                                       }
+
+                                       //succesful stepswim
+
+                                       if (flatdist <= 0)
+                                       {
+                                               org = trace_endpos;
+                                               continue;
+                                       }
+
+                                       stepswum = true;
+                               }
+
+                               if (!WETFEET(trace_endpos))
+                               {
+                                       tracebox(trace_endpos, m1, m2, trace_endpos - eZ * (stepdist + (m2.z - m1.z)), movemode, e);
+                                       // if stepswum we'll land on the obstacle, avoid the SUBMERGED check
+                                       if (!stepswum && SUBMERGED(trace_endpos))
+                                       {
+                                               RESURFACE_LIMITED(trace_endpos, end2.z);
+                                               org = trace_endpos;
+                                               nav_action = NAV_SWIM_ONWATER;
+                                               continue;
+                                       }
+
+                                       // not submerged
+                                       org = trace_endpos;
+                                       nav_action = NAV_WALK;
+                                       continue;
+                               }
+
+                               // wetfeet
+                               org = trace_endpos;
+                               nav_action = NAV_SWIM_UNDERWATER;
+                               continue;
+                       }
+                       else //if (move.z < org.z) // swimming downwards
+                       {
+                               tracebox(org, m1, m2, move, movemode, e); // swim
+
+                               // hit something
+                               if (trace_fraction < 1)
+                               {
+                                       // stepswim
+                                       tracebox(org + stepheightvec, m1, m2, move + stepheightvec, movemode, e);
+
+                                       // hit something
+                                       if (trace_fraction < 1 || trace_startsolid) // can't jump obstacle out of water
+                                       {
+                                               if(autocvar_bot_debug_tracewalk)
+                                                       debugnodestatus(move, DEBUG_NODE_FAIL);
+
+                                               //print("tracewalk: ", vtos(start), " hit something when trying to reach ", vtos(end), "\n");
+                                               return false;
+                                       }
+
+                                       //succesful stepswim
+
+                                       if (flatdist <= 0)
+                                       {
+                                               org = trace_endpos;
+                                               continue;
+                                       }
+
+                                       if (trace_endpos.z > org.z && !SUBMERGED(trace_endpos))
+                                       {
+                                               // stepswim caused upwards direction
+                                               tracebox(trace_endpos, m1, m2, trace_endpos - stepheightvec, movemode, e);
+                                               if (!SUBMERGED(trace_endpos))
+                                               {
+                                                       org = trace_endpos;
+                                                       nav_action = NAV_WALK;
+                                                       continue;
+                                               }
+                                       }
+                               }
+
+                               org = trace_endpos;
+                               nav_action = NAV_SWIM_UNDERWATER;
+                               continue;
+                       }
+               }
+               else if(nav_action == NAV_WALK)
                {
-                       move = dir * stepdist + org;
+                       // walk
                        tracebox(org, m1, m2, move, movemode, e);
 
                        if(autocvar_bot_debug_tracewalk)
@@ -177,48 +594,55 @@ bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float m
                                tracebox(org + stepheightvec, m1, m2, move + stepheightvec, movemode, e);
                                if (trace_fraction < 1 || trace_startsolid)
                                {
-                                       tracebox(org + jumpstepheightvec, m1, m2, move + jumpstepheightvec, movemode, e);
-                                       if (trace_fraction < 1 || trace_startsolid)
+                                       if (trace_startsolid) // hit ceiling above org
+                                       {
+                                               // reduce stepwalk height
+                                               tracebox(org, m1, m2, org + stepheightvec, movemode, e);
+                                               tracebox(trace_endpos, m1, m2, move + eZ * (trace_endpos.z - move.z), movemode, e);
+                                       }
+                                       else //if (trace_fraction < 1)
+                                       {
+                                               tracebox(org + jumpstepheightvec, m1, m2, move + jumpstepheightvec, movemode, e);
+                                               if (trace_startsolid) // hit ceiling above org
+                                               {
+                                                       // reduce jumpstepwalk height
+                                                       tracebox(org, m1, m2, org + jumpstepheightvec, movemode, e);
+                                                       tracebox(trace_endpos, m1, m2, move + eZ * (trace_endpos.z - move.z), movemode, e);
+                                               }
+                                       }
+
+                                       if (trace_fraction < 1)
                                        {
+                                               vector v = trace_endpos;
+                                               v.z = org.z + jumpheight_vec.z;
+                                               if(navigation_checkladders(e, v, m1, m2, end, end2, movemode))
+                                               {
+                                                       if(autocvar_bot_debug_tracewalk)
+                                                       {
+                                                               debugnode(e, v);
+                                                               debugnodestatus(v, DEBUG_NODE_SUCCESS);
+                                                       }
+
+                                                       //print("tracewalk: ", vtos(start), " can reach ", vtos(end), "\n");
+                                                       return true;
+                                               }
+
                                                if(autocvar_bot_debug_tracewalk)
                                                        debugnodestatus(trace_endpos, DEBUG_NODE_WARNING);
 
-                                               IL_EACH(g_ladders, it.classname == "func_ladder",
-                                                       { it.solid = SOLID_BSP; });
-
                                                traceline( org, move, movemode, e);
 
-                                               IL_EACH(g_ladders, it.classname == "func_ladder",
-                                                       { it.solid = SOLID_TRIGGER; });
-
                                                if ( trace_ent.classname == "door_rotating" || trace_ent.classname == "door")
                                                {
                                                        vector nextmove;
                                                        move = trace_endpos;
                                                        while(trace_ent.classname == "door_rotating" || trace_ent.classname == "door")
                                                        {
-                                                               nextmove = move + (dir * stepdist);
+                                                               nextmove = move + (flatdir * stepdist);
                                                                traceline( move, nextmove, movemode, e);
                                                                move = nextmove;
                                                        }
-                                               }
-                                               else if (trace_ent.classname == "func_ladder")
-                                               {
-                                                       tw_ladder = trace_ent;
-                                                       vector ladder_bottom = trace_endpos - dir * m2.x;
-                                                       vector ladder_top = ladder_bottom;
-                                                       ladder_top.z = trace_ent.absmax.z + (-m1.z + 1);
-                                                       tracebox(ladder_bottom, m1, m2, ladder_top, movemode, e);
-                                                       if (trace_fraction < 1 || trace_startsolid)
-                                                       {
-                                                               if(autocvar_bot_debug_tracewalk)
-                                                                       debugnodestatus(trace_endpos, DEBUG_NODE_FAIL);
-
-                                                               return false; // failed
-                                                       }
-                                                       org = ladder_top + dir * m2.x;
-                                                       move = org + dir * stepdist;
-                                                       continue;
+                                                       flatdist = vlen(vec2(end - move));
                                                }
                                                else
                                                {
@@ -246,28 +670,43 @@ bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float m
                        // (this is the same logic as the Quake walkmove function used)
                        tracebox(move, m1, m2, move + '0 0 -65536', movemode, e);
 
-                       // moved successfully
-                       if(swimming)
+                       org = trace_endpos;
+
+                       if (!ignorehazards)
                        {
-                               float c;
-                               c = pointcontents(org + '0 0 1');
-                               if (!(c == CONTENT_WATER || c == CONTENT_LAVA || c == CONTENT_SLIME))
-                                       swimming = false;
-                               else
-                                       continue;
+                               if (IN_LAVA(org))
+                               {
+                                       if(autocvar_bot_debug_tracewalk)
+                                       {
+                                               debugnode(e, trace_endpos);
+                                               debugnodestatus(org, DEBUG_NODE_FAIL);
+                                       }
+
+                                       //print("tracewalk: ", vtos(start), " hits a hazard when trying to reach ", vtos(end), "\n");
+                                       return false;
+                               }
                        }
 
-                       org = trace_endpos;
-               }
+                       if (flatdist <= 0)
+                       {
+                               if(move.z >= end2.z && org.z < end2.z)
+                                       org.z = end2.z;
+                               continue;
+                       }
 
-               if(tw_ladder && org.z < tw_ladder.absmax.z)
-               {
-                       // stop tracewalk if destination height is lower than the top of the ladder
-                       // otherwise bot can't easily figure out climbing direction
+                       if(org.z > move.z - 1 || !SUBMERGED(org))
+                       {
+                               nav_action = NAV_WALK;
+                               continue;
+                       }
+
+                       // ended up submerged while walking
                        if(autocvar_bot_debug_tracewalk)
-                               debugnodestatus(org, DEBUG_NODE_FAIL);
+                               debugnode(e, org);
 
-                       return false;
+                       RESURFACE_LIMITED(org, move.z);
+                       nav_action = NAV_SWIM_ONWATER;
+                       continue;
                }
        }
 
@@ -287,7 +726,11 @@ bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float m
 // completely empty the goal stack, used when deciding where to go
 void navigation_clearroute(entity this)
 {
-       //print("bot ", etos(this), " clear\n");
+       this.goalcurrent_prev = this.goalcurrent;
+       this.goalcurrent_distance_2d = FLOAT_MAX;
+       this.goalcurrent_distance_z = FLOAT_MAX;
+       this.goalcurrent_distance_time = 0;
+       this.goalentity_lock_timeout = 0;
        this.goalentity = NULL;
        this.goalcurrent = NULL;
        this.goalstack01 = NULL;
@@ -331,6 +774,10 @@ void navigation_clearroute(entity this)
 // steps to the goal, and then recalculate the path.
 void navigation_pushroute(entity this, entity e)
 {
+       this.goalcurrent_prev = this.goalcurrent;
+       this.goalcurrent_distance_2d = FLOAT_MAX;
+       this.goalcurrent_distance_z = FLOAT_MAX;
+       this.goalcurrent_distance_time = 0;
        //print("bot ", etos(this), " push ", etos(e), "\n");
        if(this.goalstack31 == this.goalentity)
                this.goalentity = NULL;
@@ -373,9 +820,16 @@ void navigation_pushroute(entity this, entity e)
 // (used when a spawnfunc_waypoint is reached)
 void navigation_poproute(entity this)
 {
+       this.goalcurrent_prev = this.goalcurrent;
+       this.goalcurrent_distance_2d = FLOAT_MAX;
+       this.goalcurrent_distance_z = FLOAT_MAX;
+       this.goalcurrent_distance_time = 0;
        //print("bot ", etos(this), " pop\n");
        if(this.goalcurrent == this.goalentity)
+       {
                this.goalentity = NULL;
+               this.goalentity_lock_timeout = 0;
+       }
        this.goalcurrent = this.goalstack01;
        this.goalstack01 = this.goalstack02;
        this.goalstack02 = this.goalstack03;
@@ -410,23 +864,24 @@ void navigation_poproute(entity this)
        this.goalstack31 = NULL;
 }
 
-float navigation_waypoint_will_link(vector v, vector org, entity ent, float walkfromwp, float bestdist)
+// walking to wp (walkfromwp == false) v2 and v2_height will be used as
+// waypoint destination coordinates instead of v (only useful for box waypoints)
+// for normal waypoints v2 == v and v2_height == 0
+float navigation_waypoint_will_link(vector v, vector org, entity ent, vector v2, float v2_height, vector o2, float o2_height, float walkfromwp, float bestdist)
 {
-       float dist;
-       dist = vlen(v - org);
-       if (bestdist > dist)
+       if (vdist(v - org, <, bestdist))
        {
                traceline(v, org, true, ent);
                if (trace_fraction == 1)
                {
                        if (walkfromwp)
                        {
-                               if (tracewalk(ent, v, PL_MIN_CONST, PL_MAX_CONST, org, bot_navigation_movemode))
+                               if (tracewalk(ent, v, PL_MIN_CONST, PL_MAX_CONST, v2, v2_height, bot_navigation_movemode))
                                        return true;
                        }
                        else
                        {
-                               if (tracewalk(ent, org, PL_MIN_CONST, PL_MAX_CONST, v, bot_navigation_movemode))
+                               if (tracewalk(ent, org, PL_MIN_CONST, PL_MAX_CONST, o2, o2_height, bot_navigation_movemode))
                                        return true;
                        }
                }
@@ -437,6 +892,9 @@ float navigation_waypoint_will_link(vector v, vector org, entity ent, float walk
 // find the spawnfunc_waypoint near a dynamic goal such as a dropped weapon
 entity navigation_findnearestwaypoint_withdist_except(entity ent, float walkfromwp, float bestdist, entity except)
 {
+       if(ent.tag_entity)
+               ent = ent.tag_entity;
+
        vector pm1 = ent.origin + ent.mins;
        vector pm2 = ent.origin + ent.maxs;
 
@@ -444,39 +902,92 @@ entity navigation_findnearestwaypoint_withdist_except(entity ent, float walkfrom
        IL_EACH(g_waypoints, it != ent && it != except,
        {
                if(boxesoverlap(pm1, pm2, it.absmin, it.absmax))
+               {
+                       if(!autocvar_g_waypointeditor && walkfromwp && !ent.navigation_dynamicgoal)
+                       {
+                               waypoint_clearlinks(ent); // initialize wpXXmincost fields
+                               navigation_item_addlink(it, ent);
+                       }
                        return it;
+               }
        });
 
-       vector org = ent.origin + 0.5 * (ent.mins + ent.maxs);
-       org.z = ent.origin.z + ent.mins.z - PL_MIN_CONST.z; // player height
-       // TODO possibly make other code have the same support for bboxes
-       if(ent.tag_entity)
-               org = org + ent.tag_entity.origin;
+       vector org = ent.origin;
        if (navigation_testtracewalk)
                te_plasmaburn(org);
 
        entity best = NULL;
-       vector v;
+       vector v = '0 0 0';
+
+       if(ent.size && !IS_PLAYER(ent))
+       {
+               org += 0.5 * (ent.mins + ent.maxs);
+               org.z = ent.origin.z + ent.mins.z - PL_MIN_CONST.z; // player height
+       }
+
+       if(!autocvar_g_waypointeditor && walkfromwp && !ent.navigation_dynamicgoal)
+       {
+               waypoint_clearlinks(ent); // initialize wpXXmincost fields
+               IL_EACH(g_waypoints, it != ent,
+               {
+                       if(walkfromwp && (it.wpflags & WAYPOINTFLAG_NORELINK))
+                               continue;
+
+                       set_tracewalk_dest(ent, it.origin, false);
+                       if (vdist(tracewalk_dest - it.origin, <, 1050)
+                               && tracewalk(ent, it.origin, PL_MIN_CONST, PL_MAX_CONST,
+                               tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode))
+                       {
+                               navigation_item_addlink(it, ent);
+                       }
+               });
+       }
 
        // box check failed, try walk
        IL_EACH(g_waypoints, it != ent,
        {
-               if(it.wpisbox)
+               if(walkfromwp && (it.wpflags & WAYPOINTFLAG_NORELINK))
+                       continue;
+               v = it.origin;
+
+               if (walkfromwp)
                {
-                       vector wm1 = it.origin + it.mins;
-                       vector wm2 = it.origin + it.maxs;
-                       v.x = bound(wm1_x, org.x, wm2_x);
-                       v.y = bound(wm1_y, org.y, wm2_y);
-                       v.z = bound(wm1_z, org.z, wm2_z);
+                       set_tracewalk_dest(ent, v, true);
+                       if (trace_ent == ent)
+                       {
+                               bestdist = 0;
+                               best = it;
+                               break;
+                       }
                }
                else
-                       v = it.origin;
-               if(navigation_waypoint_will_link(v, org, ent, walkfromwp, bestdist))
+                       set_tracewalk_dest(it, org, false);
+
+               if (navigation_waypoint_will_link(v, org, ent,
+                       tracewalk_dest, tracewalk_dest_height,
+                       tracewalk_dest, tracewalk_dest_height, walkfromwp, bestdist))
                {
-                       bestdist = vlen(v - org);
+                       if (walkfromwp)
+                               bestdist = vlen(tracewalk_dest - v);
+                       else
+                               bestdist = vlen(v - org);
                        best = it;
                }
        });
+       if(!best && !ent.navigation_dynamicgoal)
+       {
+               int solid_save = ent.solid;
+               ent.solid = SOLID_BSP;
+               IL_EACH(g_jumppads, true,
+               {
+                       if(trigger_push_test(it, ent))
+                       {
+                               best = it.nearestwaypoint;
+                               break;
+                       }
+               });
+               ent.solid = solid_save;
+       }
        return best;
 }
 entity navigation_findnearestwaypoint(entity ent, float walkfromwp)
@@ -494,31 +1005,22 @@ entity navigation_findnearestwaypoint(entity ent, float walkfromwp)
 // finds the waypoints near the bot initiating a navigation query
 float navigation_markroutes_nearestwaypoints(entity this, float maxdist)
 {
-       vector v, m1, m2;
-//     navigation_testtracewalk = true;
+       //navigation_testtracewalk = true;
        int c = 0;
        IL_EACH(g_waypoints, !it.wpconsidered,
        {
-               if (it.wpisbox)
-               {
-                       m1 = it.origin + it.mins;
-                       m2 = it.origin + it.maxs;
-                       v = this.origin;
-                       v.x = bound(m1_x, v.x, m2_x);
-                       v.y = bound(m1_y, v.y, m2_y);
-                       v.z = bound(m1_z, v.z, m2_z);
-               }
-               else
-                       v = it.origin;
-               vector diff = v - this.origin;
+               set_tracewalk_dest(it, this.origin, false);
+
+               vector diff = tracewalk_dest - this.origin;
                diff.z = max(0, diff.z);
                if(vdist(diff, <, maxdist))
                {
                        it.wpconsidered = true;
-                       if (tracewalk(this, this.origin, this.mins, this.maxs, v, bot_navigation_movemode))
+                       if (tracewalk(this, this.origin, this.mins, this.maxs,
+                               tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode))
                        {
-                               it.wpnearestpoint = v;
-                               it.wpcost = vlen(v - this.origin) + it.dmg;
+                               it.wpnearestpoint = tracewalk_dest;
+                               it.wpcost = waypoint_gettravelcost(this.origin, tracewalk_dest, this, it) + it.dmg;
                                it.wpfire = 1;
                                it.enemy = NULL;
                                c = c + 1;
@@ -530,25 +1032,27 @@ float navigation_markroutes_nearestwaypoints(entity this, float maxdist)
 }
 
 // updates a path link if a spawnfunc_waypoint link is better than the current one
-void navigation_markroutes_checkwaypoint(entity w, entity wp, float cost2, vector p)
+void navigation_markroutes_checkwaypoint(entity w, entity wp, float cost, vector p)
 {
-       vector m1;
-       vector m2;
+       vector m1, m2;
        vector v;
        if (wp.wpisbox)
        {
-               m1 = wp.absmin;
-               m2 = wp.absmax;
+               m1 = wp.origin + wp.mins;
+               m2 = wp.origin + wp.maxs;
                v.x = bound(m1_x, p.x, m2_x);
                v.y = bound(m1_y, p.y, m2_y);
                v.z = bound(m1_z, p.z, m2_z);
        }
        else
                v = wp.origin;
-       cost2 = cost2 + vlen(v - p);
-       if (wp.wpcost > cost2)
+       if (w.wpflags & WAYPOINTFLAG_TELEPORT)
+               cost += w.wp00mincost; // assuming teleport has exactly one destination
+       else
+               cost += waypoint_gettravelcost(p, v, w, wp);
+       if (wp.wpcost > cost)
        {
-               wp.wpcost = cost2;
+               wp.wpcost = cost;
                wp.enemy = w;
                wp.wpfire = 1;
                wp.wpnearestpoint = v;
@@ -683,16 +1187,9 @@ void navigation_markroutes_inverted(entity fixed_source_waypoint)
                        cost = it.wpcost; // cost to walk from it to home
                        p = it.wpnearestpoint;
                        entity wp = it;
-                       IL_EACH(g_waypoints, true,
+                       IL_EACH(g_waypoints, it != wp,
                        {
-                               if(wp != it.wp00) if(wp != it.wp01) if(wp != it.wp02) if(wp != it.wp03)
-                               if(wp != it.wp04) if(wp != it.wp05) if(wp != it.wp06) if(wp != it.wp07)
-                               if(wp != it.wp08) if(wp != it.wp09) if(wp != it.wp10) if(wp != it.wp11)
-                               if(wp != it.wp12) if(wp != it.wp13) if(wp != it.wp14) if(wp != it.wp15)
-                               if(wp != it.wp16) if(wp != it.wp17) if(wp != it.wp18) if(wp != it.wp19)
-                               if(wp != it.wp20) if(wp != it.wp21) if(wp != it.wp22) if(wp != it.wp23)
-                               if(wp != it.wp24) if(wp != it.wp25) if(wp != it.wp26) if(wp != it.wp27)
-                               if(wp != it.wp28) if(wp != it.wp29) if(wp != it.wp30) if(wp != it.wp31)
+                               if(!waypoint_islinked(it, wp))
                                        continue;
                                cost2 = cost + it.dmg;
                                navigation_markroutes_checkwaypoint(wp, it, cost2, p);
@@ -710,6 +1207,9 @@ void navigation_routerating(entity this, entity e, float f, float rangebias)
        if(e.blacklisted)
                return;
 
+       rangebias = waypoint_getlinearcost(rangebias);
+       f = waypoint_getlinearcost(f);
+
        if (IS_PLAYER(e))
        {
                bool rate_wps = false;
@@ -751,7 +1251,7 @@ void navigation_routerating(entity this, entity e, float f, float rangebias)
                }
        }
 
-       vector o = (e.absmin + e.absmax) * 0.5;
+       vector goal_org = (e.absmin + e.absmax) * 0.5;
 
        //print("routerating ", etos(e), " = ", ftos(f), " - ", ftos(rangebias), "\n");
 
@@ -759,7 +1259,7 @@ void navigation_routerating(entity this, entity e, float f, float rangebias)
        if(g_jetpack)
        if(this.items & IT_JETPACK)
        if(autocvar_bot_ai_navigation_jetpack)
-       if(vdist(this.origin - o, >, autocvar_bot_ai_navigation_jetpack_mindistance))
+       if(vdist(this.origin - goal_org, >, autocvar_bot_ai_navigation_jetpack_mindistance))
        {
                vector pointa, pointb;
 
@@ -770,7 +1270,7 @@ void navigation_routerating(entity this, entity e, float f, float rangebias)
                pointa = trace_endpos - '0 0 1';
 
                // Point B
-               traceline(o, o + '0 0 65535', MOVE_NORMAL, e);
+               traceline(goal_org, goal_org + '0 0 65535', MOVE_NORMAL, e);
                pointb = trace_endpos - '0 0 1';
 
                // Can I see these two points from the sky?
@@ -861,10 +1361,11 @@ void navigation_routerating(entity this, entity e, float f, float rangebias)
                if ((!e.nearestwaypoint || e.navigation_dynamicgoal)
                        && e.nearestwaypointtimeout >= 0 && time > e.nearestwaypointtimeout)
                {
-                       nwp = navigation_findnearestwaypoint(e, true);
-                       if(nwp)
-                               e.nearestwaypoint = nwp;
+                       if(IS_BOT_CLIENT(e) && e.goalcurrent && e.goalcurrent.classname == "waypoint")
+                               e.nearestwaypoint = nwp = e.goalcurrent;
                        else
+                               e.nearestwaypoint = nwp = navigation_findnearestwaypoint(e, true);
+                       if(!nwp)
                        {
                                LOG_DEBUG("FAILED to find a nearest waypoint to '", e.classname, "' #", etos(e));
 
@@ -887,12 +1388,17 @@ void navigation_routerating(entity this, entity e, float f, float rangebias)
        }
 
        LOG_DEBUG("-- checking ", e.classname, " (with cost ", ftos(nwp.wpcost), ")");
-       if (nwp)
-       if (nwp.wpcost < 10000000)
+       if (nwp && nwp.wpcost < 10000000)
        {
                //te_wizspike(nwp.wpnearestpoint);
-               LOG_DEBUG(e.classname, " ", ftos(f), "/(1+", ftos((nwp.wpcost + vlen(e.origin - nwp.wpnearestpoint))), "/", ftos(rangebias), ") = ");
-               f = f * rangebias / (rangebias + (nwp.wpcost + vlen(o - nwp.wpnearestpoint)));
+               float nwptoitem_cost = 0;
+               if(nwp.wpflags & WAYPOINTFLAG_TELEPORT)
+                       nwptoitem_cost = nwp.wp00mincost;
+               else
+                       nwptoitem_cost = waypoint_gettravelcost(nwp.wpnearestpoint, goal_org, nwp, e);
+               float cost = nwp.wpcost + nwptoitem_cost;
+               LOG_DEBUG(e.classname, " ", ftos(f), "/(1+", ftos(cost), "/", ftos(rangebias), ") = ");
+               f = f * rangebias / (rangebias + cost);
                LOG_DEBUG("considering ", e.classname, " (with rating ", ftos(f), ")");
                if (navigation_bestrating < f)
                {
@@ -910,12 +1416,25 @@ bool navigation_routetogoal(entity this, entity e, vector startposition)
        if (!e)
                return false;
 
+       entity teleport_goal = NULL;
+
        this.goalentity = e;
 
+       if(e.wpflags & WAYPOINTFLAG_TELEPORT)
+       {
+               // force teleport destination as route destination
+               teleport_goal = e;
+               navigation_pushroute(this, e.wp00);
+               this.goalentity = e.wp00;
+       }
+
        // put the entity on the goal stack
        //print("routetogoal ", etos(e), "\n");
        navigation_pushroute(this, e);
 
+       if(teleport_goal)
+               e = this.goalentity;
+
        if(e.classname == "waypoint" && !(e.wpflags & WAYPOINTFLAG_PERSONAL))
        {
                this.wp_goal_prev1 = this.wp_goal_prev0;
@@ -927,8 +1446,13 @@ bool navigation_routetogoal(entity this, entity e, vector startposition)
                return true;
 
        // if it can reach the goal there is nothing more to do
-       if (tracewalk(this, startposition, STAT(PL_MIN, this), STAT(PL_MAX, this), (e.absmin + e.absmax) * 0.5, bot_navigation_movemode))
+       set_tracewalk_dest(e, startposition, true);
+       if ((!IS_MOVABLE(this.goalcurrent) || vdist(tracewalk_dest - this.origin, <, MAX_CHASE_DISTANCE))
+               && (trace_ent == this || tracewalk(this, startposition, STAT(PL_MIN, this), STAT(PL_MAX, this),
+               tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode)))
+       {
                return true;
+       }
 
        entity nearest_wp = NULL;
        // see if there are waypoints describing a path to the item
@@ -937,6 +1461,8 @@ bool navigation_routetogoal(entity this, entity e, vector startposition)
                e = e.nearestwaypoint;
                nearest_wp = e;
        }
+       else if(teleport_goal)
+               e = teleport_goal;
        else
                e = e.enemy; // we already have added it, so...
 
@@ -946,7 +1472,26 @@ bool navigation_routetogoal(entity this, entity e, vector startposition)
        if(nearest_wp && nearest_wp.enemy)
        {
                // often path can be optimized by not adding the nearest waypoint
-               if(tracewalk(this, nearest_wp.enemy.origin, STAT(PL_MIN, this), STAT(PL_MAX, this), (this.goalentity.absmin + this.goalentity.absmax) * 0.5, bot_navigation_movemode))
+               if (this.goalentity.navigation_dynamicgoal || autocvar_g_waypointeditor)
+               {
+                       if (nearest_wp.enemy.wpcost < autocvar_bot_ai_strategyinterval_movingtarget)
+                       {
+                               if (vdist(vec2(this.goalentity.origin - nearest_wp.origin), <, 32))
+                                       e = nearest_wp.enemy;
+                               else
+                               {
+                                       set_tracewalk_dest(this.goalentity, nearest_wp.enemy.origin, true);
+                                       if (trace_ent == this || (vdist(tracewalk_dest - nearest_wp.enemy.origin, <, 1050)
+                                               && vlen2(tracewalk_dest - nearest_wp.enemy.origin) < vlen2(nearest_wp.origin - nearest_wp.enemy.origin)
+                                               && tracewalk(this, nearest_wp.enemy.origin, STAT(PL_MIN, this), STAT(PL_MAX, this),
+                                               tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode)))
+                                       {
+                                               e = nearest_wp.enemy;
+                                       }
+                               }
+                       }
+               }
+               else if(navigation_item_islinked(nearest_wp.enemy, this.goalentity))
                        e = nearest_wp.enemy;
        }
 
@@ -963,21 +1508,91 @@ bool navigation_routetogoal(entity this, entity e, vector startposition)
        return false;
 }
 
+// shorten path by removing intermediate goals
+void navigation_shortenpath(entity this)
+{
+       if (!this.goalstack01 || wasfreed(this.goalstack01))
+               return;
+       if (this.bot_tracewalk_time > time)
+               return;
+       this.bot_tracewalk_time = max(time, this.bot_tracewalk_time) + 0.25;
+
+       bool cut_allowed = false;
+       entity next = this.goalentity;
+       // evaluate whether bot can discard current route and chase directly a player, trying to
+       // keep waypoint route as long as possible, as it is safer and faster (bot can bunnyhop)
+       if (IS_MOVABLE(next))
+       {
+               set_tracewalk_dest(next, this.origin, true);
+               if (vdist(this.origin - tracewalk_dest, <, 200))
+                       cut_allowed = true;
+               else if (vdist(tracewalk_dest - this.origin, <, MAX_CHASE_DISTANCE)
+                       && vdist(tracewalk_dest - this.goalcurrent.origin, >, 200)
+                       && vdist(this.origin - this.goalcurrent.origin, >, 100)
+                       && checkpvs(this.origin + this.view_ofs, next))
+               {
+                       if (vlen2(next.origin - this.origin) < vlen2(this.goalcurrent.origin - this.origin))
+                               cut_allowed = true;
+                       else
+                       {
+                               vector deviation = vectoangles(this.goalcurrent.origin - this.origin) - vectoangles(next.origin - this.origin);
+                               while (deviation.y < -180) deviation.y += 360;
+                               while (deviation.y > 180) deviation.y -= 360;
+                               if (fabs(deviation.y) > 25)
+                                       cut_allowed = true;
+                       }
+               }
+               if (cut_allowed)
+               {
+                       if (trace_ent == this || tracewalk(this, this.origin, this.mins, this.maxs,
+                               tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode))
+                       {
+                               LOG_DEBUG("path optimized for ", this.netname, ", route cleared");
+                               do
+                               {
+                                       navigation_poproute(this);
+                               }
+                               while (this.goalcurrent != next);
+                       }
+                       return;
+               }
+       }
+
+       next = this.goalstack01;
+       // if for some reason the bot is closer to the next goal, pop the current one
+       if (!IS_MOVABLE(next) // already checked in the previous case
+               && vlen2(this.goalcurrent.origin - next.origin) > vlen2(next.origin - this.origin)
+               && checkpvs(this.origin + this.view_ofs, next))
+       {
+               set_tracewalk_dest(next, this.origin, true);
+               cut_allowed = true;
+       }
+
+       if (cut_allowed)
+       {
+               if (trace_ent == this || tracewalk(this, this.origin, this.mins, this.maxs,
+                       tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode))
+               {
+                       LOG_DEBUG("path optimized for ", this.netname, ", removed a goal from the queue"); 
+                       navigation_poproute(this);
+               }
+       }
+}
+
 // removes any currently touching waypoints from the goal stack
 // (this is how bots detect if they reached a goal)
-void navigation_poptouchedgoals(entity this)
+int navigation_poptouchedgoals(entity this)
 {
-       vector org, m1, m2;
-       org = this.origin;
-       m1 = org + this.mins;
-       m2 = org + this.maxs;
+       int removed_goals = 0;
+
+       if(!this.goalcurrent)
+               return removed_goals;
 
        if(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
        {
                // make sure jumppad is really hit, don't rely on distance based checks
                // as they may report a touch even if it didn't really happen
-               if(this.lastteleporttime>0)
-               if(time - this.lastteleporttime < ((this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL) ? 2 : 0.15))
+               if(this.lastteleporttime > 0 && TELEPORT_USED(this, this.goalcurrent))
                {
                        if(this.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
                        if(this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL && this.goalcurrent.owner==this)
@@ -986,31 +1601,48 @@ void navigation_poptouchedgoals(entity this)
                                this.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_REACHED;
                        }
                        navigation_poproute(this);
-                       return;
+                       this.lastteleporttime = 0;
+                       ++removed_goals;
                }
+               else
+                       return removed_goals;
        }
-
-       // If for some reason the bot is closer to the next goal, pop the current one
-       if(this.goalstack01 && !wasfreed(this.goalstack01))
-       if(vlen2(this.goalcurrent.origin - this.origin) > vlen2(this.goalstack01.origin - this.origin))
-       if(checkpvs(this.origin + this.view_ofs, this.goalstack01))
-       if(tracewalk(this, this.origin, this.mins, this.maxs, (this.goalstack01.absmin + this.goalstack01.absmax) * 0.5, bot_navigation_movemode))
+       else if (this.lastteleporttime > 0)
        {
-               LOG_DEBUG("path optimized for ", this.netname, ", removed a goal from the queue");
-               navigation_poproute(this);
-               // TODO this may also be a nice idea to do "early" (e.g. by
-               // manipulating the vlen() comparisons) to shorten paths in
-               // general - this would make bots walk more "on rails" than
-               // "zigzagging" which they currently do with sufficiently
-               // random-like waypoints, and thus can make a nice bot
-               // personality property
+               // sometimes bot is pushed so hard (by a jumppad or a shot) that ends up touching the next
+               // teleport / jumppad / warpzone present in its path skipping check of one or more goals
+               // if so immediately fix bot path by removing skipped goals
+               entity tele_ent = NULL;
+               if (this.goalstack01 && (this.goalstack01.wpflags & WAYPOINTFLAG_TELEPORT))
+                       tele_ent = this.goalstack01;
+               else if (this.goalstack02 && (this.goalstack02.wpflags & WAYPOINTFLAG_TELEPORT))
+                       tele_ent = this.goalstack02;
+               else if (this.goalstack03 && (this.goalstack03.wpflags & WAYPOINTFLAG_TELEPORT))
+                       tele_ent = this.goalstack03;
+               if (tele_ent && TELEPORT_USED(this, tele_ent))
+               {
+                       if (this.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
+                       if (tele_ent.wpflags & WAYPOINTFLAG_PERSONAL && tele_ent.owner == this)
+                       {
+                               this.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING;
+                               this.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_REACHED;
+                       }
+                       while (this.goalcurrent != tele_ent)
+                       {
+                               navigation_poproute(this);
+                               ++removed_goals;
+                       }
+                       navigation_poproute(this);
+                       this.lastteleporttime = 0;
+                       ++removed_goals;
+                       return removed_goals;
+               }
        }
 
        // Loose goal touching check when running
        if(this.aistatus & AI_STATUS_RUNNING)
        if(this.goalcurrent.classname=="waypoint")
-       if(!(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT))
-       if(vlen(this.velocity - eZ * this.velocity.z) >= autocvar_sv_maxspeed) // if -really- running
+       if(vdist(vec2(this.velocity), >=, autocvar_sv_maxspeed)) // if -really- running
        {
                if(vdist(this.origin - this.goalcurrent.origin, <, 150))
                {
@@ -1026,6 +1658,9 @@ void navigation_poptouchedgoals(entity this)
                                }
 
                                navigation_poproute(this);
+                               ++removed_goals;
+                               if(this.goalcurrent && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
+                                       return removed_goals;
                        }
                }
        }
@@ -1039,10 +1674,7 @@ void navigation_poptouchedgoals(entity this)
                        gc_min = this.goalcurrent.origin - '1 1 1' * 12;
                        gc_max = this.goalcurrent.origin + '1 1 1' * 12;
                }
-               if(!boxesoverlap(m1, m2, gc_min, gc_max))
-                       break;
-
-               if((this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT))
+               if(!boxesoverlap(this.absmin, this.absmax, gc_min, gc_max))
                        break;
 
                // Detect personal waypoints
@@ -1054,7 +1686,56 @@ void navigation_poptouchedgoals(entity this)
                }
 
                navigation_poproute(this);
+               ++removed_goals;
+               if(this.goalcurrent && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
+                       return removed_goals;
        }
+       return removed_goals;
+}
+
+entity navigation_get_really_close_waypoint(entity this)
+{
+       entity wp = this.goalcurrent;
+       if(!wp)
+               wp = this.goalcurrent_prev;
+       if(!wp)
+               return NULL;
+       if(wp != this.goalcurrent_prev && vdist(wp.origin - this.origin, >, 50))
+       {
+               wp = this.goalcurrent_prev;
+               if(!wp)
+                       return NULL;
+       }
+       if(wp.classname != "waypoint")
+       {
+               wp = wp.nearestwaypoint;
+               if(!wp)
+                       return NULL;
+       }
+       if(vdist(wp.origin - this.origin, >, 50))
+       {
+               wp = NULL;
+               IL_EACH(g_waypoints, !(it.wpflags & WAYPOINTFLAG_TELEPORT),
+               {
+                       if(vdist(it.origin - this.origin, <, 50))
+                       {
+                               wp = it;
+                               break;
+                       }
+               });
+               if(!wp)
+                       return NULL;
+       }
+       if(wp.wpflags & WAYPOINTFLAG_TELEPORT)
+               return NULL;
+
+       set_tracewalk_dest(wp, this.origin, false);
+       if (!tracewalk(this, this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this),
+               tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode))
+       {
+               return NULL;
+       }
+       return wp;
 }
 
 // begin a goal selection session (queries spawnfunc_waypoint network)
@@ -1065,9 +1746,10 @@ void navigation_goalrating_start(entity this)
 
        this.navigation_jetpack_goal = NULL;
        navigation_bestrating = -1;
+       entity wp = navigation_get_really_close_waypoint(this);
        navigation_clearroute(this);
        navigation_bestgoal = NULL;
-       navigation_markroutes(this, NULL);
+       navigation_markroutes(this, wp);
 }
 
 // ends a goal selection session (updates goal stack to the best goal)
@@ -1095,11 +1777,13 @@ void botframe_updatedangerousobjects(float maxupdate)
        vector m1, m2, v, o;
        float c, d, danger;
        c = 0;
+       entity wp_cur;
        IL_EACH(g_waypoints, true,
        {
                danger = 0;
-               m1 = it.mins;
-               m2 = it.maxs;
+               m1 = it.absmin;
+               m2 = it.absmax;
+               wp_cur = it;
                IL_EACH(g_bot_dodge, it.bot_dodge,
                {
                        v = it.origin;
@@ -1107,7 +1791,7 @@ void botframe_updatedangerousobjects(float maxupdate)
                        v.y = bound(m1_y, v.y, m2_y);
                        v.z = bound(m1_z, v.z, m2_z);
                        o = (it.absmin + it.absmax) * 0.5;
-                       d = it.bot_dodgerating - vlen(o - v);
+                       d = waypoint_getlinearcost(it.bot_dodgerating) - waypoint_gettravelcost(o, v, it, wp_cur);
                        if (d > 0)
                        {
                                traceline(o, v, true, NULL);
@@ -1124,11 +1808,20 @@ void botframe_updatedangerousobjects(float maxupdate)
 
 void navigation_unstuck(entity this)
 {
-       float search_radius = 1000;
-
        if (!autocvar_bot_wander_enable)
                return;
 
+       bool has_user_waypoints = false;
+       IL_EACH(g_waypoints, !(it.wpflags & WAYPOINTFLAG_GENERATED),
+       {
+               has_user_waypoints = true;
+               break;
+       });
+       if (!has_user_waypoints)
+               return;
+
+       float search_radius = 1000;
+
        if (!bot_waypoint_queue_owner)
        {
                LOG_DEBUG(this.netname, " stuck, taking over the waypoints queue");
@@ -1145,7 +1838,9 @@ void navigation_unstuck(entity this)
                // evaluate the next goal on the queue
                float d = vlen2(this.origin - bot_waypoint_queue_goal.origin);
                LOG_DEBUG(this.netname, " evaluating ", bot_waypoint_queue_goal.classname, " with distance ", ftos(d));
-               if(tracewalk(bot_waypoint_queue_goal, this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this), bot_waypoint_queue_goal.origin, bot_navigation_movemode))
+               set_tracewalk_dest(bot_waypoint_queue_goal, this.origin, false);
+               if (tracewalk(bot_waypoint_queue_goal, this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this),
+                       tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode))
                {
                        if( d > bot_waypoint_queue_bestgoalrating)
                        {
@@ -1161,7 +1856,7 @@ void navigation_unstuck(entity this)
                        {
                                LOG_DEBUG(this.netname, " stuck, reachable waypoint found, heading to it");
                                navigation_routetogoal(this, bot_waypoint_queue_bestgoal, this.origin);
-                               this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+                               navigation_goalrating_timeout_set(this);
                                this.aistatus &= ~AI_STATUS_STUCK;
                        }
                        else
index 8f1f03ad1933f9512a2f96126d82ab8770002233..f3103cc4fcd39d875708a0612e83c59addd03095 100644 (file)
@@ -9,6 +9,7 @@ float navigation_testtracewalk;
 
 vector jumpstepheightvec;
 vector stepheightvec;
+vector jumpheight_vec;
 
 entity navigation_bestgoal;
 
@@ -22,13 +23,45 @@ entity navigation_bestgoal;
 .entity goalstack20, goalstack21, goalstack22, goalstack23;
 .entity goalstack24, goalstack25, goalstack26, goalstack27;
 .entity goalstack28, goalstack29, goalstack30, goalstack31;
+
+.entity goalcurrent_prev;
+.float goalcurrent_distance_z;
+.float goalcurrent_distance_2d;
+.float goalcurrent_distance_time;
+
+.float goalentity_lock_timeout;
+
 .entity nearestwaypoint;
+.float nearestwaypointtimeout;
+
+/*
+// item it is linked from waypoint it.wpXX (INCOMING link)
+// links are sorted by their cost (wpXXmincost)
+.entity wp00, wp01, wp02, wp03, wp04, wp05, wp06, wp07, wp08, wp09, wp10, wp11, wp12, wp13, wp14, wp15;
+.entity wp16, wp17, wp18, wp19, wp20, wp21, wp22, wp23, wp24, wp25, wp26, wp27, wp28, wp29, wp30, wp31;
+
+.float wp00mincost, wp01mincost, wp02mincost, wp03mincost, wp04mincost, wp05mincost, wp06mincost, wp07mincost;
+.float wp08mincost, wp09mincost, wp10mincost, wp11mincost, wp12mincost, wp13mincost, wp14mincost, wp15mincost;
+.float wp16mincost, wp17mincost, wp18mincost, wp19mincost, wp20mincost, wp21mincost, wp22mincost, wp23mincost;
+.float wp24mincost, wp25mincost, wp26mincost, wp27mincost, wp28mincost, wp29mincost, wp30mincost, wp31mincost;
+*/
+
+#define navigation_item_islinked(from_wp, to_item) waypoint_islinked(to_item, from_wp)
+#define navigation_item_addlink(from_wp, to_item) \
+       waypoint_addlink_customcost(to_item, from_wp, waypoint_getlinkcost(from_wp, to_item))
+
+#define TELEPORT_USED(pl, tele_wp) \
+       (time - pl.lastteleporttime < ((tele_wp.wpflags & WAYPOINTFLAG_PERSONAL) ? 2 : 0.15) \
+       && boxesoverlap(tele_wp.absmin, tele_wp.absmax, pl.lastteleport_origin + STAT(PL_MIN, pl), pl.lastteleport_origin + STAT(PL_MAX, pl)))
+
+vector tracewalk_dest;
+float tracewalk_dest_height;
 
 .entity wp_goal_prev0;
 .entity wp_goal_prev1;
 
-.float nearestwaypointtimeout;
 .float lastteleporttime;
+.vector lastteleport_origin;
 
 .float blacklisted;
 
@@ -52,6 +85,12 @@ void navigation_dynamicgoal_init(entity this, bool initially_static);
 void navigation_dynamicgoal_set(entity this);
 void navigation_dynamicgoal_unset(entity this);
 
+.int nav_submerged_state;
+#define SUBMERGED_UNDEFINED 0
+#define SUBMERGED_NO 1
+#define SUBMERGED_YES 2
+bool navigation_check_submerged_state(entity ent, vector pos);
+
 
 /*
  * Functions
@@ -63,7 +102,7 @@ void debugnodestatus(vector position, float status);
 
 void debuggoalstack(entity this);
 
-float tracewalk(entity e, vector start, vector m1, vector m2, vector end, float movemode);
+float tracewalk(entity e, vector start, vector m1, vector m2, vector end, float end_height, float movemode);
 
 float navigation_markroutes_nearestwaypoints(entity this, float maxdist);
 float navigation_routetogoal(entity this, entity e, vector startposition);
@@ -75,12 +114,16 @@ void navigation_markroutes_checkwaypoint(entity w, entity wp, float cost2, vecto
 void navigation_markroutes(entity this, entity fixed_source_waypoint);
 void navigation_markroutes_inverted(entity fixed_source_waypoint);
 void navigation_routerating(entity this, entity e, float f, float rangebias);
-void navigation_poptouchedgoals(entity this);
+void navigation_shortenpath(entity this);
+int navigation_poptouchedgoals(entity this);
 void navigation_goalrating_start(entity this);
 void navigation_goalrating_end(entity this);
+void navigation_goalrating_timeout_set(entity this);
+void navigation_goalrating_timeout_force(entity this);
+bool navigation_goalrating_timeout(entity this);
 void navigation_unstuck(entity this);
 
 void botframe_updatedangerousobjects(float maxupdate);
 
 entity navigation_findnearestwaypoint(entity ent, float walkfromwp);
-float navigation_waypoint_will_link(vector v, vector org, entity ent, float walkfromwp, float bestdist);
+float navigation_waypoint_will_link(vector v, vector org, entity ent, vector v2, float v2_height, vector o2, float o2_height, float walkfromwp, float bestdist);
index b1ff75db72eb477d7942f285ac31c82b62a5c313..fa82d926b63e92776cfd293dbebccf15ba20ad04 100644 (file)
 
 #include <common/constants.qh>
 #include <common/net_linked.qh>
+#include <common/physics/player.qh>
 
 #include <lib/warpzone/common.qh>
 #include <lib/warpzone/util_server.qh>
 
+.entity spawnpointmodel;
+void waypoint_unreachable(entity pl)
+{
+       IL_EACH(g_waypoints, true,
+       {
+               it.colormod = '0.5 0.5 0.5';
+               it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE);
+       });
+
+       entity e2 = navigation_findnearestwaypoint(pl, false);
+       if(!e2)
+       {
+               LOG_INFOF("Can't find any waypoint nearby\n");
+               return;
+       }
+
+       navigation_markroutes(pl, e2);
+
+       int j = 0;
+       int m = 0;
+       IL_EACH(g_waypoints, it.wpcost >= 10000000,
+       {
+               LOG_INFO("unreachable: ", etos(it), " ", vtos(it.origin), "\n");
+               it.colormod_z = 8;
+               it.effects |= EF_NODEPTHTEST | EF_BLUE;
+               j++;
+               m++;
+       });
+       if (j) LOG_INFOF("%d waypoints cannot be reached from here in any way (marked with blue light)\n", j);
+       navigation_markroutes_inverted(e2);
+
+       j = 0;
+       IL_EACH(g_waypoints, it.wpcost >= 10000000,
+       {
+               LOG_INFO("cannot reach me: ", etos(it), " ", vtos(it.origin), "\n");
+               it.colormod_x = 8;
+               if (!(it.effects & EF_NODEPTHTEST))  // not already reported before
+                       m++;
+               it.effects |= EF_NODEPTHTEST | EF_RED;
+               j++;
+       });
+       if (j) LOG_INFOF("%d waypoints cannot walk to here in any way (marked with red light)\n", j);
+       if (m) LOG_INFOF("%d waypoints have been marked total\n", m);
+
+       j = 0;
+       IL_EACH(g_spawnpoints, true,
+       {
+               if (navigation_findnearestwaypoint(it, false))
+               {
+                       if(it.spawnpointmodel)
+                       {
+                               delete(it.spawnpointmodel);
+                               it.spawnpointmodel = NULL;
+                       }
+               }
+               else
+               {
+                       if(!it.spawnpointmodel)
+                       {
+                               tracebox(it.origin, PL_MIN_CONST, PL_MAX_CONST, it.origin - '0 0 512', MOVE_NOMONSTERS, NULL);
+                               entity e = new(spawnpointmodel);
+                               vector org = trace_endpos + eZ;
+                               setorigin(e, org);
+                               e.solid = SOLID_TRIGGER;
+                               it.spawnpointmodel = e;
+                       }
+                       LOG_INFO("spawn without waypoint: ", etos(it), " ", vtos(it.origin), "\n");
+                       it.spawnpointmodel.effects |= EF_NODEPTHTEST;
+                       _setmodel(it.spawnpointmodel, pl.model);
+                       it.spawnpointmodel.frame = pl.frame;
+                       it.spawnpointmodel.skin = pl.skin;
+                       it.spawnpointmodel.colormap = pl.colormap;
+                       it.spawnpointmodel.colormod = pl.colormod;
+                       it.spawnpointmodel.glowmod = pl.glowmod;
+                       setsize(it.spawnpointmodel, PL_MIN_CONST, PL_MAX_CONST);
+                       j++;
+               }
+       });
+       if (j) LOG_INFOF("%d spawnpoints have no nearest waypoint (marked by player model)\n", j);
+
+       j = 0;
+       IL_EACH(g_items, true,
+       {
+               it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE);
+               it.colormod = '0.5 0.5 0.5';
+       });
+       IL_EACH(g_items, true,
+       {
+               if (navigation_findnearestwaypoint(it, false))
+                       continue;
+               LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin), "\n");
+               it.effects |= EF_NODEPTHTEST | EF_RED;
+               it.colormod_x = 8;
+               j++;
+       });
+       if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked away from (marked with red light)\n", j);
+
+       j = 0;
+       IL_EACH(g_items, true,
+       {
+               if (navigation_findnearestwaypoint(it, true))
+                       continue;
+               LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin), "\n");
+               it.effects |= EF_NODEPTHTEST | EF_BLUE;
+               it.colormod_z = 8;
+               j++;
+       });
+       if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked to (marked with blue light)\n", j);
+}
+
+vector waypoint_getSymmetricalOrigin(vector org, int ctf_flags)
+{
+       vector new_org = org;
+       if (fabs(autocvar_g_waypointeditor_symmetrical) == 1)
+       {
+               vector map_center = havocbot_middlepoint;
+               if (autocvar_g_waypointeditor_symmetrical == -1)
+                       map_center = autocvar_g_waypointeditor_symmetrical_origin;
+
+               new_org = Rotate(org - map_center, 360 * DEG2RAD / ctf_flags) + map_center;
+       }
+       else if (fabs(autocvar_g_waypointeditor_symmetrical) == 2)
+       {
+               float m = havocbot_symmetryaxis_equation.x;
+               float q = havocbot_symmetryaxis_equation.y;
+               if (autocvar_g_waypointeditor_symmetrical == -2)
+               {
+                       m = autocvar_g_waypointeditor_symmetrical_axis.x;
+                       q = autocvar_g_waypointeditor_symmetrical_axis.y;
+               }
+
+               new_org.x = (1 / (1 + m*m)) * ((1 - m*m) * org.x + 2 * m * org.y - 2 * m * q);
+               new_org.y = (1 / (1 + m*m)) * (2 * m * org.x + (m*m - 1) * org.y + 2 * q);
+       }
+       new_org.z = org.z;
+       return new_org;
+}
+
 void waypoint_setupmodel(entity wp)
 {
        if (autocvar_g_waypointeditor)
@@ -31,6 +170,8 @@ void waypoint_setupmodel(entity wp)
                        wp.colormod = '1 0 0';
                else if (wp.wpflags & WAYPOINTFLAG_GENERATED)
                        wp.colormod = '1 1 0';
+               else if (wp.wphardwired)
+                       wp.colormod = '0.5 0 1';
                else
                        wp.colormod = '1 1 1';
        }
@@ -38,14 +179,13 @@ void waypoint_setupmodel(entity wp)
                wp.model = "";
 }
 
-// create a new spawnfunc_waypoint and automatically link it to other waypoints, and link
-// them back to it as well
-// (suitable for spawnfunc_waypoint editor)
 entity waypoint_spawn(vector m1, vector m2, float f)
 {
-       if(!(f & WAYPOINTFLAG_PERSONAL))
+       if(!(f & (WAYPOINTFLAG_PERSONAL | WAYPOINTFLAG_GENERATED)) && m1 == m2)
        {
-               IL_EACH(g_waypoints, boxesoverlap(m1, m2, it.absmin, it.absmax),
+               vector em1 = m1 - '8 8 8';
+               vector em2 = m2 + '8 8 8';
+               IL_EACH(g_waypoints, boxesoverlap(em1, em2, it.absmin, it.absmax),
                {
                        return it;
                });
@@ -92,6 +232,120 @@ entity waypoint_spawn(vector m1, vector m2, float f)
        return w;
 }
 
+void waypoint_spawn_fromeditor(entity pl)
+{
+       entity e;
+       vector org = pl.origin;
+       int ctf_flags = havocbot_symmetryaxis_equation.z;
+       bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2)
+                  || (autocvar_g_waypointeditor_symmetrical < 0));
+       int order = ctf_flags;
+       if(autocvar_g_waypointeditor_symmetrical_order >= 2)
+       {
+               order = autocvar_g_waypointeditor_symmetrical_order;
+               ctf_flags = order;
+       }
+
+       if(!PHYS_INPUT_BUTTON_CROUCH(pl))
+       {
+               // snap waypoint to item's origin if close enough
+               IL_EACH(g_items, true,
+               {
+                       vector item_org = (it.absmin + it.absmax) * 0.5;
+                       item_org.z = it.absmin.z - PL_MIN_CONST.z;
+                       if(vlen(item_org - org) < 30)
+                       {
+                               org = item_org;
+                               break;
+                       }
+               });
+       }
+
+       LABEL(add_wp);
+       e = waypoint_spawn(org, org, 0);
+       if(!e)
+       {
+               LOG_INFOF("Couldn't spawn waypoint at %v\n", org);
+               return;
+       }
+       waypoint_schedulerelink(e);
+       bprint(strcat("Waypoint spawned at ", vtos(e.origin), "\n"));
+       if(sym)
+       {
+               org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
+               if (vdist(org - pl.origin, >, 32))
+               {
+                       if(order > 2)
+                               order--;
+                       else
+                               sym = false;
+                       goto add_wp;
+               }
+       }
+}
+
+void waypoint_remove(entity wp)
+{
+       // tell all waypoints linked to wp that they need to relink
+       IL_EACH(g_waypoints, it != wp,
+       {
+               if (waypoint_islinked(it, wp))
+                       waypoint_removelink(it, wp);
+       });
+       delete(wp);
+}
+
+void waypoint_remove_fromeditor(entity pl)
+{
+       entity e = navigation_findnearestwaypoint(pl, false);
+
+       int ctf_flags = havocbot_symmetryaxis_equation.z;
+       bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2)
+                  || (autocvar_g_waypointeditor_symmetrical < 0));
+       int order = ctf_flags;
+       if(autocvar_g_waypointeditor_symmetrical_order >= 2)
+       {
+               order = autocvar_g_waypointeditor_symmetrical_order;
+               ctf_flags = order;
+       }
+
+       LABEL(remove_wp);
+       if (!e) return;
+       if (e.wpflags & WAYPOINTFLAG_GENERATED) return;
+
+       if (e.wphardwired)
+       {
+               LOG_INFO("^1Warning: ^7Removal of hardwired waypoints is not allowed in the editor. Please remove links from/to this waypoint (", vtos(e.origin), ") by hand from maps/", mapname, ".waypoints.hardwired\n");
+               return;
+       }
+
+       entity wp_sym = NULL;
+       if (sym)
+       {
+               vector org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
+               FOREACH_ENTITY_CLASS("waypoint", !(it.wpflags & WAYPOINTFLAG_GENERATED), {
+                       if(vdist(org - it.origin, <, 3))
+                       {
+                               wp_sym = it;
+                               break;
+                       }
+               });
+       }
+
+       bprint(strcat("Waypoint removed at ", vtos(e.origin), "\n"));
+       waypoint_remove(e);
+
+       if (sym && wp_sym)
+       {
+               e = wp_sym;
+               if(order > 2)
+                       order--;
+               else
+                       sym = false;
+               goto remove_wp;
+       }
+}
+
 void waypoint_removelink(entity from, entity to)
 {
        if (from == to || (from.wpflags & WAYPOINTFLAG_NORELINK))
@@ -145,41 +399,113 @@ bool waypoint_islinked(entity from, entity to)
        return false;
 }
 
-// add a new link to the spawnfunc_waypoint, replacing the furthest link it already has
-void waypoint_addlink(entity from, entity to)
+void waypoint_updatecost_foralllinks()
 {
-       float c;
+       IL_EACH(g_waypoints, !(it.wpflags & WAYPOINTFLAG_TELEPORT),
+       {
+               if(it.wp00) it.wp00mincost = waypoint_getlinkcost(it, it.wp00);
+               if(it.wp01) it.wp01mincost = waypoint_getlinkcost(it, it.wp01);
+               if(it.wp02) it.wp02mincost = waypoint_getlinkcost(it, it.wp02);
+               if(it.wp03) it.wp03mincost = waypoint_getlinkcost(it, it.wp03);
+               if(it.wp04) it.wp04mincost = waypoint_getlinkcost(it, it.wp04);
+               if(it.wp05) it.wp05mincost = waypoint_getlinkcost(it, it.wp05);
+               if(it.wp06) it.wp06mincost = waypoint_getlinkcost(it, it.wp06);
+               if(it.wp07) it.wp07mincost = waypoint_getlinkcost(it, it.wp07);
+               if(it.wp08) it.wp08mincost = waypoint_getlinkcost(it, it.wp08);
+               if(it.wp09) it.wp09mincost = waypoint_getlinkcost(it, it.wp09);
+               if(it.wp10) it.wp10mincost = waypoint_getlinkcost(it, it.wp10);
+               if(it.wp11) it.wp11mincost = waypoint_getlinkcost(it, it.wp11);
+               if(it.wp12) it.wp12mincost = waypoint_getlinkcost(it, it.wp12);
+               if(it.wp13) it.wp13mincost = waypoint_getlinkcost(it, it.wp13);
+               if(it.wp14) it.wp14mincost = waypoint_getlinkcost(it, it.wp14);
+               if(it.wp15) it.wp15mincost = waypoint_getlinkcost(it, it.wp15);
+               if(it.wp16) it.wp16mincost = waypoint_getlinkcost(it, it.wp16);
+               if(it.wp17) it.wp17mincost = waypoint_getlinkcost(it, it.wp17);
+               if(it.wp18) it.wp18mincost = waypoint_getlinkcost(it, it.wp18);
+               if(it.wp19) it.wp19mincost = waypoint_getlinkcost(it, it.wp19);
+               if(it.wp20) it.wp20mincost = waypoint_getlinkcost(it, it.wp20);
+               if(it.wp21) it.wp21mincost = waypoint_getlinkcost(it, it.wp21);
+               if(it.wp22) it.wp22mincost = waypoint_getlinkcost(it, it.wp22);
+               if(it.wp23) it.wp23mincost = waypoint_getlinkcost(it, it.wp23);
+               if(it.wp24) it.wp24mincost = waypoint_getlinkcost(it, it.wp24);
+               if(it.wp25) it.wp25mincost = waypoint_getlinkcost(it, it.wp25);
+               if(it.wp26) it.wp26mincost = waypoint_getlinkcost(it, it.wp26);
+               if(it.wp27) it.wp27mincost = waypoint_getlinkcost(it, it.wp27);
+               if(it.wp28) it.wp28mincost = waypoint_getlinkcost(it, it.wp28);
+               if(it.wp29) it.wp29mincost = waypoint_getlinkcost(it, it.wp29);
+               if(it.wp30) it.wp30mincost = waypoint_getlinkcost(it, it.wp30);
+               if(it.wp31) it.wp31mincost = waypoint_getlinkcost(it, it.wp31);
+       });
+}
 
-       if (from == to)
-               return;
-       if (from.wpflags & WAYPOINTFLAG_NORELINK)
-               return;
+float waypoint_getlinearcost(float dist)
+{
+       if(skill >= autocvar_bot_ai_bunnyhop_skilloffset)
+               return dist / (autocvar_sv_maxspeed * 1.25);
+       return dist / autocvar_sv_maxspeed;
+}
+float waypoint_getlinearcost_underwater(float dist)
+{
+       // NOTE: this value is hardcoded on the engine too, see SV_WaterMove
+       return dist / (autocvar_sv_maxspeed * 0.7);
+}
 
-       if (waypoint_islinked(from, to))
-               return;
+float waypoint_gettravelcost(vector from, vector to, entity from_ent, entity to_ent)
+{
+       bool submerged_from = navigation_check_submerged_state(from_ent, from);
+       bool submerged_to = navigation_check_submerged_state(to_ent, to);
+
+       if (submerged_from && submerged_to)
+               return waypoint_getlinearcost_underwater(vlen(to - from));
+
+       float c = waypoint_getlinearcost(vlen(to - from));
 
-       if (to.wpisbox || from.wpisbox)
+       float height = from.z - to.z;
+       if(height > jumpheight_vec.z && autocvar_sv_gravity > 0)
        {
-               // if either is a box we have to find the nearest points on them to
-               // calculate the distance properly
-               vector v1, v2, m1, m2;
-               v1 = from.origin;
-               m1 = to.absmin;
-               m2 = to.absmax;
-               v1_x = bound(m1_x, v1_x, m2_x);
-               v1_y = bound(m1_y, v1_y, m2_y);
-               v1_z = bound(m1_z, v1_z, m2_z);
-               v2 = to.origin;
-               m1 = from.absmin;
-               m2 = from.absmax;
-               v2_x = bound(m1_x, v2_x, m2_x);
-               v2_y = bound(m1_y, v2_y, m2_y);
-               v2_z = bound(m1_z, v2_z, m2_z);
-               v2 = to.origin;
-               c = vlen(v2 - v1);
+               float height_cost = sqrt(height / (autocvar_sv_gravity / 2));
+               c = waypoint_getlinearcost(vlen(vec2(to - from))); // xy distance cost
+               if(height_cost > c)
+                       c = height_cost;
        }
-       else
-               c = vlen(to.origin - from.origin);
+
+       if (submerged_from || submerged_to)
+               return (c + waypoint_getlinearcost_underwater(vlen(to - from))) / 2;
+       return c;
+}
+
+float waypoint_getlinkcost(entity from, entity to)
+{
+       vector v1 = from.origin;
+       vector v2 = to.origin;
+       if (from.wpisbox)
+       {
+               vector m1 = from.absmin, m2 = from.absmax;
+               v1.x = bound(m1.x, v2.x, m2.x);
+               v1.y = bound(m1.y, v2.y, m2.y);
+               v1.z = bound(m1.z, v2.z, m2.z);
+       }
+       if (to.wpisbox)
+       {
+               vector m1 = to.absmin, m2 = to.absmax;
+               v2.x = bound(m1.x, v1.x, m2.x);
+               v2.y = bound(m1.y, v1.y, m2.y);
+               v2.z = bound(m1.z, v1.z, m2.z);
+       }
+       return waypoint_gettravelcost(v1, v2, from, to);
+}
+
+// add a new link to the spawnfunc_waypoint, replacing the furthest link it already has
+// if c == -1 automatically determine cost of the link
+void waypoint_addlink_customcost(entity from, entity to, float c)
+{
+       if (from == to || waypoint_islinked(from, to))
+               return;
+       if (c == -1 && (from.wpflags & WAYPOINTFLAG_NORELINK))
+               return;
+
+       if(c == -1)
+               c = waypoint_getlinkcost(from, to);
 
        if (from.wp31mincost < c) return;
        if (from.wp30mincost < c) {from.wp31 = to;from.wp31mincost = c;return;} from.wp31 = from.wp30;from.wp31mincost = from.wp30mincost;
@@ -216,20 +542,24 @@ void waypoint_addlink(entity from, entity to)
        from.wp00 = to;from.wp00mincost = c;return;
 }
 
+void waypoint_addlink(entity from, entity to)
+{
+       waypoint_addlink_customcost(from, to, -1);
+}
+
 // relink this spawnfunc_waypoint
 // (precompile a list of all reachable waypoints from this spawnfunc_waypoint)
 // (SLOW!)
 void waypoint_think(entity this)
 {
-       vector sv, sm1, sm2, ev, em1, em2, dv;
+       vector sv = '0 0 0', sv2 = '0 0 0', ev = '0 0 0', ev2 = '0 0 0', dv;
+       float sv2_height = 0, ev2_height = 0;
 
        bot_calculate_stepheightvec();
 
        bot_navigation_movemode = ((autocvar_bot_navigation_ignoreplayers) ? MOVE_NOMONSTERS : MOVE_NORMAL);
 
        //dprint("waypoint_think wpisbox = ", ftos(this.wpisbox), "\n");
-       sm1 = this.origin + this.mins;
-       sm2 = this.origin + this.maxs;
        IL_EACH(g_waypoints, this != it,
        {
                if (boxesoverlap(this.absmin, this.absmax, it.absmin, it.absmax))
@@ -245,16 +575,14 @@ void waypoint_think(entity this)
                                ++relink_pvsculled;
                                continue;
                        }
-                       sv = it.origin;
-                       sv.x = bound(sm1_x, sv.x, sm2_x);
-                       sv.y = bound(sm1_y, sv.y, sm2_y);
-                       sv.z = bound(sm1_z, sv.z, sm2_z);
-                       ev = this.origin;
-                       em1 = it.origin + it.mins;
-                       em2 = it.origin + it.maxs;
-                       ev.x = bound(em1_x, ev.x, em2_x);
-                       ev.y = bound(em1_y, ev.y, em2_y);
-                       ev.z = bound(em1_z, ev.z, em2_z);
+
+                       sv = set_tracewalk_dest_2(this, it.origin);
+                       sv2 = tracewalk_dest;
+                       sv2_height = tracewalk_dest_height;
+                       ev = set_tracewalk_dest_2(it, this.origin);
+                       ev2 = tracewalk_dest;
+                       ev2_height = tracewalk_dest_height;
+
                        dv = ev - sv;
                        dv.z = 0;
                        if(vdist(dv, >=, 1050)) // max search distance in XY
@@ -262,35 +590,30 @@ void waypoint_think(entity this)
                                ++relink_lengthculled;
                                continue;
                        }
+
                        navigation_testtracewalk = 0;
-                       if (!this.wpisbox)
-                       {
-                               tracebox(sv - PL_MIN_CONST.z * '0 0 1', PL_MIN_CONST, PL_MAX_CONST, sv, false, this);
-                               if (!trace_startsolid)
-                               {
-                                       //dprint("sv deviation", vtos(trace_endpos - sv), "\n");
-                                       sv = trace_endpos + '0 0 1';
-                               }
-                       }
-                       if (!it.wpisbox)
-                       {
-                               tracebox(ev - PL_MIN_CONST.z * '0 0 1', PL_MIN_CONST, PL_MAX_CONST, ev, false, it);
-                               if (!trace_startsolid)
-                               {
-                                       //dprint("ev deviation", vtos(trace_endpos - ev), "\n");
-                                       ev = trace_endpos + '0 0 1';
-                               }
-                       }
+
                        //traceline(this.origin, it.origin, false, NULL);
                        //if (trace_fraction == 1)
-                       if (!this.wpisbox && tracewalk(this, sv, PL_MIN_CONST, PL_MAX_CONST, ev, MOVE_NOMONSTERS))
-                               waypoint_addlink(this, it);
-                       else
+                       if (this.wpisbox)
                                relink_walkculled += 0.5;
-                       if (!it.wpisbox && tracewalk(it, ev, PL_MIN_CONST, PL_MAX_CONST, sv, MOVE_NOMONSTERS))
-                               waypoint_addlink(it, this);
                        else
+                       {
+                               if (tracewalk(this, sv, PL_MIN_CONST, PL_MAX_CONST, ev2, ev2_height, MOVE_NOMONSTERS))
+                                       waypoint_addlink(this, it);
+                               else
+                                       relink_walkculled += 0.5;
+                       }
+
+                       if (it.wpisbox)
                                relink_walkculled += 0.5;
+                       else
+                       {
+                               if (tracewalk(it, ev, PL_MIN_CONST, PL_MAX_CONST, sv2, sv2_height, MOVE_NOMONSTERS))
+                                       waypoint_addlink(it, this);
+                               else
+                                       relink_walkculled += 0.5;
+                       }
                }
        });
        navigation_testtracewalk = 0;
@@ -344,25 +667,6 @@ spawnfunc(waypoint)
        //waypoint_schedulerelink(this);
 }
 
-void waypoint_remove(entity wp)
-{
-       // tell all waypoints linked to wp that they need to relink
-       IL_EACH(g_waypoints, it != wp,
-       {
-               if (waypoint_islinked(it, wp))
-                       waypoint_removelink(it, wp);
-       });
-       delete(wp);
-}
-
-void waypoint_removeall()
-{
-       IL_EACH(g_waypoints, true,
-       {
-               delete(it);
-       });
-}
-
 // tell all waypoints to relink
 // actually this is useful only to update relink_* stats
 void waypoint_schedulerelinkall()
@@ -375,33 +679,76 @@ void waypoint_schedulerelinkall()
        waypoint_load_links_hardwired();
 }
 
+#define GET_GAMETYPE_EXTENSION() ((g_race) ? ".race" : "")
+
 // Load waypoint links from file
-float waypoint_load_links()
+bool waypoint_load_links()
 {
-       string filename, s;
+       string s;
        float file, tokens, c = 0, found;
        entity wp_from = NULL, wp_to;
        vector wp_to_pos, wp_from_pos;
-       filename = strcat("maps/", mapname);
-       filename = strcat(filename, ".waypoints.cache");
+
+       string gt_ext = GET_GAMETYPE_EXTENSION();
+
+       string filename = sprintf("maps/%s.waypoints.cache", strcat(mapname, gt_ext));
        file = fopen(filename, FILE_READ);
 
+       if (gt_ext != "" && file < 0)
+       {
+               // if race waypoint file doesn't exist load the default one
+               filename = sprintf("maps/%s.waypoints.cache", mapname);
+               file = fopen(filename, FILE_READ);
+       }
+
        if (file < 0)
        {
-               LOG_TRACE("waypoint links load from ");
-               LOG_TRACE(filename);
-               LOG_TRACE(" failed");
+               LOG_TRACE("waypoint links load from ", filename, " failed");
+               waypoint_schedulerelinkall();
                return false;
        }
 
+       bool parse_comments = true;
+       float ver = 0;
+
        while ((s = fgets(file)))
        {
+               if(parse_comments)
+               {
+                       if(substring(s, 0, 2) == "//")
+                       {
+                               if(substring(s, 2, 17) == "WAYPOINT_VERSION ")
+                                       ver = stof(substring(s, 19, -1));
+                               continue;
+                       }
+                       else
+                       {
+                               if(ver < WAYPOINT_VERSION)
+                               {
+                                       LOG_TRACE("waypoint links for this map are outdated.");
+                                       if (g_assault)
+                                       {
+                                               LOG_TRACE("Assault waypoint links need to be manually updated in the editor");
+                                       }
+                                       else
+                                       {
+                                               LOG_TRACE("automatically updating...");
+                                               waypoint_schedulerelinkall();
+                                               fclose(file);
+                                               return false;
+                                       }
+                               }
+                               parse_comments = false;
+                       }
+               }
+
                tokens = tokenizebyseparator(s, "*");
 
                if (tokens!=2)
                {
                        // bad file format
                        fclose(file);
+                       waypoint_schedulerelinkall(); // link all the autogenerated waypoints (teleporters)
                        return false;
                }
 
@@ -429,7 +776,6 @@ float waypoint_load_links()
                                LOG_TRACE("waypoint_load_links: couldn't find 'from' waypoint at ", vtos(wp_from_pos));
                                continue;
                        }
-
                }
 
                // Search "to" waypoint
@@ -458,7 +804,19 @@ float waypoint_load_links()
 
        fclose(file);
 
-       LOG_TRACE("loaded ", ftos(c), " waypoint links from maps/", mapname, ".waypoints.cache");
+       LOG_TRACE("loaded ", ftos(c), " waypoint links from ", filename);
+
+       bool scheduled = false;
+       IL_EACH(g_waypoints, it.wpflags & WAYPOINTFLAG_ITEM,
+       {
+               if (!it.wp00)
+               {
+                       waypoint_schedulerelink(it);
+                       scheduled = true;
+               }
+       });
+       if (scheduled)
+               return false;
 
        botframe_cachedwaypointlinks = true;
        return true;
@@ -466,14 +824,23 @@ float waypoint_load_links()
 
 void waypoint_load_or_remove_links_hardwired(bool removal_mode)
 {
-       string filename, s;
+       string s;
        float file, tokens, c = 0, found;
        entity wp_from = NULL, wp_to;
        vector wp_to_pos, wp_from_pos;
-       filename = strcat("maps/", mapname);
-       filename = strcat(filename, ".waypoints.hardwired");
+
+       string gt_ext = GET_GAMETYPE_EXTENSION();
+
+       string filename = sprintf("maps/%s.waypoints.hardwired", strcat(mapname, gt_ext));
        file = fopen(filename, FILE_READ);
 
+       if (gt_ext != "" && file < 0)
+       {
+               // if race waypoint file doesn't exist load the default one
+               filename = sprintf("maps/%s.waypoints.hardwired", mapname);
+               file = fopen(filename, FILE_READ);
+       }
+
        botframe_loadedforcedlinks = true;
 
        if (file < 0)
@@ -554,17 +921,16 @@ void waypoint_load_or_remove_links_hardwired(bool removal_mode)
                waypoint_addlink(wp_from, wp_to);
                wp_from.wphardwired = true;
                wp_to.wphardwired = true;
+               waypoint_setupmodel(wp_from);
+               waypoint_setupmodel(wp_to);
        }
 
        fclose(file);
 
-       if(!removal_mode)
-               LOG_TRACE("loaded ", ftos(c), " waypoint links from maps/", mapname, ".waypoints.hardwired");
+       LOG_TRACE(((removal_mode) ? "unloaded " : "loaded "),
+               ftos(c), " waypoint links from maps/", mapname, ".waypoints.hardwired");
 }
 
-void waypoint_load_links_hardwired() { waypoint_load_or_remove_links_hardwired(false); }
-void waypoint_remove_links_hardwired() { waypoint_load_or_remove_links_hardwired(true); }
-
 entity waypoint_get_link(entity w, float i)
 {
        switch(i)
@@ -611,7 +977,9 @@ void waypoint_save_links()
        // temporarily remove hardwired links so they don't get saved among normal links
        waypoint_remove_links_hardwired();
 
-       string filename = sprintf("maps/%s.waypoints.cache", mapname);
+       string gt_ext = GET_GAMETYPE_EXTENSION();
+
+       string filename = sprintf("maps/%s.waypoints.cache", strcat(mapname, gt_ext));
        int file = fopen(filename, FILE_WRITE);
        if (file < 0)
        {
@@ -619,6 +987,8 @@ void waypoint_save_links()
                return;
        }
 
+       fputs(file, strcat("//", "WAYPOINT_VERSION ", ftos_decimals(WAYPOINT_VERSION, 2), "\n"));
+
        int c = 0;
        IL_EACH(g_waypoints, true,
        {
@@ -634,9 +1004,10 @@ void waypoint_save_links()
                }
        });
        fclose(file);
+
        botframe_cachedwaypointlinks = true;
 
-       LOG_INFOF("saved %d waypoint links to maps/%s.waypoints.cache", c, mapname);
+       LOG_INFOF("saved %d waypoint links to %s", c, filename);
 
        waypoint_load_links_hardwired();
 }
@@ -644,7 +1015,9 @@ void waypoint_save_links()
 // save waypoints to gamedir/data/maps/mapname.waypoints
 void waypoint_saveall()
 {
-       string filename = sprintf("maps/%s.waypoints", mapname);
+       string gt_ext = GET_GAMETYPE_EXTENSION();
+
+       string filename = sprintf("maps/%s.waypoints", strcat(mapname, gt_ext));
        int file = fopen(filename, FILE_WRITE);
        if (file < 0)
        {
@@ -655,6 +1028,12 @@ void waypoint_saveall()
                return;
        }
 
+       // add 3 comments to not break compatibility with older Xonotic versions
+       // (they are read as a waypoint with origin '0 0 0' and flag 0 though)
+       fputs(file, strcat("//", "WAYPOINT_VERSION ", ftos_decimals(WAYPOINT_VERSION, 2), "\n"));
+       fputs(file, strcat("//", "\n"));
+       fputs(file, strcat("//", "\n"));
+
        int c = 0;
        IL_EACH(g_waypoints, true,
        {
@@ -674,63 +1053,99 @@ void waypoint_saveall()
        waypoint_save_links();
        botframe_loadedforcedlinks = false;
 
-       LOG_INFOF("saved %d waypoints to maps/%s.waypoints", c, mapname);
+       LOG_INFOF("saved %d waypoints to %s", c, filename);
 }
 
 // load waypoints from file
 float waypoint_loadall()
 {
-       string filename, s;
+       string s;
        float file, cwp, cwb, fl;
        vector m1, m2;
        cwp = 0;
        cwb = 0;
-       filename = strcat("maps/", mapname);
-       filename = strcat(filename, ".waypoints");
+
+       string gt_ext = GET_GAMETYPE_EXTENSION();
+
+       string filename = sprintf("maps/%s.waypoints", strcat(mapname, gt_ext));
        file = fopen(filename, FILE_READ);
-       if (file >= 0)
+
+       if (gt_ext != "" && file < 0)
        {
-               while ((s = fgets(file)))
-               {
-                       m1 = stov(s);
-                       s = fgets(file);
-                       if (!s)
-                               break;
-                       m2 = stov(s);
-                       s = fgets(file);
-                       if (!s)
-                               break;
-                       fl = stof(s);
-                       waypoint_spawn(m1, m2, fl);
-                       if (m1 == m2)
-                               cwp = cwp + 1;
-                       else
-                               cwb = cwb + 1;
-               }
-               fclose(file);
-               LOG_TRACE("loaded ", ftos(cwp), " waypoints and ", ftos(cwb), " wayboxes from maps/", mapname, ".waypoints");
+               // if race waypoint file doesn't exist load the default one
+               filename = sprintf("maps/%s.waypoints", mapname);
+               file = fopen(filename, FILE_READ);
        }
-       else
+
+       if (file < 0)
        {
                LOG_TRACE("waypoint load from ", filename, " failed");
+               return 0;
        }
+
+       bool parse_comments = true;
+       float ver = 0;
+
+       while ((s = fgets(file)))
+       {
+               if(parse_comments)
+               {
+                       if(substring(s, 0, 2) == "//")
+                       {
+                               if(substring(s, 2, 17) == "WAYPOINT_VERSION ")
+                                       ver = stof(substring(s, 19, -1));
+                               continue;
+                       }
+                       else
+                       {
+                               if(floor(ver) < floor(WAYPOINT_VERSION))
+                               {
+                                       LOG_TRACE("waypoints for this map are outdated");
+                                       LOG_TRACE("please update them in the editor");
+                               }
+                               parse_comments = false;
+                       }
+               }
+               m1 = stov(s);
+               s = fgets(file);
+               if (!s)
+                       break;
+               m2 = stov(s);
+               s = fgets(file);
+               if (!s)
+                       break;
+               fl = stof(s);
+               waypoint_spawn(m1, m2, fl);
+               if (m1 == m2)
+                       cwp = cwp + 1;
+               else
+                       cwb = cwb + 1;
+       }
+       fclose(file);
+       LOG_TRACE("loaded ", ftos(cwp), " waypoints and ", ftos(cwb), " wayboxes from maps/", mapname, ".waypoints");
+
        return cwp + cwb;
 }
 
-vector waypoint_fixorigin(vector position)
+#define waypoint_fixorigin(position, tracetest_ent) \
+       waypoint_fixorigin_down_dir(position, tracetest_ent, '0 0 -1')
+
+vector waypoint_fixorigin_down_dir(vector position, entity tracetest_ent, vector down_dir)
 {
-       tracebox(position + '0 0 1' * (1 - PL_MIN_CONST.z), PL_MIN_CONST, PL_MAX_CONST, position + '0 0 -512', MOVE_NOMONSTERS, NULL);
+       tracebox(position + '0 0 1', PL_MIN_CONST, PL_MAX_CONST, position + down_dir * 3000, MOVE_NOMONSTERS, tracetest_ent);
+       if(trace_startsolid)
+               tracebox(position + '0 0 1' * (1 - PL_MIN_CONST.z / 2), PL_MIN_CONST, PL_MAX_CONST, position + down_dir * 3000, MOVE_NOMONSTERS, tracetest_ent);
+       if(trace_startsolid)
+               tracebox(position + '0 0 1' * (1 - PL_MIN_CONST.z), PL_MIN_CONST, PL_MAX_CONST, position + down_dir * 3000, MOVE_NOMONSTERS, tracetest_ent);
        if(trace_fraction < 1)
                position = trace_endpos;
-       //traceline(position, position + '0 0 -512', MOVE_NOMONSTERS, NULL);
-       //print("position is ", ftos(trace_endpos_z - position_z), " above solid\n");
        return position;
 }
 
 void waypoint_spawnforitem_force(entity e, vector org)
 {
        // Fix the waypoint altitude if necessary
-       org = waypoint_fixorigin(org);
+       org = waypoint_fixorigin(org, NULL);
 
        // don't spawn an item spawnfunc_waypoint if it already exists
        IL_EACH(g_waypoints, true,
@@ -764,11 +1179,11 @@ void waypoint_spawnforitem(entity e)
        waypoint_spawnforitem_force(e, e.origin);
 }
 
-void waypoint_spawnforteleporter_boxes(entity e, vector org1, vector org2, vector destination1, vector destination2, float timetaken)
+void waypoint_spawnforteleporter_boxes(entity e, int teleport_flag, vector org1, vector org2, vector destination1, vector destination2, float timetaken)
 {
        entity w;
        entity dw;
-       w = waypoint_spawn(org1, org2, WAYPOINTFLAG_GENERATED | WAYPOINTFLAG_TELEPORT | WAYPOINTFLAG_NORELINK);
+       w = waypoint_spawn(org1, org2, WAYPOINTFLAG_GENERATED | teleport_flag | WAYPOINTFLAG_NORELINK);
        dw = waypoint_spawn(destination1, destination2, WAYPOINTFLAG_GENERATED);
        // one way link to the destination
        w.wp00 = dw;
@@ -779,17 +1194,25 @@ void waypoint_spawnforteleporter_boxes(entity e, vector org1, vector org2, vecto
        e.nearestwaypointtimeout = -1;
 }
 
-void waypoint_spawnforteleporter_v(entity e, vector org, vector destination, float timetaken)
+void waypoint_spawnforteleporter_wz(entity e, vector org, vector destination, float timetaken, vector down_dir, entity tracetest_ent)
 {
-       org = waypoint_fixorigin(org);
-       destination = waypoint_fixorigin(destination);
-       waypoint_spawnforteleporter_boxes(e, org, org, destination, destination, timetaken);
+       // warpzones with oblique warp plane rely on down_dir to snap waypoints
+       // to the ground without leaving the warp plane
+       // warpzones with horizontal warp plane (down_dir.x == -1) generate
+       // destination waypoint snapped to the ground (leaving warpzone), source
+       // waypoint in the center of the warp plane
+       if(down_dir.x != -1)
+               org = waypoint_fixorigin_down_dir(org, tracetest_ent, down_dir);
+       if(down_dir.x == -1)
+               down_dir = '0 0 -1';
+       destination = waypoint_fixorigin_down_dir(destination, tracetest_ent, down_dir);
+       waypoint_spawnforteleporter_boxes(e, WAYPOINTFLAG_TELEPORT, org, org, destination, destination, timetaken);
 }
 
-void waypoint_spawnforteleporter(entity e, vector destination, float timetaken)
+void waypoint_spawnforteleporter(entity e, vector destination, float timetaken, entity tracetest_ent)
 {
-       destination = waypoint_fixorigin(destination);
-       waypoint_spawnforteleporter_boxes(e, e.absmin, e.absmax, destination, destination, timetaken);
+       destination = waypoint_fixorigin(destination, tracetest_ent);
+       waypoint_spawnforteleporter_boxes(e, WAYPOINTFLAG_TELEPORT, e.absmin - PL_MAX_CONST + '1 1 1', e.absmax - PL_MIN_CONST + '-1 -1 -1', destination, destination, timetaken);
 }
 
 entity waypoint_spawnpersonal(entity this, vector position)
@@ -799,7 +1222,7 @@ entity waypoint_spawnpersonal(entity this, vector position)
        // drop the waypoint to a proper location:
        //   first move it up by a player height
        //   then move it down to hit the floor with player bbox size
-       position = waypoint_fixorigin(position);
+       position = waypoint_fixorigin(position, this);
 
        w = waypoint_spawn(position, position, WAYPOINTFLAG_GENERATED | WAYPOINTFLAG_PERSONAL);
        w.nearestwaypoint = NULL;
@@ -822,6 +1245,35 @@ void waypoint_showlink(entity wp1, entity wp2, int display_type)
                te_lightning2(NULL, wp1.origin, wp2.origin);
 }
 
+void waypoint_showlinks_to(entity wp, int display_type)
+{
+       IL_EACH(g_waypoints, it != wp,
+       {
+               if (waypoint_islinked(it, wp))
+                       waypoint_showlink(it, wp, display_type);
+       });
+}
+
+void waypoint_showlinks_from(entity wp, int display_type)
+{
+       waypoint_showlink(wp.wp00, wp, display_type); waypoint_showlink(wp.wp16, wp, display_type);
+       waypoint_showlink(wp.wp01, wp, display_type); waypoint_showlink(wp.wp17, wp, display_type);
+       waypoint_showlink(wp.wp02, wp, display_type); waypoint_showlink(wp.wp18, wp, display_type);
+       waypoint_showlink(wp.wp03, wp, display_type); waypoint_showlink(wp.wp19, wp, display_type);
+       waypoint_showlink(wp.wp04, wp, display_type); waypoint_showlink(wp.wp20, wp, display_type);
+       waypoint_showlink(wp.wp05, wp, display_type); waypoint_showlink(wp.wp21, wp, display_type);
+       waypoint_showlink(wp.wp06, wp, display_type); waypoint_showlink(wp.wp22, wp, display_type);
+       waypoint_showlink(wp.wp07, wp, display_type); waypoint_showlink(wp.wp23, wp, display_type);
+       waypoint_showlink(wp.wp08, wp, display_type); waypoint_showlink(wp.wp24, wp, display_type);
+       waypoint_showlink(wp.wp09, wp, display_type); waypoint_showlink(wp.wp25, wp, display_type);
+       waypoint_showlink(wp.wp10, wp, display_type); waypoint_showlink(wp.wp26, wp, display_type);
+       waypoint_showlink(wp.wp11, wp, display_type); waypoint_showlink(wp.wp27, wp, display_type);
+       waypoint_showlink(wp.wp12, wp, display_type); waypoint_showlink(wp.wp28, wp, display_type);
+       waypoint_showlink(wp.wp13, wp, display_type); waypoint_showlink(wp.wp29, wp, display_type);
+       waypoint_showlink(wp.wp14, wp, display_type); waypoint_showlink(wp.wp30, wp, display_type);
+       waypoint_showlink(wp.wp15, wp, display_type); waypoint_showlink(wp.wp31, wp, display_type);
+}
+
 void botframe_showwaypointlinks()
 {
        if (time < botframe_waypointeditorlightningtime)
@@ -844,38 +1296,10 @@ void botframe_showwaypointlinks()
                        if (head)
                        {
                                te_lightning2(NULL, head.origin, it.origin);
-                               waypoint_showlink(head.wp00, head, display_type);
-                               waypoint_showlink(head.wp01, head, display_type);
-                               waypoint_showlink(head.wp02, head, display_type);
-                               waypoint_showlink(head.wp03, head, display_type);
-                               waypoint_showlink(head.wp04, head, display_type);
-                               waypoint_showlink(head.wp05, head, display_type);
-                               waypoint_showlink(head.wp06, head, display_type);
-                               waypoint_showlink(head.wp07, head, display_type);
-                               waypoint_showlink(head.wp08, head, display_type);
-                               waypoint_showlink(head.wp09, head, display_type);
-                               waypoint_showlink(head.wp10, head, display_type);
-                               waypoint_showlink(head.wp11, head, display_type);
-                               waypoint_showlink(head.wp12, head, display_type);
-                               waypoint_showlink(head.wp13, head, display_type);
-                               waypoint_showlink(head.wp14, head, display_type);
-                               waypoint_showlink(head.wp15, head, display_type);
-                               waypoint_showlink(head.wp16, head, display_type);
-                               waypoint_showlink(head.wp17, head, display_type);
-                               waypoint_showlink(head.wp18, head, display_type);
-                               waypoint_showlink(head.wp19, head, display_type);
-                               waypoint_showlink(head.wp20, head, display_type);
-                               waypoint_showlink(head.wp21, head, display_type);
-                               waypoint_showlink(head.wp22, head, display_type);
-                               waypoint_showlink(head.wp23, head, display_type);
-                               waypoint_showlink(head.wp24, head, display_type);
-                               waypoint_showlink(head.wp25, head, display_type);
-                               waypoint_showlink(head.wp26, head, display_type);
-                               waypoint_showlink(head.wp27, head, display_type);
-                               waypoint_showlink(head.wp28, head, display_type);
-                               waypoint_showlink(head.wp29, head, display_type);
-                               waypoint_showlink(head.wp30, head, display_type);
-                               waypoint_showlink(head.wp31, head, display_type);
+                               if(PHYS_INPUT_BUTTON_CROUCH(it))
+                                       waypoint_showlinks_to(head, display_type);
+                               else
+                                       waypoint_showlinks_from(head, display_type);
                        }
                }
        });
@@ -927,7 +1351,7 @@ float botframe_autowaypoints_fix_from(entity p, float walkfromwp, entity wp, .en
 
                // if wp -> porg, then OK
                float maxdist;
-               if(navigation_waypoint_will_link(wp.origin, porg, p, walkfromwp, 1050))
+               if(navigation_waypoint_will_link(wp.origin, porg, p, porg, 0, wp.origin, 0, walkfromwp, 1050))
                {
                        // we may find a better one
                        maxdist = vlen(wp.origin - porg);
@@ -943,8 +1367,8 @@ float botframe_autowaypoints_fix_from(entity p, float walkfromwp, entity wp, .en
                {
                        float d = vlen(wp.origin - it.origin) + vlen(it.origin - porg);
                        if(d < bestdist)
-                       if(navigation_waypoint_will_link(wp.origin, it.origin, p, walkfromwp, 1050))
-                       if(navigation_waypoint_will_link(it.origin, porg, p, walkfromwp, 1050))
+                       if(navigation_waypoint_will_link(wp.origin, it.origin, p, it.origin, 0, wp.origin, 0, walkfromwp, 1050))
+                       if(navigation_waypoint_will_link(it.origin, porg, p, porg, 0, it.origin, 0, walkfromwp, 1050))
                        {
                                bestdist = d;
                                p.(fld) = it;
@@ -998,7 +1422,7 @@ float botframe_autowaypoints_fix_from(entity p, float walkfromwp, entity wp, .en
 
                if(wp)
                {
-                       if(!navigation_waypoint_will_link(wp.origin, o, p, walkfromwp, 1050))
+                       if(!navigation_waypoint_will_link(wp.origin, o, p, o, 0, wp.origin, 0, walkfromwp, 1050))
                        {
                                // we cannot walk from wp.origin to o
                                // get closer to tmax
@@ -1024,7 +1448,7 @@ float botframe_autowaypoints_fix_from(entity p, float walkfromwp, entity wp, .en
                // if we get here, o is valid regarding waypoints
                // check if o is connected right to the player
                // we break if it succeeds, as that means o is a good waypoint location
-               if(navigation_waypoint_will_link(o, porg, p, walkfromwp, 1050))
+               if(navigation_waypoint_will_link(o, porg, p, porg, 0, o, 0, walkfromwp, 1050))
                        break;
 
                // o is no good, we need to get closer to the player
@@ -1071,6 +1495,8 @@ void botframe_deleteuselesswaypoints()
                        it.wpflags |= WAYPOINTFLAG_USEFUL;
                if (it.wpflags & WAYPOINTFLAG_TELEPORT)
                        it.wpflags |= WAYPOINTFLAG_USEFUL;
+               if (it.wpflags & WAYPOINTFLAG_LADDER)
+                       it.wpflags |= WAYPOINTFLAG_USEFUL;
                if (it.wpflags & WAYPOINTFLAG_PROTECTED)
                        it.wpflags |= WAYPOINTFLAG_USEFUL;
                // b) WP is closest WP for an item/spawnpoint/other entity
index 38d57a04a1395a1f472e2e41690703dc466789a5..bea11e9299986ce180765f09a520e37c8295f6de 100644 (file)
@@ -3,6 +3,11 @@
  * Globals and Fields
  */
 
+// increase by 0.01 when changes require only waypoint relinking
+// increase by 1 when changes require to manually edit waypoints
+// max 2 decimal places, always specified
+#define WAYPOINT_VERSION 1.00
+
 // fields you can query using prvm_global server to get some statistics about waypoint linking culling
 float relink_total, relink_walkculled, relink_pvsculled, relink_lengthculled;
 
@@ -10,10 +15,11 @@ float botframe_waypointeditorlightningtime;
 float botframe_loadedforcedlinks;
 float botframe_cachedwaypointlinks;
 
+// waypoint wp links to waypoint wp.wpXX (OUTGOING link)
+// links are sorted by their cost (wpXXmincost)
 .entity wp00, wp01, wp02, wp03, wp04, wp05, wp06, wp07, wp08, wp09, wp10, wp11, wp12, wp13, wp14, wp15;
 .entity wp16, wp17, wp18, wp19, wp20, wp21, wp22, wp23, wp24, wp25, wp26, wp27, wp28, wp29, wp30, wp31;
 
-// itemscore = (howmuchmoreIwant / howmuchIcanwant) / itemdistance
 .float wp00mincost, wp01mincost, wp02mincost, wp03mincost, wp04mincost, wp05mincost, wp06mincost, wp07mincost;
 .float wp08mincost, wp09mincost, wp10mincost, wp11mincost, wp12mincost, wp13mincost, wp14mincost, wp15mincost;
 .float wp16mincost, wp17mincost, wp18mincost, wp19mincost, wp20mincost, wp21mincost, wp22mincost, wp23mincost;
@@ -29,30 +35,43 @@ float botframe_cachedwaypointlinks;
  */
 
 spawnfunc(waypoint);
+void waypoint_removelink(entity from, entity to);
+bool waypoint_islinked(entity from, entity to);
+void waypoint_addlink_customcost(entity from, entity to, float c);
 void waypoint_addlink(entity from, entity to);
 void waypoint_think(entity this);
 void waypoint_clearlinks(entity wp);
 void waypoint_schedulerelink(entity wp);
 
-void waypoint_remove(entity e);
-void waypoint_removeall();
+float waypoint_getlinkcost(entity from, entity to);
+float waypoint_gettravelcost(vector from, vector to, entity from_ent, entity to_ent);
+float waypoint_getlinearcost(float dist);
+void waypoint_updatecost_foralllinks();
+
+void waypoint_remove_fromeditor(entity pl);
+void waypoint_remove(entity wp);
 void waypoint_schedulerelinkall();
-void waypoint_load_links_hardwired();
 void waypoint_save_links();
 void waypoint_saveall();
 
 void waypoint_spawnforitem_force(entity e, vector org);
 void waypoint_spawnforitem(entity e);
-void waypoint_spawnforteleporter(entity e, vector destination, float timetaken);
-void waypoint_spawnforteleporter_v(entity e, vector org, vector destination, float timetaken);
+void waypoint_spawnforteleporter(entity e, vector destination, float timetaken, entity tracetest_ent);
+void waypoint_spawnforteleporter_wz(entity e, vector org, vector destination, float timetaken, vector down_dir, entity tracetest_ent);
 void botframe_showwaypointlinks();
 
 float waypoint_loadall();
-float waypoint_load_links();
+bool waypoint_load_links();
+#define waypoint_load_links_hardwired() waypoint_load_or_remove_links_hardwired(false)
+#define waypoint_remove_links_hardwired() waypoint_load_or_remove_links_hardwired(true)
+void waypoint_load_or_remove_links_hardwired(bool removal_mode);
 
+void waypoint_spawn_fromeditor(entity pl);
 entity waypoint_spawn(vector m1, vector m2, float f);
 entity waypoint_spawnpersonal(entity this, vector position);
 
-vector waypoint_fixorigin(vector position);
+void waypoint_unreachable(entity pl);
+
+vector waypoint_fixorigin_down_dir(vector position, entity tracetest_ent, vector down_dir);
 
 void botframe_autowaypoints();
index 68ae41670644dd1e241752f946ba0d5aeb071c54..f8738f80fb5cdc855eda4bd6bb9ad351ddf9d5cb 100644 (file)
@@ -28,15 +28,17 @@ void navigation_markroutes(entity this, entity fixed_source_waypoint) { }
 void navigation_markroutes_inverted(entity fixed_source_waypoint) { }
 void navigation_routerating(entity this, entity e, float f, float rangebias) { }
 
-bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float movemode) { return false; }
+bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float end_height, float movemode) { return false; }
 
-void waypoint_remove(entity e) { }
+void waypoint_remove_fromeditor(entity pl) { }
+void waypoint_remove(entity wp) { }
 void waypoint_saveall() { }
 void waypoint_schedulerelinkall() { }
 void waypoint_schedulerelink(entity wp) { }
 void waypoint_spawnforitem(entity e) { }
 void waypoint_spawnforitem_force(entity e, vector org) { }
-void waypoint_spawnforteleporter(entity e, vector destination, float timetaken) { }
-void waypoint_spawnforteleporter_v(entity e, vector org, vector destination, float timetaken) { }
+void waypoint_spawnforteleporter(entity e, vector destination, float timetaken, entity tracetest_ent) { }
+void waypoint_spawnforteleporter_wz(entity e, vector org, vector destination, float timetaken, vector down_dir, entity tracetest_ent) { }
+void waypoint_spawn_fromeditor(entity pl) { }
 entity waypoint_spawn(vector m1, vector m2, float f) { return NULL; }
 #endif
index 4f7dd14a54da6040d8bc1c93965c515b8a37d049..3333c04e49180286b6ef8806c91aa410baf0de1e 100644 (file)
@@ -507,7 +507,7 @@ void PutPlayerInServer(entity this)
        accuracy_resend(this);
 
        if (this.team < 0)
-               JoinBestTeam(this, false, true);
+               JoinBestTeam(this, true);
 
        entity spot = SelectSpawnPoint(this, false);
        if (!spot) {
@@ -558,8 +558,11 @@ 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);
+               if (MUTATOR_CALLHOOK(ForbidRandomStartWeapons, this) == false)
+               {
+                       GiveRandomWeapons(this, random_start_weapons_count,
+                               autocvar_g_random_start_weapons, random_start_ammo);
+               }
        }
        SetSpectatee_status(this, 0);
 
@@ -614,7 +617,10 @@ void PutPlayerInServer(entity this)
        this.fire_endtime = -1;
        this.revive_progress = 0;
        this.revival_time = 0;
+
        this.air_finished = time + 12;
+       this.waterlevel = WATERLEVEL_NONE;
+       this.watertype = CONTENT_EMPTY;
 
        entity spawnevent = new_pure(spawnevent);
        spawnevent.owner = this;
@@ -636,7 +642,6 @@ void PutPlayerInServer(entity this)
        setorigin(this, spot.origin + '0 0 1' * (1 - this.mins.z - 24));
        // don't reset back to last position, even if new position is stuck in solid
        this.oldorigin = this.origin;
-       this.lastteleporttime = time; // prevent insane speeds due to changing origin
        if(this.conveyor)
                IL_REMOVE(g_conveyed, this);
        this.conveyor = NULL; // prevent conveyors at the previous location from moving a freshly spawned player
@@ -654,9 +659,11 @@ void PutPlayerInServer(entity this)
 
        PHYS_INPUT_BUTTON_ATCK(this) = PHYS_INPUT_BUTTON_JUMP(this) = PHYS_INPUT_BUTTON_ATCK2(this) = false;
 
+       // player was spectator
        if (CS(this).killcount == FRAGS_SPECTATOR) {
                PlayerScore_Clear(this);
                CS(this).killcount = 0;
+               CS(this).startplaytime = time;
        }
 
        for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
@@ -873,19 +880,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 );
+               JoinBestTeam( this, 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)
@@ -893,7 +900,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');
@@ -905,7 +912,7 @@ 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) && MUTATOR_CALLHOOK(ClientKill_Now, this) == false)
@@ -970,7 +977,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)
        {
@@ -1184,12 +1191,10 @@ void ClientConnect(entity this)
        }
        if (!teamplay && this.team_forced > 0) this.team_forced = 0;
 
-    {
-        int id = this.playerid;
-        this.playerid = 0; // silent
-           JoinBestTeam(this, false, false); // if the team number is valid, keep it
-           this.playerid = id;
-    }
+       int playerid_save = this.playerid;
+       this.playerid = 0; // silent
+       JoinBestTeam(this, false); // if the team number is valid, keep it
+       this.playerid = playerid_save;
 
        if (autocvar_sv_spectate || autocvar_g_campaign || this.team_forced < 0) {
                TRANSMUTE(Observer, this);
@@ -2000,7 +2005,7 @@ void Join(entity this)
 
        if(!this.team_selected)
        if(autocvar_g_campaign || autocvar_g_balance_teams)
-               JoinBestTeam(this, false, true);
+               JoinBestTeam(this, true);
 
        if(autocvar_g_campaign)
                campaign_bots_may_start = true;
@@ -2658,7 +2663,7 @@ void PlayerPreThink (entity this)
 
 void DrownPlayer(entity this)
 {
-       if(IS_DEAD(this))
+       if(IS_DEAD(this) || game_stopped || time < game_starttime)
                return;
 
        if (this.waterlevel != WATERLEVEL_SUBMERGED || this.vehicle)
index 2282c09cbb1cafd54e3a667886430b98b554c342..42d7d9560b4e92359242e19428f242d146cbff0a 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);
@@ -86,6 +85,7 @@ CLASS(Client, Object)
     ATTRIB(Client, motd_actived_time, float, this.motd_actived_time);
     ATTRIB(Client, jointime, float, this.jointime);
     ATTRIB(Client, spectatortime, float, this.spectatortime);
+    ATTRIB(Client, startplaytime, float, this.startplaytime);
     ATTRIB(Client, version_nagtime, float, this.version_nagtime);
     ATTRIB(Client, netname_previous, string, this.netname_previous);
     ATTRIB(Client, allowed_timeouts, int, this.allowed_timeouts);
index f5569c08f2697d4baf879ff63dc659efaaa88e74..6de4507b1708ef54516d9d5db859bdb960a37457 100644 (file)
@@ -1574,11 +1574,13 @@ void GameCommand_trace(float request, float argc)
 
                                case "walk":
                                {
-                                       if (argc == 4)
+                                       if (argc == 4 || argc == 5)
                                        {
                                                e = nextent(NULL);
-                                               if (tracewalk(e, stov(argv(2)), e.mins, e.maxs, stov(argv(3)), MOVE_NORMAL)) LOG_INFO("can walk");
-                                               else LOG_INFO("cannot walk");
+                                               if (tracewalk(e, stov(argv(2)), e.mins, e.maxs, stov(argv(3)), stof(argv(4)), MOVE_NORMAL))
+                                                       LOG_INFO("can walk");
+                                               else
+                                                       LOG_INFO("cannot walk");
                                                return;
                                        }
                                }
@@ -1604,7 +1606,9 @@ void GameCommand_trace(float request, float argc)
                        LOG_INFO("Incorrect parameters for ^2trace^7");
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("Usage:^3 sv_cmd trace command (startpos endpos)");
+                       LOG_INFO("Usage:^3 sv_cmd trace command [startpos endpos] [endpos_height]");
+                       LOG_INFO("  Where startpos and endpos are parameters for 'walk' and 'showline' commands,");
+                       LOG_INFO("  'endpos_height' is an optional parameter for 'walk' command,");
                        LOG_INFO("  Full list of commands here: \"debug, debug2, walk, showline.\"");
                        LOG_INFO("See also: ^2bbox, gettaginfo^7");
                        return;
index 126a0f6f6e7a98fc357917b8707f0c34ebf7dc8b..e830fe6c4fe8ed7caf27748935faa8d41cdd4967 100644 (file)
@@ -4,29 +4,15 @@
 #include <server/miscfunctions.qh>
 #include <common/weapons/_all.qh>
 
-spawnfunc(weapon_electro);
-spawnfunc(weapon_hagar);
-spawnfunc(weapon_machinegun);
-spawnfunc(item_bullets);
-spawnfunc(item_armor_mega);
-spawnfunc(item_health_mega);
-spawnfunc(item_health_medium);
-
 //***********************
 //QUAKE 1 ENTITIES - So people can play quake1 maps with the xonotic weapons
 //***********************
-spawnfunc(weapon_nailgun) {spawnfunc_weapon_electro(this);}
-spawnfunc(weapon_supernailgun) {spawnfunc_weapon_hagar(this);}
-spawnfunc(weapon_supershotgun) {spawnfunc_weapon_machinegun(this);}
+SPAWNFUNC_WEAPON(weapon_nailgun, WEP_ELECTRO)
+SPAWNFUNC_WEAPON(weapon_supernailgun, WEP_HAGAR)
+SPAWNFUNC_WEAPON(weapon_supershotgun, WEP_MACHINEGUN)
 
-spawnfunc(item_spikes) {spawnfunc_item_bullets(this);}
+SPAWNFUNC_ITEM(item_spikes, ITEM_Bullets)
 //spawnfunc(item_armor1) {spawnfunc_item_armor_medium(this);}  // FIXME: in Quake this is green armor, in Xonotic maps it is an armor shard
-spawnfunc(item_armor2) {spawnfunc_item_armor_mega(this);}
-spawnfunc(item_armorInv) {spawnfunc_item_armor_mega(this);} // TODO: make sure we actually want this
-spawnfunc(item_health) {if (this.spawnflags & 2) spawnfunc_item_health_mega(this);else spawnfunc_item_health_medium(this);}
-
-//spawnfunc_item_spikes
-//spawnfunc_item_health
-
-
-
+SPAWNFUNC_ITEM(item_armor2, ITEM_ArmorMega)
+SPAWNFUNC_ITEM(item_armorInv, ITEM_ArmorMega) // TODO: make sure we actually want this
+spawnfunc(item_health) {if (this.spawnflags & 2) StartItem(this, ITEM_HealthMega);else StartItem(this, ITEM_HealthMedium);}
index df99f1570388c1a92bdcc6300d80b81027c83262..19f9ebf404426be0080aa8e389821860a431a54c 100644 (file)
@@ -1,15 +1,12 @@
 #include "quake2.qh"
 
-spawnfunc(item_armor_medium);
-
-spawnfunc(item_invincible);
-
+#include <common/items/_mod.qh>
 
 //***********************
 //QUAKE 2 ENTITIES - So people can play quake2 maps with the xonotic weapons
 //***********************
-spawnfunc(item_armor_jacket) {spawnfunc_item_armor_medium(this);}
+SPAWNFUNC_ITEM(item_armor_jacket, ITEM_ArmorMedium)
 
-spawnfunc(item_invulnerability) {spawnfunc_item_invincible(this);}
+SPAWNFUNC_ITEM(item_invulnerability, ITEM_Shield)
 
 // rest of the quake 2 entities are handled by q1 and q3 compat
index 2e2301d0bdec150704ae2b6f297beddbda5870a2..6a7f21aa674d319bad7ece6abeb908b28b764d60 100644 (file)
@@ -2,32 +2,11 @@
 
 #include <server/defs.qh>
 #include <server/miscfunctions.qh>
+#include <server/items.qh>
 #include <common/weapons/_all.qh>
 
-spawnfunc(weapon_crylink);
-spawnfunc(weapon_electro);
-spawnfunc(weapon_hagar);
-spawnfunc(weapon_hook);
-spawnfunc(weapon_machinegun);
-spawnfunc(weapon_vortex);
-spawnfunc(weapon_minelayer);
-
 spawnfunc(target_items);
 
-spawnfunc(item_bullets);
-spawnfunc(item_cells);
-spawnfunc(item_rockets);
-spawnfunc(item_shells);
-
-spawnfunc(item_strength);
-
-spawnfunc(item_armor_big);
-spawnfunc(item_armor_mega);
-spawnfunc(item_armor_small);
-
-spawnfunc(item_health_medium);
-spawnfunc(item_health_mega);
-
 //***********************
 //QUAKE 3 ENTITIES - So people can play quake3 maps with the xonotic weapons
 //***********************
@@ -35,51 +14,51 @@ spawnfunc(item_health_mega);
 // NOTE: for best experience, you need to swap MGs with SGs in the map or it won't have a MG
 
 // SG -> SG
-spawnfunc(ammo_shells)         { spawnfunc_item_shells(this);         }
+SPAWNFUNC_ITEM(ammo_shells, ITEM_Shells)
 
 // MG -> MG
-spawnfunc(ammo_bullets)        { spawnfunc_item_bullets(this);        }
+SPAWNFUNC_ITEM(ammo_bullets, ITEM_Bullets)
 
 // GL -> Mortar
-spawnfunc(ammo_grenades)       { spawnfunc_item_rockets(this);        }
+SPAWNFUNC_ITEM(ammo_grenades, ITEM_Rockets)
 
 // Mines -> Rockets
-spawnfunc(weapon_prox_launcher) { spawnfunc_weapon_minelayer(this);   }
-spawnfunc(ammo_mines)           { spawnfunc_item_rockets(this);       }
+SPAWNFUNC_WEAPON(weapon_prox_launcher, WEP_MINE_LAYER)
+SPAWNFUNC_ITEM(ammo_mines, ITEM_Rockets)
 
 // LG -> Lightning
-spawnfunc(weapon_lightning)    { spawnfunc_weapon_electro(this);      }
-spawnfunc(ammo_lightning)      { spawnfunc_item_cells(this);          }
+SPAWNFUNC_WEAPON(weapon_lightning, WEP_ELECTRO)
+SPAWNFUNC_ITEM(ammo_lightning, ITEM_Cells)
 
 // Plasma -> Hagar
-spawnfunc(weapon_plasmagun)    { spawnfunc_weapon_hagar(this);        }
-spawnfunc(ammo_cells)          { spawnfunc_item_rockets(this);        }
+SPAWNFUNC_WEAPON(weapon_plasmagun, WEP_HAGAR)
+SPAWNFUNC_ITEM(ammo_cells, ITEM_Rockets)
 
 // Rail -> Vortex
-spawnfunc(weapon_railgun)      { spawnfunc_weapon_vortex(this);       }
-spawnfunc(ammo_slugs)          { spawnfunc_item_cells(this);          }
+SPAWNFUNC_WEAPON(weapon_railgun, WEP_VORTEX)
+SPAWNFUNC_ITEM(ammo_slugs, ITEM_Cells)
 
 // BFG -> Crylink
-spawnfunc(weapon_bfg)          { spawnfunc_weapon_crylink(this);      }
-spawnfunc(ammo_bfg)            { spawnfunc_item_cells(this);          }
+SPAWNFUNC_WEAPON(weapon_bfg, WEP_CRYLINK)
+SPAWNFUNC_ITEM(ammo_bfg, ITEM_Cells)
 
 // grappling hook -> hook
-spawnfunc(weapon_grapplinghook) { spawnfunc_weapon_hook(this);        }
+SPAWNFUNC_WEAPON(weapon_grapplinghook, WEP_HOOK)
 
 // RL -> RL
-spawnfunc(ammo_rockets)        { spawnfunc_item_rockets(this);        }
+SPAWNFUNC_ITEM(ammo_rockets, ITEM_Rockets)
 
 // Armor
-spawnfunc(item_armor_body)     { spawnfunc_item_armor_mega(this);     }
-spawnfunc(item_armor_combat)   { spawnfunc_item_armor_big(this);      }
-spawnfunc(item_armor_shard)    { spawnfunc_item_armor_small(this);    }
-spawnfunc(item_enviro)         { spawnfunc_item_invincible(this);     }
+SPAWNFUNC_ITEM(item_armor_body, ITEM_ArmorMega)
+SPAWNFUNC_ITEM(item_armor_combat, ITEM_ArmorBig)
+SPAWNFUNC_ITEM(item_armor_shard, ITEM_ArmorSmall)
+SPAWNFUNC_ITEM(item_enviro, ITEM_Shield)
 
 // medkit -> armor (we have no holdables)
-spawnfunc(holdable_medkit)        { spawnfunc_item_armor_mega(this);     }
+SPAWNFUNC_ITEM(holdable_medkit, ITEM_ArmorMega)
 
 // doubler -> strength
-spawnfunc(item_doubler)        { spawnfunc_item_strength(this); }
+SPAWNFUNC_ITEM(item_doubler, ITEM_Strength)
 
 .float wait;
 .float delay;
@@ -116,27 +95,31 @@ void target_give_init(entity this)
 {
        IL_EACH(g_items, it.targetname == this.target,
        {
-               if (it.classname == "weapon_rocketlauncher" || it.classname == "weapon_devastator") {
+               if (it.classname == "weapon_devastator") {
                        this.ammo_rockets += it.count * WEP_CVAR(devastator, ammo);
                        this.netname = cons(this.netname, "devastator");
                }
-               else if (it.classname == "weapon_lightning") {
+               else if (it.classname == "weapon_vortex") {
+                       this.ammo_cells += it.count * WEP_CVAR_PRI(vortex, ammo); // WEAPONTODO
+                       this.netname = cons(this.netname, "vortex");
+               }
+               else if (it.classname == "weapon_electro") {
                        this.ammo_cells += it.count * WEP_CVAR_PRI(electro, ammo); // WEAPONTODO
                        this.netname = cons(this.netname, "electro");
                }
-               else if (it.classname == "weapon_plasmagun") {
+               else if (it.classname == "weapon_hagar") {
                        this.ammo_rockets += it.count * WEP_CVAR_PRI(hagar, ammo); // WEAPONTODO
                        this.netname = cons(this.netname, "hagar");
                }
-               else if (it.classname == "weapon_bfg") {
+               else if (it.classname == "weapon_crylink") {
                        this.ammo_cells += it.count * WEP_CVAR_PRI(crylink, ammo);
                        this.netname = cons(this.netname, "crylink");
                }
-               else if (it.classname == "weapon_grenadelauncher" || it.classname == "weapon_mortar") {
+               else if (it.classname == "weapon_mortar") {
                        this.ammo_rockets += it.count * WEP_CVAR_PRI(mortar, ammo); // WEAPONTODO
                        this.netname = cons(this.netname, "mortar");
                }
-               else if (it.classname == "item_armor_body")
+               else if (it.classname == "item_armor_mega")
                        this.armorvalue = 100;
                else if (it.classname == "item_health_mega")
                        this.health = 200;
index 6c69859fdfe0c6e748acc5b227227f49d5e9626c..d2577b46afb356fef97917bf577493f0e483fbaf 100644 (file)
@@ -2,65 +2,41 @@
 
 #include <server/defs.qh>
 #include <server/miscfunctions.qh>
+#include <server/items.qh>
 #include <common/weapons/_all.qh>
-// #include <server/mutators/gamemode.qh>
-
-spawnfunc(weapon_arc);
-spawnfunc(weapon_crylink);
-spawnfunc(weapon_electro);
-spawnfunc(weapon_mortar);
-spawnfunc(weapon_hagar);
-spawnfunc(weapon_machinegun);
-spawnfunc(weapon_devastator);
-spawnfunc(weapon_shotgun);
-spawnfunc(weapon_vortex);
-
-spawnfunc(item_armor_big);
-spawnfunc(item_armor_mega);
-spawnfunc(item_armor_small);
-
-spawnfunc(item_bullets);
-spawnfunc(item_cells);
-spawnfunc(item_quad);
-spawnfunc(item_rockets);
-spawnfunc(item_shells);
-
-spawnfunc(item_jetpack);
 
 spawnfunc(item_haste);
-spawnfunc(item_health_medium);
-spawnfunc(item_health_mega);
 spawnfunc(item_invis);
 
 //***********************
-//WORD OF PADMAN ENTITIES - So people can play wop maps with the xonotic weapons
+//WORLD OF PADMAN ENTITIES - So people can play wop maps with the xonotic weapons
 //***********************
 
 //spawnfunc(item_revival)     /* handled by buffs mutator */
 //spawnfunc(item_jumper)      /* handled by buffs mutator */
 
-spawnfunc(weapon_punchy)       { spawnfunc_weapon_arc(this);                   }
-spawnfunc(weapon_nipper)       { spawnfunc_weapon_machinegun(this);    }
-spawnfunc(weapon_pumper)       { spawnfunc_weapon_shotgun(this);               }
-spawnfunc(weapon_boaster)      { spawnfunc_weapon_electro(this);               }
-spawnfunc(weapon_splasher)     { spawnfunc_weapon_vortex(this);                }
-spawnfunc(weapon_bubbleg)      { spawnfunc_weapon_hagar(this);                 }
-spawnfunc(weapon_balloony)     { spawnfunc_weapon_mortar(this);                }
-spawnfunc(weapon_betty)                { spawnfunc_weapon_devastator(this);    }
-spawnfunc(weapon_imperius)     { spawnfunc_weapon_crylink(this);               }
-
-spawnfunc(ammo_pumper)         { spawnfunc_item_shells(this);                  }
-spawnfunc(ammo_nipper)         { spawnfunc_item_bullets(this);                 }
-spawnfunc(ammo_balloony)       { spawnfunc_item_rockets(this);                 }
-spawnfunc(ammo_bubbleg)                { spawnfunc_item_rockets(this);                 }
-spawnfunc(ammo_boaster)                { spawnfunc_item_cells(this);                   }
-spawnfunc(ammo_betty)          { spawnfunc_item_rockets(this);                 }
-spawnfunc(ammo_imperius)       { spawnfunc_item_cells(this);                   }
-
-spawnfunc(item_padpower)       { spawnfunc_item_quad(this);                    }
-spawnfunc(item_climber)                { spawnfunc_item_invincible(this);              }
+SPAWNFUNC_WEAPON(weapon_punchy, WEP_ARC)
+SPAWNFUNC_WEAPON(weapon_nipper, WEP_MACHINEGUN)
+SPAWNFUNC_WEAPON(weapon_pumper, WEP_SHOTGUN)
+SPAWNFUNC_WEAPON(weapon_boaster, WEP_ELECTRO)
+SPAWNFUNC_WEAPON(weapon_splasher, WEP_VORTEX)
+SPAWNFUNC_WEAPON(weapon_bubbleg, WEP_HAGAR)
+SPAWNFUNC_WEAPON(weapon_balloony, WEP_MORTAR)
+SPAWNFUNC_WEAPON(weapon_betty, WEP_DEVASTATOR)
+SPAWNFUNC_WEAPON(weapon_imperius, WEP_CRYLINK)
+
+SPAWNFUNC_ITEM(ammo_pumper, ITEM_Shells)
+SPAWNFUNC_ITEM(ammo_nipper, ITEM_Bullets)
+SPAWNFUNC_ITEM(ammo_balloony, ITEM_Rockets)
+SPAWNFUNC_ITEM(ammo_bubbleg, ITEM_Rockets)
+SPAWNFUNC_ITEM(ammo_boaster, ITEM_Cells)
+SPAWNFUNC_ITEM(ammo_betty, ITEM_Rockets)
+SPAWNFUNC_ITEM(ammo_imperius, ITEM_Cells)
+
+SPAWNFUNC_ITEM(item_padpower, ITEM_Strength)
+SPAWNFUNC_ITEM(item_climber, ITEM_Shield)
 spawnfunc(item_speedy)         { spawnfunc_item_haste(this);                   }
 spawnfunc(item_visionless)     { spawnfunc_item_invis(this);                   }
-spawnfunc(item_armor_padshield)        { spawnfunc_item_armor_mega(this);      }
+SPAWNFUNC_ITEM(item_armor_padshield, ITEM_ArmorMega)
 
-spawnfunc(holdable_floater)            { spawnfunc_item_jetpack(this);         }
+SPAWNFUNC_ITEM(holdable_floater, ITEM_Jetpack)
index 4c3b1d221ea4ae73be7f0ff7cc7b7ed5d7b9000f..e9cf6ee286a7d8831806504435737977436d8e08 100644 (file)
@@ -1,6 +1,5 @@
 #pragma once
 
-float warmup_limit;
 #include <common/weapons/_all.qh>
 #include <common/stats.qh>
 
@@ -139,7 +138,8 @@ void checkSpectatorBlock(entity this);
 
 float game_completion_ratio; // 0 at start, 1 near end
 .float winning;
-.float jointime; // time of joining
+.float jointime; // time of connecting
+.float startplaytime; // time of switching from spectator to player
 .float alivetime; // time of being alive
 .float motd_actived_time; // used for both motd and campaign_message
 
@@ -222,6 +222,8 @@ int have_team_spawns_forteams; // if Xth bit is 1 then team X has spawns else it
 // set when showing a kill countdown
 .entity killindicator;
 
+.bool canteamdamage;
+
 void Damage (entity targ, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force);
 
 float lockteams;
@@ -376,6 +378,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 b5c0abbc07ead855ffef499de860ee66b2f430cf..8778fba6fce54623d8c7ddbe7bf2df4ef570745c 100644 (file)
@@ -47,14 +47,14 @@ void GiveFrags (entity attacker, entity targ, float f, int deathtype)
                else
                {
                        // teamkill
-                       GameRules_scoring_add(attacker, KILLS, -1); // or maybe add a teamkills field?
+                       GameRules_scoring_add(attacker, TEAMKILLS, 1);
                }
        }
        else
        {
                // regular frag
                GameRules_scoring_add(attacker, KILLS, 1);
-               if(targ.playerid)
+               if(!warmup_stage && targ.playerid)
                        PlayerStats_GameReport_Event_Player(attacker, sprintf("kills-%d", targ.playerid), 1);
        }
 
@@ -360,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); \
-                                       PlayerStats_GameReport_Event_Player(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)
@@ -374,7 +379,7 @@ 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
@@ -494,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);
-                       PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1);
+                       if (!warmup_stage)
+                       {
+                               PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1);
+                       }
                }
        }
 
@@ -696,7 +704,7 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, int d
                                                                        force = '0 0 0';
                                                        }
                                                }
-                                               else
+                                               else if(!targ.canteamdamage)
                                                        damage = 0;
                                        }
                                }
index 713577fad17beb0c5d46932b7d845cea4b18cd7e..d9372e0aa561ea44f4d57850db09a656ef18e070 100644 (file)
@@ -50,14 +50,7 @@ void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma,
                source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
 
        if (lag)
-       {
-               // take players back into the past
-               FOREACH_CLIENT(IS_PLAYER(it) && it != forent, antilag_takeback(it, CS(it), time - lag));
-               IL_EACH(g_monsters, it != forent,
-               {
-                       antilag_takeback(it, it, time - lag);
-               });
-       }
+               antilag_takeback_all(forent, lag);
 
        // do the trace
        if(wz)
@@ -67,13 +60,7 @@ void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma,
 
        // restore players to current positions
        if (lag)
-       {
-               FOREACH_CLIENT(IS_PLAYER(it) && it != forent, antilag_restore(it, CS(it)));
-               IL_EACH(g_monsters, it != forent,
-               {
-                       antilag_restore(it, it);
-               });
-       }
+               antilag_restore_all(forent);
 
        // restore shooter solid type
        if(source)
index ce25dfa522772b08e3f1dfb41dcc2a536a2f4902..2eda8584ef5d82eeb9f346cb131aa9a049a58488 100644 (file)
@@ -2093,6 +2093,10 @@ void EndFrame()
        {
                antilag_record(it, it, altime);
        });
+       IL_EACH(g_projectiles, it.classname == "nade",
+       {
+               antilag_record(it, it, altime);
+       });
        systems_update();
        IL_ENDFRAME();
 }
index 56ea478ae2624ed9e766e77e2a8a318412d6a106..5010d283711ff0e795ed3f146bb097b56cfbcd10 100644 (file)
@@ -343,8 +343,9 @@ IMPULSE(weapon_reload)
                Weapon w = this.(weaponentity).m_weapon;
                w.wr_reload(w, actor, weaponentity);
 
-               if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
-                       break;
+               // allow reloading all active slots?
+               //if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
+                       //break;
        }
 }
 
@@ -571,114 +572,16 @@ IMPULSE(waypoint_clear)
        sprint(this, "all waypoints cleared\n");
 }
 
-vector waypoint_getSymmetricalOrigin(vector org, int ctf_flags)
-{
-       vector new_org = org;
-       if (fabs(autocvar_g_waypointeditor_symmetrical) == 1)
-       {
-               vector map_center = havocbot_middlepoint;
-               if (autocvar_g_waypointeditor_symmetrical == -1)
-                       map_center = autocvar_g_waypointeditor_symmetrical_origin;
-
-               new_org = Rotate(org - map_center, 360 * DEG2RAD / ctf_flags) + map_center;
-       }
-       else if (fabs(autocvar_g_waypointeditor_symmetrical) == 2)
-       {
-               float m = havocbot_symmetryaxis_equation.x;
-               float q = havocbot_symmetryaxis_equation.y;
-               if (autocvar_g_waypointeditor_symmetrical == -2)
-               {
-                       m = autocvar_g_waypointeditor_symmetrical_axis.x;
-                       q = autocvar_g_waypointeditor_symmetrical_axis.y;
-               }
-
-               new_org.x = (1 / (1 + m*m)) * ((1 - m*m) * org.x + 2 * m * org.y - 2 * m * q);
-               new_org.y = (1 / (1 + m*m)) * (2 * m * org.x + (m*m - 1) * org.y + 2 * q);
-       }
-       new_org.z = org.z;
-       return new_org;
-}
-
 IMPULSE(navwaypoint_spawn)
 {
        if (!autocvar_g_waypointeditor) return;
-       entity e;
-       vector org = this.origin;
-       int ctf_flags = havocbot_symmetryaxis_equation.z;
-       bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2)
-                  || (autocvar_g_waypointeditor_symmetrical < 0));
-       int order = ctf_flags;
-       if(autocvar_g_waypointeditor_symmetrical_order >= 2)
-       {
-               order = autocvar_g_waypointeditor_symmetrical_order;
-               ctf_flags = order;
-       }
-
-       LABEL(add_wp);
-       e = waypoint_spawn(org, org, 0);
-       waypoint_schedulerelink(e);
-       bprint(strcat("Waypoint spawned at ", vtos(org), "\n"));
-       if(sym)
-       {
-               org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
-               if (vdist(org - this.origin, >, 32))
-               {
-                       if(order > 2)
-                               order--;
-                       else
-                               sym = false;
-                       goto add_wp;
-               }
-       }
+       waypoint_spawn_fromeditor(this);
 }
 
 IMPULSE(navwaypoint_remove)
 {
        if (!autocvar_g_waypointeditor) return;
-       entity e = navigation_findnearestwaypoint(this, false);
-       int ctf_flags = havocbot_symmetryaxis_equation.z;
-       bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2)
-                  || (autocvar_g_waypointeditor_symmetrical < 0));
-       int order = ctf_flags;
-       if(autocvar_g_waypointeditor_symmetrical_order >= 2)
-       {
-               order = autocvar_g_waypointeditor_symmetrical_order;
-               ctf_flags = order;
-       }
-
-       LABEL(remove_wp);
-       if (!e) return;
-       if (e.wpflags & WAYPOINTFLAG_GENERATED) return;
-
-       if (e.wphardwired)
-       {
-               LOG_INFO("^1Warning: ^7Removal of hardwired waypoints is not allowed in the editor. Please remove links from/to this waypoint (", vtos(e.origin), ") by hand from maps/", mapname, ".waypoints.hardwired");
-               return;
-       }
-
-       entity wp_sym = NULL;
-       if (sym)
-       {
-               vector org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
-               IL_EACH(g_waypoints, !(it.wpflags & WAYPOINTFLAG_GENERATED), {
-                       if(vdist(org - it.origin, <, 3))
-                       {
-                               wp_sym = it;
-                               break;
-                       }
-               });
-       }
-       bprint(strcat("Waypoint removed at ", vtos(e.origin), "\n"));
-       waypoint_remove(e);
-       if (sym && wp_sym)
-       {
-               e = wp_sym;
-               if(order > 2)
-                       order--;
-               else
-                       sym = false;
-               goto remove_wp;
-       }
+       waypoint_remove_fromeditor(this);
 }
 
 IMPULSE(navwaypoint_relink)
@@ -696,93 +599,5 @@ IMPULSE(navwaypoint_save)
 IMPULSE(navwaypoint_unreachable)
 {
        if (!autocvar_g_waypointeditor) return;
-       IL_EACH(g_waypoints, true,
-       {
-               it.colormod = '0.5 0.5 0.5';
-               it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE);
-       });
-       entity e2 = navigation_findnearestwaypoint(this, false);
-       navigation_markroutes(this, e2);
-
-       int j, m;
-
-       j = 0;
-       m = 0;
-       IL_EACH(g_waypoints, it.wpcost >= 10000000,
-       {
-               LOG_INFO("unreachable: ", etos(it), " ", vtos(it.origin));
-               it.colormod_z = 8;
-               it.effects |= EF_NODEPTHTEST | EF_BLUE;
-               ++j;
-               ++m;
-       });
-       if (j) LOG_INFOF("%d waypoints cannot be reached from here in any way (marked with blue light)", j);
-       navigation_markroutes_inverted(e2);
-
-       j = 0;
-       IL_EACH(g_waypoints, it.wpcost >= 10000000,
-       {
-               LOG_INFO("cannot reach me: ", etos(it), " ", vtos(it.origin));
-               it.colormod_x = 8;
-               if (!(it.effects & EF_NODEPTHTEST))  // not already reported before
-                       ++m;
-               it.effects |= EF_NODEPTHTEST | EF_RED;
-               ++j;
-       });
-       if (j) LOG_INFOF("%d waypoints cannot walk to here in any way (marked with red light)", j);
-       if (m) LOG_INFOF("%d waypoints have been marked total", m);
-
-       j = 0;
-       IL_EACH(g_spawnpoints, true,
-       {
-               vector org = it.origin;
-               tracebox(it.origin, PL_MIN_CONST, PL_MAX_CONST, it.origin - '0 0 512', MOVE_NOMONSTERS, NULL);
-               setorigin(it, trace_endpos);
-               if (navigation_findnearestwaypoint(it, false))
-               {
-                       setorigin(it, org);
-                       it.effects &= ~EF_NODEPTHTEST;
-                       it.model = "";
-               }
-               else
-               {
-                       setorigin(it, org);
-                       LOG_INFO("spawn without waypoint: ", etos(it), " ", vtos(it.origin));
-                       it.effects |= EF_NODEPTHTEST;
-                       _setmodel(it, this.model);
-                       it.frame = this.frame;
-                       it.skin = this.skin;
-                       it.colormod = '8 0.5 8';
-                       setsize(it, '0 0 0', '0 0 0');
-                       ++j;
-               }
-       });
-       if (j) LOG_INFOF("%d spawnpoints have no nearest waypoint (marked by player model)", j);
-
-       j = 0;
-       IL_EACH(g_items, true,
-       {
-               it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE);
-               it.colormod = '0.5 0.5 0.5';
-       });
-       IL_EACH(g_items, true,
-       {
-               if (navigation_findnearestwaypoint(it, false)) continue;
-               LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin));
-               it.effects |= EF_NODEPTHTEST | EF_RED;
-               it.colormod_x = 8;
-               ++j;
-       });
-       if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked away from (marked with red light)", j);
-
-       j = 0;
-       IL_EACH(g_items, true,
-       {
-               if (navigation_findnearestwaypoint(it, true)) continue;
-               LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin));
-               it.effects |= EF_NODEPTHTEST | EF_BLUE;
-               it.colormod_z = 8;
-               ++j;
-       });
-       if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked to (marked with blue light)", j);
+       waypoint_unreachable(this);
 }
diff --git a/qcsrc/server/items.qc b/qcsrc/server/items.qc
new file mode 100644 (file)
index 0000000..7d24883
--- /dev/null
@@ -0,0 +1,119 @@
+#include "items.qh"
+
+/// \file
+/// \brief Source file that contains implementation of the functions related to
+/// game items.
+/// \copyright GNU GPLv2 or any later version.
+
+#include "g_subs.qh"
+#include <common/weapons/all.qh>
+
+.bool m_isloot; ///< Holds whether item is loot.
+/// \brief Holds whether strength, shield or superweapon timers expire while
+/// this item is on the ground.
+.bool m_isexpiring;
+
+entity Item_Create(string class_name, vector position, bool no_align)
+{
+       entity item = spawn();
+       item.classname = class_name;
+       item.spawnfunc_checked = true;
+       setorigin(item, position);
+       item.noalign = no_align;
+       Item_Initialize(item, class_name);
+       if (wasfreed(item))
+       {
+               return NULL;
+       }
+       return item;
+}
+
+void Item_Initialize(entity item, string class_name)
+{
+       FOREACH(Weapons, it.m_canonical_spawnfunc == class_name,
+       {
+               weapon_defaultspawnfunc(item, it);
+               return;
+       });
+       FOREACH(Items, it.m_canonical_spawnfunc == class_name,
+       {
+               StartItem(item, it);
+               return;
+       });
+       LOG_FATALF("Item_Initialize: Invalid classname: %s", class_name);
+}
+
+entity Item_CreateLoot(string class_name, vector position, vector vel,
+       float time_to_live)
+{
+       entity item = spawn();
+       if (!Item_InitializeLoot(item, class_name, position, vel, time_to_live))
+       {
+               return NULL;
+       }
+       return item;
+}
+
+bool Item_InitializeLoot(entity item, string class_name, vector position,
+       vector vel, float time_to_live)
+{
+       item.classname = class_name;
+       Item_SetLoot(item, true);
+       item.noalign = true;
+       setorigin(item, position);
+       item.pickup_anyway = true;
+       item.spawnfunc_checked = true;
+       Item_Initialize(item, class_name);
+       if (wasfreed(item))
+       {
+               return false;
+       }
+       item.gravity = 1;
+       item.velocity = vel;
+       SUB_SetFade(item, time + time_to_live, 1);
+       return true;
+}
+
+bool Item_IsLoot(entity item)
+{
+       return item.m_isloot;
+}
+
+void Item_SetLoot(entity item, bool loot)
+{
+       item.m_isloot = loot;
+}
+
+bool Item_ShouldKeepPosition(entity item)
+{
+       return item.noalign || (item.spawnflags & 1);
+}
+
+bool Item_IsExpiring(entity item)
+{
+       return item.m_isexpiring;
+}
+
+void Item_SetExpiring(entity item, bool expiring)
+{
+       item.m_isexpiring = expiring;
+}
+
+// Compatibility spawn functions
+
+// FIXME: in Quake this is green armor, in Xonotic maps it is an armor shard
+SPAWNFUNC_ITEM(item_armor1, ITEM_ArmorSmall)
+
+SPAWNFUNC_ITEM(item_armor25, ITEM_ArmorMega)
+
+SPAWNFUNC_ITEM(item_armor_large, ITEM_ArmorMega)
+
+SPAWNFUNC_ITEM(item_health1, ITEM_HealthSmall)
+
+SPAWNFUNC_ITEM(item_health25, ITEM_HealthMedium)
+
+SPAWNFUNC_ITEM(item_health_large, ITEM_HealthBig)
+
+SPAWNFUNC_ITEM(item_health100, ITEM_HealthMega)
+
+SPAWNFUNC_ITEM(item_quad, ITEM_Strength)
diff --git a/qcsrc/server/items.qh b/qcsrc/server/items.qh
new file mode 100644 (file)
index 0000000..1abcf64
--- /dev/null
@@ -0,0 +1,72 @@
+#pragma once
+
+/// \file
+/// \brief Header file that describes the functions related to game items.
+/// \copyright GNU GPLv2 or any later version.
+
+/// \brief Creates a new item.
+/// \param[in] class_name Class name of the item.
+/// \param[in] position Position of the item.
+/// \param[in] no_align True if item should be placed directly at specified
+/// position, false to let it drop to the ground.
+/// \return Item on success, NULL otherwise.
+entity Item_Create(string class_name, vector position, bool no_align);
+
+/// \brief Initializes the item according to classname.
+/// \param[in,out] item Item to initialize.
+/// \param[in] class_name Class name to use.
+/// \return No return.
+/// \nore This function is useful if you want to set some item properties before
+/// initialization.
+void Item_Initialize(entity item, string class_name);
+
+/// \brief Creates a loot item.
+/// \param[in] class_name Class name of the item.
+/// \param[in] position Position of the item.
+/// \param[in] velocity of the item.
+/// \param[in] time_to_live Amount of time after which the item will disappear.
+/// \return Item on success, NULL otherwise.
+entity Item_CreateLoot(string class_name, vector position, vector vel,
+       float time_to_live);
+
+/// \brief Initializes the loot item.
+/// \param[in] class_name Class name of the item.
+/// \param[in] position Position of the item.
+/// \param[in] velocity of the item.
+/// \param[in] time_to_live Amount of time after which the item will disappear.
+/// \return True on success, false otherwise.
+/// \nore This function is useful if you want to set some item properties before
+/// initialization.
+bool Item_InitializeLoot(entity item, string class_name, vector position,
+       vector vel, float time_to_live);
+
+/// \brief Returns whether the item is loot.
+/// \param[in] item Item to check.
+/// \return True if the item is loot, false otherwise.
+bool Item_IsLoot(entity item);
+
+/// \brief Sets the item loot status.
+/// \param[in,out] item Item to adjust.
+/// \param[in] loot Whether item is loot.
+/// \return No return.
+void Item_SetLoot(entity item, bool loot);
+
+/// \brief Returns whether item should keep its position or be dropped to the
+/// ground.
+/// \param[in] item Item to check.
+/// \return True if item should keep its position or false if it should be
+/// dropped to the ground.
+bool Item_ShouldKeepPosition(entity item);
+
+/// \brief Returns whether the item is expiring (i.e. its strength, shield and
+/// superweapon timers expire while it is on the ground).
+/// \param[in] item Item to check.
+/// \return True if the item is expiring, false otherwise.
+bool Item_IsExpiring(entity item);
+
+/// \brief Sets the item expiring status (i.e. whether its strength, shield
+/// and superweapon timers expire while it is on the ground).
+/// \param[in,out] item Item to adjust.
+/// \param[in] expiring Whether item is expiring.
+/// \return No return.
+void Item_SetExpiring(entity item, bool expiring);
index ab837c8945b873c0982d13834693c4a1a0f1fe9e..8b4204b126dfa1d1b44306a0abc51222391bc837 100644 (file)
@@ -8,6 +8,7 @@
 #include "mutators/_mod.qh"
 #include "../common/t_items.qh"
 #include "resources.qh"
+#include "items.qh"
 #include "weapons/accuracy.qh"
 #include "weapons/csqcprojectile.qh"
 #include "weapons/selection.qh"
@@ -653,7 +654,7 @@ void readplayerstartcvars()
                        "g_random_start_shells"));
                SetResourceAmount(random_start_ammo, RESOURCE_BULLETS, cvar(
                        "g_random_start_bullets"));
-               SetResourceAmount(random_start_ammo, RESOURCE_ROCKETS, 
+               SetResourceAmount(random_start_ammo, RESOURCE_ROCKETS,
                        cvar("g_random_start_rockets"));
                SetResourceAmount(random_start_ammo, RESOURCE_CELLS, cvar(
                        "g_random_start_cells"));
@@ -1243,6 +1244,7 @@ float MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundma
     });
        if(!sp)
        {
+               int items_checked = 0;
                IL_EACH(g_items, checkpvs(mstart, it),
                {
                        if((traceline(mstart, it.origin + (it.mins + it.maxs) * 0.5, MOVE_NORMAL, e), trace_fraction) >= 1)
@@ -1250,6 +1252,10 @@ float MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundma
                                sp = it;
                                break;
                        }
+
+                       ++items_checked;
+                       if(items_checked >= attempts)
+                               break; // sanity
                });
 
                if(!sp)
@@ -1437,10 +1443,13 @@ bool isPushable(entity e)
                return false;
        if(e.iscreature)
                return true;
+       if (Item_IsLoot(e))
+       {
+               return true;
+       }
        switch(e.classname)
        {
                case "body":
-               case "droppedweapon":
                        return true;
                case "bullet": // antilagged bullets can't hit this either
                        return false;
index e0c4198cc7adbea3354f350544950a5cf8ac7397..fb452c3399b9bb4ab39721abb574ec32944ccff4 100644 (file)
@@ -24,6 +24,12 @@ MUTATOR_HOOKABLE(PutClientInServer, EV_PutClientInServer);
     /**/
 MUTATOR_HOOKABLE(ForbidSpawn, EV_ForbidSpawn);
 
+/** called when player spawns to determine whether to give them random start weapons. Return true to forbid giving them. */
+#define EV_ForbidRandomStartWeapons(i, o) \
+       /** player */ i(entity, MUTATOR_ARGV_0_entity) \
+    /**/
+MUTATOR_HOOKABLE(ForbidRandomStartWeapons, EV_ForbidRandomStartWeapons);
+
 /** called when a player spawns as player, after shared setup, before his weapon is chosen (so items may be changed in here) */
 #define EV_PlayerSpawn(i, o) \
        /** player spawning */ i(entity, MUTATOR_ARGV_0_entity) \
@@ -645,6 +651,13 @@ enum {
        MUT_ITEMTOUCH_PICKUP // return this flag to have the item "picked up" and taken even after mutator handled it
 };
 
+/** called after the item has been touched. */
+#define EV_ItemTouched(i, o) \
+    /** item */    i(entity, MUTATOR_ARGV_0_entity) \
+    /** toucher */ i(entity, MUTATOR_ARGV_1_entity) \
+    /**/
+MUTATOR_HOOKABLE(ItemTouched, EV_ItemTouched);
+
 /** Called when the amount of entity resources changes. Can be used to override
 resource limit. */
 #define EV_GetResourceLimit(i, o) \
index affa033de1da73e3082bd12b1c8925cf7ac85330..50861c32f7d005bf40435df9449128e62eb1d05e 100644 (file)
@@ -461,7 +461,7 @@ void havocbot_role_ast_offense(entity this)
        if(this.havocbot_attack_time>time)
                return;
 
-       if (this.bot_strategytime < time)
+       if (navigation_goalrating_timeout(this))
        {
                navigation_goalrating_start(this);
                havocbot_goalrating_enemyplayers(this, 20000, this.origin, 650);
@@ -469,7 +469,7 @@ void havocbot_role_ast_offense(entity this)
                havocbot_goalrating_items(this, 15000, this.origin, 10000);
                navigation_goalrating_end(this);
 
-               this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+               navigation_goalrating_timeout_set(this);
        }
 }
 
@@ -495,7 +495,7 @@ void havocbot_role_ast_defense(entity this)
        if(this.havocbot_attack_time>time)
                return;
 
-       if (this.bot_strategytime < time)
+       if (navigation_goalrating_timeout(this))
        {
                navigation_goalrating_start(this);
                havocbot_goalrating_enemyplayers(this, 20000, this.origin, 3000);
@@ -503,7 +503,7 @@ void havocbot_role_ast_defense(entity this)
                havocbot_goalrating_items(this, 15000, this.origin, 10000);
                navigation_goalrating_end(this);
 
-               this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+               navigation_goalrating_timeout_set(this);
        }
 }
 
index f437d98b52c837c6db59520c37d840660ece1003..ea714e6a23ba4528f38b6e189386a69215dd6603 100644 (file)
@@ -31,9 +31,6 @@ const int HAVOCBOT_AST_ROLE_OFFENSE = 4;
 .int havocbot_role_flags;
 .float havocbot_attack_time;
 
-.void(entity this) havocbot_role;
-.void(entity this) havocbot_previous_role;
-
 void(entity this) havocbot_role_ast_defense;
 void(entity this) havocbot_role_ast_offense;
 
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 3cf560ebedf752c3fc99b33cfc2aa3248bcaad2b..3c714ad5396bbb2bf237a24782722d42162d551b 100644 (file)
@@ -540,6 +540,9 @@ void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
        if(CTF_DIFFTEAM(player, flag)) { return; }
        if((flag.cnt || enemy_flag.cnt) && flag.cnt != enemy_flag.cnt) { return; } // this should catch some edge cases (capturing grouped flag at ungrouped flag disallowed etc)
 
+       if (toucher.goalentity == flag.bot_basewaypoint)
+               toucher.goalentity_lock_timeout = 0;
+
        if(ctf_oneflag)
        for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
        if(SAME_TEAM(tmp_entity, player))
@@ -594,6 +597,8 @@ void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
                        { GameRules_scoring_add_team(enemy_flag.ctf_dropper, SCORE, ((enemy_flag.score_assist) ? enemy_flag.score_assist : autocvar_g_ctf_score_capture_assist)); }
        }
 
+       flag.enemy = toucher;
+
        // reset the flag
        player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
        ctf_RespawnFlag(enemy_flag);
@@ -636,6 +641,8 @@ void ctf_Handle_Return(entity flag, entity player)
        if(player.flagcarried == flag)
                WaypointSprite_Kill(player.wps_flagcarrier);
 
+       flag.enemy = player;
+
        // reset the flag
        ctf_RespawnFlag(flag);
 }
@@ -773,6 +780,7 @@ void ctf_CheckFlagReturn(entity flag, int returntype)
                        }
                        _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
                        ctf_EventLog("returned", flag.team, NULL);
+                       flag.enemy = NULL;
                        ctf_RespawnFlag(flag);
                }
        }
@@ -1176,8 +1184,9 @@ void ctf_RespawnFlag(entity flag)
 void ctf_Reset(entity this)
 {
        if(this.owner && IS_PLAYER(this.owner))
-        ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
+               ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
 
+       this.enemy = NULL;
        ctf_RespawnFlag(this);
 }
 
@@ -1653,11 +1662,10 @@ void havocbot_role_ctf_carrier(entity this)
                return;
        }
 
-       if (this.bot_strategytime < time)
+       if (navigation_goalrating_timeout(this))
        {
-               this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-
                navigation_goalrating_start(this);
+
                if(ctf_oneflag)
                        havocbot_goalrating_ctf_enemybase(this, 50000);
                else
@@ -1668,6 +1676,19 @@ void havocbot_role_ctf_carrier(entity this)
 
                navigation_goalrating_end(this);
 
+               navigation_goalrating_timeout_set(this);
+
+               entity head = ctf_worldflaglist;
+               while (head)
+               {
+                       if (this.goalentity == head.bot_basewaypoint)
+                       {
+                               this.goalentity_lock_timeout = time + 5;
+                               break;
+                       }
+                       head = head.ctf_worldflagnext;
+               }
+
                if (this.goalentity)
                        this.havocbot_cantfindflag = time + 10;
                else if (time > this.havocbot_cantfindflag)
@@ -1729,14 +1750,17 @@ void havocbot_role_ctf_escort(entity this)
        }
 
        // Chase the flag carrier
-       if (this.bot_strategytime < time)
+       if (navigation_goalrating_timeout(this))
        {
-               this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
                navigation_goalrating_start(this);
+
                havocbot_goalrating_ctf_enemyflag(this, 30000);
                havocbot_goalrating_ctf_ourstolenflag(this, 40000);
                havocbot_goalrating_items(this, 10000, this.origin, 10000);
+
                navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
        }
 }
 
@@ -1809,15 +1833,18 @@ void havocbot_role_ctf_offense(entity this)
                return;
        }
 
-       if (this.bot_strategytime < time)
+       if (navigation_goalrating_timeout(this))
        {
-               this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
                navigation_goalrating_start(this);
+
                havocbot_goalrating_ctf_ourstolenflag(this, 50000);
                havocbot_goalrating_ctf_enemybase(this, 20000);
                havocbot_goalrating_items(this, 5000, this.origin, 1000);
                havocbot_goalrating_items(this, 1000, this.origin, 10000);
+
                navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
        }
 }
 
@@ -1842,11 +1869,8 @@ void havocbot_role_ctf_retriever(entity this)
        mf = havocbot_ctf_find_flag(this);
        if(mf.ctf_status==FLAG_BASE)
        {
-               if(this.goalcurrent == mf)
-               {
-                       navigation_clearroute(this);
-                       this.bot_strategytime = 0;
-               }
+               if (mf.enemy == this) // did this bot return the flag?
+                       navigation_goalrating_timeout_force(this);
                havocbot_ctf_reset_role(this);
                return;
        }
@@ -1860,18 +1884,21 @@ void havocbot_role_ctf_retriever(entity this)
                return;
        }
 
-       if (this.bot_strategytime < time)
+       if (navigation_goalrating_timeout(this))
        {
                float rt_radius;
                rt_radius = 10000;
 
-               this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
                navigation_goalrating_start(this);
+
                havocbot_goalrating_ctf_ourstolenflag(this, 50000);
                havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
                havocbot_goalrating_ctf_enemybase(this, 30000);
                havocbot_goalrating_items(this, 500, this.origin, rt_radius);
+
                navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
        }
 }
 
@@ -1907,22 +1934,25 @@ void havocbot_role_ctf_middle(entity this)
                return;
        }
 
-       if (this.bot_strategytime < time)
+       if (navigation_goalrating_timeout(this))
        {
                vector org;
 
                org = havocbot_middlepoint;
                org.z = this.origin.z;
 
-               this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
                navigation_goalrating_start(this);
+
                havocbot_goalrating_ctf_ourstolenflag(this, 50000);
                havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
                havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_middlepoint_radius * 0.5);
                havocbot_goalrating_items(this, 5000, org, havocbot_middlepoint_radius * 0.5);
                havocbot_goalrating_items(this, 2500, this.origin, 10000);
                havocbot_goalrating_ctf_enemybase(this, 2500);
+
                navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
        }
 }
 
@@ -1958,11 +1988,10 @@ void havocbot_role_ctf_defense(entity this)
                havocbot_ctf_reset_role(this);
                return;
        }
-       if (this.bot_strategytime < time)
+       if (navigation_goalrating_timeout(this))
        {
                vector org = mf.dropped_origin;
 
-               this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
                navigation_goalrating_start(this);
 
                // if enemies are closer to our base, go there
@@ -1988,7 +2017,10 @@ void havocbot_role_ctf_defense(entity this)
                havocbot_goalrating_enemyplayers(this, 15000, org, havocbot_middlepoint_radius);
                havocbot_goalrating_items(this, 10000, org, havocbot_middlepoint_radius);
                havocbot_goalrating_items(this, 5000, this.origin, 10000);
+
                navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
        }
 }
 
@@ -2002,7 +2034,8 @@ void havocbot_role_ctf_setrole(entity bot, int role)
                        bot.havocbot_role = havocbot_role_ctf_carrier;
                        bot.havocbot_role_timeout = 0;
                        bot.havocbot_cantfindflag = time + 10;
-                       bot.bot_strategytime = 0;
+                       if (bot.havocbot_previous_role != bot.havocbot_role)
+                               navigation_goalrating_timeout_force(bot);
                        break;
                case HAVOCBOT_CTF_ROLE_DEFENSE:
                        s = "defense";
@@ -2024,14 +2057,16 @@ void havocbot_role_ctf_setrole(entity bot, int role)
                        bot.havocbot_previous_role = bot.havocbot_role;
                        bot.havocbot_role = havocbot_role_ctf_retriever;
                        bot.havocbot_role_timeout = time + 10;
-                       bot.bot_strategytime = 0;
+                       if (bot.havocbot_previous_role != bot.havocbot_role)
+                               navigation_goalrating_timeout_expire(bot, 2);
                        break;
                case HAVOCBOT_CTF_ROLE_ESCORT:
                        s = "escort";
                        bot.havocbot_previous_role = bot.havocbot_role;
                        bot.havocbot_role = havocbot_role_ctf_escort;
                        bot.havocbot_role_timeout = time + 30;
-                       bot.bot_strategytime = 0;
+                       if (bot.havocbot_previous_role != bot.havocbot_role)
+                               navigation_goalrating_timeout_expire(bot, 2);
                        break;
        }
        LOG_TRACE(bot.netname, " switched to ", s);
index 0b86a57f809936604fd51a5514b899108558dcac..33d64a074a290610fdb43a45dbfa7f08a996ede4 100644 (file)
@@ -133,6 +133,7 @@ float ctf_captimerecord; // record time for capturing the flag
 .float next_take_time;
 .bool ctf_flagdamaged_byworld;
 int ctf_teams;
+.entity enemy; // when flag is back in the base, it remembers last player who carried/touched the flag, useful to bots
 
 // passing/throwing properties
 .float pass_distance;
index 20f4b383d11296c1c2265dd5e65e9550762ea954..daaf8a969592e810af3531a1cdcded327b46a8fc 100644 (file)
@@ -1,7 +1,7 @@
 #include "gamemode_cts.qh"
-#include <server/race.qh>
 
 #include <server/race.qh>
+#include <server/items.qh>
 
 float autocvar_g_cts_finish_kill_delay;
 bool autocvar_g_cts_selfdamage;
@@ -13,20 +13,34 @@ void havocbot_role_cts(entity this)
        if(IS_DEAD(this))
                return;
 
-       if (this.bot_strategytime < time)
+       if (navigation_goalrating_timeout(this))
        {
-               this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
                navigation_goalrating_start(this);
 
+               bool raw_touch_check = true;
+               int cp = this.race_checkpoint;
+
+               LABEL(search_racecheckpoints)
                IL_EACH(g_racecheckpoints, true,
                {
-                       if(it.cnt == this.race_checkpoint)
-                               navigation_routerating(this, it, 1000000, 5000);
-                       else if(this.race_checkpoint == -1)
+                       if(it.cnt == cp || cp == -1)
+                       {
+                               // redirect bot to next goal if it touched the waypoint of an untouchable checkpoint
+                               // e.g. checkpoint in front of Stormkeep's warpzone
+                               // the same workaround is applied in Race game mode
+                               if (raw_touch_check && vdist(this.origin - it.nearestwaypoint.origin, <, 30))
+                               {
+                                       cp = race_NextCheckpoint(cp);
+                                       raw_touch_check = false;
+                                       goto search_racecheckpoints;
+                               }
                                navigation_routerating(this, it, 1000000, 5000);
+                       }
                });
 
                navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
        }
 }
 
@@ -314,8 +328,10 @@ MUTATOR_HOOKFUNCTION(cts, FilterItem)
 {
        entity item = M_ARGV(0, entity);
 
-       if(item.classname == "droppedweapon")
+       if (Item_IsLoot(item))
+       {
                return true;
+       }
 }
 
 MUTATOR_HOOKFUNCTION(cts, Damage_Calculate)
index be38553c9588bcd4ba1bfd79042432f79aebd583..5980cfbaf38241826244c3f27e3b44d4998017aa 100644 (file)
@@ -402,15 +402,16 @@ void havocbot_role_dom(entity this)
        if(IS_DEAD(this))
                return;
 
-       if (this.bot_strategytime < time)
+       if (navigation_goalrating_timeout(this))
        {
-               this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
                navigation_goalrating_start(this);
                havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
                havocbot_goalrating_items(this, 8000, this.origin, 8000);
                //havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000);
                havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
                navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
        }
 }
 
index 88afaa755d7b9f995419411e912d431b7d4ec166..2ea70c1f1ac16971c6af11a3aa33a4b3f3b0f9d5 100644 (file)
@@ -257,16 +257,16 @@ void havocbot_role_ft_offense(entity this)
                return;
        }
 
-       if (time > this.bot_strategytime)
+       if (navigation_goalrating_timeout(this))
        {
-               this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-
                navigation_goalrating_start(this);
                havocbot_goalrating_items(this, 10000, this.origin, 10000);
                havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
                havocbot_goalrating_freeplayers(this, 9000, this.origin, 10000);
                havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
                navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
        }
 }
 
@@ -286,16 +286,16 @@ void havocbot_role_ft_freeing(entity this)
                return;
        }
 
-       if (time > this.bot_strategytime)
+       if (navigation_goalrating_timeout(this))
        {
-               this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-
                navigation_goalrating_start(this);
                havocbot_goalrating_items(this, 8000, this.origin, 10000);
                havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000);
                havocbot_goalrating_freeplayers(this, 20000, this.origin, 10000);
                havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
                navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
        }
 }
 
index 7b33bea6f374dd585755e6a31f572830b514b73d..badae12c2d4897090aaec805bc594d4f1cc9de76 100644 (file)
@@ -232,21 +232,21 @@ void havocbot_role_ka_carrier(entity this)
        if (IS_DEAD(this))
                return;
 
-       if (time > this.bot_strategytime)
+       if (navigation_goalrating_timeout(this))
        {
-               this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-
                navigation_goalrating_start(this);
                havocbot_goalrating_items(this, 10000, this.origin, 10000);
                havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
                havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
                navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
        }
 
        if (!this.ballcarried)
        {
                this.havocbot_role = havocbot_role_ka_collector;
-               this.bot_strategytime = 0;
+               navigation_goalrating_timeout_expire(this, 2);
        }
 }
 
@@ -255,21 +255,21 @@ void havocbot_role_ka_collector(entity this)
        if (IS_DEAD(this))
                return;
 
-       if (time > this.bot_strategytime)
+       if (navigation_goalrating_timeout(this))
        {
-               this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-
                navigation_goalrating_start(this);
                havocbot_goalrating_items(this, 10000, this.origin, 10000);
                havocbot_goalrating_enemyplayers(this, 1000, this.origin, 10000);
                havocbot_goalrating_ball(this, 20000, this.origin);
                navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
        }
 
        if (this.ballcarried)
        {
                this.havocbot_role = havocbot_role_ka_carrier;
-               this.bot_strategytime = 0;
+               navigation_goalrating_timeout_expire(this, 2);
        }
 }
 
index 15b6e0f4a6251d81a94d9501858abca0ee0ae602..e6253b091fe49136f822a7e26c9b6df51de7bab4 100644 (file)
@@ -1071,9 +1071,8 @@ void havocbot_role_kh_carrier(entity this)
                return;
        }
 
-       if (this.bot_strategytime < time)
+       if (navigation_goalrating_timeout(this))
        {
-               this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
                navigation_goalrating_start(this);
 
                if(kh_Key_AllOwnedByWhichTeam() == this.team)
@@ -1082,6 +1081,8 @@ void havocbot_role_kh_carrier(entity this)
                        havocbot_goalrating_kh(this, 4, 4, 1); // play defensively
 
                navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
        }
 }
 
@@ -1108,10 +1109,9 @@ void havocbot_role_kh_defense(entity this)
                return;
        }
 
-       if (this.bot_strategytime < time)
+       if (navigation_goalrating_timeout(this))
        {
                float key_owner_team;
-               this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
                navigation_goalrating_start(this);
 
                key_owner_team = kh_Key_AllOwnedByWhichTeam();
@@ -1123,6 +1123,8 @@ void havocbot_role_kh_defense(entity this)
                        havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK ANYWAY
 
                navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
        }
 }
 
@@ -1149,11 +1151,10 @@ void havocbot_role_kh_offense(entity this)
                return;
        }
 
-       if (this.bot_strategytime < time)
+       if (navigation_goalrating_timeout(this))
        {
                float key_owner_team;
 
-               this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
                navigation_goalrating_start(this);
 
                key_owner_team = kh_Key_AllOwnedByWhichTeam();
@@ -1165,6 +1166,8 @@ void havocbot_role_kh_offense(entity this)
                        havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK! EMERGENCY!
 
                navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
        }
 }
 
@@ -1199,9 +1202,8 @@ void havocbot_role_kh_freelancer(entity this)
                return;
        }
 
-       if (this.bot_strategytime < time)
+       if (navigation_goalrating_timeout(this))
        {
-               this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
                navigation_goalrating_start(this);
 
                int key_owner_team = kh_Key_AllOwnedByWhichTeam();
@@ -1213,6 +1215,8 @@ void havocbot_role_kh_freelancer(entity this)
                        havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK ANYWAY
 
                navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
        }
 }
 
index 4d6f70438583b0f805b40070754e303b1a3f4726..94c4a998c2cc616b4d3f1f64487eb4b5fb98e42e 100644 (file)
@@ -1,6 +1,6 @@
 #include "gamemode_lms.qh"
 
-#include <common/mutators/mutator/instagib/items.qc>
+#include <common/mutators/mutator/instagib/items.qh>
 #include <server/campaign.qh>
 #include <server/command/_mod.qh>
 
@@ -35,25 +35,29 @@ void ClearWinners();
 // limit.
 int WinningCondition_LMS()
 {
-       entity head, head2;
-       bool have_player = false;
-       bool have_players = false;
-
-       int l = LMS_NewPlayerLives();
-
-       head = find(NULL, classname, STR_PLAYER);
-       if(head)
-               have_player = true;
-       head2 = find(head, classname, STR_PLAYER);
-       if(head2)
-               have_players = true;
+       entity first_player = NULL;
+       int total_players = 0;
+       FOREACH_CLIENT(IS_PLAYER(it), {
+               if (!total_players)
+                       first_player = it;
+               ++total_players;
+       });
 
-       if(have_player)
+       if (total_players)
        {
-               // we have at least one player
-               if(have_players)
+               if (total_players > 1)
                {
                        // two or more active players - continue with the game
+
+                       if (autocvar_g_campaign)
+                       {
+                               FOREACH_CLIENT(IS_REAL_CLIENT(it), {
+                                       float pl_lives = GameRules_scoring_add(it, LMS_LIVES, 0);
+                                       if (!pl_lives)
+                                               return WINNING_YES; // human player lost, game over
+                                       break;
+                               });
+                       }
                }
                else
                {
@@ -62,7 +66,7 @@ int WinningCondition_LMS()
                        ClearWinners();
                        SetWinners(winning, 0); // NOTE: exactly one player is still "player", so this works out
 
-                       if(l)
+                       if (LMS_NewPlayerLives())
                        {
                                // game still running (that is, nobody got removed from the game by a frag yet)? then continue
                                return WINNING_NO;
@@ -71,7 +75,7 @@ int WinningCondition_LMS()
                        {
                                // a winner!
                                // and assign him his first place
-                               GameRules_scoring_add(head, LMS_RANK, 1);
+                               GameRules_scoring_add(first_player, LMS_RANK, 1);
                                if(warmup_stage)
                                        return WINNING_NO;
                                else
@@ -82,7 +86,7 @@ int WinningCondition_LMS()
        else
        {
                // nobody is playing at all...
-               if(l)
+               if (LMS_NewPlayerLives())
                {
                        // wait for players...
                }
index 2f581d8c433af0c6b8047e71ab7571825eb7ceba..aa6d12a83e063166da288364d0314e521d3c9970 100644 (file)
@@ -14,24 +14,34 @@ void havocbot_role_race(entity this)
        if(IS_DEAD(this))
                return;
 
-       if (this.bot_strategytime < time)
+       if (navigation_goalrating_timeout(this))
        {
-               this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
                navigation_goalrating_start(this);
 
+               bool raw_touch_check = true;
+               int cp = this.race_checkpoint;
+
+               LABEL(search_racecheckpoints)
                IL_EACH(g_racecheckpoints, true,
                {
-                       if(it.cnt == this.race_checkpoint)
-                       {
-                               navigation_routerating(this, it, 1000000, 5000);
-                       }
-                       else if(this.race_checkpoint == -1)
+                       if(it.cnt == cp || cp == -1)
                        {
+                               // redirect bot to next goal if it touched the waypoint of an untouchable checkpoint
+                               // e.g. checkpoint in front of Stormkeep's warpzone
+                               // the same workaround is applied in CTS game mode
+                               if (raw_touch_check && vdist(this.origin - it.nearestwaypoint.origin, <, 30))
+                               {
+                                       cp = race_NextCheckpoint(cp);
+                                       raw_touch_check = false;
+                                       goto search_racecheckpoints;
+                               }
                                navigation_routerating(this, it, 1000000, 5000);
                        }
                });
 
                navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
        }
 }
 
index ac2dcb1ede667f10ab90dd9953895faeff3c1437..26fc9e659204d7bf6305a5d2483bf1b2b4ba9819 100644 (file)
@@ -538,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")
@@ -558,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);
@@ -586,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
@@ -621,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);
index 32fe3c6aaa2dd178f3af6cff5d89606688e3209f..c9948660efe1165c7ed8654dac1ba770e6763277 100644 (file)
@@ -96,7 +96,7 @@ void TeamScore_Spawn(float t, string name)
        PlayerStats_GameReport_AddTeam(t);
 }
 
-float TeamScore_AddToTeam(float t, float scorefield, float score)
+float TeamScore_AddToTeam(int t, float scorefield, float score)
 {
        entity s;
 
index 79b65299f41e1bfa2c01d5a09e528011d5a560cd..e2a57f43fb4a3ffaf36b6a5d33f1d475d793e9db 100644 (file)
@@ -52,7 +52,7 @@ float TeamScore_Add(entity player, float scorefield, float score);
  * NEVER call this if team has not been set yet!
  * Returns the new score.
  */
-float TeamScore_AddToTeam(float t, float scorefield, float score);
+float TeamScore_AddToTeam(int t, float scorefield, float score);
 
 /**
  * Returns a value indicating the team score (and higher is better).
index d46d95bd1c6114ee5138527c74cb726afc8a7dab..8d87407e64ebd3bc165efe3e27b53c6ceef25f0b 100644 (file)
@@ -44,7 +44,10 @@ void ScoreRules_basics(int teams, float sprio, float stprio, float score_enabled
        ScoreInfo_SetLabel_PlayerScore(SP_DEATHS,       "deaths",    SFL_LOWER_IS_BETTER);
 
        if (!INDEPENDENT_PLAYERS)
+       {
                ScoreInfo_SetLabel_PlayerScore(SP_SUICIDES,     "suicides",  SFL_LOWER_IS_BETTER);
+               ScoreInfo_SetLabel_PlayerScore(SP_TEAMKILLS,     "teamkills", SFL_LOWER_IS_BETTER);
+       }
 
        if(score_enabled)
                ScoreInfo_SetLabel_PlayerScore(SP_SCORE,        "score",     sprio);
index 97299ffeffa7e4a7c114ad53bf7885ca7585ed79..d2cc9b960b650dbcdab5fdeb39e616c7e44a3cb0 100644 (file)
@@ -126,6 +126,9 @@ void CreatureFrame_FallDamage(entity this)
 
 void CreatureFrame_All()
 {
+       if(game_stopped || time < game_starttime)
+               return;
+
        IL_EACH(g_damagedbycontents, it.damagedbycontents,
        {
                if (it.move_movetype == MOVETYPE_NOCLIP) continue;
@@ -216,8 +219,6 @@ void StartFrame()
        if (timeout_status == TIMEOUT_LEADTIME) // just before the timeout (when timeout_status will be TIMEOUT_ACTIVE)
                orig_slowmo = autocvar_slowmo; // slowmo will be restored after the timeout
 
-       skill = autocvar_skill;
-
        // detect when the pre-game countdown (if any) has ended and the game has started
        bool game_delay = (time < game_starttime);
        if (autocvar_sv_eventlog && game_delay_last && !game_delay)
@@ -365,6 +366,9 @@ LABEL(cleanup)
 void WarpZone_PostInitialize_Callback()
 {
        // create waypoint links for warpzones
+       entity tracetest_ent = spawn();
+       setsize(tracetest_ent, PL_MIN_CONST, PL_MAX_CONST);
+       tracetest_ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
        //for(entity e = warpzone_first; e; e = e.warpzone_next)
        for(entity e = NULL; (e = find(e, classname, "trigger_warpzone")); )
        {
@@ -375,6 +379,7 @@ void WarpZone_PostInitialize_Callback()
                dst = (e.enemy.absmin + e.enemy.absmax) * 0.5;
                makevectors(e.enemy.warpzone_angles);
                dst = dst + ((e.enemy.warpzone_origin - dst) * v_forward) * v_forward - 16 * v_right;
-               waypoint_spawnforteleporter_v(e, src, dst, 0);
+               waypoint_spawnforteleporter_wz(e, src, dst, 0, -v_up, tracetest_ent);
        }
+       delete(tracetest_ent);
 }
index 12aca2133a0f67aa48a09d9bd9e8a07eee5f73e2..33ad8f8ed634f6123bf3a5c247eb83fad0dccf3d 100644 (file)
@@ -545,6 +545,14 @@ void GetTeamCounts(entity ignore)
 bool IsTeamSmallerThanTeam(int team_a, int team_b, entity player,
        bool use_score)
 {
+       if (!Team_IsValidNumber(team_a))
+       {
+               LOG_FATALF("IsTeamSmallerThanTeam: team_a is invalid: %f", team_a);
+       }
+       if (!Team_IsValidNumber(team_b))
+       {
+               LOG_FATALF("IsTeamSmallerThanTeam: team_b is invalid: %f", team_b);
+       }
        if (team_a == team_b)
        {
                return false;
@@ -642,6 +650,14 @@ bool IsTeamSmallerThanTeam(int team_a, int team_b, entity player,
 
 bool IsTeamEqualToTeam(int team_a, int team_b, entity player, bool use_score)
 {
+       if (!Team_IsValidNumber(team_a))
+       {
+               LOG_FATALF("IsTeamEqualToTeam: team_a is invalid: %f", team_a);
+       }
+       if (!Team_IsValidNumber(team_b))
+       {
+               LOG_FATALF("IsTeamEqualToTeam: team_b is invalid: %f", team_b);
+       }
        if (team_a == team_b)
        {
                return true;
@@ -837,53 +853,56 @@ int FindSmallestTeam(entity player, float ignore_player)
        return RandomSelection_chosen_float;
 }
 
-int JoinBestTeam(entity this, bool only_return_best, bool force_best_team)
+void JoinBestTeam(entity this, bool force_best_team)
 {
        // don't join a team if we're not playing a team game
        if (!teamplay)
        {
-               return 0;
+               return;
        }
 
        // 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 we don't care what team they end up on, put them on whatever team they entered as.
+       // if they're not on a valid team, then let other code put them on the smallest team
        if (!force_best_team)
        {
                int selected_team;
-               if(     c1 >= 0 && this.team == NUM_TEAM_1)
+               if ((c1 >= 0) && (this.team == NUM_TEAM_1))
+               {
                        selected_team = this.team;
-               else if(c2 >= 0 && this.team == NUM_TEAM_2)
+               }
+               else if ((c2 >= 0) && (this.team == NUM_TEAM_2))
+               {
                        selected_team = this.team;
-               else if(c3 >= 0 && this.team == NUM_TEAM_3)
+               }
+               else if ((c3 >= 0) && (this.team == NUM_TEAM_3))
+               {
                        selected_team = this.team;
-               else if(c4 >= 0 && this.team == NUM_TEAM_4)
+               }
+               else if ((c4 >= 0) && (this.team == NUM_TEAM_4))
+               {
                        selected_team = this.team;
+               }
                else
+               {
                        selected_team = -1;
+               }
 
                if (selected_team > 0)
                {
-                       if (!only_return_best)
-                       {
-                               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 selected_team;
+                       SetPlayerTeamSimple(this, selected_team);
+                       LogTeamchange(this.playerid, this.team, 99);
+                       return;
                }
-               // otherwise end up on the smallest team (handled below)
        }
-
-       int best_team = FindSmallestTeam(this, true);
-       if (only_return_best || this.bot_forced_team)
+       // otherwise end up on the smallest team (handled below)
+       if (this.bot_forced_team)
        {
-               return best_team;
+               return;
        }
+       int best_team = FindSmallestTeam(this, true);
        best_team = Team_NumberToTeam(best_team);
        if (best_team == -1)
        {
@@ -893,12 +912,11 @@ int JoinBestTeam(entity this, bool only_return_best, bool force_best_team)
        TeamchangeFrags(this);
        SetPlayerTeamSimple(this, best_team);
        LogTeamchange(this.playerid, this.team, 2); // log auto join
-       if (!IS_BOT_CLIENT(this))
+       if ((old_team != -1) && !IS_BOT_CLIENT(this))
        {
                AutoBalanceBots(old_team, Team_TeamToNumber(best_team));
        }
        KillPlayerForTeamChange(this);
-       return best_team;
 }
 
 void SV_ChangeTeam(entity this, float _color)
@@ -925,6 +943,11 @@ void SV_ChangeTeam(entity this, float _color)
        source_team = Team_TeamToNumber(source_color + 1);
        destination_team = Team_TeamToNumber(destination_color + 1);
 
+       if (destination_team == -1)
+       {
+               return;
+       }
+
        CheckAllowedTeams(this);
 
        if (destination_team == 1 && c1 < 0) destination_team = 4;
@@ -973,8 +996,15 @@ void SV_ChangeTeam(entity this, float _color)
 
 void AutoBalanceBots(int source_team, int destination_team)
 {
-       if ((source_team == -1) || (destination_team == -1))
+       if (!Team_IsValidNumber(source_team))
        {
+               LOG_WARNF("AutoBalanceBots: Source team is invalid: %f", source_team);
+               return;
+       }
+       if (!Team_IsValidNumber(destination_team))
+       {
+               LOG_WARNF("AutoBalanceBots: Destination team is invalid: %f",
+                       destination_team);
                return;
        }
        if (!autocvar_g_balance_teams ||
@@ -1008,6 +1038,10 @@ void AutoBalanceBots(int source_team, int destination_team)
                        break;
                }
        }
+       if (num_players_source_team < 0)
+       {
+               return;
+       }
        switch (destination_team)
        {
                case 1:
index 1813db04d8d111387a6a1667c6033e9b2e890f6c..7c4ebe77b6c2a82a51ae2949999734d5da4022e8 100644 (file)
@@ -107,7 +107,7 @@ int FindBestTeams(entity player, bool use_score);
 // NOTE: Assumes CheckAllowedTeams has already been called!
 int FindSmallestTeam(entity player, float ignore_player);
 
-int JoinBestTeam(entity this, bool only_return_best, bool force_best_team);
+void JoinBestTeam(entity this, bool force_best_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).
index 4a603c02256d244dd69fca54040a80b9f4faeed1..097685abf1766820a9e3f964928da71172c31d5b 100644 (file)
@@ -22,6 +22,8 @@ const string STR_OBSERVER = "observer";
 #define IS_VEHICLE(v) (v.vehicle_flags & VHF_ISVEHICLE)
 #define IS_TURRET(v) (v.turret_flags & TUR_FLAG_ISTURRET)
 
+#define IS_MOVABLE(v) ((IS_PLAYER(v) || IS_MONSTER(v)) && !STAT(FROZEN, v))
+
 // NOTE: FOR_EACH_CLIENTSLOT deprecated! Use the following instead: FOREACH_CLIENTSLOT(true, { code; });
 // NOTE: FOR_EACH_CLIENT deprecated! Use the following instead: FOREACH_CLIENT(true, { code; });
 // NOTE: FOR_EACH_REALCLIENT deprecated! Use the following instead: FOREACH_CLIENT(IS_REAL_CLIENT(it), { code; });
index 776d8d8d0f43800999a3b10829cb3c9c20cc2d93..693d5a240456323adab1c259b51dfb2551275386 100644 (file)
@@ -2,6 +2,7 @@
 
 #include "weaponsystem.qh"
 #include <common/t_items.qh>
+#include <server/items.qh>
 #include <common/constants.qh>
 #include <common/net_linked.qh>
 #include <common/util.qh>
@@ -25,8 +26,10 @@ void Weapon_whereis(Weapon this, entity cl)
        if (!autocvar_g_showweaponspawns) return;
        IL_EACH(g_items, it.weapon == this.m_id && (!it.team || (it.ItemStatus & ITS_AVAILABLE)),
        {
-               if (it.classname == "droppedweapon" && autocvar_g_showweaponspawns < 2)
+               if (Item_IsLoot(it) && (autocvar_g_showweaponspawns < 2))
+               {
                        continue;
+               }
                entity wp = WaypointSprite_Spawn(
                        WP_Weapon,
                        -2, 0,
index d5b78aa1cd064424b267ca3cfc2ce7c782714d09..d47351cb37a727aab87f100d2ccb61ddd98a5824 100644 (file)
@@ -4,8 +4,11 @@
 #include "../resources.qh"
 #include "../mutators/_mod.qh"
 #include <common/t_items.qh>
+#include <server/items.qh>
 #include <common/weapons/_all.qh>
 
+.bool m_isreplaced; ///< Holds whether the weapon has been replaced.
+
 string W_Apply_Weaponreplace(string in)
 {
        string out = "";
@@ -26,7 +29,9 @@ string W_Apply_Weaponreplace(string in)
 void weapon_defaultspawnfunc(entity this, Weapon e)
 {
        Weapon wpn = e;
-       if (this.classname != "droppedweapon" && this.classname != "replacedweapon")
+       e = wpn = wpn.m_spawnfunc_hookreplace(wpn, this);
+       this.classname = wpn.m_canonical_spawnfunc;
+       if (!Item_IsLoot(this) && !this.m_isreplaced)
        {
                if (e.spawnflags & WEP_FLAG_MUTATORBLOCKED)
                {
@@ -56,7 +61,7 @@ void weapon_defaultspawnfunc(entity this, Weapon e)
                                        {
                                                entity replacement = spawn();
                                                copyentity(this, replacement);
-                                               replacement.classname = "replacedweapon";
+                                               replacement.m_isreplaced = true;
                                                weapon_defaultspawnfunc(replacement, it);
                                                break;
                                        }
index 52bf4a60788509039e72818a421099eb0c9aa907..ae745efd6fdaa90f6ed39b9d8b59e4736a3c26fa 100644 (file)
@@ -2,6 +2,7 @@
 
 #include "weaponsystem.qh"
 #include "../resources.qh"
+#include "../items.qh"
 #include "../mutators/_mod.qh"
 #include <common/t_items.qh>
 #include "../g_damage.qh"
@@ -40,8 +41,8 @@ string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vecto
        Weapon info = Weapons_from(wpn);
        int ammotype = info.ammo_type;
 
-       entity wep = new(droppedweapon);
-
+       entity wep = spawn();
+       Item_SetLoot(wep, true);
        setorigin(wep, org);
        wep.velocity = velo;
        wep.owner = wep.enemy = own;
@@ -54,6 +55,7 @@ string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vecto
 
        if(WepSet_FromWeapon(Weapons_from(wpn)) & WEPSET_SUPERWEAPONS)
        {
+               Item_SetExpiring(wep, true);
                if(own.items & IT_UNLIMITED_SUPERWEAPONS)
                {
                        wep.superweapons_finished = time + autocvar_g_balance_superweapons_time;
index bf272a01a94e02d75041436a36b2c7ce1846ddad..486ae180a2a0c80c3dea73f34dc0f45b36c45322 100644 (file)
@@ -16,6 +16,7 @@
 #include <common/util.qh>
 
 #include <common/weapons/_all.qh>
+#include <common/wepent.qh>
 #include <common/state.qh>
 
 #include <lib/warpzone/common.qh>
@@ -134,7 +135,14 @@ void W_SetupShot_Dir_ProjectileSize_Range(entity ent, .entity weaponentity, vect
                ent.punchangle_x = recoil * -1;
 
        if (snd != SND_Null) {
-               sound (ent, chan, snd, VOL_BASE, ATTN_NORM);
+               int held_weapons = 0; // HACK: muffle weapon sounds slightly while dual wielding!
+               for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+               {
+                       .entity wep_ent = weaponentities[slot];
+                       if(ent.(wep_ent) && ent.(wep_ent).m_switchweapon != WEP_Null)
+                               ++held_weapons;
+               }
+               sound (ent, chan, snd, ((held_weapons > 1) ? VOL_BASE * 0.7 : VOL_BASE), ATTN_NORM);
                W_PlayStrengthSound(ent);
        }
 
@@ -357,13 +365,7 @@ void fireBullet(entity this, .entity weaponentity, vector start, vector dir, flo
        if(autocvar_g_antilag == 0 || noantilag)
                lag = 0; // only do hitscan, but no antilag
        if(lag)
-       {
-               FOREACH_CLIENT(IS_PLAYER(it) && it != this, antilag_takeback(it, CS(it), time - lag));
-               IL_EACH(g_monsters, it != this,
-               {
-                       antilag_takeback(it, it, time - lag);
-               });
-       }
+               antilag_takeback_all(this, lag);
 
        // change shooter to SOLID_BBOX so the shot can hit corpses
        int oldsolid = this.dphitcontentsmask;
@@ -480,13 +482,7 @@ void fireBullet(entity this, .entity weaponentity, vector start, vector dir, flo
        }
 
        if(lag)
-       {
-               FOREACH_CLIENT(IS_PLAYER(it) && it != this, antilag_restore(it, CS(it)));
-               IL_EACH(g_monsters, it != this,
-               {
-                       antilag_restore(it, it);
-               });
-       }
+               antilag_restore_all(this);
 
        // restore shooter solid type
        if(this)
index 4ddf5a5159eac5adf534d6c61cafd0dc8a933445..c53b150944425aa1d96fcc84e3ecc239454091f7 100644 (file)
@@ -4,7 +4,6 @@
 #include <server/miscfunctions.qh>
 
 float internalteam;
-float weaponswapping;
 entity weapon_dropevent_item;
 
 ..entity weaponentity_fld;
diff --git a/randomitems-xonotic.cfg b/randomitems-xonotic.cfg
new file mode 100644 (file)
index 0000000..803e6c3
--- /dev/null
@@ -0,0 +1,163 @@
+// Random items mutator config
+
+// Map items
+
+set g_random_items_replace_item_health_small "random" "Classnames to replace small health with."
+set g_random_items_replace_item_health_medium "random" "Classnames to replace medium health with."
+set g_random_items_replace_item_health_big "random" "Classnames to replace big health with."
+set g_random_items_replace_item_health_mega "random" "Classnames to replace mega health with."
+set g_random_items_replace_item_armor_small "random" "Classnames to replace small armor with."
+set g_random_items_replace_item_armor_medium "random" "Classnames to replace medium armor with."
+set g_random_items_replace_item_armor_big "random" "Classnames to replace big armor with."
+set g_random_items_replace_item_armor_mega "random" "Classnames to replace mega armor with."
+set g_random_items_replace_item_shells "random" "Classnames to replace shells with."
+set g_random_items_replace_item_bullets "random" "Classnames to replace bullets with."
+set g_random_items_replace_item_rockets "random" "Classnames to replace rockets with."
+set g_random_items_replace_item_cells "random" "Classnames to replace cells with."
+set g_random_items_replace_item_plasma "random" "Classnames to replace plasma with."
+set g_random_items_replace_item_fuel "random" "Classnames to replace fuel with."
+set g_random_items_replace_weapon_blaster "random" "Classnames to replace blaster with."
+set g_random_items_replace_weapon_shotgun "random" "Classnames to replace shotgun with."
+set g_random_items_replace_weapon_machinegun "random" "Classnames to replace machinegun with."
+set g_random_items_replace_weapon_mortar "random" "Classnames to replace mortar with."
+set g_random_items_replace_weapon_electro "random" "Classnames to replace electro with."
+set g_random_items_replace_weapon_crylink "random" "Classnames to replace crylink with."
+set g_random_items_replace_weapon_vortex "random" "Classnames to replace vortex with."
+set g_random_items_replace_weapon_hagar "random" "Classnames to replace hagar with."
+set g_random_items_replace_weapon_devastator "random" "Classnames to replace devastator with."
+set g_random_items_replace_weapon_shockwave "random" "Classnames to replace shockwave with."
+set g_random_items_replace_weapon_arc "random" "Classnames to replace arc with."
+set g_random_items_replace_weapon_hook "random" "Classnames to replace hook with."
+set g_random_items_replace_weapon_tuba "random" "Classnames to replace tuba with."
+set g_random_items_replace_weapon_porto "random" "Classnames to replace port-o-launch with."
+set g_random_items_replace_weapon_fireball "random" "Classnames to replace fireball with."
+set g_random_items_replace_weapon_minelayer "random" "Classnames to replace mine layer with."
+set g_random_items_replace_weapon_hlac "random" "Classnames to replace HLAC with."
+set g_random_items_replace_weapon_rifle "random" "Classnames to replace rifle with."
+set g_random_items_replace_weapon_seeker "random" "Classnames to replace TAG seeker with."
+set g_random_items_replace_weapon_vaporizer "random" "Classnames to replace vaporizer with."
+set g_random_items_replace_weapon_hmg "random" "Classnames to replace HMG with."
+set g_random_items_replace_weapon_rpc "random" "Classnames to replace RPC with."
+set g_random_items_replace_item_strength "random" "Classnames to replace strength with."
+set g_random_items_replace_item_shield "random" "Classnames to replace shield with."
+set g_random_items_replace_item_fuel_regen "random" "Classnames to replace fuel regeneration with."
+set g_random_items_replace_item_jetpack "random" "Classnames to replace jetpack with."
+set g_random_items_replace_item_vaporizer_cells "random" "Classnames to replace vaporizer cells with."
+set g_random_items_replace_item_invisibility "random" "Classnames to replace invisibility with."
+set g_random_items_replace_item_extralife "random" "Classnames to replace extra life with."
+set g_random_items_replace_item_speed "random" "Classnames to replace speed with."
+set g_random_items_health_probability 1 "Probability of random health items spawning in the map."
+set g_random_items_armor_probability 1 "Probability of random armor items spawning in the map."
+set g_random_items_resource_probability 1 "Probability of random resource items spawning in the map."
+set g_random_items_weapon_probability 1 "Probability of random weapons spawning in the map."
+set g_random_items_powerup_probability 0.15 "Probability of random powerups spawning in the map."
+set g_random_items_item_health_small_probability 10 "Probability of random small health spawning in the map."
+set g_random_items_item_health_medium_probability 4 "Probability of random medium health spawning in the map."
+set g_random_items_item_health_big_probability 2 "Probability of random big health spawning in the map."
+set g_random_items_item_health_mega_probability 1 "Probability of random mega health spawning in the map."
+set g_random_items_item_armor_small_probability 10 "Probability of random small armor spawning in the map."
+set g_random_items_item_armor_medium_probability 4 "Probability of random medium armor spawning in the map."
+set g_random_items_item_armor_big_probability 2 "Probability of random big armor spawning in the map."
+set g_random_items_item_armor_mega_probability 1 "Probability of random mega armor spawning in the map."
+set g_random_items_item_shells_probability 1 "Probability of random shells spawning in the map."
+set g_random_items_item_bullets_probability 1 "Probability of random bullets spawning in the map."
+set g_random_items_item_rockets_probability 1 "Probability of random rockets spawning in the map."
+set g_random_items_item_cells_probability 1 "Probability of random cells spawning in the map."
+set g_random_items_item_plasma_probability 0 "Probability of random plasma spawning in the map."
+set g_random_items_item_fuel_probability 0 "Probability of random fuel spawning in the map."
+set g_random_items_weapon_blaster_probability 0 "Probability of random blaster spawning in the map."
+set g_random_items_weapon_shotgun_probability 0 "Probability of random shotgun spawning in the map."
+set g_random_items_weapon_machinegun_probability 1 "Probability of random machinegun spawning in the map."
+set g_random_items_weapon_mortar_probability 1 "Probability of random mortar spawning in the map."
+set g_random_items_weapon_electro_probability 1 "Probability of random electro spawning in the map."
+set g_random_items_weapon_crylink_probability 1 "Probability of random crylink spawning in the map."
+set g_random_items_weapon_vortex_probability 1 "Probability of random vortex spawning in the map."
+set g_random_items_weapon_hagar_probability 1 "Probability of random hagar spawning in the map."
+set g_random_items_weapon_devastator_probability 1 "Probability of random devastator spawning in the map."
+set g_random_items_weapon_shockwave_probability 0 "Probability of random shockwave spawning in the map."
+set g_random_items_weapon_arc_probability 0 "Probability of random arc spawning in the map."
+set g_random_items_weapon_hook_probability 0 "Probability of random hook spawning in the map."
+set g_random_items_weapon_tuba_probability 0 "Probability of random tuba spawning in the map."
+set g_random_items_weapon_porto_probability 0 "Probability of random port-o-launch spawning in the map."
+set g_random_items_weapon_fireball_probability 0 "Probability of random fireball spawning in the map."
+set g_random_items_weapon_minelayer_probability 0 "Probability of random mine layer spawning in the map."
+set g_random_items_weapon_hlac_probability 0 "Probability of random HLAC spawning in the map."
+set g_random_items_weapon_rifle_probability 0 "Probability of random rifle spawning in the map."
+set g_random_items_weapon_seeker_probability 0 "Probability of random TAG seeker spawning in the map."
+set g_random_items_weapon_vaporizer_probability 0 "Probability of random vaporizer spawning in the map."
+set g_random_items_item_strength_probability 1 "Probability of random strength spawning in the map."
+set g_random_items_item_shield_probability 1 "Probability of random shield spawning in the map."
+set g_random_items_item_fuel_regen_probability 0 "Probability of random fuel regeneration spawning in the map."
+set g_random_items_item_jetpack_probability 0 "Probability of random jetpack spawning in the map."
+set g_random_items_item_vaporizer_cells_probability 20 "Probability of random vaporizer cells spawning in the map."
+set g_random_items_item_invisibility_probability 1 "Probability of random invisibility spawning in the map."
+set g_random_items_item_extralife_probability 1 "Probability of random extra life spawning in the map."
+set g_random_items_item_speed_probability 1 "Probability of random speed spawning in the map."
+set g_random_items_overkill_item_health_mega_probability 1 "Probability of random mega health spawning in the map during overkill."
+set g_random_items_overkill_item_armor_small_probability 10 "Probability of random small armor spawning in the map during overkill."
+set g_random_items_overkill_item_armor_medium_probability 4 "Probability of random medium armor spawning in the map during overkill."
+set g_random_items_overkill_item_armor_big_probability 2 "Probability of random big armor spawning in the map during overkill."
+set g_random_items_overkill_item_armor_mega_probability 1 "Probability of random mega armor spawning in the map during overkill."
+set g_random_items_overkill_weapon_hmg_probability 0.5 "Probability of random HMG spawning in the map during overkill."
+set g_random_items_overkill_weapon_rpc_probability 0.5 "Probability of random RPC spawning in the map during overkill."
+
+// Loot
+
+set g_random_loot_min 0 "Minimum amount of loot items."
+set g_random_loot_max 4 "Maximum amount of loot items."
+set g_random_loot_time 10 "Amount of time the loot will stay in seconds."
+set g_random_loot_spread 200 "How far can loot be thrown."
+set g_random_loot_health_probability 1 "Probability of random health items spawning as loot."
+set g_random_loot_armor_probability 1 "Probability of random armor items spawning as loot."
+set g_random_loot_resource_probability 1 "Probability of random ammo items spawning as loot."
+set g_random_loot_weapon_probability 1 "Probability of random weapons spawning as loot."
+set g_random_loot_powerup_probability 0.2 "Probability of random powerups spawning as loot."
+set g_random_loot_item_health_small_probability 4 "Probability of random small health spawning as loot."
+set g_random_loot_item_health_medium_probability 3 "Probability of random medium health spawning as loot."
+set g_random_loot_item_health_big_probability 2 "Probability of random big health spawning as loot."
+set g_random_loot_item_health_mega_probability 1 "Probability of random mega health spawning as loot."
+set g_random_loot_item_armor_small_probability 4 "Probability of random small armor spawning as loot."
+set g_random_loot_item_armor_medium_probability 3 "Probability of random medium armor spawning as loot."
+set g_random_loot_item_armor_big_probability 2 "Probability of random big armor spawning as loot."
+set g_random_loot_item_armor_mega_probability 1 "Probability of random mega armor spawning as loot."
+set g_random_loot_item_shells_probability 1 "Probability of random shells spawning as loot."
+set g_random_loot_item_bullets_probability 1 "Probability of random bullets spawning as loot."
+set g_random_loot_item_rockets_probability 1 "Probability of random rockets spawning as loot."
+set g_random_loot_item_cells_probability 1 "Probability of random cells spawning as loot."
+set g_random_loot_item_plasma_probability 0 "Probability of random plasma spawning as loot."
+set g_random_loot_item_fuel_probability 0 "Probability of random fuel spawning as loot."
+set g_random_loot_weapon_blaster_probability 0 "Probability of random blaster spawning as loot."
+set g_random_loot_weapon_shotgun_probability 0 "Probability of random shotgun spawning as loot."
+set g_random_loot_weapon_machinegun_probability 1 "Probability of random machinegun spawning as loot."
+set g_random_loot_weapon_mortar_probability 1 "Probability of random mortar spawning as loot."
+set g_random_loot_weapon_electro_probability 1 "Probability of random electro spawning as loot."
+set g_random_loot_weapon_crylink_probability 1 "Probability of random crylink spawning as loot."
+set g_random_loot_weapon_vortex_probability 1 "Probability of random vortex spawning as loot."
+set g_random_loot_weapon_hagar_probability 1 "Probability of random hagar spawning as loot."
+set g_random_loot_weapon_devastator_probability 1 "Probability of random devastator spawning as loot."
+set g_random_loot_weapon_shockwave_probability 0 "Probability of random shockwave spawning as loot."
+set g_random_loot_weapon_arc_probability 0 "Probability of random arc spawning as loot."
+set g_random_loot_weapon_hook_probability 0 "Probability of random hook spawning as loot."
+set g_random_loot_weapon_tuba_probability 0 "Probability of random tuba spawning as loot."
+set g_random_loot_weapon_porto_probability 0 "Probability of random port-o-launch spawning as loot."
+set g_random_loot_weapon_fireball_probability 0 "Probability of random fireball spawning as loot."
+set g_random_loot_weapon_minelayer_probability 0 "Probability of random mine layer spawning as loot."
+set g_random_loot_weapon_hlac_probability 0 "Probability of random HLAC spawning as loot."
+set g_random_loot_weapon_rifle_probability 0 "Probability of random rifle spawning as loot."
+set g_random_loot_weapon_seeker_probability 0 "Probability of random TAG seeker spawning as loot."
+set g_random_loot_weapon_vaporizer_probability 0 "Probability of random vaporizer spawning as loot."
+set g_random_loot_item_strength_probability 1 "Probability of random strength spawning as loot."
+set g_random_loot_item_shield_probability 1 "Probability of random shield spawning as loot."
+set g_random_loot_item_fuel_regen_probability 0 "Probability of random fuel regeneration spawning as loot."
+set g_random_loot_item_jetpack_probability 0 "Probability of random jetpack spawning as loot."
+set g_random_loot_item_vaporizer_cells_probability 20 "Probability of random vaporizer cells spawning as loot."
+set g_random_loot_item_invisibility_probability 1 "Probability of random invisibility spawning as loot."
+set g_random_loot_item_extralife_probability 1 "Probability of random extra life spawning as loot."
+set g_random_loot_item_speed_probability 1 "Probability of random speed spawning as loot."
+set g_random_loot_overkill_item_health_mega_probability 1 "Probability of random mega health spawning as loot during overkill."
+set g_random_loot_overkill_item_armor_small_probability 10 "Probability of random small armor spawning as loot during overkill."
+set g_random_loot_overkill_item_armor_medium_probability 4 "Probability of random medium armor spawning as loot during overkill."
+set g_random_loot_overkill_item_armor_big_probability 2 "Probability of random big armor spawning as loot during overkill."
+set g_random_loot_overkill_item_armor_mega_probability 1 "Probability of random mega armor spawning as loot during overkill."
+set g_random_loot_overkill_weapon_hmg_probability 1 "Probability of random HMG spawning as loot during overkill."
+set g_random_loot_overkill_weapon_rpc_probability 1 "Probability of random RPC spawning as loot during overkill."