- 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=b8f4fa5002af1f9f2d5ac3d1809ed188
+ - EXPECT=538cd6a692f22150ac60d9902eddc75b
- HASH=$(${ENGINE} -noconfig -nohome +exec serverbench.cfg
| tee /dev/stderr
| grep '^:'
-Wed Jul 11 07:24:32 CEST 2018
+Sun Sep 30 07:24:04 CEST 2018
seta hud_panel_scoreboard_spectators_aligned 0 "align spectators in columns"
seta hud_panel_scoreboard_minwidth 0.6 "minimum width of the scoreboard"
+seta hud_panel_scoreboard_accuracy_showdelay 2 "how long to delay displaying accuracy below the scoreboard if it's too far down"
+seta hud_panel_scoreboard_accuracy_showdelay_minpos 0.75 "delay displaying the accuracy panel only if its position is lower than this percentage of the screen height from the top"
+
// hud panel aliases
alias quickmenu "cl_cmd hud quickmenu ${* ?}"
set g_balance_shotgun_secondary_alt_refire 1.2
set g_balance_shotgun_switchdelay_drop 0.2
set g_balance_shotgun_switchdelay_raise 0.2
-set g_balance_shotgun_weaponreplace "shockwave"
-set g_balance_shotgun_weaponstart 0
+set g_balance_shotgun_weaponreplace ""
+set g_balance_shotgun_weaponstart 1
set g_balance_shotgun_weaponstartoverride -1
set g_balance_shotgun_weaponthrowable 1
// }}}
set g_balance_crylink_secondary_animtime 0.2
set g_balance_crylink_secondary_bouncedamagefactor 0.5
set g_balance_crylink_secondary_bounces 0
-set g_balance_crylink_secondary_damage 50
-set g_balance_crylink_secondary_edgedamage 15
-set g_balance_crylink_secondary_force -400
+set g_balance_crylink_secondary_damage 10
+set g_balance_crylink_secondary_edgedamage 5
+set g_balance_crylink_secondary_force -200
set g_balance_crylink_secondary_joindelay 0
set g_balance_crylink_secondary_joinexplode 0
set g_balance_crylink_secondary_joinexplode_damage 0
set g_balance_crylink_secondary_joinexplode_force 0
set g_balance_crylink_secondary_joinexplode_radius 0
set g_balance_crylink_secondary_joinspread 0
-set g_balance_crylink_secondary_linkexplode 1
+set g_balance_crylink_secondary_linkexplode 0
set g_balance_crylink_secondary_middle_fadetime 5
set g_balance_crylink_secondary_middle_lifetime 5
-set g_balance_crylink_secondary_other_fadetime 5
-set g_balance_crylink_secondary_other_lifetime 5
-set g_balance_crylink_secondary_radius 70
-set g_balance_crylink_secondary_refire 0.8
-set g_balance_crylink_secondary_shots 1
-set g_balance_crylink_secondary_speed 3000
-set g_balance_crylink_secondary_spread 0
-set g_balance_crylink_secondary_spreadtype 1
+set g_balance_crylink_secondary_other_fadetime 2
+set g_balance_crylink_secondary_other_lifetime 2
+set g_balance_crylink_secondary_radius 100
+set g_balance_crylink_secondary_refire 0.65
+set g_balance_crylink_secondary_shots 5
+set g_balance_crylink_secondary_speed 7000
+set g_balance_crylink_secondary_spread 0.08
+set g_balance_crylink_secondary_spreadtype 0
set g_balance_crylink_switchdelay_drop 0.2
set g_balance_crylink_switchdelay_raise 0.2
set g_balance_crylink_weaponreplace ""
set g_balance_shockwave_switchdelay_drop 0.2
set g_balance_shockwave_switchdelay_raise 0.2
set g_balance_shockwave_weaponreplace ""
-set g_balance_shockwave_weaponstart 1
+set g_balance_shockwave_weaponstart 0
set g_balance_shockwave_weaponstartoverride -1
set g_balance_shockwave_weaponthrowable 0
// }}}
set g_balance_arc_burst_heat 5
set g_balance_arc_beam_maxangle 10
set g_balance_arc_beam_nonplayerdamage 80
-set g_balance_arc_beam_range 1250
+set g_balance_arc_beam_range 1500
set g_balance_arc_beam_refire 0.25
set g_balance_arc_beam_returnspeed 8
-set g_balance_arc_beam_tightness 0.5
+set g_balance_arc_beam_tightness 0.6
set g_balance_arc_bolt 1
set g_balance_arc_bolt_ammo 1
set g_balance_arc_bolt_damage 25
set g_balance_okmachinegun_primary_damage 25
set g_balance_okmachinegun_primary_force 5
set g_balance_okmachinegun_primary_refire 0.1
-set g_balance_okmachinegun_primary_solidpenetration 13.1
+set g_balance_okmachinegun_primary_solidpenetration 63
set g_balance_okmachinegun_primary_spread_add 0.012
set g_balance_okmachinegun_primary_spread_max 0.05
set g_balance_okmachinegun_primary_spread_min 0
set g_balance_arc_bolt_damage 25
set g_balance_arc_bolt_damageforcescale 0
set g_balance_arc_bolt_edgedamage 12.5
-set g_balance_arc_bolt_force 120
+set g_balance_arc_bolt_force 200
set g_balance_arc_bolt_health 15
set g_balance_arc_bolt_lifetime 5
set g_balance_arc_bolt_radius 65
-set g_balance_arc_bolt_refire 0.16667
+set g_balance_arc_bolt_refire 0.033333
set g_balance_arc_bolt_speed 2300
set g_balance_arc_bolt_spread 0
set g_balance_arc_burst_ammo 15
bind MWHEELDOWN weapprev
bind r reload
bind BACKSPACE dropweapon
+bind k kill
bind g dropweapon
bind f +use
bind v +button8 // drag object
// networked/server common commands
alias cvar_changes "qc_cmd_svcmd cvar_changes ${* ?}" // Prints a list of all changed server cvars
alias cvar_purechanges "qc_cmd_svcmd cvar_purechanges ${* ?}" // Prints a list of all changed gameplay cvars
+alias editmob "qc_cmd_svcmd editmob ${* ?}" // Modifies a monster or all monsters
alias info "qc_cmd_svcmd info ${* ?}" // Request for unique server information set up by admin
alias ladder "qc_cmd_svcmd ladder ${* ?}" // Get information about top players if supported
alias lsmaps "qc_cmd_svcmd lsmaps ${* ?}" // List maps which can be used with the current game mode
// generic commands (across all programs)
alias addtolist "qc_cmd_svmenu addtolist ${* ?}" // Add a string to a cvar
+alias bufstr_get "qc_cmd_svmenu bufstr_get ${* ?}" // Examine a string buffer object
+alias cvar_localchanges "qc_cmd_svmenu cvar_localchanges ${* ?}" // Print locally changed cvars
alias dumpcommands "qc_cmd_svmenu dumpcommands ${* ?}" // Dump all commands on the program to *_cmd_dump.txt
-alias dumpnotifs "qc_cmd_svcl dumpnotifs ${* ?}" // Dump all notifications into notifications_dump.txt
+alias dumpnotifs "qc_cmd_svmenu dumpnotifs ${* ?}" // Dump all notifications into notifications_dump.txt
+alias dumpitems "qc_cmd_svmenu dumpitems ${* ?}" // Dump all items to the console
+alias dumpturrets "qc_cmd_svmenu dumpturrets ${* ?}" // Dump all turrets into turrets_dump.txt
+alias dumpweapons "qc_cmd_svmenu dumpweapons ${* ?}" // Dump all weapons into weapons_dump.txt
+alias find "qc_cmd_svmenu find ${* ?}" // Search through entities for matching classname
+alias findat "qc_cmd_svmenu findat ${* ?}" // Search through entities for matching origin
alias maplist "qc_cmd_svmenu maplist ${* ?}" // Automatic control of maplist
+alias mx "qc_cmd_svmenu mx ${* ?}" // Send a matrix command
alias nextframe "qc_cmd_svmenu nextframe ${* ?}" // Execute the given command next frame of this VM
alias qc_curl "qc_cmd_svmenu qc_curl ${* ?}" // Queries a URL
alias removefromlist "qc_cmd_svmenu removefromlist ${* ?}" // Remove a string from a cvar
-alias restartnotifs "qc_cmd_svcl restartnotifs ${* ?}" // Re-initialize all notifications
+alias restartnotifs "qc_cmd_svmenu restartnotifs ${* ?}" // Re-initialize all notifications
alias rpn "qc_cmd_svmenu rpn ${* ?}" // RPN calculator
+alias runtest "qc_cmd_svmenu runtest ${* ?}" // Run unit tests
//alias settemp "qc_cmd_svmenu settemp ${* ?}" // Temporarily set a value to a cvar which is restored later
//alias settemp_restore "qc_cmd_svmenu settemp_restore ${* ?}" // Restore all cvars set by settemp command
+alias version "qc_cmd_svmenu version ${* ?}" // Print the current version
// other aliases for common commands
alias g_hitplots_add "qc_cmd_svmenu rpn /g_hitplots_individuals g_hitplots_individuals ${1 !} union def"
// ==========================================================
// commented out commands are really only intended for internal use
alias blurtest "qc_cmd_cl blurtest ${* ?}" // Feature for testing blur postprocessing
+alias boxparticles "qc_cmd_cl boxparticles ${* ?}" // Spawn particles manually
alias create_scrshot_ent "qc_cmd_cl create_scrshot_ent ${* ?}" // Create an entity at this location for automatic screenshots
alias debugmodel "qc_cmd_cl debugmodel ${* ?}" // Spawn a debug model manually
//alias handlevote "qc_cmd_cl handlevote ${* ?}" // System to handle selecting a vote or option
alias localprint "qc_cmd_cl localprint ${* ?}" // Create your own centerprint sent to yourself
//alias mv_download "qc_cmd_cl mv_download ${* ?}" // Retrieve mapshot picture from the server
alias sendcvar "qc_cmd_cl sendcvar ${* ?}" // Send a cvar to the server (like weaponpriority)
+alias weapon_find "qc_cmd_cl weapon_find ${* ?}" // Show spawn locations of a weapon
+
alias exit "quit"
// other aliases for local commands
// commented out commands are really only intended for internal use, or already have declaration in the engine
alias autoswitch "qc_cmd_cmd autoswitch ${* ?}" // Whether or not to switch automatically when getting a better weapon
alias clientversion "qc_cmd_cmd clientversion ${* ?}" // Release version of the game
-//alias mv_getpicture "qc_cmd_cmd mv_getpicture ${* ?}" // Retrieve mapshot picture from the server
alias join "qc_cmd_cmd join ${* ?}" // Become a player in the game
+alias minigame "qc_cmd_cmd minigame ${* ?}" // Start a minigame
+//alias mv_getpicture "qc_cmd_cmd mv_getpicture ${* ?}" // Retrieve mapshot picture from the server
+alias physics "qc_cmd_cmd physics ${* ?}" // Change physics set
alias ready "qc_cmd_cmd ready ${* ?}" // Qualify as ready to end warmup stage (or restart server if allowed)
-alias reportcvar "qc_cmd_cmd reportcvar ${* ?}" // Old system for sending a client cvar to the server
//alias say "qc_cmd_cmd say ${* ?}" // Print a message to chat to all players
//alias say_team "qc_cmd_cmd say_team ${* ?}" // Print a message to chat to all team mates
alias selectteam "qc_cmd_cmd selectteam ${* ?}" // Attempt to choose a team to join into
alias selfstuff "qc_cmd_cmd selfstuff ${* ?}" // Stuffcmd a command to your own client
alias sentcvar "qc_cmd_cmd sentcvar ${* ?}" // New system for sending a client cvar to the server
-alias editmob "qc_cmd_cmd editmob ${* ?}" // Edit a monster's properties
-alias physics "qc_cmd_cmd physics ${* ?}" // Change physics set
alias spectate "qc_cmd_cmd spectate ${* ?}" // Become an observer
alias suggestmap "qc_cmd_cmd suggestmap ${* ?}" // Suggest a map to the mapvote at match end
//alias tell "qc_cmd_cmd tell ${* ?}" // Send a message directly to a player
alias voice "qc_cmd_cmd voice ${* ?}" // Send voice message via sound
+alias wpeditor "qc_cmd_cmd wpeditor ${* ?}" // Waypoint editor commands
// other aliases for client-to-server commands
alias autoswitch "set cl_autoswitch ${1 ?} ; cmd autoswitch ${1 ?}" // todo
alias adminmsg "qc_cmd_sv adminmsg ${* ?}" // Send an admin message to a client directly
alias allready "qc_cmd_sv allready ${* ?}" // Restart the server and reset the players
alias allspec "qc_cmd_sv allspec ${* ?}" // Force all players to spectate
+alias animbench "qc_cmd_sv animbench ${* ?}" // Benchmark model animation (LAGS)
alias anticheat "qc_cmd_sv anticheat ${* ?}" // Create an anticheat report for a client
alias bbox "qc_cmd_sv bbox ${* ?}" // Print detailed information about world size
alias bot_cmd "qc_cmd_sv bot_cmd ${* ?}" // Control and send commands to bots
alias delrec "qc_cmd_sv delrec ${* ?}" // Delete race time record for a map
alias effectindexdump "qc_cmd_sv effectindexdump ${* ?}" // Dump list of effects from code and effectinfo.txt
alias extendmatchtime "qc_cmd_sv extendmatchtime ${* ?}" // Increase the timelimit value incrementally
-alias find "qc_cmd_sv find ${* ?}" // Search through entities for matching classname
alias gametype "qc_cmd_sv gametype ${* ?}" // Simple command to change the active gametype
alias gettaginfo "qc_cmd_sv gettaginfo ${* ?}" // Get specific information about a weapon model
alias gotomap "qc_cmd_sv gotomap ${* ?}" // Simple command to switch to another map
"Project-Id-Version: Xonotic\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-07-09 00:35+0200\n"
-"PO-Revision-Date: 2018-07-07 11:33+0000\n"
+"PO-Revision-Date: 2018-07-13 16:04+0000\n"
"Last-Translator: Wuzzy <almikes@aol.com>\n"
"Language-Team: German (http://www.transifex.com/team-xonotic/xonotic/"
"language/de/)\n"
#: qcsrc/client/hud/panel/modicons.qc:576
msgid "Server best"
-msgstr "Server Bestzeit"
+msgstr "Server-Bestzeit"
#: qcsrc/client/hud/panel/notify.qc:115 qcsrc/client/hud/panel/notify.qc:116
#: qcsrc/client/hud/panel/score.qc:59
#: qcsrc/client/hud/panel/weapons.qc:534
msgid "Don't have"
-msgstr "Nicht vorhanden"
+msgstr "Hast du nicht"
#: qcsrc/client/hud/panel/weapons.qc:538
msgid "Unavailable"
-msgstr "Nicht verfügbar"
+msgstr "Fehlend"
#: qcsrc/client/main.qc:1014
msgid " qu/s"
#: qcsrc/common/notifications/all.inc:412
#, c-format
msgid "^BG%s^BG captured %s^BG control point"
-msgstr "^BG%s^BG hat den Kontrollpunkt von %s^BG erobert"
+msgstr "^BG%s^BG hat einen Kontrollpunkt erobert: %s^BG"
#: qcsrc/common/notifications/all.inc:413
#, c-format
msgid "^TC^TT^BG team %s^BG control point has been destroyed by %s"
-msgstr "^TC^TT^BGDer Kontrollpunkt vom Team %s^BG wurde von %s zerstört"
+msgstr "^TC^TT^BGEin Kontrollpunkt vom Team %s^BG wurde von %s zerstört"
#: qcsrc/common/notifications/all.inc:414
msgid "^TC^TT^BG generator has been destroyed"
#: qcsrc/common/notifications/all.inc:733
#, c-format
msgid "^BGYou captured %s^BG control point"
-msgstr "^BGDu hast den Kontrollpunkt von %s^BG erobert"
+msgstr "^BGDu hast einen Kontrollpunkt erobert: %s^BG"
#: qcsrc/common/notifications/all.inc:734
#, c-format
msgid "^TC^TT^BG team captured %s^BG control point"
-msgstr "Team ^TC^TT^BG hat %ss^BG Kontrollpunkt erobert"
+msgstr "Team ^TC^TT^BG hat einen Kontrollpunkt erobert: %s^BG"
#: qcsrc/common/notifications/all.inc:735
msgid "^BGThis control point currently cannot be captured"
"Project-Id-Version: Xonotic\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-07-09 00:35+0200\n"
-"PO-Revision-Date: 2018-07-07 11:33+0000\n"
+"PO-Revision-Date: 2018-07-13 16:04+0000\n"
"Last-Translator: Wuzzy <almikes@aol.com>\n"
"Language-Team: German (http://www.transifex.com/team-xonotic/xonotic/"
"language/de/)\n"
#: qcsrc/client/hud/panel/modicons.qc:576
msgid "Server best"
-msgstr "Server Bestzeit"
+msgstr "Server-Bestzeit"
#: qcsrc/client/hud/panel/notify.qc:115 qcsrc/client/hud/panel/notify.qc:116
#: qcsrc/client/hud/panel/score.qc:59
#: qcsrc/client/hud/panel/weapons.qc:534
msgid "Don't have"
-msgstr "Nicht vorhanden"
+msgstr "Hast du nicht"
#: qcsrc/client/hud/panel/weapons.qc:538
msgid "Unavailable"
-msgstr "Nicht verfügbar"
+msgstr "Fehlend"
#: qcsrc/client/main.qc:1014
msgid " qu/s"
#: qcsrc/common/notifications/all.inc:412
#, c-format
msgid "^BG%s^BG captured %s^BG control point"
-msgstr "^BG%s^BG hat den Kontrollpunkt von %s^BG erobert"
+msgstr "^BG%s^BG hat einen Kontrollpunkt erobert: %s^BG"
#: qcsrc/common/notifications/all.inc:413
#, c-format
msgid "^TC^TT^BG team %s^BG control point has been destroyed by %s"
-msgstr "^TC^TT^BGDer Kontrollpunkt vom Team %s^BG wurde von %s zerstört"
+msgstr "^TC^TT^BGEin Kontrollpunkt vom Team %s^BG wurde von %s zerstört"
#: qcsrc/common/notifications/all.inc:414
msgid "^TC^TT^BG generator has been destroyed"
#: qcsrc/common/notifications/all.inc:733
#, c-format
msgid "^BGYou captured %s^BG control point"
-msgstr "^BGDu hast den Kontrollpunkt von %s^BG erobert"
+msgstr "^BGDu hast einen Kontrollpunkt erobert: %s^BG"
#: qcsrc/common/notifications/all.inc:734
#, c-format
msgid "^TC^TT^BG team captured %s^BG control point"
-msgstr "Team ^TC^TT^BG hat %ss^BG Kontrollpunkt erobert"
+msgstr "Team ^TC^TT^BG hat einen Kontrollpunkt erobert: %s^BG"
#: qcsrc/common/notifications/all.inc:735
msgid "^BGThis control point currently cannot be captured"
"Project-Id-Version: Xonotic\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-07-09 00:35+0200\n"
-"PO-Revision-Date: 2018-07-10 10:55+0000\n"
+"PO-Revision-Date: 2018-07-18 12:01+0000\n"
"Last-Translator: nad le <nadavlevi726@gmail.com>\n"
"Language-Team: Hebrew (http://www.transifex.com/team-xonotic/xonotic/"
"language/he/)\n"
#: qcsrc/common/mutators/mutator/nades/nades.qh:32
msgid "Grenade"
-msgstr ""
+msgstr "רימון"
#: qcsrc/common/mutators/mutator/overkill/hmg.qh:17
msgid "Heavy Machine Gun"
#: qcsrc/common/mutators/mutator/waypoints/all.inc:5
msgid "Here"
-msgstr ""
+msgstr "כאן"
#: qcsrc/common/mutators/mutator/waypoints/all.inc:6
msgid "DANGER"
-msgstr ""
+msgstr "סכנה"
#: qcsrc/common/mutators/mutator/waypoints/all.inc:8
msgid "Frozen!"
-msgstr ""
+msgstr "קפוא!"
#: qcsrc/common/mutators/mutator/waypoints/all.inc:10
msgid "Item"
#: qcsrc/common/mutators/mutator/waypoints/all.inc:39
msgid "Run here"
-msgstr ""
+msgstr "רוץ לכאן"
#: qcsrc/common/mutators/mutator/waypoints/all.inc:45
#: qcsrc/common/mutators/mutator/waypoints/all.inc:48
#: qcsrc/menu/xonotic/dialog_settings_input.qc:59
msgid "Sensitivity:"
-msgstr ""
+msgstr "רגישות:"
#: qcsrc/menu/xonotic/dialog_settings_input.qc:61
msgid "Mouse speed multiplier"
#: qcsrc/menu/xonotic/dialog_settings_input.qc:96
msgid "Automatically repeat jumping if holding jump"
-msgstr ""
+msgstr "המשך לקפוץ באופן אוטומטי אם מקש קפיצה לחוץ"
#: qcsrc/menu/xonotic/dialog_settings_input.qc:99
msgid "Jetpack on jump:"
#: qcsrc/menu/xonotic/dialog_settings_video.qc:119
msgid "Brightness:"
-msgstr ""
+msgstr "בהירות:"
#: qcsrc/menu/xonotic/dialog_settings_video.qc:121
msgid "Brightness of black (default: 0)"
#: qcsrc/menu/xonotic/dialog_settings_video.qc:123
msgid "Contrast:"
-msgstr ""
+msgstr "ניגודיות:"
#: qcsrc/menu/xonotic/dialog_settings_video.qc:125
msgid "Brightness of white (default: 1)"
#: qcsrc/menu/xonotic/serverlist.qc:763
msgid "Ping"
-msgstr ""
+msgstr "פינג"
#: qcsrc/menu/xonotic/serverlist.qc:764
msgid "Hostname"
#: qcsrc/menu/xonotic/serverlist.qc:766
msgid "Type"
-msgstr ""
+msgstr "סוג"
#: qcsrc/menu/xonotic/serverlist.qc:1060
#, c-format
"texture memory usage, but make the textures appear very blurry. (default: "
"good)"
msgstr ""
+"שנה את החדות של טקסטורות.\n"
+"הנמכה תגרום לצמצום יעיל של זיכרון שמשומש על ידי טקסטורות, אבל יגרום "
+"לטקסטורות להראות מאוד מטושטשות. (ברירת מחדל: טוב)"
#: qcsrc/menu/xonotic/slider_resolution.qc:115
msgid "Screen resolution"
#: qcsrc/menu/xonotic/statslist.qc:103
msgid "Last_Seen:"
-msgstr ""
+msgstr "נראה_לאחרונה:"
#: qcsrc/menu/xonotic/statslist.qc:110
msgid "Time_Played:"
# Translators:
# Ákos RUSZKAI, 2012
# divVerent <divVerent@xonotic.org>, 2011
+# MmAaXx500 <viktor.balogh2000@gmail.com>, 2018
# Peter Ferenczy <fpeterhu@gmail.com>, 2017
# divVerent <divVerent@xonotic.org>, 2011
+# MmAaXx500 <viktor.balogh2000@gmail.com>, 2018
msgid ""
msgstr ""
"Project-Id-Version: Xonotic\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-07-09 00:35+0200\n"
-"PO-Revision-Date: 2017-09-19 19:55+0000\n"
-"Last-Translator: divVerent <divVerent@xonotic.org>\n"
+"PO-Revision-Date: 2018-08-26 13:48+0000\n"
+"Last-Translator: MmAaXx500 <viktor.balogh2000@gmail.com>\n"
"Language-Team: Hungarian (http://www.transifex.com/team-xonotic/xonotic/"
"language/hu/)\n"
"Language: hu\n"
#: qcsrc/client/hud/panel/quickmenu.qc:823
#: qcsrc/client/hud/panel/quickmenu.qc:860
msgid "QMCMD^Settings"
-msgstr ""
+msgstr "QMCMD^Beállítások"
#: qcsrc/client/hud/panel/quickmenu.qc:824
#: qcsrc/client/hud/panel/quickmenu.qc:831
msgid "QMCMD^View/HUD settings"
-msgstr ""
+msgstr "QMCMD^Nézet/HUD beállítások"
#: qcsrc/client/hud/panel/quickmenu.qc:825
msgid "QMCMD^3rd person view"
#: qcsrc/client/hud/panel/quickmenu.qc:827
msgid "QMCMD^Names above players"
-msgstr ""
+msgstr "QMCMD^Nevek a játékosok fölött"
#: qcsrc/client/hud/panel/quickmenu.qc:828
msgid "QMCMD^Crosshair per weapon"
-msgstr ""
+msgstr "QMCMD^Célkereszt fegyverenként"
#: qcsrc/client/hud/panel/quickmenu.qc:829
msgid "QMCMD^FPS"
-msgstr ""
+msgstr "QMCMD^FPS"
#: qcsrc/client/hud/panel/quickmenu.qc:830
msgid "QMCMD^Net graph"
-msgstr ""
+msgstr "QMCMD^Hálózati grafikon"
#: qcsrc/client/hud/panel/quickmenu.qc:833
#: qcsrc/client/hud/panel/quickmenu.qc:836
msgid "QMCMD^Sound settings"
-msgstr ""
+msgstr "Hang beállítások"
#: qcsrc/client/hud/panel/quickmenu.qc:834
msgid "QMCMD^Hit sound"
-msgstr ""
+msgstr "QMCMD^Találat hang"
#: qcsrc/client/hud/panel/quickmenu.qc:835
msgid "QMCMD^Chat sound"
-msgstr ""
+msgstr "QMCMD^Chat hang"
#: qcsrc/client/hud/panel/quickmenu.qc:840
#: qcsrc/client/hud/panel/quickmenu.qc:844
msgid "QMCMD^Spectator camera"
-msgstr ""
+msgstr "QMCMD^Szemlélő kamera"
#: qcsrc/client/hud/panel/quickmenu.qc:841
msgid "QMCMD^1st person"
-msgstr ""
+msgstr "QMCMD^Első személy"
#: qcsrc/client/hud/panel/quickmenu.qc:842
msgid "QMCMD^3rd person around player"
-msgstr ""
+msgstr "QMCMD^Harmadik személy a játékos körül"
#: qcsrc/client/hud/panel/quickmenu.qc:843
msgid "QMCMD^3rd person behind"
-msgstr ""
+msgstr "QMCMD^Harmadik személy mögött"
#: qcsrc/client/hud/panel/quickmenu.qc:849
#: qcsrc/client/hud/panel/quickmenu.qc:854
#: qcsrc/client/hud/panel/quickmenu.qc:850
msgid "QMCMD^Increase speed"
-msgstr ""
+msgstr "QMCMD^Sebesség növelése"
#: qcsrc/client/hud/panel/quickmenu.qc:851
msgid "QMCMD^Decrease speed"
-msgstr ""
+msgstr "QMCMD^Sebesség csökkentése"
#: qcsrc/client/hud/panel/quickmenu.qc:852
msgid "QMCMD^Wall collision off"
#: qcsrc/client/hud/panel/quickmenu.qc:857
msgid "QMCMD^Fullscreen"
-msgstr ""
+msgstr "QMCMD^Teljes képernyő"
#: qcsrc/client/hud/panel/quickmenu.qc:859
msgid "QMCMD^Translate chat messages"
-msgstr ""
+msgstr "QMCMD^Chat üzenetek lefordítása"
#: qcsrc/client/hud/panel/quickmenu.qc:862
#: qcsrc/client/hud/panel/quickmenu.qc:872
msgid "QMCMD^Call a vote"
-msgstr ""
+msgstr "QMCMD^Szavazás indítása"
#: qcsrc/client/hud/panel/quickmenu.qc:863
msgid "QMCMD^Restart the map"
-msgstr ""
+msgstr "QMCMD^Játék újraindítása"
#: qcsrc/client/hud/panel/quickmenu.qc:864
msgid "QMCMD^End match"
-msgstr ""
+msgstr "QMCMD^Játék vége"
#: qcsrc/client/hud/panel/quickmenu.qc:867
msgid "QMCMD^Reduce match time"
-msgstr ""
+msgstr "QMCMD^Játékidő csökkentése"
#: qcsrc/client/hud/panel/quickmenu.qc:868
msgid "QMCMD^Extend match time"
-msgstr ""
+msgstr "QMCMD^Játékidő csökkentése"
#: qcsrc/client/hud/panel/quickmenu.qc:871
msgid "QMCMD^Shuffle teams"
-msgstr ""
+msgstr "QMCMD^Csapatok összekeverése"
#: qcsrc/client/hud/panel/racetimer.qc:37
#, c-format
#: qcsrc/client/hud/panel/scoreboard.qc:84
msgid "SCO^damage"
-msgstr ""
+msgstr "SCO^sérülés"
#: qcsrc/client/hud/panel/scoreboard.qc:85
msgid "SCO^dmgtaken"
#: qcsrc/client/hud/panel/scoreboard.qc:99
msgid "SCO^sum"
-msgstr ""
+msgstr "SCO^össz"
#: qcsrc/client/hud/panel/scoreboard.qc:100
msgid "SCO^nick"
#: qcsrc/client/hud/panel/scoreboard.qc:1354
msgid "Capture time rankings"
-msgstr ""
+msgstr "Célbaérési idő rangsor"
#: qcsrc/client/hud/panel/scoreboard.qc:1354
msgid "Rankings"
#: qcsrc/client/hud/panel/scoreboard.qc:1688
#, c-format
msgid "^1Respawning in ^3%s^1..."
-msgstr ""
+msgstr "^1Respawning: ^3%s^1..."
#: qcsrc/client/hud/panel/scoreboard.qc:1698
#, c-format
"Project-Id-Version: Xonotic\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-07-09 00:35+0200\n"
-"PO-Revision-Date: 2018-05-22 14:42+0000\n"
+"PO-Revision-Date: 2018-07-22 18:03+0000\n"
"Last-Translator: Jean Trindade Pereira <jean_trindade2@hotmail.com>\n"
"Language-Team: Portuguese (Brazil) (http://www.transifex.com/team-xonotic/"
"xonotic/language/pt_BR/)\n"
#: qcsrc/client/hud/hud_config.qc:239
#, c-format
msgid "^2Successfully exported to %s! (Note: It's saved in data/data/)\n"
-msgstr "^2Exportado com sucesso para %s! (Nota: Foi salvo em data/data/)\n"
+msgstr "^2Exportado com sucesso para %s! (Nota: foi salvo em data/data/)\n"
#: qcsrc/client/hud/hud_config.qc:243
#, c-format
#: qcsrc/client/hud/panel/infomessages.qc:167
msgid "^2Waiting for others to ready up to end warmup..."
msgstr ""
-"^2Esperando que os outros jogadores estejam prontos para acabar o "
-"aquecimento..."
+"^2Aguardando outros jogadores ficarem prontos para terminar o aquecimento..."
#: qcsrc/client/hud/panel/infomessages.qc:169
msgid "^2Waiting for others to ready up..."
-msgstr "^2Esperando que os outros jogadores estejam prontos..."
+msgstr "^2Aguardando outros jogadores ficarem prontos..."
#: qcsrc/client/hud/panel/infomessages.qc:175
#, c-format
#: qcsrc/client/hud/panel/infomessages.qc:209
msgid "^1Spectating this player:"
-msgstr "^1Também estão assistindo a este jogador:"
+msgstr "^1Assistindo a este jogador:"
#: qcsrc/client/hud/panel/infomessages.qc:209
msgid "^1Spectating you:"
#: qcsrc/client/hud/panel/infomessages.qc:225
msgid "^7Press ^3ESC ^7to show HUD options."
-msgstr "^7Aperte ^3ESC ^7para exibir as opções de HUD."
+msgstr "^7Aperte ^3ESC ^7para exibir as opções de interface."
#: qcsrc/client/hud/panel/infomessages.qc:226
msgid "^3Doubleclick ^7a panel for panel-specific options."
#: qcsrc/client/hud/panel/infomessages.qc:227
msgid "^3CTRL ^7to disable collision testing, ^3SHIFT ^7and"
-msgstr "^3CTRL ^7para desligar teste de colisão, ^3SHIFT ^7e"
+msgstr "Use ^3CTRL ^7para desligar o teste de colisão, e ^3SHIFT ^7e"
#: qcsrc/client/hud/panel/infomessages.qc:228
msgid "^3ALT ^7+ ^3ARROW KEYS ^7for fine adjustments."
#: qcsrc/client/hud/panel/quickmenu.qc:816
msgid "QMCMD^dropped weapon %w^7 (l:%l^7)"
-msgstr "Arma solta %w^7 (l:%l^7)"
+msgstr "Arma largada %w^7 (l:%l^7)"
#: qcsrc/client/hud/panel/quickmenu.qc:817
msgid "QMCMD^drop flag/key, icon"
#: qcsrc/client/hud/panel/quickmenu.qc:821
msgid "QMCMD^Send private message to"
-msgstr "Mandar mensagem privada para"
+msgstr "Enviar mensagem privada para"
#: qcsrc/client/hud/panel/quickmenu.qc:823
#: qcsrc/client/hud/panel/quickmenu.qc:860
#: qcsrc/client/hud/panel/quickmenu.qc:824
#: qcsrc/client/hud/panel/quickmenu.qc:831
msgid "QMCMD^View/HUD settings"
-msgstr "Configurações de Exibição/HUD"
+msgstr "Configurações de exibição/interface"
#: qcsrc/client/hud/panel/quickmenu.qc:825
msgid "QMCMD^3rd person view"
#: qcsrc/client/hud/panel/quickmenu.qc:851
msgid "QMCMD^Decrease speed"
-msgstr "Diminuir velocidade"
+msgstr "Reduzir velocidade"
#: qcsrc/client/hud/panel/quickmenu.qc:852
msgid "QMCMD^Wall collision off"
msgid ""
"You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"
msgstr ""
-"Você pode modificar o placar usando o comando ^2scoreboard_columns_set.\n"
+"É possível modificar o placar usando o comando ^2scoreboard_columns_set.\n"
#: qcsrc/client/hud/panel/scoreboard.qc:296
msgid "^3|---------------------------------------------------------------|\n"
#: qcsrc/client/hud/panel/scoreboard.qc:299
msgid "^2scoreboard_columns_set ^7field1 field2 ...\n"
-msgstr "^2scoreboard_columns_set ^7campo1 campo2...\n"
+msgstr "^2scoreboard_columns_set ^7field1 field2...\n"
#: qcsrc/client/hud/panel/scoreboard.qc:300
msgid "The following field names are recognized (case insensitive):\n"
msgstr ""
-"Os seguintes nomes de campos são reconhecidos (maiúsculas/minúsculas não são "
-"distintas):\n"
+"Os seguintes nomes de campo são reconhecidos (maiúsculas e minúsculas não "
+"diferem):\n"
#: qcsrc/client/hud/panel/scoreboard.qc:301
msgid "You can use a ^3|^7 to start the right-aligned fields.\n"
#: qcsrc/client/hud/panel/scoreboard.qc:308
msgid "^3kills^7 Number of kills\n"
-msgstr "^3vítimas^7 Número de aniquilações\n"
+msgstr "^3vítimas^7 Número de vítimas\n"
#: qcsrc/client/hud/panel/scoreboard.qc:309
msgid "^3deaths^7 Number of deaths\n"
#: qcsrc/client/hud/panel/scoreboard.qc:319
msgid "^3fckills^7 Number of flag carrier kills\n"
-msgstr ""
-"^3pbndvítimas^7 Número de portadores de bandeiras mortos pelo jogador\n"
+msgstr "^3pbndvítimas^7 Número de portadores de bandeiras aniquilados\n"
#: qcsrc/client/hud/panel/scoreboard.qc:320
msgid "^3returns^7 Number of flag returns\n"
#: qcsrc/client/hud/panel/scoreboard.qc:321
msgid "^3drops^7 Number of flag drops\n"
-msgstr "^3quedas^7 Número de bandeiras que foram soltas\n"
+msgstr "^3quedas^7 Número de bandeiras largadas\n"
#: qcsrc/client/hud/panel/scoreboard.qc:322
msgid "^3lives^7 Number of lives (LMS)\n"
#: qcsrc/client/hud/panel/scoreboard.qc:324
msgid "^3pushes^7 Number of players pushed into void\n"
-msgstr "^3empurrões^7 Número de jogadores empurrados para fora do mapa\n"
+msgstr "^3empurrões^7 Número de jogadores empurrados para o vazio\n"
#: qcsrc/client/hud/panel/scoreboard.qc:325
msgid ""
"^3destroyed^7 Number of keys destroyed by pushing them into "
"void\n"
msgstr ""
-"^3destruídas^7 Número de chaves destruídas ao empurrá-las para fora do mapa\n"
+"^3destruídas^7 Número de chaves destruídas ao empurrá-las para o vazio\n"
#: qcsrc/client/hud/panel/scoreboard.qc:326
msgid "^3kckills^7 Number of keys carrier kills\n"
-msgstr "^3pcvítimas^7 Número de portadores de chaves mortos pelo jogador\n"
+msgstr "^3pcvítimas^7 Número de portadores de chaves aniquilados\n"
#: qcsrc/client/hud/panel/scoreboard.qc:327
msgid "^3losses^7 Number of times a key was lost\n"
#: qcsrc/client/hud/panel/scoreboard.qc:329
msgid "^3time^7 Total time raced (race/cts)\n"
-msgstr "^3tempo^7 Tempo total de corrida (corrida/cts)\n"
+msgstr "^3tempo^7 Tempo total em corridas (corrida/cts)\n"
#: qcsrc/client/hud/panel/scoreboard.qc:330
msgid "^3fastest^7 Time of fastest lap (race/cts)\n"
#: qcsrc/client/hud/panel/scoreboard.qc:333
msgid "^3bckills^7 Number of ball carrier kills\n"
-msgstr "^3pblvítimas^7 Número de portadores de bolas mortos pelo jogador\n"
+msgstr "^3pblvítimas^7 Número de portadores de bolas aniquilados\n"
#: qcsrc/client/hud/panel/scoreboard.qc:334
msgid ""
#: qcsrc/client/hud/panel/vote.qc:24
msgid "^1You must answer before entering hud configure mode\n"
msgstr ""
-"^1Você tem que responder antes de entrar no modo de configuração do HUD\n"
+"^1Você tem que responder antes de entrar no modo de configuração da "
+"interface\n"
#: qcsrc/client/hud/panel/vote.qc:29
msgid "^2Name ^7instead of \"^1Anonymous player^7\" in stats"
#: qcsrc/client/hud/panel/vote.qc:121
msgid "^1Configure the HUD"
-msgstr "^1Configurar o HUD"
+msgstr "^1Configurar a interface"
#: qcsrc/client/hud/panel/vote.qc:125 qcsrc/menu/xonotic/dialog_firstrun.qc:82
#: qcsrc/menu/xonotic/dialog_multiplayer_media_demo_startconfirm.qc:18
#: qcsrc/menu/xonotic/dialog_hudsetup_exit.qc:19
msgid "HUD skins"
-msgstr "Visuais de HUD"
+msgstr "Visuais de interface"
#: qcsrc/menu/xonotic/dialog_hudsetup_exit.qc:22
#: qcsrc/menu/xonotic/dialog_multiplayer_create.qc:196
#: qcsrc/menu/xonotic/dialog_hudsetup_exit.qc:93
msgid "HUD Dock:"
-msgstr "Camada do HUD:"
+msgstr "Camada da interface:"
#: qcsrc/menu/xonotic/dialog_hudsetup_exit.qc:95
msgid "DOCK^Disabled"
#: qcsrc/menu/xonotic/dialog_hudsetup_exit.qh:6
msgid "Panel HUD Setup"
-msgstr "Painel de Configuração do HUD"
+msgstr "Painel de configuração da interface"
#: qcsrc/menu/xonotic/dialog_monstertools.qc:13
msgid "Monster:"
#: qcsrc/menu/xonotic/dialog_settings_game_hud.qc:160
msgid "Dynamic HUD"
-msgstr "HUD dinâmico"
+msgstr "Interface dinâmica"
#: qcsrc/menu/xonotic/dialog_settings_game_hud.qc:161
msgid "HUD moves around following player's movement"
-msgstr "O HUD se move de acordo com o movimento do jogador"
+msgstr "A interface se move de acordo com o movimento do jogador"
#: qcsrc/menu/xonotic/dialog_settings_game_hud.qc:163
msgid "Shake the HUD when hurt"
-msgstr "Vibrar o HUD ao ser atingido"
+msgstr "Vibrar a interface ao receber dano"
#: qcsrc/menu/xonotic/dialog_settings_game_hud.qc:167
#: qcsrc/menu/xonotic/dialog_settings_game_hudconfirm.qh:6
msgid "Enter HUD editor"
-msgstr "Entrar no editor do HUD"
+msgstr "Entrar no editor de interface"
#: qcsrc/menu/xonotic/dialog_settings_game_hud.qh:7
msgid "HUD"
-msgstr "HUD"
+msgstr "Interface"
#: qcsrc/menu/xonotic/dialog_settings_game_hudconfirm.qc:21
msgid "In order for the HUD editor to show, you must first be in game."
-msgstr "Para o editor do HUD aparecer, é necessário estar jogando em um mapa."
+msgstr ""
+"Para abrir o editor de interface, é necessário estar jogando em um mapa."
#: qcsrc/menu/xonotic/dialog_settings_game_hudconfirm.qc:23
msgid "Do you wish to start a local game to set up the HUD?"
-msgstr "Quer iniciar um jogo local para personalizar o HUD?"
+msgstr "Quer iniciar um jogo local para personalizar a interface?"
#: qcsrc/menu/xonotic/dialog_settings_game_messages.qc:24
msgid "Frag Information"
#: qcsrc/menu/xonotic/dialog_settings_game_messages.qc:101
msgid "Killstreak sounds"
-msgstr "Sons de sequências de mortes"
+msgstr "Sons de sequência de mortes"
#: qcsrc/menu/xonotic/dialog_settings_game_messages.qc:104
msgid "Achievement sounds"
# adem4ik, 2014
# Alex Talker <alextalker7@gmail.com>, 2014-2015
# Andrei Stepanov, 2014
-# Andrei Stepanov, 2014-2018
+# Andrei Stepanov <adem4ik@gmail.com>, 2014-2018
# Andrey P <andrey.pyntikov@gmail.com>, 2016
# Artem Vorotnikov <artem@vorotnikov.me>, 2015
# Lord Canistra <lordcanistra@gmail.com>, 2011
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-07-09 00:35+0200\n"
"PO-Revision-Date: 2018-03-16 06:43+0000\n"
-"Last-Translator: Andrei Stepanov\n"
+"Last-Translator: Andrei Stepanov <adem4ik@gmail.com>\n"
"Language-Team: Russian (http://www.transifex.com/team-xonotic/xonotic/"
"language/ru/)\n"
"Language: ru\n"
"Project-Id-Version: Xonotic\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-07-09 00:35+0200\n"
-"PO-Revision-Date: 2018-06-02 05:39+0000\n"
+"PO-Revision-Date: 2018-09-02 02:29+0000\n"
"Last-Translator: Losier Blackheath <losier.cc@gmail.com>\n"
"Language-Team: Chinese (China) (http://www.transifex.com/team-xonotic/"
"xonotic/language/zh_CN/)\n"
#: qcsrc/client/hud/panel/infomessages.qc:227
msgid "^3CTRL ^7to disable collision testing, ^3SHIFT ^7and"
-msgstr ""
+msgstr "^3CTRL ^7以禁用碰撞检测, ^3SHIFT ^7以及"
#: qcsrc/client/hud/panel/infomessages.qc:228
msgid "^3ALT ^7+ ^3ARROW KEYS ^7for fine adjustments."
#: qcsrc/client/hud/panel/quickmenu.qc:805
msgid "QMCMD^took item (l:%l^7)"
-msgstr ""
+msgstr "QMCMD^捡起物品 (l:%l^7)"
#: qcsrc/client/hud/panel/quickmenu.qc:805
msgid "QMCMD^took item, icon"
#: qcsrc/client/hud/panel/quickmenu.qc:852
msgid "QMCMD^Wall collision off"
-msgstr ""
+msgstr "QMCMD^关闭墙壁碰撞"
#: qcsrc/client/hud/panel/quickmenu.qc:853
msgid "QMCMD^Wall collision on"
-msgstr ""
+msgstr "QMCMD^开启墙壁碰撞"
#: qcsrc/client/hud/panel/quickmenu.qc:857
msgid "QMCMD^Fullscreen"
#: qcsrc/client/hud/panel/scoreboard.qc:315
msgid "^3sum^7 frags - deaths\n"
-msgstr ""
+msgstr "^3sum^7 击杀数 - 死亡数\n"
#: qcsrc/client/hud/panel/scoreboard.qc:316
msgid ""
"^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a "
"ball (Keepaway) was picked up\n"
msgstr ""
+"^3pickups^7 旗帜 (CTF) 或钥匙 (KeyHunt) 或球 (Keepaway) 被捡"
+"起来\n"
#: qcsrc/client/hud/panel/scoreboard.qc:318
msgid "^3captime^7 Time of fastest cap (CTF)\n"
#: qcsrc/client/hud/panel/scoreboard.qc:1584
#, c-format
msgid "Speed award: %d%s ^7(%s^7)"
-msgstr ""
+msgstr "速度奖励: %d%s ^7(%s^7)"
#: qcsrc/client/hud/panel/scoreboard.qc:1588
#, c-format
#: qcsrc/common/notifications/all.inc:559
msgid "^BGYou captured the flag!"
-msgstr ""
+msgstr "^BG你捡到了旗帜!"
#: qcsrc/common/notifications/all.inc:560
#, c-format
#: qcsrc/common/notifications/all.inc:568
#, c-format
msgid "^BGYou passed the flag to %s"
-msgstr ""
+msgstr "^BG你把旗帜交给了 %s"
#: qcsrc/common/notifications/all.inc:569
msgid "^BGYou got the ^TC^TT^BG flag!"
velocityjitter 256 256 256
velocityoffset 0 0 80
effect tr_bullet
- type spark
- alpha 256 256 2560
- color 0xff8960 0xff8533
- size 4 4
- stretchfactor 0.200000
- tex 70 70
- trailspacing 750
- velocitymultiplier 3
+ type beam
+ alpha 500 600 10000
+ color 0xf03000 0xff6010
+ countabsolute 1
+ sizeincrease -3
+ size 0.6 0.8
+ tex 200 200
+effect tr_bullet
+ type smoke
+ airfriction -4
+ alpha 256 256 350
+ color 0x202020 0x404040
+ notunderwater
+ sizeincrease 0.400000
+ size 1 2
+ tex 0 8
+ trailspacing 16
+ velocityjitter 4 4 4
+effect tr_bullet
+ type bubble
+ alpha 256 256 128
+ bounce 1.500000
+ color 0x404040 0x808080
+ gravity -0.125000
+ liquidfriction 4
+ size 0.5 0.6
+ tex 62 62
+ trailspacing 16
+ underwater
+ velocityjitter 16 16 16
effect smoking_smallemitter
type alphastatic
airfriction -1
tex 40 40
velocityjitter 224 224 224
velocityoffset 0 0 80
+effect tr_bullet_weak
+ type beam
+ alpha 75 100 3000
+ color 0xf03000 0xff6010
+ countabsolute 1
+ sizeincrease -3
+ size 0.6 0.8
+ tex 200 200
+effect tr_bullet_weak
+ type smoke
+ airfriction -4
+ alpha 256 256 350
+ color 0x202020 0x404040
+ notunderwater
+ sizeincrease 0.400000
+ size 1 2
+ tex 0 8
+ trailspacing 16
+ velocityjitter 4 4 4
+effect tr_bullet_weak
+ type bubble
+ alpha 256 256 128
+ bounce 1.500000
+ color 0x404040 0x808080
+ gravity -0.125000
+ liquidfriction 4
+ size 0.5 0.6
+ tex 62 62
+ trailspacing 32
+ underwater
+ velocityjitter 16 16 16
alias cl_hook_gamestart_ka
alias cl_hook_gamestart_ft
alias cl_hook_gamestart_inv
+alias cl_hook_gamestart_duel
alias cl_hook_gameend "rpn /cl_matchcount dup load 1 + =" // increase match count every time a game ends
alias cl_hook_shutdown
alias cl_hook_activeweapon
alias sv_hook_gamestart_ka
alias sv_hook_gamestart_ft
alias sv_hook_gamestart_inv
+alias sv_hook_gamestart_duel
alias sv_hook_gamerestart
alias sv_hook_gameend
// These are called when the mode is switched via gametype vote screen,
// earlier than gamestart hooks (useful for enabling per-gamemode mutators)
// The _all hook is called before the specific one
-// here it sets g_maxplayers to undo what duel does
-alias sv_vote_gametype_hook_all "set g_maxplayers 0"
+alias sv_vote_gametype_hook_all
alias sv_vote_gametype_hook_as
alias sv_vote_gametype_hook_ca
alias sv_vote_gametype_hook_ctf
alias sv_vote_gametype_hook_ons
alias sv_vote_gametype_hook_rc
alias sv_vote_gametype_hook_tdm
+alias sv_vote_gametype_hook_duel
-// Preset to allow duel to be used for the gametype voting screen
+// Example preset to allow 1v1ctf to be used for the gametype voting screen
// sv_vote_gametype_*_type Must be set to the name of the gametype the option is based on
// sv_vote_gametype_*_name Contains a human-readable name of the gametype
// sv_vote_gametype_*_description Contains a longer description
-set sv_vote_gametype_duel_type dm
-set sv_vote_gametype_duel_name Duel
-set sv_vote_gametype_duel_description "One vs One match"
-alias sv_vote_gametype_hook_duel "set g_maxplayers 2"
+//set sv_vote_gametype_1v1ctf_type ctf
+//set sv_vote_gametype_1v1ctf_name "Capture the Flag Duel"
+//set sv_vote_gametype_1v1ctf_description "One vs One match in CTF"
+//alias sv_vote_gametype_hook_all "set g_maxplayers 0"
+//alias sv_vote_gametype_hook_1v1ctf "set g_maxplayers 2"
// ===========
set g_inv_respawn_delay_max 0
set g_inv_respawn_waves 0
set g_inv_weapon_stay 0
+set g_duel_respawn_delay_small 0
+set g_duel_respawn_delay_small_count 0
+set g_duel_respawn_delay_large 0
+set g_duel_respawn_delay_large_count 0
+set g_duel_respawn_delay_max 0
+set g_duel_respawn_waves 0
+set g_duel_weapon_stay 0
// =========
set g_cts_selfdamage 1 "0 = disable all selfdamage and falldamage in cts"
set g_cts_finish_kill_delay 10 "prevent cheating by running back to the start line, and starting out with more speed than otherwise possible"
set g_cts_send_rankings_cnt 15 "send this number of map records to clients"
+set g_cts_removeprojectiles 0 "remove projectiles when the player dies, to prevent using weapons earlier in the stage than intended"
// ==========================
// deathmatch (ffa or team)
// ==========================
set g_dm 1 "Deathmatch: killing any other player is one frag, player with most frags wins"
+set g_tdm 0 "Team Deathmatch: the team who kills their opponents most often wins"
+set g_tdm_on_dm_maps 0 "when this is set, all DM maps automatically support TDM"
set g_tdm_teams 2 "how many teams are in team deathmatch (set by mapinfo)"
set g_tdm_team_spawns 0 "when 1, players spawn from the team spawnpoints of the map, if any"
seta g_tdm_teams_override 0 "how many teams are in team deathmatch"
set g_invasion_teams 0 "number of teams in invasion (note: use mapinfo to set this)"
set g_invasion_team_spawns 1 "use team spawns in teamplay invasion mode"
set g_invasion_type 0 "type of invasion mode - 0: round-based, 1: hunting, 2: complete the stage (note: use mapinfo to set this)"
+
+// ======
+// duel
+// ======
+set g_duel 0 "Duel: frag the opponent more in a one versus one arena battle"
+//set g_duel_warmup 180 "Have a short warmup period before beginning the actual duel"
+set g_duel_with_powerups 0 "Enable powerups to spawn in the duel gamemode"
ALIGN_BACKGROUND c5h5
ALIGN_BACKGROUND_INGAME c5h5
ALPHA_BACKGROUND_INGAME 1
-ALPHA_DISABLED 0.2
+ALPHA_DISABLED 0.25
ALPHA_BEHIND 0.5
// button
POSITION_DIALOG_QUIT '1.05 1.2 0'
// font
-ALPHA_TEXT 0.8
+ALPHA_TEXT 0.875
COLOR_TEXT '0.96 0.99 1'
ALPHA_HEADER 0.5
COLOR_HEADER '0.96 0.99 1'
ALPHA_LISTBOX_BACKGROUND 0.5
COLOR_LISTBOX_BACKGROUND '0 0 0'
ALPHA_LISTBOX_SELECTED 1
-COLOR_LISTBOX_SELECTED '0.97 0.56 0.27'
+COLOR_LISTBOX_SELECTED '0.9 0.53 0.28'
ALPHA_LISTBOX_WAITING 0.8
COLOR_LISTBOX_WAITING '0.73 0.82 0.9'
ALPHA_LISTBOX_FOCUSED 0.55
// tooltip
ALPHA_TOOLTIP 0.8
-COLOR_TOOLTIP '1 0.97 0.94'
+COLOR_TOOLTIP '1 0.97 0.95'
AVOID_TOOLTIP '8 8 0'
BORDER_TOOLTIP '16 16 0'
MARGIN_TOOLTIP '10 8 0'
--- /dev/null
+set help_msg_0 "Wondering why you die so often? Because you're ignoring armor and health pickups"
+set help_msg_1 "Use secondary fire (blaster) on the floor to jump higher"
+set help_msg_2 "Double press W after spawning to accelerate by jumping forward (=dodging)"
+set help_msg_3 "Keep the jump key pressed to stay fast (=bunny-hopping)"
+set help_msg_4 "Run along a flat wall and quickly press W twice to gain speed (=wall dodging)"
+set help_msg_5 "Pick up armor shards from dead enemies to survive the next hit"
+set help_msg_6 "Stand *completely* still, then quickly double press W, A, S or D to dodge"
+set help_msg_7 "Use the blaster to make large jumps or climb walls"
+set help_msg_8 "Use the Shotgun (or Machine Gun) to slow down fast players"
+set help_msg_9 "When running, blaster the floor or walls to gain more speed"
+set help_msg_10 "Use G (default key) to throw Nades (you want the 'dropweapon' bind, not 'hook', for better timing)"
+set help_msg_11 "Don't reload, switch to Shotgun"
+set help_msg_12 "You can blaster the flag or dropped Nades to push them away"
+set help_msg_13 "This is how most pros throw Nades: press G (dropweapon bind), wait *several* seconds, press G again"
+set help_msg_14 "Dodge forward to climb walls faster"
+set help_msg_15 "Double press W, then hold space to start moving"
+set help_msg_16 "Spectate stronger players to learn their tricks"
+set help_msg_count 17 // update this when adding messages - it should be the number of messages (which means last message index + 1)
--- /dev/null
+set help_msg_0 "Big Admin is watching you, so please be friendly or feel their almighty ban-hammer!"
+set help_msg_1 "If you want to learn more about Xonotic, read ^1'Halogene's Newbie Corner' (https://xonotic.org/guide) ${help_cfg_prefix}as it contains lots of useful tips and tricks, explains all the weapons and helps to improve your gameplay."
+set help_msg_2 "Please watch out for balanced teams and change by pressing F5 (teammenu) or F6 (auto join 'best' team)."
+set help_msg_3 "When trying to bunny-hop you can ^1hold the jump button ${help_cfg_prefix}while you are still in the air, this will make those jumps VERY easy to time and work more reliable."
+set help_msg_4 "When a vote is called you can accept it via F1 or reject it via F2 (default keys)."
+set help_msg_5 "Spectating other (good) players helps to learn new tricks. To spectate press F3 and then Mouse1 to switch between the players you want to spectate. F5, F6 or jump will get you back into the game (default keys)."
+set help_msg_6 "Being a beginner is great! You can learn so many new tricks and improve quickly. Watch others, ask for advice and use your common sense effectively."
+set help_msg_7 "If others are better than you, it does not mean they cheat. Save such complaints for when you have more experience and know what kind of funky stuff is possible."
+set help_msg_8 "In CTF, it's good to move around and get involved in the action. You get fragged quite a bit but you are also most helpful to your team."
+set help_msg_9 "Use the radar to see where your teammates are. Pressing zoom will expand the radar image to give you a better overview."
+set help_msg_10 "Most teammessages display waypoints by default. Use those to guide your teammates. You can also see them and the flagcarrier in the radar."
+set help_msg_11 "Protect your flagcarrier at all cost! Also save health and armor for him, he might need them more than you!"
+set help_msg_12 "You can use the Blaster and most explosive weapons to jump around. Just look 'at your feet' and press fire. If you also jump at the same time, you get even higher."
+set help_msg_13 "Be friendly and helpful to other players! Being angry at others' mistakes is understandable, but nobody is perfect. Try to use calm words when telling them how to correct their mistake."
+set help_msg_14 "You can use the zoom key with all guns, only the Vortex has it as a extra function on Mouse2 (default key)."
+set help_msg_15 "Notice what is happening around you! If your base is empty in CTF, then STAY and defend the flag! Make sure someone defends the flagcarrier or assist him yourself."
+set help_msg_16 "You can drop the weapon you currently have with Backspace (default key). You can help your unarmed teammates this way."
+set help_msg_17 "Learn to use the team messages! You find them in the Setting/Input menu. Try changing them to keys you can press easier than the defaults."
+set help_msg_18 "Gaming should be fun! Try to have a nice time, be helpful, mindful and treat others like you want to be treated."
+set help_msg_19 "Visit the official forum on ^1https://forums.xonotic.org/ ${help_cfg_prefix}and feel free to open a thread if you have questions."
+set help_msg_20 "If you already have a good weapon, it's a great idea to let your teammates get something better than the Shotgun too!"
+set help_msg_21 "Press T to chat with others, press Y for messages to your team only, press TAB to see the scores and U for the chat history (default keys)."
+set help_msg_22 "You can use ^1'suggestmap PART_OF_NAME' ${help_cfg_prefix}to make a map come up at the vote screen after a map was played."
+set help_msg_23 "The console is accessible through the ~ key or by pressing Shift+ESC. It has many more advanced features, use 'cmdlist' and 'cvarlist' to get a full list of available commands/settings."
+set help_msg_24 "The Blaster is a useful tool for gaining speed and jumping around, but it does little damage."
+set help_msg_25 "The Shotgun's primary firemode has spread and is more useful at a closer distance, use secondary for the melee attack and slap into other players faces!"
+set help_msg_26 "The Machine Gun secondary has a burst fire mode and less spread than the primary mode."
+set help_msg_27 "The Mortar is a good all around gun but takes some practice to aim it, because of the projectile's curve."
+set help_msg_28 "The Electro has a combo attack. Fire the primary mode at the balls from the secondary mode for a huge and powerful explosion."
+set help_msg_29 "The Crylink's primary fire bounces. Both firemodes pull your enemies, making it a great tool to stop a flag carrier."
+set help_msg_30 "The Vortex is a powerful sniper gun. Aim carefully!"
+set help_msg_31 "The Hagar is underestimated, but very powerful if you aim right. The secondary mode charges up to four rockets and causes devasting damage if you release them."
+set help_msg_32 "The Devastator is powerful but slow. Keep Mouse1 pressed to guide the rockets. Secondary firemode makes the rocket(s) explode."
+set help_msg_33 "The Arc is a strong lighting beam, which bends slighty if you move your mouse. The secondary firemode is very strong but short. Both firemodes also heal teammates if you shoot at them."
+set help_msg_34 "By default, explosions go through walls. Be careful when hiding behind walls!"
+set help_msg_35 "Get on IRC to chat with fellow players. Take a look at ^1https://xonotic.org/chat/${help_cfg_prefix}."
+set help_msg_36 "Don't drink and frag."
+set help_msg_37 "Don't shoot at players who are typing/chatting. You recognize those players by the keyboard symbols above their head."
+set help_msg_38 "'gg' is shorthand for 'Good Game', 'gl' means 'Good luck' and 'hf' 'Have fun'."
+set help_msg_39 "Players with the prefix '$bot_prefix${help_cfg_prefix}' in their nick are bots on this server. There is also a clan named [BOT]."
+set help_msg_40 "You spawn with ^1two ${help_cfg_prefix}weapons. Use the Blaster for much faster movement."
+set help_msg_41 "Visit the stats page at ^1https://stats.xonotic.org/ ${help_cfg_prefix}and check out how you are doing in the rankings!"
+set help_msg_42 "Start playing 1on1 if you want to learn fast"
+set help_msg_43 "Visit ^1https://xonotic.org/pickup/ ${help_cfg_prefix}to get in touch with the experienced players, ask them stuff and play with them!"
+set help_msg_44 "Look for servers that have a good ping for you. You can't play this game well with a ping > 100. If there are no servers close enough to you, cooperate with your friends to setup one."
+set help_msg_45 "If you want to play the next map you can cast a vote via 'vcall endmatch' in the console. Other players can vote using F1 and F2 (default keys)."
+set help_msg_46 "Always watch your back. Do not just run away, fight back as you retreat. Otherwise, you could be shot in the back."
+set help_msg_47 "Try to get as much armor and health as you can, but remember your teammates need armor and health too."
+set help_msg_48 "Do not attack if you have neither a good weapon, nor health, nor armor."
+set help_msg_49 "Standing still makes you an easy target. You can move around the map faster by bunny hopping."
+set help_msg_50 "You can use the Blaster to climb up walls. Before trying this, become familiar with the basics of Blaster movement."
+set help_msg_51 "You can control your movement in air. Use it to prevent yourself from falling off the map when somebody starts pushing you around."
+set help_msg_52 "Use the Blaster, Mortar or Devastator in space maps to push other players off the map. They will enjoy it."
+set help_msg_53 "You can turn off automatic weapon changing in the Player menu. If you configure your key bindings, manually switching weapons can be faster and easier."
+set help_msg_54 "Choose the right weapon for the job, not just the one that the game automatically puts in your hand."
+set help_msg_55 "Enter ^1'lsmaps' ${help_cfg_prefix}in the console to get a list of maps configured on the server."
+set help_msg_56 "While you are in the air, release the forward key, press one of the left/right keys and move your mouse in the same direction. This will bend your fly/jump path."
+set help_msg_57 "Hold the jump key to keep on jumping (called ^1'bunny-hopping'${help_cfg_prefix}) which will accelerate your speed."
+set help_msg_58 "'xonotic.org/guide changed my life' -- ^x777S^x090Є^x088Є^x000Қ^x900⁻ʸ${help_cfg_prefix}, 2018"
+set help_msg_count 59 // update this when adding messages - it should be the number of messages (which means last message index + 1)
--- /dev/null
+// Simple help message system
+// It prints messages with the configured name
+
+// You can start the help system with the command help_loop but this has been found to annoy players.
+// A better way is to put `alias sv_hook_gamestart_all "defer 20 help_next"` into your server.cfg,
+// that way you get one message per match.
+
+// the messages need to be starting from 0 and be consecutive
+// for manual use: help_inc switches to the next message, help_doit will print the current message, help_next will do both together
+
+// settings
+set help_cfg_nick "^2Help System^3" "the messages will appear in chat coming from the sever using this name"
+set help_cfg_time 5 "the time between two messages in seconds when started using help_loop"
+set help_cfg_prefix "^2" "prepended to each message, useful to color the nick and message differently"
+
+// aliases making up the actual helpsystem
+set help_tmp_index -1 // -1 since we first increment, then show it
+alias help_say "set help_tmp_oldnick \"$sv_adminnick\"; set sv_adminnick \"$help_cfg_nick\"; say \"$*\"; help_say2"
+alias help_say2 "set sv_adminnick \"$help_tmp_oldnick\""
+alias help_doit "sv_cmd rpn /help_tmp_msg help_msg_$help_tmp_index def; help_doit2"
+alias help_doit2 "help_say $help_cfg_prefix$help_tmp_msg"
+alias help_inc "sv_cmd rpn /help_tmp_index help_tmp_index 1 add $help_msg_count mod def"
+alias help_next "help_inc; help_doit" // increment first - if the ruleset changed, the number of tips could have too, this avoids overflow
+alias help_loop "help_next; defer $help_cfg_time help_loop"
fr "French" "Français" 99%
ga "Irish" "Irish" 35%
it "Italian" "Italiano" 99%
-hu "Hungarian" "Magyar" 55%
+hu "Hungarian" "Magyar" 57%
nl "Dutch" "Nederlands" 70%
pl "Polish" "Polski" 81%
pt "Portuguese" "Português" 98%
ru "Russian" "Русский" 99%
sr "Serbian" "Српски" 71%
uk "Ukrainian" "Українська" 57%
-zh_CN "Chinese (China)" "中文" 62%
+zh_CN "Chinese (China)" "中文" 63%
zh_TW "Chinese (Taiwan)" "國語" 68%
ko "Korean" "한국의" 33%
-1 8 20 0 // fire
-9 5 20 0 // fire2
-15 200 20 1 // idle
-215 40 20 0 // reload
+/*
+Generated framegroups file for h_crylink
+Used by DarkPlaces to simulate frame groups in DPM models.
+*/
+
+1 26 30 0 // h_crylink fire
+27 26 30 0 // h_crylink fire
+53 101 3 1 // h_crylink idle
+154 101 3 1 // h_crylink idle
-1 8 20 0 // fire
-9 5 20 0 // fire2
-15 200 20 1 // idle
-215 40 20 0 // reload
+/*
+Generated framegroups file for h_electro
+Used by DarkPlaces to simulate frame groups in DPM models.
+*/
+
+1 36 30 0 // h_electro fire
+37 36 30 0 // h_electro fire
+73 101 3 1 // h_electro idle
+174 101 3 1 // h_electro idle
seta notification_INFO_ITEM_WEAPON_UNAVAILABLE "0" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
seta notification_INFO_JETPACK_NOFUEL "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
seta notification_INFO_JOIN_CONNECT "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
-seta notification_INFO_JOIN_CONNECT_TEAM "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
seta notification_INFO_JOIN_PLAY "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
seta notification_INFO_JOIN_PLAY_TEAM "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
seta notification_INFO_KEEPAWAY_DROPPED "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
seta notification_INFO_QUIT_DISCONNECT "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
seta notification_INFO_QUIT_KICK_IDLING "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
seta notification_INFO_QUIT_KICK_SPECTATING "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
-seta notification_INFO_QUIT_SPECTATE "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
+seta notification_INFO_QUIT_SPECTATE "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
seta notification_INFO_RACE_ABANDONED "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
seta notification_INFO_RACE_FAIL_RANKED "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
seta notification_INFO_RACE_FAIL_UNRANKED "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
seta notification_INFO_SUPERWEAPON_PICKUP "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
seta notification_INFO_TEAMCHANGE_LARGERTEAM "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
seta notification_INFO_TEAMCHANGE_NOTALLOWED "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
-seta notification_INFO_VERSION_BETA "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
+seta notification_INFO_VERSION_BETA "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
seta notification_INFO_VERSION_OLD "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
seta notification_INFO_VERSION_OUTDATED "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
seta notification_INFO_WATERMARK "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
seta notification_WEAPON_VORTEX_MURDER "1" "Enable this multiple notification"
// MSG_CHOICE notifications (count = 28):
-seta notification_CHOICE_CTF_CAPTURE_BROKEN "1" "Choice for this notification 0 = off, 1 = default message, 2 = verbose message"
+seta notification_CHOICE_CTF_CAPTURE_BROKEN "2" "Choice for this notification 0 = off, 1 = default message, 2 = verbose message"
seta notification_CHOICE_CTF_CAPTURE_BROKEN_ALLOWED "2" "Allow choice for this notification 0 = off, 1 = only in warmup mode, 2 = always"
-seta notification_CHOICE_CTF_CAPTURE_TIME "1" "Choice for this notification 0 = off, 1 = default message, 2 = verbose message"
+seta notification_CHOICE_CTF_CAPTURE_TIME "2" "Choice for this notification 0 = off, 1 = default message, 2 = verbose message"
seta notification_CHOICE_CTF_CAPTURE_TIME_ALLOWED "2" "Allow choice for this notification 0 = off, 1 = only in warmup mode, 2 = always"
-seta notification_CHOICE_CTF_CAPTURE_UNBROKEN "1" "Choice for this notification 0 = off, 1 = default message, 2 = verbose message"
+seta notification_CHOICE_CTF_CAPTURE_UNBROKEN "2" "Choice for this notification 0 = off, 1 = default message, 2 = verbose message"
seta notification_CHOICE_CTF_CAPTURE_UNBROKEN_ALLOWED "2" "Allow choice for this notification 0 = off, 1 = only in warmup mode, 2 = always"
seta notification_CHOICE_CTF_PICKUP_ENEMY "1" "Choice for this notification 0 = off, 1 = default message, 2 = verbose message"
seta notification_CHOICE_CTF_PICKUP_ENEMY_ALLOWED "2" "Allow choice for this notification 0 = off, 1 = only in warmup mode, 2 = always"
float autocvar_cl_effects_lightningarc_drift_start;
float autocvar_cl_effects_lightningarc_segmentlength;
bool autocvar_cl_effects_lightningarc_simple;
-int autocvar_cl_gentle;
+bool autocvar_cl_gentle;
int autocvar_cl_gentle_damage;
int autocvar_cl_gentle_gibs;
int autocvar_cl_gentle_messages;
float autocvar_cl_gibs_damageforcescale = 3.5;
float autocvar_cl_gibs_lifetime = 14;
-float autocvar_cl_gibs_maxcount = 100;
+int autocvar_cl_gibs_maxcount = 100;
bool autocvar_cl_gibs_sloppy = 1;
float autocvar_cl_gibs_ticrate = 0.1;
float autocvar_cl_gibs_velocity_random = 1;
float autocvar_menu_mouse_speed;
string autocvar_menu_skin;
int autocvar_r_fakelight;
-int autocvar_r_fullbright;
+bool autocvar_r_fullbright;
float autocvar_r_letterbox;
string autocvar_scoreboard_columns;
bool autocvar_v_flipped;
-float autocvar_vid_conheight;
-float autocvar_vid_conwidth;
+int autocvar_vid_conheight;
+int autocvar_vid_conwidth;
float autocvar_vid_pixelheight;
float autocvar_viewsize;
bool autocvar_cl_eventchase_vehicle = 1;
float autocvar_cl_hitsound_max_pitch = 1.5;
float autocvar_cl_hitsound_nom_damage = 25;
float autocvar_cl_hitsound_antispam_time;
+int autocvar_cl_eventchase_spectated_change = 1;
+float autocvar_cl_eventchase_spectated_change_time = 1;
int autocvar_cl_eventchase_death = 1;
float autocvar_cl_eventchase_distance = 140;
bool autocvar_cl_eventchase_frozen = false;
return;
this.csqcmodel_predraw_run = framecount;
- if(!this.modelindex || this.model == "null")
+ if(!this.modelindex || this.model == "null" || this.alpha < 0)
{
this.drawmask = 0;
return;
else
this.drawmask = MASK_NORMAL;
- if(this.isplayermodel) // this checks if it's a player MODEL!
+ if(this.isplayermodel && this.drawmask) // this checks if it's a player MODEL!
{
CSQCPlayer_ModelAppearance_Apply(this, this.entnum == player_localnum + 1);
CSQCPlayer_LOD_Apply(this);
float scoreboard_showscores;
float scoreboard_showaccuracy;
.string message;
-.int renderflags;
+.float renderflags;
// float coop;
// float deathmatch;
// Darkplaces Render Modifications
#if 0
.float alpha;
-.float renderflags;
.vector colormod;
.float scale;
#endif
// 0 - playing
// >0 - id of spectated player
float spectatee_status;
+float spectatee_status_changed_time;
// short mapname
string shortmapname;
HUD_Scale_Disable();
}
+bool HUD_WouldShowCursor()
+{
+ if(autocvar__hud_configure)
+ return true;
+ if(hud_panel_radar_mouse)
+ return true;
+ if(mv_active)
+ return true;
+ //entity local_player = ((csqcplayer) ? csqcplayer : CSQCModel_server2csqc(player_localentnum - 1)); // TODO: doesn't use regular cursor handling
+ //if(local_player.viewloc && (local_player.viewloc.spawnflags & VIEWLOC_FREEAIM))
+ //return true;
+ if(HUD_Radar_Clickable())
+ return true;
+ if(HUD_MinigameMenu_IsOpened())
+ return true;
+ if(QuickMenu_IsOpened())
+ return true;
+ return false;
+}
+
void HUD_Main()
{
int i;
HUD_Panel_Draw(HUD_PANEL(RADAR));
if(autocvar__con_chat_maximized)
HUD_Panel_Draw(HUD_PANEL(CHAT));
- if(hud_panel_quickmenu)
+ if (QuickMenu_IsOpened())
HUD_Panel_Draw(HUD_PANEL(QUICKMENU));
HUD_Panel_Draw(HUD_PANEL(SCOREBOARD));
+ bool cursor_active_prev = cursor_active;
+ cursor_active = HUD_WouldShowCursor();
+ if (cursor_active_prev != cursor_active && autocvar_hud_cursormode)
+ setcursormode(cursor_active);
+
if (intermission == 2)
HUD_Reset();
bool HUD_Radar_Clickable();
void HUD_Radar_Mouse();
+bool HUD_WouldShowCursor();
+bool QuickMenu_IsOpened();
REGISTRY(hud_panels, BITS(6))
#define hud_panels_from(i) _hud_panels_from(i, NULL)
float vote_alpha;
float vote_change; // "time" when vote_active changed
-float hud_panel_quickmenu;
-
vector mousepos;
vector panel_click_distance; // mouse cursor distance from the top left corner of the panel (saved only upon a click)
vector panel_click_resizeorigin; // coordinates for opposite point when resizing
REGISTER_HUD_PANEL(MINIGAMEMENU, HUD_MinigameMenu, PANEL_CONFIG_NO , PANEL_SHOW_MAINGAME | PANEL_SHOW_MINIGAME | PANEL_SHOW_MAPVOTE | PANEL_SHOW_WITH_SB) // MINIGAMEMENU
REGISTER_HUD_PANEL(MAPVOTE, MapVote_Draw, PANEL_CONFIG_NO , PANEL_SHOW_MAPVOTE ) // MAPVOTE
REGISTER_HUD_PANEL(ITEMSTIME, HUD_ItemsTime, PANEL_CONFIG_MAIN | PANEL_CONFIG_CANBEOFF, PANEL_SHOW_MAINGAME ) // ITEMSTIME
-REGISTER_HUD_PANEL(QUICKMENU, HUD_QuickMenu, PANEL_CONFIG_MAIN , PANEL_SHOW_MAINGAME ) // QUICKMENU
+REGISTER_HUD_PANEL(QUICKMENU, HUD_QuickMenu, PANEL_CONFIG_MAIN , PANEL_SHOW_MAINGAME | PANEL_SHOW_MINIGAME ) // QUICKMENU
REGISTER_HUD_PANEL(SCOREBOARD, Scoreboard_Draw, PANEL_CONFIG_NO , PANEL_SHOW_MAINGAME | PANEL_SHOW_MINIGAME | PANEL_SHOW_MAPVOTE | PANEL_SHOW_WITH_SB) // SCOREBOARD
// always add new panels to the end of list
#include <client/autocvars.qh>
#include <client/defs.qh>
#include <client/miscfunctions.qh>
+#include <client/view.qh>
#define HUD_Write(s) fputs(fh, s)
#define HUD_Write_Cvar(cvar) HUD_Write(strcat("seta ", cvar, " \"", cvar_string(cvar), "\"\n"))
hud_configure_menu_open = 0;
localcmd("togglemenu\n");
}
+ cursor_type = CURSOR_NORMAL;
cvar_set("_hud_configure", "0");
}
if (bInputType == 1)
return true;
if (!hud_configure_menu_open)
- cvar_set("_hud_configure", "0");
+ HUD_Configure_Exit_Force();
}
else if(nPrimary == K_TAB && hudShiftState & S_CTRL) // switch panel
{
return true;
}
-float HUD_Panel_Check_Mouse_Pos(float allow_move)
+int HUD_Panel_Check_Mouse_Pos(bool allow_move)
{
int i, j = 0;
while(j < hud_panels_COUNT)
// move
if(allow_move && mousepos.x > panel_pos.x && mousepos.y > panel_pos.y && mousepos.x < panel_pos.x + panel_size.x && mousepos.y < panel_pos.y + panel_size.y)
{
- return 1;
+ return CURSOR_MOVE;
}
// resize from topleft border
else if(mousepos.x >= panel_pos.x - border && mousepos.y >= panel_pos.y - border && mousepos.x <= panel_pos.x + 0.5 * panel_size.x && mousepos.y <= panel_pos.y + 0.5 * panel_size.y)
{
- return 2;
+ return CURSOR_RESIZE;
}
// resize from topright border
else if(mousepos.x >= panel_pos.x + 0.5 * panel_size.x && mousepos.y >= panel_pos.y - border && mousepos.x <= panel_pos.x + panel_size.x + border && mousepos.y <= panel_pos.y + 0.5 * panel_size.y)
{
- return 3;
+ return CURSOR_RESIZE2;
}
// resize from bottomleft border
else if(mousepos.x >= panel_pos.x - border && mousepos.y >= panel_pos.y + 0.5 * panel_size.y && mousepos.x <= panel_pos.x + 0.5 * panel_size.x && mousepos.y <= panel_pos.y + panel_size.y + border)
{
- return 3;
+ return CURSOR_RESIZE2;
}
// resize from bottomright border
else if(mousepos.x >= panel_pos.x + 0.5 * panel_size.x && mousepos.y >= panel_pos.y + 0.5 * panel_size.y && mousepos.x <= panel_pos.x + panel_size.x + border && mousepos.y <= panel_pos.y + panel_size.y + border)
{
- return 2;
+ return CURSOR_RESIZE;
}
}
- return 0;
+ return CURSOR_NORMAL;
}
// move a panel to the beginning of the panel order array (which means it gets drawn last, on top of everything else)
hud_configure_menu_open = 2;
localcmd("menu_showhudoptions ", highlightedPanel.panel_name, "\n");
}
-float mouse_over_panel;
void HUD_Panel_Mouse()
{
if(autocvar__menu_alpha == 1)
return;
- if (!autocvar_hud_cursormode)
- update_mousepos();
-
if(mouseClicked)
{
if(prevMouseClicked == 0)
prevMouseClickedTime = time;
prevMouseClickedPos = mousepos;
}
- mouse_over_panel = HUD_Panel_Check_Mouse_Pos(mouseClicked & S_MOUSE1);
+ cursor_type = HUD_Panel_Check_Mouse_Pos(mouseClicked & S_MOUSE1);
}
}
else
if(prevMouseClicked)
highlightedAction = 0;
if(hud_configure_menu_open == 2)
- mouse_over_panel = 0;
+ cursor_type = CURSOR_NORMAL;
else
- mouse_over_panel = HUD_Panel_Check_Mouse_Pos(true);
- if (mouse_over_panel && !tab_panel)
+ cursor_type = HUD_Panel_Check_Mouse_Pos(true);
+ if (cursor_type != CURSOR_NORMAL && !tab_panel) // mouse over a panel?
drawfill(panel_pos - '1 1 0' * panel_bg_border, panel_size + '2 2 0' * panel_bg_border, '1 1 1', .1, DRAWFLAG_NORMAL);
}
- // draw cursor after performing move/resize to have the panel pos/size updated before mouse_over_panel
- float cursor_alpha = 1 - autocvar__menu_alpha;
-
- if(!mouse_over_panel)
- draw_cursor_normal(mousepos, '1 1 1', cursor_alpha);
- else if(mouse_over_panel == 1)
- draw_cursor(mousepos, '0.5 0.5 0', "/cursor_move", '1 1 1', cursor_alpha);
- else if(mouse_over_panel == 2)
- draw_cursor(mousepos, '0.5 0.5 0', "/cursor_resize", '1 1 1', cursor_alpha);
- else
- draw_cursor(mousepos, '0.5 0.5 0', "/cursor_resize2", '1 1 1', cursor_alpha);
-
- prevMouseClicked = mouseClicked;
}
void HUD_Configure_DrawGrid()
{
if(!hud_configure_prev)
{
- if(autocvar_hud_cursormode)
- setcursormode(1);
hudShiftState = 0;
for(i = hud_panels_COUNT - 1; i >= 0; --i)
hud_panels_from(panel_order[i]).update_time = time;
{
if(hud_configure_menu_open)
hud_configure_menu_open = 0;
- if(autocvar_hud_cursormode)
- setcursormode(0);
hud_dynamic_shake_factor = -1;
}
}
MUTATOR_CALLHOOK(DrawInfoMessages, pos, mySize);
- if(!warmup_stage && gametype == MAPINFO_TYPE_LMS)
+ if(!warmup_stage && ISGAMETYPE(LMS))
{
entity sk;
sk = playerslots[player_localnum];
mod_active = 1; // required in each mod function that always shows something
int layout;
- if(gametype == MAPINFO_TYPE_CA)
+ if(ISGAMETYPE(CA))
layout = autocvar_hud_panel_modicons_ca_layout;
- else //if(gametype == MAPINFO_TYPE_FREEZETAG)
+ else //if(ISGAMETYPE(FREEZETAG))
layout = autocvar_hud_panel_modicons_freezetag_layout;
int rows, columns;
float aspect_ratio;
// clientside personal record
string rr;
- if(gametype == MAPINFO_TYPE_CTS)
+ if(ISGAMETYPE(CTS))
rr = CTS_RECORD;
else
rr = RACE_RECORD;
{
if(!autocvar_hud_panel_physics) return;
if(spectatee_status == -1 && (autocvar_hud_panel_physics == 1 || autocvar_hud_panel_physics == 3)) return;
- if(autocvar_hud_panel_physics == 3 && !(gametype == MAPINFO_TYPE_RACE || gametype == MAPINFO_TYPE_CTS)) return;
+ if(autocvar_hud_panel_physics == 3 && !(ISGAMETYPE(RACE) || ISGAMETYPE(CTS))) return;
}
HUD_Panel_LoadCvars();
addPowerupItem("Strength", "strength", autocvar_hud_progressbar_strength_color, strengthTime, 30);
if(shieldTime)
addPowerupItem("Shield", "shield", autocvar_hud_progressbar_shield_color, shieldTime, 30);
- if(superTime)
+ if(superTime && !(allItems & IT_UNLIMITED_SUPERWEAPONS))
addPowerupItem("Superweapons", "superweapons", autocvar_hud_progressbar_superweapons_color, superTime, 30);
MUTATOR_CALLHOOK(HUD_Powerups_add);
#include <client/defs.qh>
#include <client/miscfunctions.qh>
#include <common/ent_cs.qh>
+#include <common/minigames/cl_minigames.qh>
#include <client/hud/_mod.qh>
#include <client/mapvoting.qh>
QuickMenu_Page_Command_Type[i] = 0;
}
+bool HUD_QuickMenu_Forbidden()
+{
+ return (mv_active
+ || (hud_configure_prev && hud_configure_prev != -1)
+ || HUD_MinigameMenu_IsOpened()
+ || (QuickMenu_TimeOut && time > QuickMenu_TimeOut));
+}
+
+// returns true if succeded, false otherwise
bool QuickMenu_Open(string mode, string submenu, string file)
{
+ QuickMenu_TimeOut = 0;
+ if (HUD_QuickMenu_Forbidden())
+ return false;
+
int fh = -1;
string s;
while((s = fgets(fh)) && QuickMenu_Buffer_Size < QUICKMENU_BUFFER_MAXENTRIES)
{
// first skip invalid entries, so we don't check them anymore
- float argc;
+ int argc;
argc = tokenize_console(s);
if(argc == 0 || argv(0) == "")
continue;
else
QuickMenu_Page_Load("", 0);
- hud_panel_quickmenu = 1;
- if(autocvar_hud_cursormode)
- setcursormode(1);
hudShiftState = 0;
QuickMenu_TimeOut = ((autocvar_hud_panel_quickmenu_time > 0) ? time + autocvar_hud_panel_quickmenu_time : 0);
for (i = 0; i < QUICKMENU_MAXLINES; ++i)
QuickMenu_Page_ClearEntry(i);
QuickMenu_Page_Entries = 0;
- hud_panel_quickmenu = 0;
mouseClicked = 0;
prevMouseClicked = 0;
QuickMenu_Buffer_Close();
-
- if(autocvar_hud_cursormode)
- if(!mv_active)
- setcursormode(0);
}
// It assumes submenu open tag is already detected
return;
}
- if (!autocvar_hud_cursormode)
- update_mousepos();
-
panel = HUD_PANEL(QUICKMENU);
HUD_Panel_LoadCvars();
QuickMenu_Page_ActiveEntry((entry_num < QUICKMENU_MAXLINES - 1) ? entry_num + 1 : 0);
}
}
-
- draw_cursor_normal(mousepos, '1 1 1', 0.8);
-
- prevMouseClicked = mouseClicked;
}
void HUD_Quickmenu_DrawEntry(vector pos, string desc, string option, vector fontsize)
{
if(!autocvar__hud_configure)
{
- if (hud_configure_prev && hud_configure_prev != -1)
- QuickMenu_Close();
-
- if(!hud_draw_maximized) return;
- if(mv_active) return;
- //if(!autocvar_hud_panel_quickmenu) return;
- if(!hud_panel_quickmenu) return;
+ if (!hud_draw_maximized || !QuickMenu_IsOpened())
+ return;
- if(QuickMenu_TimeOut)
- if(time > QuickMenu_TimeOut)
+ if (HUD_QuickMenu_Forbidden())
{
QuickMenu_Close();
return;
if(!autocvar__hud_configure)
{
if(!autocvar_hud_panel_racetimer) return;
- if(!(gametype == MAPINFO_TYPE_RACE || gametype == MAPINFO_TYPE_CTS)) return;
+ if(!(ISGAMETYPE(RACE) || ISGAMETYPE(CTS))) return;
if(spectatee_status == -1) return;
}
{
if (clickable)
{
- if(autocvar_hud_cursormode)
- setcursormode(1);
hud_panel_radar_mouse = 1;
// we must unset the player's buttons, as they aren't released elsewhere
{
hud_panel_radar_mouse = 0;
mouseClicked = 0;
- if(autocvar_hud_cursormode)
- if(!mv_active)
- setcursormode(0);
}
}
void HUD_Radar_Hide_Maximized()
return;
}
- if (!autocvar_hud_cursormode)
- update_mousepos();
-
panel = HUD_PANEL(RADAR);
HUD_Panel_LoadCvars();
HUD_Radar_Hide_Maximized();
return;
}
-
-
- draw_cursor_normal(mousepos, '1 1 1', 0.8);
}
void HUD_Radar()
IL_EACH(g_radaricons, it.teamradar_icon, {
if ( hud_panel_radar_mouse )
if ( GetResourceAmount(it, RESOURCE_HEALTH) >= 0 )
- if ( it.team == myteam + 1 || gametype == MAPINFO_TYPE_RACE || !teamplay )
+ if ( it.team == myteam + 1 || ISGAMETYPE(RACE) || !teamplay )
{
vector coord = teamradar_texcoord_to_2dcoord(teamradar_3dcoord_to_texcoord(it.origin));
if(vdist((mousepos - coord), <, 8))
if(!autocvar__hud_configure)
{
if(!autocvar_hud_panel_score) return;
- if(spectatee_status == -1 && (gametype == MAPINFO_TYPE_RACE || gametype == MAPINFO_TYPE_CTS)) return;
+ if(spectatee_status == -1 && (ISGAMETYPE(RACE) || ISGAMETYPE(CTS))) return;
}
HUD_Panel_LoadCvars();
bool autocvar_hud_panel_scoreboard_accuracy = true;
bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
+float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
+float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
+
bool autocvar_hud_panel_scoreboard_ctf_leaderboard = true;
bool autocvar_hud_panel_scoreboard_dynamichud = false;
field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
}
+ if(pl.eliminated)
+ {
+ h_size.x = column_width + hud_fontsize.x * 0.25;
+ h_size.y = hud_fontsize.y;
+ drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
+ }
pos.x += column_width;
pos.x += hud_fontsize.x;
}
return true;
else if (intermission == 2)
return false;
- else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
+ else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !ISGAMETYPE(CTS) && !active_minigame)
return true;
else if (scoreboard_showscores_force)
return true;
float average_accuracy;
vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
{
+ if (frametime)
+ {
+ if (scoreboard_fade_alpha == 1)
+ scoreboard_acc_fade_alpha = min(1, scoreboard_acc_fade_alpha + frametime * 10);
+ else
+ scoreboard_acc_fade_alpha = 1; // sync fading with the scoreboard
+ }
+ vector initial_pos = pos;
+
WepSet weapons_stat = WepSet_GetFromStat();
WepSet weapons_inmap = WepSet_GetFromStat_InMap();
int disownedcnt = 0;
float weapon_height = 29;
float height = hud_fontsize.y + weapon_height;
- drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+ drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
pos.y += 1.25 * hud_fontsize.y;
if(panel.current_panel_bg != "0")
pos.y += panel_bg_border;
panel_pos = pos;
panel_size.y = height * rows;
panel_size.y += panel_bg_padding * 2;
+
+ float panel_bg_alpha_save = panel_bg_alpha;
+ panel_bg_alpha *= scoreboard_acc_fade_alpha;
HUD_Panel_DrawBg();
+ panel_bg_alpha = panel_bg_alpha_save;
vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
if(panel.current_panel_bg != "0")
float weapon_width = tmp.x / columnns / rows;
if (sbt_bg_alpha)
- drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
+ drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
if(sbt_highlight)
{
// column highlighting
for (int i = 0; i < columnns; ++i)
if ((i % 2) == 0)
- drawfill(pos + eX * weapon_width * rows * i, vec2(weapon_width * rows, height * rows), '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
+ drawfill(pos + eX * weapon_width * rows * i, vec2(weapon_width * rows, height * rows), '0 0 0', sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
// row highlighting
for (int i = 0; i < rows; ++i)
- drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
+ drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
}
average_accuracy = 0;
weapon_alpha = 0.2 * sbt_fg_alpha;
// weapon icon
- drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
+ drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
// the accuracy
if (weapon_stats >= 0) {
weapons_with_stats += 1;
if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
rgb = Accuracy_GetColor(weapon_stats);
- drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
+ drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
}
tmpos.x += weapon_width * rows;
pos.x += weapon_width * rows;
average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
panel_size.x += panel_bg_padding * 2; // restore initial width
- return end_pos;
+
+ if (scoreboard_acc_fade_alpha == 1)
+ return end_pos;
+ return initial_pos + (end_pos - initial_pos) * scoreboard_acc_fade_alpha;
}
vector MapStats_DrawKeyValue(vector pos, string key, string value) {
vector hl_rgb = rgb + '0.5 0.5 0.5';
pos.y += hud_fontsize.y;
- drawstring(pos + eX * panel_bg_padding, ((gametype == MAPINFO_TYPE_CTF) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+ drawstring(pos + eX * panel_bg_padding, ((ISGAMETYPE(CTF)) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
pos.y += 1.25 * hud_fontsize.y;
if(panel.current_panel_bg != "0")
pos.y += panel_bg_border;
return end_pos;
}
+float scoreboard_time;
+bool have_weapon_stats;
+bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
+{
+ if (ISGAMETYPE(CTS) || ISGAMETYPE(RACE) || ISGAMETYPE(NEXBALL))
+ return false;
+ if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
+ return false;
+
+ if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
+ && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
+ && !intermission)
+ {
+ return false;
+ }
+
+ if (!have_weapon_stats)
+ {
+ FOREACH(Weapons, it != WEP_Null, {
+ int weapon_stats = weapon_accuracy[i - WEP_FIRST];
+ if (weapon_stats >= 0)
+ {
+ have_weapon_stats = true;
+ break;
+ }
+ });
+ if (!have_weapon_stats)
+ return false;
+ }
+
+ return true;
+}
+
void Scoreboard_Draw()
{
if(!autocvar__hud_configure)
// frametime checks allow to toggle the scoreboard even when the game is paused
if(scoreboard_active) {
+ if (scoreboard_fade_alpha < 1)
+ scoreboard_time = time;
if(hud_configure_menu_open == 1)
scoreboard_fade_alpha = 1;
float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
}
if (!scoreboard_fade_alpha)
+ {
+ scoreboard_acc_fade_alpha = 0;
return;
+ }
}
else
scoreboard_fade_alpha = 0;
pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
}
- bool show_accuracy = (gametype != MAPINFO_TYPE_CTS && gametype != MAPINFO_TYPE_RACE && gametype != MAPINFO_TYPE_NEXBALL);
-
- if (show_accuracy && autocvar_hud_panel_scoreboard_accuracy && !warmup_stage)
+ if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
- if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE || (autocvar_hud_panel_scoreboard_ctf_leaderboard && gametype == MAPINFO_TYPE_CTF && STAT(CTF_SHOWLEADERBOARD))) {
+ if(ISGAMETYPE(CTS) || ISGAMETYPE(RACE) || (autocvar_hud_panel_scoreboard_ctf_leaderboard && ISGAMETYPE(CTF) && STAT(CTF_SHOWLEADERBOARD))) {
if(race_speedaward) {
drawcolorcodedstring(pos, sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, race_speedaward_unit, ColorTranslateRGB(race_speedaward_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
pos.y += 1.25 * hud_fontsize.y;
tl = STAT(TIMELIMIT);
fl = STAT(FRAGLIMIT);
ll = STAT(LEADLIMIT);
- if(gametype == MAPINFO_TYPE_LMS)
+ if(ISGAMETYPE(LMS))
{
if(tl > 0)
str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
bool scoreboard_active;
float scoreboard_fade_alpha;
+float scoreboard_acc_fade_alpha;
-void Cmd_Scoreboard_SetFields(float argc);
+void Cmd_Scoreboard_SetFields(int argc);
void Scoreboard_Draw();
void Scoreboard_InitScores();
void Scoreboard_UpdatePlayerTeams();
if (intermission_time) {
timer = seconds_tostring(max(0, floor(intermission_time - STAT(GAMESTARTTIME))));
+ } else if (warmup_stage && warmup_timeleft >= 60) {
+ timer = _("WARMUP");
} else if (autocvar_hud_panel_timer_increment || (!warmup_stage && timelimit == 0) || (warmup_stage && warmup_timeleft <= 0)) {
if (time < STAT(GAMESTARTTIME))
timer = seconds_tostring(0); //while restart is still active, show 00:00
void HUD_Vote()
{
- if(autocvar_cl_allow_uid2name == -1 && (gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE || (serverflags & SERVERFLAG_PLAYERSTATS)))
+ if(autocvar_cl_allow_uid2name == -1 && (ISGAMETYPE(CTS) || ISGAMETYPE(RACE) || (serverflags & SERVERFLAG_PLAYERSTATS)))
{
// this dialog gets overriden by the uid2name menu dialog, if it exists
// TODO remove this client side uid2name dialog in the next release
localcmd("\ncl_hook_shutdown\n");
+ localcmd("\n-button14\n");
+
deactivate_minigame();
HUD_MinigameMenu_Close(NULL, NULL, NULL);
}
.float has_team;
-float SetTeam(entity o, int Team)
+bool SetTeam(entity o, int Team)
{
TC(int, Team);
devassert_once(Team);
// In the case of mouse input after a setcursormode(1) call, nPrimary is xpos, nSecondary is ypos.
float CSQC_InputEvent(int bInputType, float nPrimary, float nSecondary)
{
- TC(int, bInputType);
- if (HUD_Panel_InputEvent(bInputType, nPrimary, nSecondary))
+ TC(int, bInputType);
+ bool override = false;
+ override |= HUD_Panel_InputEvent(bInputType, nPrimary, nSecondary);
+ if (override)
return true;
- if (QuickMenu_InputEvent(bInputType, nPrimary, nSecondary))
- return true;
+ override |= QuickMenu_InputEvent(bInputType, nPrimary, nSecondary);
- if (HUD_Radar_InputEvent(bInputType, nPrimary, nSecondary))
- return true;
+ override |= HUD_Radar_InputEvent(bInputType, nPrimary, nSecondary);
- if (MapVote_InputEvent(bInputType, nPrimary, nSecondary))
- return true;
+ override |= MapVote_InputEvent(bInputType, nPrimary, nSecondary);
+
+ override |= HUD_Minigame_InputEvent(bInputType, nPrimary, nSecondary);
- if (HUD_Minigame_InputEvent(bInputType, nPrimary, nSecondary))
+ if(override)
return true;
return false;
race_laptime = 0;
race_checkpointtime = 0;
hud_dynamic_shake_factor = -1;
+ spectatee_status_changed_time = time;
}
if (autocvar_hud_panel_healtharmor_progressbar_gfx)
{
{
for(j = 0; j < maxclients; ++j)
if(playerslots[j])
- playerslots[j].ready = 1;
+ playerslots[j].ready = true;
for(i = 1; i <= maxclients; i += 8)
{
f = ReadByte();
for(j = i-1, b = BIT(0); b < BIT(8); b <<= 1, ++j)
if (!(f & b))
if(playerslots[j])
- playerslots[j].ready = 0;
+ playerslots[j].ready = false;
}
}
if (sf & 1) {
for (int j = 0; j < maxclients; ++j) {
if (playerslots[j]) {
- playerslots[j].eliminated = 1;
+ playerslots[j].eliminated = true;
}
}
for (int i = 1; i <= maxclients; i += 8) {
if (f & BIT(b)) continue;
int j = i - 1 + b;
if (playerslots[j]) {
- playerslots[j].eliminated = 0;
+ playerslots[j].eliminated = false;
}
}
}
// Minimap
string minimapname;
-float postinit;
+bool postinit;
entity gametype;
+// temporary hack
+#define ISGAMETYPE(NAME) (gametype == MAPINFO_TYPE_##NAME)
float FONT_USER = 8;
void Gamemode_Init();
-float SetTeam(entity pl, float Team);
+bool SetTeam(entity pl, int Team);
vector hud_fontsize;
entity playerslots[255]; // 255 is engine limit on maxclients
entity teamslots[17]; // 17 teams (including "spectator team")
-.float gotscores;
+.bool gotscores;
.entity owner;
-.float ready;
-.float eliminated;
+.bool ready;
+.bool eliminated;
.void(entity) draw;
IntrusiveList g_drawables;
float current_viewzoom;
float zoomin_effect;
-float warmup_stage;
+bool warmup_stage;
void Fog_Force();
#define getcommandkey_forcename(cmd_name, command) _getcommandkey(cmd_name, command, true)
string vote_called_vote;
-float ready_waiting;
-float ready_waiting_for_me;
-float vote_waiting;
-float vote_waiting_for_me;
+bool ready_waiting;
+bool ready_waiting_for_me;
+bool vote_waiting;
+bool vote_waiting_for_me;
float current_zoomfraction;
-float cs_project_is_b0rked;
-float vid_width, vid_height, vid_pixelheight;
+int cs_project_is_b0rked;
+int vid_width, vid_height;
+float vid_pixelheight;
float camera_active; // Demo camera is active if set to true
float chase_active_backup;
return mv_mouse_selection;
}
+vector prev_mousepos;
void MapVote_Draw()
{
string map;
if (!autocvar_hud_cursormode)
{
- vector mpos = mousepos;
- update_mousepos();
- if (mpos.x != mousepos.x || mpos.y != mousepos.y)
+ if (mousepos.x != prev_mousepos.x || mousepos.y != prev_mousepos.y)
+ {
mv_selection_keyboard = 0;
+ prev_mousepos = mousepos;
+ }
}
center = (vid_conwidth - 1)/2;
pos.x = (xmax+xmin)*0.5;
MapVote_DrawAbstain(pos, dist.x, xmax - xmin, tmp, i);
}
-
- draw_cursor_normal(mousepos, '1 1 1', panel_fg_alpha);
}
void Cmd_MapVote_MapDownload(int argc)
void MapVote_Init()
{
mv_active = 1;
- if(autocvar_hud_cursormode) setcursormode(1);
- else mousepos = '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
+ if(!autocvar_hud_cursormode) mousepos = '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
mv_selection = -1;
mv_selection_keyboard = 0;
#include <common/constants.qh>
void MapVote_Draw();
-void Cmd_MapVote_MapDownload(float argc);
+void Cmd_MapVote_MapDownload(int argc);
float MapVote_InputEvent(float bInputType, float nPrimary, float nSecondary);
const float SHOWNAMES_FADEDELAY = 0.4;
void Draw_ShowNames(entity this)
{
- if (this.sv_entnum == (current_player + 1)) // self or spectatee
- if (!(autocvar_hud_shownames_self && autocvar_chase_active)) return;
+ if (this.sv_entnum == current_player + 1) // self or spectatee
+ {
+ if (!autocvar_chase_active)
+ return;
+
+ if (!autocvar_hud_shownames_self
+ && !(spectatee_status > 0 && time <= spectatee_status_changed_time + 1))
+ {
+ return;
+ }
+ }
+
if (!this.sameteam && !autocvar_hud_shownames_enemies) return;
bool hit;
if (!autocvar_hud_shownames_crosshairdistance && this.sameteam)
// FIXME: alpha is negative when dead, breaking death fade
if (!this.csqcmodel_isdead) a *= f;
}
- if (a < ALPHA_MIN_VISIBLE && gametype != MAPINFO_TYPE_CTS) return;
+ if (a < ALPHA_MIN_VISIBLE && ISGAMETYPE(CTS)) return;
if (vdist(this.origin - view_origin, >=, max_shot_distance)) return;
float dist = vlen(this.origin - view_origin);
if (autocvar_hud_shownames_maxdistance)
#define EFMASK_CHEAP (EF_ADDITIVE | EF_DOUBLESIDED | EF_FULLBRIGHT | EF_NODEPTHTEST | EF_NODRAW | EF_NOSHADOW | EF_SELECTABLE | EF_TELEPORT_BIT)
float autocvar_cl_viewmodel_scale;
+float autocvar_cl_viewmodel_alpha;
bool autocvar_cl_bobmodel;
float autocvar_cl_bobmodel_speed;
if(!this.activeweapon || !autocvar_r_drawviewmodel)
return;
int mask = (intermission || (STAT(HEALTH) <= 0) || autocvar_chase_active) ? 0 : MASK_NORMAL;
- float a = this.alpha;
- static bool wasinvehicle;
+ float a = ((autocvar_cl_viewmodel_alpha) ? bound(-1, autocvar_cl_viewmodel_alpha, this.m_alpha) : this.m_alpha);
bool invehicle = player_localentnum > maxclients;
if (invehicle) a = -1;
- else if (wasinvehicle) a = 1;
- wasinvehicle = invehicle;
Weapon wep = this.activeweapon;
int c = entcs_GetClientColors(current_player);
vector g = weaponentity_glowmod(wep, NULL, c, this);
vector pos = view_origin;
vector dir = view_forward;
+ makevectors(((autocvar_chase_active) ? warpzone_save_view_angles : view_angles));
+ pos += v_right * -wepent.movedir.y
+ + v_up * wepent.movedir.z;
+
if (wepent.angles_held_status)
{
makevectors(wepent.angles_held);
// this function must match W_SetupShot!
float zoomscript_caught;
+bool minigame_wasactive;
+
vector wcross_origin;
float wcross_scale_prev, wcross_alpha_prev;
vector wcross_color_prev;
float eventchase_current_distance;
float eventchase_running;
-bool WantEventchase(entity this)
+int WantEventchase(entity this)
{
if(autocvar_cl_orthoview)
- return false;
+ return 0;
if(STAT(GAME_STOPPED) || intermission)
- return true;
+ return 1;
if(this.viewloc)
- return true;
+ return 1;
if(spectatee_status >= 0)
{
if(hud != HUD_NORMAL && (autocvar_cl_eventchase_vehicle || spectatee_status > 0))
- return true;
+ return 1;
if(MUTATOR_CALLHOOK(WantEventchase, this))
- return true;
+ return 1;
if(autocvar_cl_eventchase_frozen && STAT(FROZEN))
- return true;
+ return 1;
if(autocvar_cl_eventchase_death && (STAT(HEALTH) <= 0))
{
if(autocvar_cl_eventchase_death == 2)
{
// don't stop eventchase once it's started (even if velocity changes afterwards)
if(this.velocity == '0 0 0' || eventchase_running)
- return true;
+ return 1;
}
- else return true;
+ else return 1;
+ }
+ if (spectatee_status > 0 && autocvar_cl_eventchase_spectated_change)
+ {
+ if (time <= spectatee_status_changed_time + min(3, autocvar_cl_eventchase_spectated_change_time))
+ return 1;
+ else if (eventchase_running)
+ return -1; // disable chase_active while eventchase is still enabled so to avoid a glicth
}
}
- return false;
+ return 0;
}
void HUD_Crosshair_Vehicle(entity this)
if(autocvar_r_letterbox == 0)
if(autocvar_viewsize < 120)
{
- if(!(gametype == MAPINFO_TYPE_RACE || gametype == MAPINFO_TYPE_CTS))
+ if(!(ISGAMETYPE(RACE) || ISGAMETYPE(CTS)))
Accuracy_LoadLevels();
HUD_Main();
//draw_cursor(viewloc_mousepos, '0.5 0.5 0', "/cursor_move", '1 1 1', cursor_alpha);
}
+void HUD_Cursor_Show()
+{
+ float cursor_alpha = 1 - autocvar__menu_alpha;
+ if(cursor_type == CURSOR_NORMAL)
+ draw_cursor_normal(mousepos, '1 1 1', cursor_alpha);
+ else if(cursor_type == CURSOR_MOVE)
+ draw_cursor(mousepos, '0.5 0.5 0', "/cursor_move", '1 1 1', cursor_alpha);
+ else if(cursor_type == CURSOR_RESIZE)
+ draw_cursor(mousepos, '0.5 0.5 0', "/cursor_resize", '1 1 1', cursor_alpha);
+ else if(cursor_type == CURSOR_RESIZE2)
+ draw_cursor(mousepos, '0.5 0.5 0', "/cursor_resize2", '1 1 1', cursor_alpha);
+}
+
+void HUD_Mouse(entity player)
+{
+ if(autocvar__menu_alpha == 1)
+ return;
+
+ if(!cursor_active)
+ {
+ if(player.viewloc && (player.viewloc.spawnflags & VIEWLOC_FREEAIM))
+ ViewLocation_Mouse(); // NOTE: doesn't use cursormode
+ return;
+ }
+
+ if(!autocvar_hud_cursormode)
+ update_mousepos();
+
+ if(autocvar__hud_configure)
+ HUD_Panel_Mouse();
+ else
+ {
+ if (HUD_MinigameMenu_IsOpened())
+ HUD_Minigame_Mouse();
+ if (QuickMenu_IsOpened())
+ QuickMenu_Mouse();
+ if (HUD_Radar_Clickable())
+ HUD_Radar_Mouse();
+ }
+
+ prevMouseClicked = mouseClicked;
+
+ HUD_Cursor_Show();
+}
+
bool ov_enabled;
float oldr_nearclip;
float oldr_farclip_base;
}
}
- if(WantEventchase(this))
+ int eventchase = WantEventchase(this);
+ if (eventchase)
{
vector current_view_origin_override = '0 0 0';
vector view_offset_override = '0 0 0';
if(!local_player.viewloc)
setproperty(VF_ANGLES, WarpZone_TransformVAngles(WarpZone_trace_transform, view_angles));
}
- else if(autocvar_chase_active < 0) // time to disable chase_active if it was set by this code
+
+ if (eventchase <= 0 && autocvar_chase_active < 0) // time to disable chase_active if it was set by this code
{
eventchase_running = false;
cvar_set("chase_active", "0");
}
}
+ if(active_minigame && HUD_MinigameMenu_IsOpened())
+ {
+ if(!minigame_wasactive)
+ {
+ localcmd("+button14\n");
+ minigame_wasactive = true;
+ }
+ }
+ else if(minigame_wasactive)
+ {
+ localcmd("-button14\n");
+ minigame_wasactive = false;
+ }
+
ColorTranslateMode = autocvar_cl_stripcolorcodes;
for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
else if(cvar("r_glsl_postprocess") == 2)
cvar_set("r_glsl_postprocess", "0");
- /*if(gametype == MAPINFO_TYPE_CTF)
+ /*if(ISGAMETYPE(CTF))
{
ctf_view();
} else */
cvar_set("vid_conheight", h0);
}
- if(autocvar__hud_configure)
- HUD_Panel_Mouse();
- else if (HUD_MinigameMenu_IsOpened() || active_minigame)
- 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();
+ HUD_Mouse(local_player);
cl_notice_run();
unpause_update();
entity viewmodels[MAX_WEAPONSLOTS];
vector viewloc_mousepos;
+
+bool cursor_active;
+int cursor_type;
+const int CURSOR_NORMAL = 0;
+const int CURSOR_MOVE = 1;
+const int CURSOR_RESIZE = 2;
+const int CURSOR_RESIZE2 = 3;
this.fade_rate = 0;
}
- int myteam = ReadByte();
- this.team = myteam - 1;
+ int proj_team = ReadByte();
+ this.team = proj_team - 1;
if(teamplay)
{
- if(myteam)
+ if(proj_team)
this.colormap = (this.team) * 0x11; // note: team - 1 on server (client uses different numbers)
else
this.colormap = 0x00;
this.colormap |= BIT(10); // RENDER_COLORMAPPED
}
else
- this.colormap = myteam;
+ this.colormap = proj_team;
// TODO: projectiles use glowmaps for their color, not teams
#if 0
if(this.colormap > 0)
return vec3(e.anim_duckwalkbackright.x, t, ANIMPRIO_CROUCH);
case ANIMIMPLICITSTATE_BACKWARDS | ANIMIMPLICITSTATE_LEFT:
return vec3(e.anim_duckwalkbackleft.x, t, ANIMPRIO_CROUCH);
- default:
- return vec3(e.anim_duckidle.x, t, ANIMPRIO_CROUCH);
}
+ return vec3(e.anim_duckidle.x, t, ANIMPRIO_CROUCH);
}
else
{
return vec3(e.anim_backright.x, t, ANIMPRIO_ACTIVE);
case ANIMIMPLICITSTATE_BACKWARDS | ANIMIMPLICITSTATE_LEFT:
return vec3(e.anim_backleft.x, t, ANIMPRIO_ACTIVE);
- default:
- return vec3(e.anim_idle.x, t, ANIMPRIO_IDLE);
}
+ return vec3(e.anim_idle.x, t, ANIMPRIO_IDLE);
}
// can't get here
- return vec3(e.anim_idle.x, t, ANIMPRIO_IDLE);
+ //return vec3(e.anim_idle.x, t, ANIMPRIO_IDLE);
}
void animdecide_setimplicitstate(entity e, float onground)
// Sets up the campaign for the n-th array item (meaning: campaign_offset+nth
// level) using localcmd()
-void CampaignSetup(float n);
+void CampaignSetup(int n);
a = ""; \
else \
++i
-// What you're seeing here is what people will do when your compiler supports
-// C-style macros but no line continuations.
i = -1; // starts at -1 so I don't need postincrement; that is, i points to BEFORE the current arg!
CAMPAIGN_GETARG; campaign_gametype[campaign_entries] = strzone(a);
// Command Sub-Functions
// =======================
-void GenericCommand_addtolist(float request, float argc)
+void GenericCommand_addtolist(int request, int argc)
{
switch(request)
{
}
}
-void GenericCommand_qc_curl(float request, float argc)
+void GenericCommand_qc_curl(int request, int argc)
{
switch(request)
{
}
}
-GENERIC_COMMAND(dumpcommands, "Dump all commands on the program to *_cmd_dump.txt")
+GENERIC_COMMAND(dumpcommands, "Dump all commands on the program to <program>_cmd_dump.txt")
{
switch(request)
{
#ifdef SVQC
CMD_Write("dump of server console commands:\n");
GameCommand_macro_write_aliases(fh);
+ CMD_Write("\n");
- CMD_Write("\ndump of networked client only commands:\n");
+ CMD_Write("dump of networked client only commands:\n");
ClientCommand_macro_write_aliases(fh);
+ CMD_Write("\n");
- CMD_Write("\ndump of common commands:\n");
+ CMD_Write("dump of common commands:\n");
CommonCommand_macro_write_aliases(fh);
+ CMD_Write("\n");
- CMD_Write("\ndump of ban commands:\n");
+ CMD_Write("dump of ban commands:\n");
BanCommand_macro_write_aliases(fh);
+ CMD_Write("\n");
#endif
#ifdef CSQC
CMD_Write("dump of client commands:\n");
LocalCommand_macro_write_aliases(fh);
+ CMD_Write("\n");
#endif
- CMD_Write("\ndump of generic commands:\n");
+ CMD_Write("dump of generic commands:\n");
GenericCommand_macro_write_aliases(fh);
LOG_INFO("Completed dump of aliases in ^2data/data/", GetProgramCommandPrefix(), "_dump.txt^7.");
}
}
-void GenericCommand_maplist(float request, float argc)
+void GenericCommand_maplist(int request, int argc)
{
switch(request)
{
}
}
-void GenericCommand_nextframe(float request, float arguments, string command)
+void GenericCommand_nextframe(int request, string command)
{
switch(request)
{
}
}
-void GenericCommand_removefromlist(float request, float argc)
+void GenericCommand_removefromlist(int request, int argc)
{
switch(request)
{
}
}
-void GenericCommand_restartnotifs(float request)
+void GenericCommand_restartnotifs(int request)
{
switch(request)
{
}
}
-void GenericCommand_settemp(float request, float argc)
+void GenericCommand_settemp(int request, int argc)
{
switch(request)
{
}
}
-void GenericCommand_settemp_restore(float request, float argc)
+void GenericCommand_settemp_restore(int request)
{
switch(request)
{
}
}
-void GenericCommand_runtest(float request, float argc)
+void GenericCommand_runtest(int request, int argc)
{
switch(request)
{
/* use this when creating a new command, making sure to place it in alphabetical order... also,
** ADD ALL NEW COMMANDS TO commands.cfg WITH PROPER ALIASES IN THE SAME FASHION!
-void GenericCommand_(float request)
+void GenericCommand_(int request)
{
switch(request)
{
// Do not hard code aliases for these, instead create them in commands.cfg... also: keep in alphabetical order, please ;)
GENERIC_COMMAND(addtolist, "Add a string to a cvar") { GenericCommand_addtolist(request, arguments); }
GENERIC_COMMAND(maplist, "Automatic control of maplist") { GenericCommand_maplist(request, arguments); }
-GENERIC_COMMAND(nextframe, "Execute the given command next frame of this VM") { GenericCommand_nextframe(request, arguments, command); }
+GENERIC_COMMAND(nextframe, "Execute the given command next frame of this VM") { GenericCommand_nextframe(request, command); }
GENERIC_COMMAND(qc_curl, "Queries a URL") { GenericCommand_qc_curl(request, arguments); }
GENERIC_COMMAND(removefromlist, "Remove a string from a cvar") { GenericCommand_removefromlist(request, arguments); }
GENERIC_COMMAND(restartnotifs, "Re-initialize all notifications") { GenericCommand_restartnotifs(request); }
GENERIC_COMMAND(rpn, "RPN calculator") { GenericCommand_rpn(request, arguments, command); }
GENERIC_COMMAND(settemp, "Temporarily set a value to a cvar which is restored later") { GenericCommand_settemp(request, arguments); }
-GENERIC_COMMAND(settemp_restore, "Restore all cvars set by settemp command") { GenericCommand_settemp_restore(request, arguments); }
+GENERIC_COMMAND(settemp_restore, "Restore all cvars set by settemp command") { GenericCommand_settemp_restore(request); }
GENERIC_COMMAND(runtest, "Run unit tests") { GenericCommand_runtest(request, arguments); }
void GenericCommand_macro_help()
FOREACH(GENERIC_COMMANDS, true, LOG_INFOF(" ^2%s^7: %s", it.m_name, it.m_description));
}
-float GenericCommand_macro_command(float argc, string command)
+float GenericCommand_macro_command(int argc, string command)
{
string c = strtolower(argv(0));
FOREACH(GENERIC_COMMANDS, it.m_name == c, {
return false;
}
-float GenericCommand_macro_usage(float argc)
+float GenericCommand_macro_usage(int argc)
{
string c = strtolower(argv(1));
FOREACH(GENERIC_COMMANDS, it.m_name == c, {
float GenericCommand(string command)
{
- float argc = tokenize_console(command);
+ int argc = tokenize_console(command);
float n, j, f, i;
string s, s2, c;
vector rgb;
void GenericCommand_macro_help();
-float GenericCommand_macro_command(float argc, string command);
+float GenericCommand_macro_command(int argc, string command);
-float GenericCommand_macro_usage(float argc);
+float GenericCommand_macro_usage(int argc);
void GenericCommand_macro_write_aliases(float fh);
void rpn_pushf(float f) { return rpn_push(sprintf("%.9g", f)); }
void rpn_setf(float f) { return rpn_set(sprintf("%.9g", f)); }
-void GenericCommand_rpn(float request, float argc, string command)
+void GenericCommand_rpn(int request, int argc, string command)
{
switch(request)
{
int rpn_sp;
string rpn_stack[MAX_RPN_STACK];
-void GenericCommand_rpn(float request, float argc, string command);
+void GenericCommand_rpn(int request, int argc, string command);
it.debug_trace_button = btn;
if (!btn || skip) continue;
FOREACH_ENTITY(true, {
- it.solid_prev = it.solid;
+ it.solid_prev = it.solid;
it.solid = SOLID_BBOX;
});
vector forward = '0 0 0'; vector right = '0 0 0'; vector up = '0 0 0';
vector pos = it.origin + it.view_ofs;
traceline(pos, pos + forward * max_shot_distance, MOVE_NORMAL, it);
FOREACH_ENTITY(true, {
- it.solid = it.solid_prev;
- it.solid_prev = 0;
+ it.solid = it.solid_prev;
+ it.solid_prev = 0;
});
entity e = trace_ent;
int i = etof(e);
EFFECT(0, SMOKE_RING, "smoke_ring")
EFFECT(0, JUMPPAD, "jumppad_activate")
EFFECT(1, BULLET, "tr_bullet")
+EFFECT(1, BULLET_WEAK, "tr_bullet_weak")
EFFECT(0, EF_FLAME, "EF_FLAME")
EFFECT(0, EF_STARDUST, "EF_STARDUST")
EFFECT(0, TE_EXPLOSION, "TE_EXPLOSION")
case NUM_TEAM_4: e = EFFECT_ROCKETMINSTA_LASER_PINK; break;
default: e = EFFECT_ROCKETMINSTA_LASER_NEUTRAL; break;
}
- if (particleeffectnum(e) < 0 || Team_TeamToNumber(teamid) == -1) { e = EFFECT_TR_NEXUIZPLASMA; }
+ if (particleeffectnum(e) < 0 || !Team_IsValidTeam(teamid)) { e = EFFECT_TR_NEXUIZPLASMA; }
return e;
}
+// docs: https://www.quakewiki.net/darkplaces-wiki/effectinfo-scripting-reference/
+// use `cl_particles_reloadeffects` to reload effects without restarting engine
+// use `chase_active 1` and `cl_lockview 1` to see effects from different perspectives
+// `dumpeffectinfo` currently doesn't work so edit effectinfo.txt manually, just try to keep the files in sync
+
+// `tex` are indices into particles/particlefont.tga (see particles/particlefont-template.tga for numbers)
+// the first index is inclusive, second exclusive (so `tex 0 8` will use images 0 though 7)
+// unless they're equal (`tex 69 69` is the same as `tex 69 70`)
+
// item respawn effect
DEF(TE_WIZSPIKE);
// flare particle and light
// bullet trail (somewhat like a tracer)
DEF(tr_bullet);
+SUB(tr_bullet) {
+ MY(alpha) = '500 600 10000';
+ MY(color_min) = "0xf03000";
+ MY(color_max) = "0xff6010";
+ MY(countabsolute) = 1;
+ MY(sizeincrease) = -3;
+ MY(size_min) = 0.6;
+ MY(size_max) = 0.8;
+ my(tex_min) = 200;
+ my(tex_max) = 200;
+ MY(type) = "beam";
+}
+SUB(tr_bullet) {
+ MY(airfriction) = -4;
+ MY(alpha) = '256 256 350';
+ MY(color_min) = "0x202020";
+ MY(color_max) = "0x404040";
+ MY(notunderwater) = true;
+ MY(sizeincrease) = 0.4;
+ MY(size_min) = 1;
+ MY(size_max) = 2;
+ MY(tex_min) = 0;
+ MY(tex_max) = 8;
+ MY(trailspacing) = 16;
+ MY(type) = "smoke";
+ MY(velocityjitter) = '4 4 4';
+}
SUB(tr_bullet) {
MY(alpha_min) = 256;
MY(alpha_max) = 256;
- MY(alpha_fade) = 2560;
- MY(color_min) = "0xff8960";
- MY(color_max) = "0xff8533";
- MY(size_min) = 4;
- MY(size_max) = 4;
- MY(stretchfactor) = 0.200000;
- MY(tex_min) = 70;
- MY(tex_max) = 70;
- MY(trailspacing) = 750;
- MY(type) = "spark";
- MY(velocitymultiplier) = 3;
+ MY(alpha_fade) = 128;
+ MY(bounce) = 1.500000;
+ MY(color_min) = "0x404040";
+ MY(color_max) = "0x808080";
+ MY(gravity) = -0.125000;
+ MY(liquidfriction) = 4;
+ MY(size_min) = 0.5;
+ MY(size_max) = 0.6;
+ MY(tex_min) = 62;
+ MY(tex_max) = 62;
+ MY(trailspacing) = 16;
+ MY(type) = "bubble";
+ MY(underwater) = true;
+ MY(velocityjitter) = '16.0 16.0 16.0';
}
// smoke emitter for small pipes
MY(velocityjitter) = '224.0 224.0 224.0';
MY(velocityoffset) = '0.0 0.0 80.0';
}
+
+// weak bullet trail (somewhat like a tracer)
+DEF(tr_bullet_weak);
+SUB(tr_bullet_weak) {
+ MY(alpha) = '75 100 3000';
+ MY(color_min) = "0xf03000";
+ MY(color_max) = "0xff6010";
+ MY(countabsolute) = 1;
+ MY(sizeincrease) = -3;
+ MY(size_min) = 0.6;
+ MY(size_max) = 0.8;
+ my(tex_min) = 200;
+ my(tex_max) = 200;
+ MY(type) = "beam";
+}
+SUB(tr_bullet_weak) {
+ MY(airfriction) = -4;
+ MY(alpha) = '256 256 350';
+ MY(color_min) = "0x202020";
+ MY(color_max) = "0x404040";
+ MY(notunderwater) = true;
+ MY(sizeincrease) = 0.4;
+ MY(size_min) = 1;
+ MY(size_max) = 2;
+ MY(tex_min) = 0;
+ MY(tex_max) = 8;
+ MY(trailspacing) = 16;
+ MY(type) = "smoke";
+ MY(velocityjitter) = '4 4 4';
+}
+SUB(tr_bullet_weak) {
+ MY(alpha_min) = 256;
+ MY(alpha_max) = 256;
+ MY(alpha_fade) = 128;
+ MY(bounce) = 1.500000;
+ MY(color_min) = "0x404040";
+ MY(color_max) = "0x808080";
+ MY(gravity) = -0.125000;
+ MY(liquidfriction) = 4;
+ MY(size_min) = 0.5;
+ MY(size_max) = 0.6;
+ MY(tex_min) = 62;
+ MY(tex_max) = 62;
+ MY(trailspacing) = 32;
+ MY(type) = "bubble";
+ MY(underwater) = true;
+ MY(velocityjitter) = '16.0 16.0 16.0';
+}
#include <common/gamemodes/gamemode/cts/_mod.inc>
#include <common/gamemodes/gamemode/deathmatch/_mod.inc>
#include <common/gamemodes/gamemode/domination/_mod.inc>
+#include <common/gamemodes/gamemode/duel/_mod.inc>
#include <common/gamemodes/gamemode/freezetag/_mod.inc>
#include <common/gamemodes/gamemode/invasion/_mod.inc>
#include <common/gamemodes/gamemode/keepaway/_mod.inc>
#include <common/gamemodes/gamemode/cts/_mod.qh>
#include <common/gamemodes/gamemode/deathmatch/_mod.qh>
#include <common/gamemodes/gamemode/domination/_mod.qh>
+#include <common/gamemodes/gamemode/duel/_mod.qh>
#include <common/gamemodes/gamemode/freezetag/_mod.qh>
#include <common/gamemodes/gamemode/invasion/_mod.qh>
#include <common/gamemodes/gamemode/keepaway/_mod.qh>
// generated file; do not modify
-#include <common/gamemodes/gamemode/assault/assault.qc>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/assault/sv_assault.qc>
+#endif
// generated file; do not modify
-#include <common/gamemodes/gamemode/assault/assault.qh>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/assault/sv_assault.qh>
+#endif
+++ /dev/null
-#include "assault.qh"
-
-// TODO: split into sv_assault
-#ifdef SVQC
-.entity sprite;
-#define AS_ROUND_DELAY 5
-
-IntrusiveList g_assault_destructibles;
-IntrusiveList g_assault_objectivedecreasers;
-IntrusiveList g_assault_objectives;
-STATIC_INIT(g_assault)
-{
- g_assault_destructibles = IL_NEW();
- g_assault_objectivedecreasers = IL_NEW();
- g_assault_objectives = IL_NEW();
-}
-
-// random functions
-void assault_objective_use(entity this, entity actor, entity trigger)
-{
- // activate objective
- SetResourceAmountExplicit(this, RESOURCE_HEALTH, 100);
- //print("^2Activated objective ", this.targetname, "=", etos(this), "\n");
- //print("Activator is ", actor.classname, "\n");
-
- IL_EACH(g_assault_objectivedecreasers, it.target == this.targetname,
- {
- target_objective_decrease_activate(it);
- });
-}
-
-vector target_objective_spawn_evalfunc(entity this, entity player, entity spot, vector current)
-{
- float hlth = GetResourceAmount(this, RESOURCE_HEALTH);
- if (hlth < 0 || hlth >= ASSAULT_VALUE_INACTIVE)
- return '-1 0 0';
- return current;
-}
-
-// reset this objective. Used when spawning an objective
-// and when a new round starts
-void assault_objective_reset(entity this)
-{
- SetResourceAmountExplicit(this, RESOURCE_HEALTH, ASSAULT_VALUE_INACTIVE);
-}
-
-// decrease the health of targeted objectives
-void assault_objective_decrease_use(entity this, entity actor, entity trigger)
-{
- if(actor.team != assault_attacker_team)
- {
- // wrong team triggered decrease
- return;
- }
-
- if(trigger.assault_sprite)
- {
- WaypointSprite_Disown(trigger.assault_sprite, waypointsprite_deadlifetime);
- if(trigger.classname == "func_assault_destructible")
- trigger.sprite = NULL; // TODO: just unsetting it?!
- }
- else
- return; // already activated! cannot activate again!
-
- float hlth = GetResourceAmount(this.enemy, RESOURCE_HEALTH);
- if (hlth < ASSAULT_VALUE_INACTIVE)
- {
- if (hlth - this.dmg > 0.5)
- {
- GameRules_scoring_add_team(actor, SCORE, this.dmg);
- TakeResource(this.enemy, RESOURCE_HEALTH, this.dmg);
- }
- else
- {
- GameRules_scoring_add_team(actor, SCORE, hlth);
- GameRules_scoring_add_team(actor, ASSAULT_OBJECTIVES, 1);
- SetResourceAmountExplicit(this.enemy, RESOURCE_HEALTH, -1);
-
- if(this.enemy.message)
- FOREACH_CLIENT(IS_PLAYER(it), { centerprint(it, this.enemy.message); });
-
- SUB_UseTargets(this.enemy, this, trigger);
- }
- }
-}
-
-void assault_setenemytoobjective(entity this)
-{
- IL_EACH(g_assault_objectives, it.targetname == this.target,
- {
- if(this.enemy == NULL)
- this.enemy = it;
- else
- objerror(this, "more than one objective as target - fix the map!");
- break;
- });
-
- if(this.enemy == NULL)
- objerror(this, "no objective as target - fix the map!");
-}
-
-bool assault_decreaser_sprite_visible(entity this, entity player, entity view)
-{
- if(GetResourceAmount(this.assault_decreaser.enemy, RESOURCE_HEALTH) >= ASSAULT_VALUE_INACTIVE)
- return false;
-
- return true;
-}
-
-void target_objective_decrease_activate(entity this)
-{
- entity spr;
- this.owner = NULL;
- FOREACH_ENTITY_STRING(target, this.targetname,
- {
- if(it.assault_sprite != NULL)
- {
- WaypointSprite_Disown(it.assault_sprite, waypointsprite_deadlifetime);
- if(it.classname == "func_assault_destructible")
- it.sprite = NULL; // TODO: just unsetting it?!
- }
-
- spr = WaypointSprite_SpawnFixed(WP_AssaultDefend, 0.5 * (it.absmin + it.absmax), it, assault_sprite, RADARICON_OBJECTIVE);
- spr.assault_decreaser = this;
- spr.waypointsprite_visible_for_player = assault_decreaser_sprite_visible;
- spr.classname = "sprite_waypoint";
- WaypointSprite_UpdateRule(spr, assault_attacker_team, SPRITERULE_TEAMPLAY);
- if(it.classname == "func_assault_destructible")
- {
- WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultDestroy, WP_AssaultDestroy);
- WaypointSprite_UpdateMaxHealth(spr, it.max_health);
- WaypointSprite_UpdateHealth(spr, GetResourceAmount(it, RESOURCE_HEALTH));
- it.sprite = spr;
- }
- else
- WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultPush, WP_AssaultPush);
- });
-}
-
-void target_objective_decrease_findtarget(entity this)
-{
- assault_setenemytoobjective(this);
-}
-
-void target_assault_roundend_reset(entity this)
-{
- //print("round end reset\n");
- ++this.cnt; // up round counter
- this.winning = false; // up round
-}
-
-void target_assault_roundend_use(entity this, entity actor, entity trigger)
-{
- this.winning = 1; // round has been won by attackers
-}
-
-void assault_roundstart_use(entity this, entity actor, entity trigger)
-{
- SUB_UseTargets(this, this, trigger);
-
- //(Re)spawn all turrets
- IL_EACH(g_turrets, true,
- {
- // Swap turret teams
- if(it.team == NUM_TEAM_1)
- it.team = NUM_TEAM_2;
- else
- it.team = NUM_TEAM_1;
-
- // Doubles as teamchange
- turret_respawn(it);
- });
-}
-void assault_roundstart_use_this(entity this)
-{
- assault_roundstart_use(this, NULL, NULL);
-}
-
-void assault_wall_think(entity this)
-{
- if(GetResourceAmount(this.enemy, RESOURCE_HEALTH) < 0)
- {
- this.model = "";
- this.solid = SOLID_NOT;
- }
- else
- {
- this.model = this.mdl;
- this.solid = SOLID_BSP;
- }
-
- this.nextthink = time + 0.2;
-}
-
-// trigger new round
-// reset objectives, toggle spawnpoints, reset triggers, ...
-void assault_new_round(entity this)
-{
- //bprint("ASSAULT: new round\n");
-
- // up round counter
- this.winning = this.winning + 1;
-
- // swap attacker/defender roles
- if(assault_attacker_team == NUM_TEAM_1)
- assault_attacker_team = NUM_TEAM_2;
- else
- assault_attacker_team = NUM_TEAM_1;
-
- IL_EACH(g_saved_team, !IS_CLIENT(it),
- {
- if(it.team_saved == NUM_TEAM_1)
- it.team_saved = NUM_TEAM_2;
- else if(it.team_saved == NUM_TEAM_2)
- it.team_saved = NUM_TEAM_1;
- });
-
- // reset the level with a countdown
- cvar_set("timelimit", ftos(ceil(time - AS_ROUND_DELAY - game_starttime) / 60));
- ReadyRestart_force(); // sets game_starttime
-}
-
-entity as_round;
-.entity ent_winning;
-void as_round_think()
-{
- game_stopped = false;
- assault_new_round(as_round.ent_winning);
- delete(as_round);
- as_round = NULL;
-}
-
-// Assault winning condition: If the attackers triggered a round end (by fulfilling all objectives)
-// they win. Otherwise the defending team wins once the timelimit passes.
-int WinningCondition_Assault()
-{
- if(as_round)
- return WINNING_NO;
-
- WinningConditionHelper(NULL); // set worldstatus
-
- int status = WINNING_NO;
- // as the timelimit has not yet passed just assume the defending team will win
- if(assault_attacker_team == NUM_TEAM_1)
- {
- SetWinners(team, NUM_TEAM_2);
- }
- else
- {
- SetWinners(team, NUM_TEAM_1);
- }
-
- entity ent;
- ent = find(NULL, classname, "target_assault_roundend");
- if(ent)
- {
- if(ent.winning) // round end has been triggered by attacking team
- {
- bprint("Assault: round completed.\n");
- SetWinners(team, assault_attacker_team);
-
- TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 666 - TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 0));
-
- if(ent.cnt == 1 || autocvar_g_campaign) // this was the second round
- {
- status = WINNING_YES;
- }
- else
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ASSAULT_OBJ_DESTROYED, ceil(time - game_starttime));
- as_round = new(as_round);
- as_round.think = as_round_think;
- as_round.ent_winning = ent;
- as_round.nextthink = time + AS_ROUND_DELAY;
- game_stopped = true;
-
- // make sure timelimit isn't hit while the game is blocked
- if(autocvar_timelimit > 0)
- if(time + AS_ROUND_DELAY >= game_starttime + autocvar_timelimit * 60)
- cvar_set("timelimit", ftos(autocvar_timelimit + AS_ROUND_DELAY / 60));
- }
- }
- }
-
- return status;
-}
-
-// spawnfuncs
-spawnfunc(info_player_attacker)
-{
- if (!g_assault) { delete(this); return; }
-
- this.team = NUM_TEAM_1; // red, gets swapped every round
- spawnfunc_info_player_deathmatch(this);
-}
-
-spawnfunc(info_player_defender)
-{
- if (!g_assault) { delete(this); return; }
-
- this.team = NUM_TEAM_2; // blue, gets swapped every round
- spawnfunc_info_player_deathmatch(this);
-}
-
-spawnfunc(target_objective)
-{
- if (!g_assault) { delete(this); return; }
-
- this.classname = "target_objective";
- IL_PUSH(g_assault_objectives, this);
- this.use = assault_objective_use;
- this.reset = assault_objective_reset;
- this.reset(this);
- this.spawn_evalfunc = target_objective_spawn_evalfunc;
-}
-
-spawnfunc(target_objective_decrease)
-{
- if (!g_assault) { delete(this); return; }
-
- this.classname = "target_objective_decrease";
- IL_PUSH(g_assault_objectivedecreasers, this);
-
- if(!this.dmg)
- this.dmg = 101;
-
- this.use = assault_objective_decrease_use;
- SetResourceAmountExplicit(this, RESOURCE_HEALTH, ASSAULT_VALUE_INACTIVE);
- this.max_health = ASSAULT_VALUE_INACTIVE;
- this.enemy = NULL;
-
- InitializeEntity(this, target_objective_decrease_findtarget, INITPRIO_FINDTARGET);
-}
-
-// destructible walls that can be used to trigger target_objective_decrease
-bool destructible_heal(entity targ, entity inflictor, float amount, float limit)
-{
- float true_limit = ((limit != RESOURCE_LIMIT_NONE) ? limit : targ.max_health);
- float hlth = GetResourceAmount(targ, RESOURCE_HEALTH);
- if (hlth <= 0 || hlth >= true_limit)
- return false;
-
- GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, true_limit);
- if(targ.sprite)
- {
- WaypointSprite_UpdateHealth(targ.sprite, GetResourceAmount(targ, RESOURCE_HEALTH));
- }
- func_breakable_colormod(targ);
- return true;
-}
-
-spawnfunc(func_breakable);
-spawnfunc(func_assault_destructible)
-{
- if (!g_assault) { delete(this); return; }
-
- this.spawnflags = 3;
- this.classname = "func_assault_destructible";
- this.event_heal = destructible_heal;
- IL_PUSH(g_assault_destructibles, this);
-
- if(assault_attacker_team == NUM_TEAM_1)
- this.team = NUM_TEAM_2;
- else
- this.team = NUM_TEAM_1;
-
- spawnfunc_func_breakable(this);
-}
-
-spawnfunc(func_assault_wall)
-{
- if (!g_assault) { delete(this); return; }
-
- this.classname = "func_assault_wall";
- this.mdl = this.model;
- _setmodel(this, this.mdl);
- this.solid = SOLID_BSP;
- setthink(this, assault_wall_think);
- this.nextthink = time;
- InitializeEntity(this, assault_setenemytoobjective, INITPRIO_FINDTARGET);
-}
-
-spawnfunc(target_assault_roundend)
-{
- if (!g_assault) { delete(this); return; }
-
- this.winning = 0; // round not yet won by attackers
- this.classname = "target_assault_roundend";
- this.use = target_assault_roundend_use;
- this.cnt = 0; // first round
- this.reset = target_assault_roundend_reset;
-}
-
-spawnfunc(target_assault_roundstart)
-{
- if (!g_assault) { delete(this); return; }
-
- assault_attacker_team = NUM_TEAM_1;
- this.classname = "target_assault_roundstart";
- this.use = assault_roundstart_use;
- this.reset2 = assault_roundstart_use_this;
- InitializeEntity(this, assault_roundstart_use_this, INITPRIO_FINDTARGET);
-}
-
-// legacy bot code
-void havocbot_goalrating_ast_targets(entity this, float ratingscale)
-{
- IL_EACH(g_assault_destructibles, it.bot_attack,
- {
- if (it.target == "")
- continue;
-
- bool found = false;
- entity destr = it;
- IL_EACH(g_assault_objectivedecreasers, it.targetname == destr.target,
- {
- float hlth = GetResourceAmount(it.enemy, RESOURCE_HEALTH);
- if (hlth > 0 && hlth < ASSAULT_VALUE_INACTIVE)
- {
- found = true;
- break;
- }
- });
-
- if(!found)
- continue;
-
- vector p = 0.5 * (it.absmin + it.absmax);
-
- // Find and rate waypoints around it
- found = false;
- entity best = NULL;
- float bestvalue = 99999999999;
- entity des = it;
- for(float radius = 0; radius < 1500 && !found; radius += 500)
- {
- FOREACH_ENTITY_RADIUS(p, radius, it.classname == "waypoint" && !(it.wpflags & WAYPOINTFLAG_GENERATED),
- {
- if(checkpvs(it.origin, des))
- {
- found = true;
- if(it.cnt < bestvalue)
- {
- best = it;
- bestvalue = it.cnt;
- }
- }
- });
- }
-
- if(best)
- {
- /// dprint("waypoints around target were found\n");
- // te_lightning2(NULL, '0 0 0', best.origin);
- // te_knightspike(best.origin);
-
- navigation_routerating(this, best, ratingscale, 4000);
- best.cnt += 1;
-
- this.havocbot_attack_time = 0;
-
- if(checkpvs(this.origin + this.view_ofs, it))
- if(checkpvs(this.origin + this.view_ofs, best))
- {
- // dprint("increasing attack time for this target\n");
- this.havocbot_attack_time = time + 2;
- }
- }
- });
-}
-
-void havocbot_role_ast_offense(entity this)
-{
- if(IS_DEAD(this))
- {
- this.havocbot_attack_time = 0;
- havocbot_ast_reset_role(this);
- return;
- }
-
- // Set the role timeout if necessary
- if (!this.havocbot_role_timeout)
- this.havocbot_role_timeout = time + 120;
-
- if (time > this.havocbot_role_timeout)
- {
- havocbot_ast_reset_role(this);
- return;
- }
-
- if(this.havocbot_attack_time>time)
- return;
-
- if (navigation_goalrating_timeout(this))
- {
- navigation_goalrating_start(this);
- havocbot_goalrating_enemyplayers(this, 20000, this.origin, 650);
- havocbot_goalrating_ast_targets(this, 20000);
- havocbot_goalrating_items(this, 15000, this.origin, 10000);
- navigation_goalrating_end(this);
-
- navigation_goalrating_timeout_set(this);
- }
-}
-
-void havocbot_role_ast_defense(entity this)
-{
- if(IS_DEAD(this))
- {
- this.havocbot_attack_time = 0;
- havocbot_ast_reset_role(this);
- return;
- }
-
- // Set the role timeout if necessary
- if (!this.havocbot_role_timeout)
- this.havocbot_role_timeout = time + 120;
-
- if (time > this.havocbot_role_timeout)
- {
- havocbot_ast_reset_role(this);
- return;
- }
-
- if(this.havocbot_attack_time>time)
- return;
-
- if (navigation_goalrating_timeout(this))
- {
- navigation_goalrating_start(this);
- havocbot_goalrating_enemyplayers(this, 20000, this.origin, 3000);
- havocbot_goalrating_ast_targets(this, 20000);
- havocbot_goalrating_items(this, 15000, this.origin, 10000);
- navigation_goalrating_end(this);
-
- navigation_goalrating_timeout_set(this);
- }
-}
-
-void havocbot_role_ast_setrole(entity this, float role)
-{
- switch(role)
- {
- case HAVOCBOT_AST_ROLE_DEFENSE:
- this.havocbot_role = havocbot_role_ast_defense;
- this.havocbot_role_flags = HAVOCBOT_AST_ROLE_DEFENSE;
- this.havocbot_role_timeout = 0;
- break;
- case HAVOCBOT_AST_ROLE_OFFENSE:
- this.havocbot_role = havocbot_role_ast_offense;
- this.havocbot_role_flags = HAVOCBOT_AST_ROLE_OFFENSE;
- this.havocbot_role_timeout = 0;
- break;
- }
-}
-
-void havocbot_ast_reset_role(entity this)
-{
- if(IS_DEAD(this))
- return;
-
- if(this.team == assault_attacker_team)
- havocbot_role_ast_setrole(this, HAVOCBOT_AST_ROLE_OFFENSE);
- else
- havocbot_role_ast_setrole(this, HAVOCBOT_AST_ROLE_DEFENSE);
-}
-
-// mutator hooks
-MUTATOR_HOOKFUNCTION(as, PlayerSpawn)
-{
- entity player = M_ARGV(0, entity);
-
- if(player.team == assault_attacker_team)
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ASSAULT_ATTACKING);
- else
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ASSAULT_DEFENDING);
-}
-
-MUTATOR_HOOKFUNCTION(as, TurretSpawn)
-{
- entity turret = M_ARGV(0, entity);
-
- if(!turret.team || turret.team == FLOAT_MAX)
- turret.team = 5; // this gets reversed when match starts?
-}
-
-MUTATOR_HOOKFUNCTION(as, VehicleInit)
-{
- entity veh = M_ARGV(0, entity);
-
- veh.nextthink = time + 0.5;
-}
-
-MUTATOR_HOOKFUNCTION(as, HavocBot_ChooseRole)
-{
- entity bot = M_ARGV(0, entity);
-
- havocbot_ast_reset_role(bot);
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(as, PlayHitsound)
-{
- entity frag_victim = M_ARGV(0, entity);
-
- return (frag_victim.classname == "func_assault_destructible");
-}
-
-MUTATOR_HOOKFUNCTION(as, CheckAllowedTeams)
-{
- // assault always has 2 teams
- c1 = c2 = 0;
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(as, CheckRules_World)
-{
- M_ARGV(0, float) = WinningCondition_Assault();
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(as, ReadLevelCvars)
-{
- // incompatible
- warmup_stage = 0;
- sv_ready_restart_after_countdown = 0;
-}
-
-MUTATOR_HOOKFUNCTION(as, OnEntityPreSpawn)
-{
- entity ent = M_ARGV(0, entity);
-
- switch(ent.classname)
- {
- case "info_player_team1":
- case "info_player_team2":
- case "info_player_team3":
- case "info_player_team4":
- return true;
- }
-}
-
-MUTATOR_HOOKFUNCTION(as, ReadyRestart_Deny)
-{
- // readyrestart not supported (yet)
- return true;
-}
-#endif
+++ /dev/null
-#pragma once
-
-#ifdef SVQC
-#include <common/mutators/base.qh>
-#include <common/scores.qh>
-
-const int ASSAULT_VALUE_INACTIVE = 1000;
-
-const int ST_ASSAULT_OBJECTIVES = 1;
-
-REGISTER_MUTATOR(as, false)
-{
- MUTATOR_STATIC();
- MUTATOR_ONADD
- {
- GameRules_teams(true);
- int teams = BITS(2); // always red vs blue
- GameRules_scoring(teams, SFL_SORT_PRIO_SECONDARY, SFL_SORT_PRIO_SECONDARY, {
- field_team(ST_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY);
- field(SP_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY);
- });
- }
- return 0;
-}
-
-// sprites
-.entity assault_decreaser;
-.entity assault_sprite;
-
-// legacy bot defs
-const int HAVOCBOT_AST_ROLE_NONE = 0;
-const int HAVOCBOT_AST_ROLE_DEFENSE = 2;
-const int HAVOCBOT_AST_ROLE_OFFENSE = 4;
-
-.int havocbot_role_flags;
-.float havocbot_attack_time;
-
-void(entity this) havocbot_role_ast_defense;
-void(entity this) havocbot_role_ast_offense;
-
-void(entity bot) havocbot_ast_reset_role;
-
-void(entity this, float ratingscale, vector org, float sradius) havocbot_goalrating_items;
-void(entity this, float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers;
-
-// predefined spawnfuncs
-void target_objective_decrease_activate(entity this);
-#endif
--- /dev/null
+#include "sv_assault.qh"
+
+.entity sprite;
+#define AS_ROUND_DELAY 5
+
+IntrusiveList g_assault_destructibles;
+IntrusiveList g_assault_objectivedecreasers;
+IntrusiveList g_assault_objectives;
+STATIC_INIT(g_assault)
+{
+ g_assault_destructibles = IL_NEW();
+ g_assault_objectivedecreasers = IL_NEW();
+ g_assault_objectives = IL_NEW();
+}
+
+// random functions
+void assault_objective_use(entity this, entity actor, entity trigger)
+{
+ // activate objective
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, 100);
+ //print("^2Activated objective ", this.targetname, "=", etos(this), "\n");
+ //print("Activator is ", actor.classname, "\n");
+
+ IL_EACH(g_assault_objectivedecreasers, it.target == this.targetname,
+ {
+ target_objective_decrease_activate(it);
+ });
+}
+
+vector target_objective_spawn_evalfunc(entity this, entity player, entity spot, vector current)
+{
+ float hlth = GetResourceAmount(this, RESOURCE_HEALTH);
+ if (hlth < 0 || hlth >= ASSAULT_VALUE_INACTIVE)
+ return '-1 0 0';
+ return current;
+}
+
+// reset this objective. Used when spawning an objective
+// and when a new round starts
+void assault_objective_reset(entity this)
+{
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, ASSAULT_VALUE_INACTIVE);
+}
+
+// decrease the health of targeted objectives
+void assault_objective_decrease_use(entity this, entity actor, entity trigger)
+{
+ if(actor.team != assault_attacker_team)
+ {
+ // wrong team triggered decrease
+ return;
+ }
+
+ if(trigger.assault_sprite)
+ {
+ WaypointSprite_Disown(trigger.assault_sprite, waypointsprite_deadlifetime);
+ if(trigger.classname == "func_assault_destructible")
+ trigger.sprite = NULL; // TODO: just unsetting it?!
+ }
+ else
+ return; // already activated! cannot activate again!
+
+ float hlth = GetResourceAmount(this.enemy, RESOURCE_HEALTH);
+ if (hlth < ASSAULT_VALUE_INACTIVE)
+ {
+ if (hlth - this.dmg > 0.5)
+ {
+ GameRules_scoring_add_team(actor, SCORE, this.dmg);
+ TakeResource(this.enemy, RESOURCE_HEALTH, this.dmg);
+ }
+ else
+ {
+ GameRules_scoring_add_team(actor, SCORE, hlth);
+ GameRules_scoring_add_team(actor, ASSAULT_OBJECTIVES, 1);
+ SetResourceAmountExplicit(this.enemy, RESOURCE_HEALTH, -1);
+
+ if(this.enemy.message)
+ FOREACH_CLIENT(IS_PLAYER(it), { centerprint(it, this.enemy.message); });
+
+ SUB_UseTargets(this.enemy, this, trigger);
+ }
+ }
+}
+
+void assault_setenemytoobjective(entity this)
+{
+ IL_EACH(g_assault_objectives, it.targetname == this.target,
+ {
+ if(this.enemy == NULL)
+ this.enemy = it;
+ else
+ objerror(this, "more than one objective as target - fix the map!");
+ break;
+ });
+
+ if(this.enemy == NULL)
+ objerror(this, "no objective as target - fix the map!");
+}
+
+bool assault_decreaser_sprite_visible(entity this, entity player, entity view)
+{
+ if(GetResourceAmount(this.assault_decreaser.enemy, RESOURCE_HEALTH) >= ASSAULT_VALUE_INACTIVE)
+ return false;
+
+ return true;
+}
+
+void target_objective_decrease_activate(entity this)
+{
+ entity spr;
+ this.owner = NULL;
+ FOREACH_ENTITY_STRING(target, this.targetname,
+ {
+ if(it.assault_sprite != NULL)
+ {
+ WaypointSprite_Disown(it.assault_sprite, waypointsprite_deadlifetime);
+ if(it.classname == "func_assault_destructible")
+ it.sprite = NULL; // TODO: just unsetting it?!
+ }
+
+ spr = WaypointSprite_SpawnFixed(WP_AssaultDefend, 0.5 * (it.absmin + it.absmax), it, assault_sprite, RADARICON_OBJECTIVE);
+ spr.assault_decreaser = this;
+ spr.waypointsprite_visible_for_player = assault_decreaser_sprite_visible;
+ spr.classname = "sprite_waypoint";
+ WaypointSprite_UpdateRule(spr, assault_attacker_team, SPRITERULE_TEAMPLAY);
+ if(it.classname == "func_assault_destructible")
+ {
+ WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultDestroy, WP_AssaultDestroy);
+ WaypointSprite_UpdateMaxHealth(spr, it.max_health);
+ WaypointSprite_UpdateHealth(spr, GetResourceAmount(it, RESOURCE_HEALTH));
+ it.sprite = spr;
+ }
+ else
+ WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultPush, WP_AssaultPush);
+ });
+}
+
+void target_objective_decrease_findtarget(entity this)
+{
+ assault_setenemytoobjective(this);
+}
+
+void target_assault_roundend_reset(entity this)
+{
+ //print("round end reset\n");
+ ++this.cnt; // up round counter
+ this.winning = false; // up round
+}
+
+void target_assault_roundend_use(entity this, entity actor, entity trigger)
+{
+ this.winning = 1; // round has been won by attackers
+}
+
+void assault_roundstart_use(entity this, entity actor, entity trigger)
+{
+ SUB_UseTargets(this, this, trigger);
+
+ //(Re)spawn all turrets
+ IL_EACH(g_turrets, true,
+ {
+ // Swap turret teams
+ if(it.team == NUM_TEAM_1)
+ it.team = NUM_TEAM_2;
+ else
+ it.team = NUM_TEAM_1;
+
+ // Doubles as teamchange
+ turret_respawn(it);
+ });
+}
+void assault_roundstart_use_this(entity this)
+{
+ assault_roundstart_use(this, NULL, NULL);
+}
+
+void assault_wall_think(entity this)
+{
+ if(GetResourceAmount(this.enemy, RESOURCE_HEALTH) < 0)
+ {
+ this.model = "";
+ this.solid = SOLID_NOT;
+ }
+ else
+ {
+ this.model = this.mdl;
+ this.solid = SOLID_BSP;
+ }
+
+ this.nextthink = time + 0.2;
+}
+
+// trigger new round
+// reset objectives, toggle spawnpoints, reset triggers, ...
+void assault_new_round(entity this)
+{
+ //bprint("ASSAULT: new round\n");
+
+ // up round counter
+ this.winning = this.winning + 1;
+
+ // swap attacker/defender roles
+ if(assault_attacker_team == NUM_TEAM_1)
+ assault_attacker_team = NUM_TEAM_2;
+ else
+ assault_attacker_team = NUM_TEAM_1;
+
+ IL_EACH(g_saved_team, !IS_CLIENT(it),
+ {
+ if(it.team_saved == NUM_TEAM_1)
+ it.team_saved = NUM_TEAM_2;
+ else if(it.team_saved == NUM_TEAM_2)
+ it.team_saved = NUM_TEAM_1;
+ });
+
+ // reset the level with a countdown
+ cvar_set("timelimit", ftos(ceil(time - AS_ROUND_DELAY - game_starttime) / 60));
+ ReadyRestart_force(); // sets game_starttime
+}
+
+entity as_round;
+.entity ent_winning;
+void as_round_think()
+{
+ game_stopped = false;
+ assault_new_round(as_round.ent_winning);
+ delete(as_round);
+ as_round = NULL;
+}
+
+// Assault winning condition: If the attackers triggered a round end (by fulfilling all objectives)
+// they win. Otherwise the defending team wins once the timelimit passes.
+int WinningCondition_Assault()
+{
+ if(as_round)
+ return WINNING_NO;
+
+ WinningConditionHelper(NULL); // set worldstatus
+
+ int status = WINNING_NO;
+ // as the timelimit has not yet passed just assume the defending team will win
+ if(assault_attacker_team == NUM_TEAM_1)
+ {
+ SetWinners(team, NUM_TEAM_2);
+ }
+ else
+ {
+ SetWinners(team, NUM_TEAM_1);
+ }
+
+ entity ent;
+ ent = find(NULL, classname, "target_assault_roundend");
+ if(ent)
+ {
+ if(ent.winning) // round end has been triggered by attacking team
+ {
+ bprint("Assault: round completed.\n");
+ SetWinners(team, assault_attacker_team);
+
+ TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 666 - TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 0));
+
+ if(ent.cnt == 1 || autocvar_g_campaign) // this was the second round
+ {
+ status = WINNING_YES;
+ }
+ else
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ASSAULT_OBJ_DESTROYED, ceil(time - game_starttime));
+ as_round = new(as_round);
+ as_round.think = as_round_think;
+ as_round.ent_winning = ent;
+ as_round.nextthink = time + AS_ROUND_DELAY;
+ game_stopped = true;
+
+ // make sure timelimit isn't hit while the game is blocked
+ if(autocvar_timelimit > 0)
+ if(time + AS_ROUND_DELAY >= game_starttime + autocvar_timelimit * 60)
+ cvar_set("timelimit", ftos(autocvar_timelimit + AS_ROUND_DELAY / 60));
+ }
+ }
+ }
+
+ return status;
+}
+
+// spawnfuncs
+spawnfunc(info_player_attacker)
+{
+ if (!g_assault) { delete(this); return; }
+
+ this.team = NUM_TEAM_1; // red, gets swapped every round
+ spawnfunc_info_player_deathmatch(this);
+}
+
+spawnfunc(info_player_defender)
+{
+ if (!g_assault) { delete(this); return; }
+
+ this.team = NUM_TEAM_2; // blue, gets swapped every round
+ spawnfunc_info_player_deathmatch(this);
+}
+
+spawnfunc(target_objective)
+{
+ if (!g_assault) { delete(this); return; }
+
+ this.classname = "target_objective";
+ IL_PUSH(g_assault_objectives, this);
+ this.use = assault_objective_use;
+ this.reset = assault_objective_reset;
+ this.reset(this);
+ this.spawn_evalfunc = target_objective_spawn_evalfunc;
+}
+
+spawnfunc(target_objective_decrease)
+{
+ if (!g_assault) { delete(this); return; }
+
+ this.classname = "target_objective_decrease";
+ IL_PUSH(g_assault_objectivedecreasers, this);
+
+ if(!this.dmg)
+ this.dmg = 101;
+
+ this.use = assault_objective_decrease_use;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, ASSAULT_VALUE_INACTIVE);
+ this.max_health = ASSAULT_VALUE_INACTIVE;
+ this.enemy = NULL;
+
+ InitializeEntity(this, target_objective_decrease_findtarget, INITPRIO_FINDTARGET);
+}
+
+// destructible walls that can be used to trigger target_objective_decrease
+bool destructible_heal(entity targ, entity inflictor, float amount, float limit)
+{
+ float true_limit = ((limit != RESOURCE_LIMIT_NONE) ? limit : targ.max_health);
+ float hlth = GetResourceAmount(targ, RESOURCE_HEALTH);
+ if (hlth <= 0 || hlth >= true_limit)
+ return false;
+
+ GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, true_limit);
+ if(targ.sprite)
+ {
+ WaypointSprite_UpdateHealth(targ.sprite, GetResourceAmount(targ, RESOURCE_HEALTH));
+ }
+ func_breakable_colormod(targ);
+ return true;
+}
+
+spawnfunc(func_breakable);
+spawnfunc(func_assault_destructible)
+{
+ if (!g_assault) { delete(this); return; }
+
+ this.spawnflags = 3;
+ this.classname = "func_assault_destructible";
+ this.event_heal = destructible_heal;
+ IL_PUSH(g_assault_destructibles, this);
+
+ if(assault_attacker_team == NUM_TEAM_1)
+ this.team = NUM_TEAM_2;
+ else
+ this.team = NUM_TEAM_1;
+
+ spawnfunc_func_breakable(this);
+}
+
+spawnfunc(func_assault_wall)
+{
+ if (!g_assault) { delete(this); return; }
+
+ this.classname = "func_assault_wall";
+ this.mdl = this.model;
+ _setmodel(this, this.mdl);
+ this.solid = SOLID_BSP;
+ setthink(this, assault_wall_think);
+ this.nextthink = time;
+ InitializeEntity(this, assault_setenemytoobjective, INITPRIO_FINDTARGET);
+}
+
+spawnfunc(target_assault_roundend)
+{
+ if (!g_assault) { delete(this); return; }
+
+ this.winning = 0; // round not yet won by attackers
+ this.classname = "target_assault_roundend";
+ this.use = target_assault_roundend_use;
+ this.cnt = 0; // first round
+ this.reset = target_assault_roundend_reset;
+}
+
+spawnfunc(target_assault_roundstart)
+{
+ if (!g_assault) { delete(this); return; }
+
+ assault_attacker_team = NUM_TEAM_1;
+ this.classname = "target_assault_roundstart";
+ this.use = assault_roundstart_use;
+ this.reset2 = assault_roundstart_use_this;
+ InitializeEntity(this, assault_roundstart_use_this, INITPRIO_FINDTARGET);
+}
+
+// legacy bot code
+void havocbot_goalrating_ast_targets(entity this, float ratingscale)
+{
+ IL_EACH(g_assault_destructibles, it.bot_attack,
+ {
+ if (it.target == "")
+ continue;
+
+ bool found = false;
+ entity destr = it;
+ IL_EACH(g_assault_objectivedecreasers, it.targetname == destr.target,
+ {
+ float hlth = GetResourceAmount(it.enemy, RESOURCE_HEALTH);
+ if (hlth > 0 && hlth < ASSAULT_VALUE_INACTIVE)
+ {
+ found = true;
+ break;
+ }
+ });
+
+ if(!found)
+ continue;
+
+ vector p = 0.5 * (it.absmin + it.absmax);
+
+ // Find and rate waypoints around it
+ found = false;
+ entity best = NULL;
+ float bestvalue = FLOAT_MAX;
+ entity des = it;
+ for (float radius = 500; radius <= 1500 && !found; radius += 500)
+ {
+ FOREACH_ENTITY_RADIUS(p, radius, it.classname == "waypoint" && !(it.wpflags & WAYPOINTFLAG_GENERATED),
+ {
+ if(checkpvs(it.origin, des))
+ {
+ found = true;
+ if(it.cnt < bestvalue)
+ {
+ best = it;
+ bestvalue = it.cnt;
+ }
+ }
+ });
+ }
+
+ if(best)
+ {
+ /// dprint("waypoints around target were found\n");
+ // te_lightning2(NULL, '0 0 0', best.origin);
+ // te_knightspike(best.origin);
+
+ navigation_routerating(this, best, ratingscale, 4000);
+ best.cnt += 1;
+
+ this.havocbot_attack_time = 0;
+
+ if(checkpvs(this.origin + this.view_ofs, it))
+ if(checkpvs(this.origin + this.view_ofs, best))
+ {
+ // dprint("increasing attack time for this target\n");
+ this.havocbot_attack_time = time + 2;
+ }
+ }
+ });
+}
+
+void havocbot_role_ast_offense(entity this)
+{
+ if(IS_DEAD(this))
+ {
+ this.havocbot_attack_time = 0;
+ havocbot_ast_reset_role(this);
+ return;
+ }
+
+ // Set the role timeout if necessary
+ if (!this.havocbot_role_timeout)
+ this.havocbot_role_timeout = time + 120;
+
+ if (time > this.havocbot_role_timeout)
+ {
+ havocbot_ast_reset_role(this);
+ return;
+ }
+
+ if(this.havocbot_attack_time>time)
+ return;
+
+ if (navigation_goalrating_timeout(this))
+ {
+ // role: offense
+ navigation_goalrating_start(this);
+ havocbot_goalrating_enemyplayers(this, 10000, this.origin, 650);
+ havocbot_goalrating_ast_targets(this, 20000);
+ havocbot_goalrating_items(this, 30000, this.origin, 10000);
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+void havocbot_role_ast_defense(entity this)
+{
+ if(IS_DEAD(this))
+ {
+ this.havocbot_attack_time = 0;
+ havocbot_ast_reset_role(this);
+ return;
+ }
+
+ // Set the role timeout if necessary
+ if (!this.havocbot_role_timeout)
+ this.havocbot_role_timeout = time + 120;
+
+ if (time > this.havocbot_role_timeout)
+ {
+ havocbot_ast_reset_role(this);
+ return;
+ }
+
+ if(this.havocbot_attack_time>time)
+ return;
+
+ if (navigation_goalrating_timeout(this))
+ {
+ // role: defense
+ navigation_goalrating_start(this);
+ havocbot_goalrating_enemyplayers(this, 10000, this.origin, 3000);
+ havocbot_goalrating_ast_targets(this, 20000);
+ havocbot_goalrating_items(this, 30000, this.origin, 10000);
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+void havocbot_role_ast_setrole(entity this, float role)
+{
+ switch(role)
+ {
+ case HAVOCBOT_AST_ROLE_DEFENSE:
+ this.havocbot_role = havocbot_role_ast_defense;
+ this.havocbot_role_timeout = 0;
+ break;
+ case HAVOCBOT_AST_ROLE_OFFENSE:
+ this.havocbot_role = havocbot_role_ast_offense;
+ this.havocbot_role_timeout = 0;
+ break;
+ }
+}
+
+void havocbot_ast_reset_role(entity this)
+{
+ if(IS_DEAD(this))
+ return;
+
+ if(this.team == assault_attacker_team)
+ havocbot_role_ast_setrole(this, HAVOCBOT_AST_ROLE_OFFENSE);
+ else
+ havocbot_role_ast_setrole(this, HAVOCBOT_AST_ROLE_DEFENSE);
+}
+
+// mutator hooks
+MUTATOR_HOOKFUNCTION(as, PlayerSpawn)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(player.team == assault_attacker_team)
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ASSAULT_ATTACKING);
+ else
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ASSAULT_DEFENDING);
+}
+
+MUTATOR_HOOKFUNCTION(as, TurretSpawn)
+{
+ entity turret = M_ARGV(0, entity);
+
+ if(!turret.team || turret.team == FLOAT_MAX)
+ turret.team = 5; // this gets reversed when match starts?
+}
+
+MUTATOR_HOOKFUNCTION(as, VehicleInit)
+{
+ entity veh = M_ARGV(0, entity);
+
+ veh.nextthink = time + 0.5;
+}
+
+MUTATOR_HOOKFUNCTION(as, HavocBot_ChooseRole)
+{
+ entity bot = M_ARGV(0, entity);
+
+ havocbot_ast_reset_role(bot);
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(as, PlayHitsound)
+{
+ entity frag_victim = M_ARGV(0, entity);
+
+ return (frag_victim.classname == "func_assault_destructible");
+}
+
+MUTATOR_HOOKFUNCTION(as, TeamBalance_CheckAllowedTeams)
+{
+ // assault always has 2 teams
+ M_ARGV(0, float) = BIT(0) | BIT(1);
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(as, CheckRules_World)
+{
+ M_ARGV(0, float) = WinningCondition_Assault();
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(as, ReadLevelCvars)
+{
+ // incompatible
+ warmup_stage = 0;
+ sv_ready_restart_after_countdown = 0;
+}
+
+MUTATOR_HOOKFUNCTION(as, OnEntityPreSpawn)
+{
+ entity ent = M_ARGV(0, entity);
+
+ switch(ent.classname)
+ {
+ case "info_player_team1":
+ case "info_player_team2":
+ case "info_player_team3":
+ case "info_player_team4":
+ return true;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(as, ReadyRestart_Deny)
+{
+ // readyrestart not supported (yet)
+ return true;
+}
--- /dev/null
+#pragma once
+
+#include <common/mutators/base.qh>
+#include <common/scores.qh>
+
+const int ASSAULT_VALUE_INACTIVE = 1000;
+
+const int ST_ASSAULT_OBJECTIVES = 1;
+
+REGISTER_MUTATOR(as, false)
+{
+ MUTATOR_STATIC();
+ MUTATOR_ONADD
+ {
+ GameRules_teams(true);
+ int teams = BITS(2); // always red vs blue
+ GameRules_scoring(teams, SFL_SORT_PRIO_SECONDARY, SFL_SORT_PRIO_SECONDARY, {
+ field_team(ST_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY);
+ field(SP_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY);
+ });
+ }
+ return 0;
+}
+
+// sprites
+.entity assault_decreaser;
+.entity assault_sprite;
+
+// legacy bot defs
+const int HAVOCBOT_AST_ROLE_NONE = 0;
+const int HAVOCBOT_AST_ROLE_DEFENSE = 2;
+const int HAVOCBOT_AST_ROLE_OFFENSE = 4;
+
+.float havocbot_attack_time;
+
+void(entity this) havocbot_role_ast_defense;
+void(entity this) havocbot_role_ast_offense;
+
+void(entity bot) havocbot_ast_reset_role;
+
+void(entity this, float ratingscale, vector org, float sradius) havocbot_goalrating_items;
+void(entity this, float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers;
+
+// assault game mode: Which team is attacking in this round?
+float assault_attacker_team;
+
+// predefined spawnfuncs
+void target_objective_decrease_activate(entity this);
// generated file; do not modify
-#include <common/gamemodes/gamemode/clanarena/clanarena.qc>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/clanarena/sv_clanarena.qc>
+#endif
// generated file; do not modify
-#include <common/gamemodes/gamemode/clanarena/clanarena.qh>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/clanarena/sv_clanarena.qh>
+#endif
+++ /dev/null
-#include "clanarena.qh"
-
-// TODO: split into sv_clanarena
-#ifdef SVQC
-float autocvar_g_ca_damage2score_multiplier;
-bool autocvar_g_ca_spectate_enemies;
-
-void CA_count_alive_players()
-{
- total_players = redalive = bluealive = yellowalive = pinkalive = 0;
- FOREACH_CLIENT(IS_PLAYER(it), {
- switch(it.team)
- {
- case NUM_TEAM_1: ++total_players; if(!IS_DEAD(it)) ++redalive; break;
- case NUM_TEAM_2: ++total_players; if(!IS_DEAD(it)) ++bluealive; break;
- case NUM_TEAM_3: ++total_players; if(!IS_DEAD(it)) ++yellowalive; break;
- case NUM_TEAM_4: ++total_players; if(!IS_DEAD(it)) ++pinkalive; break;
- }
- });
- FOREACH_CLIENT(IS_REAL_CLIENT(it), {
- STAT(REDALIVE, it) = redalive;
- STAT(BLUEALIVE, it) = bluealive;
- STAT(YELLOWALIVE, it) = yellowalive;
- STAT(PINKALIVE, it) = pinkalive;
- });
-}
-
-float CA_GetWinnerTeam()
-{
- float winner_team = 0;
- if(redalive >= 1)
- winner_team = NUM_TEAM_1;
- if(bluealive >= 1)
- {
- if(winner_team) return 0;
- winner_team = NUM_TEAM_2;
- }
- if(yellowalive >= 1)
- {
- if(winner_team) return 0;
- winner_team = NUM_TEAM_3;
- }
- if(pinkalive >= 1)
- {
- if(winner_team) return 0;
- winner_team = NUM_TEAM_4;
- }
- if(winner_team)
- return winner_team;
- return -1; // no player left
-}
-
-void nades_Clear(entity player);
-
-#define CA_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0))
-#define CA_ALIVE_TEAMS_OK() (CA_ALIVE_TEAMS() == NumTeams(ca_teams))
-float CA_CheckWinner()
-{
- if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
- FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); });
-
- allowed_to_spawn = false;
- game_stopped = true;
- round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
- return 1;
- }
-
- CA_count_alive_players();
- if(CA_ALIVE_TEAMS() > 1)
- return 0;
-
- int winner_team = CA_GetWinnerTeam();
- if(winner_team > 0)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
- TeamScore_AddToTeam(winner_team, ST_CA_ROUNDS, +1);
- }
- else if(winner_team == -1)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
- }
-
- allowed_to_spawn = false;
- game_stopped = true;
- round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
-
- FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); });
-
- return 1;
-}
-
-void CA_RoundStart()
-{
- allowed_to_spawn = boolean(warmup_stage);
-}
-
-bool CA_CheckTeams()
-{
- static int prev_missing_teams_mask;
- allowed_to_spawn = true;
- CA_count_alive_players();
- if(CA_ALIVE_TEAMS_OK())
- {
- if(prev_missing_teams_mask > 0)
- Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
- prev_missing_teams_mask = -1;
- return true;
- }
- if(total_players == 0)
- {
- if(prev_missing_teams_mask > 0)
- Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
- prev_missing_teams_mask = -1;
- return false;
- }
- int missing_teams_mask = 0;
- if(ca_teams & BIT(0))
- missing_teams_mask += (!redalive) * 1;
- if(ca_teams & BIT(1))
- missing_teams_mask += (!bluealive) * 2;
- if(ca_teams & BIT(2))
- missing_teams_mask += (!yellowalive) * 4;
- if(ca_teams & BIT(3))
- missing_teams_mask += (!pinkalive) * 8;
- if(prev_missing_teams_mask != missing_teams_mask)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
- prev_missing_teams_mask = missing_teams_mask;
- }
- return false;
-}
-
-bool ca_isEliminated(entity e)
-{
- if(e.caplayer == 1 && (IS_DEAD(e) || e.frags == FRAGS_LMS_LOSER))
- return true;
- if(e.caplayer == 0.5)
- return true;
- return false;
-}
-
-/** Returns next available player to spectate if g_ca_spectate_enemies == 0 */
-entity CA_SpectateNext(entity player, entity start)
-{
- if (SAME_TEAM(start, player)) return start;
- // continue from current player
- for (entity e = start; (e = find(e, classname, STR_PLAYER)); )
- {
- if (SAME_TEAM(player, e)) return e;
- }
- // restart from begining
- for (entity e = NULL; (e = find(e, classname, STR_PLAYER)); )
- {
- if (SAME_TEAM(player, e)) return e;
- }
- return start;
-}
-
-
-MUTATOR_HOOKFUNCTION(ca, PlayerSpawn)
-{
- entity player = M_ARGV(0, entity);
-
- player.caplayer = 1;
- if (!warmup_stage)
- eliminatedPlayers.SendFlags |= 1;
-}
-
-MUTATOR_HOOKFUNCTION(ca, ForbidSpawn)
-{
- entity player = M_ARGV(0, entity);
-
- // spectators / observers that weren't playing can join; they are
- // immediately forced to observe in the PutClientInServer hook
- // this way they are put in a team and can play in the next round
- if (!allowed_to_spawn && player.caplayer)
- return true;
- return false;
-}
-
-MUTATOR_HOOKFUNCTION(ca, PutClientInServer)
-{
- entity player = M_ARGV(0, entity);
-
- if (!allowed_to_spawn && IS_PLAYER(player)) // this is true even when player is trying to join
- {
- TRANSMUTE(Observer, player);
- if (CS(player).jointime != time && !player.caplayer) // not when connecting
- {
- player.caplayer = 0.5;
- Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(ca, reset_map_players)
-{
- FOREACH_CLIENT(true, {
- CS(it).killcount = 0;
- if (!it.caplayer && IS_BOT_CLIENT(it))
- {
- it.team = -1;
- it.caplayer = 1;
- }
- if (it.caplayer)
- {
- TRANSMUTE(Player, it);
- it.caplayer = 1;
- PutClientInServer(it);
- }
- });
- bot_relinkplayerlist();
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, ClientConnect)
-{
- entity player = M_ARGV(0, entity);
-
- TRANSMUTE(Observer, player);
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, reset_map_global)
-{
- allowed_to_spawn = true;
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
-{
- M_ARGV(0, float) = ca_teams;
-}
-
-entity ca_LastPlayerForTeam(entity this)
-{
- entity last_pl = NULL;
- FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
- if (!IS_DEAD(it))
- if (SAME_TEAM(this, it))
- if (!last_pl)
- last_pl = it;
- else
- return NULL;
- });
- return last_pl;
-}
-
-void ca_LastPlayerForTeam_Notify(entity this)
-{
- if (!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
- {
- entity pl = ca_LastPlayerForTeam(this);
- if (pl)
- Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
- }
-}
-
-MUTATOR_HOOKFUNCTION(ca, PlayerDies)
-{
- entity frag_target = M_ARGV(2, entity);
-
- ca_LastPlayerForTeam_Notify(frag_target);
- if (!allowed_to_spawn)
- {
- frag_target.respawn_flags = RESPAWN_SILENT;
- // prevent unwanted sudden rejoin as spectator and movement of spectator camera
- frag_target.respawn_time = time + 2;
- }
- frag_target.respawn_flags |= RESPAWN_FORCE;
- if (!warmup_stage)
- {
- eliminatedPlayers.SendFlags |= 1;
- if (IS_BOT_CLIENT(frag_target))
- bot_clear(frag_target);
- }
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, ClientDisconnect)
-{
- entity player = M_ARGV(0, entity);
-
- if (player.caplayer == 1)
- ca_LastPlayerForTeam_Notify(player);
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, MakePlayerObserver)
-{
- entity player = M_ARGV(0, entity);
-
- if (!IS_DEAD(player))
- ca_LastPlayerForTeam_Notify(player);
- if (player.killindicator_teamchange == -2) // player wants to spectate
- player.caplayer = 0;
- if (player.caplayer)
- player.frags = FRAGS_LMS_LOSER;
- if (!warmup_stage)
- eliminatedPlayers.SendFlags |= 1;
- if (!player.caplayer)
- return false; // allow team reset
- return true; // prevent team reset
-}
-
-MUTATOR_HOOKFUNCTION(ca, ForbidThrowCurrentWeapon)
-{
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, GiveFragsForKill, CBC_ORDER_FIRST)
-{
- M_ARGV(2, float) = 0; // score will be given to the winner team when the round ends
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, SetStartItems)
-{
- start_items &= ~IT_UNLIMITED_AMMO;
- start_health = warmup_start_health = cvar("g_lms_start_health");
- start_armorvalue = warmup_start_armorvalue = cvar("g_lms_start_armor");
- start_ammo_shells = warmup_start_ammo_shells = cvar("g_lms_start_ammo_shells");
- start_ammo_nails = warmup_start_ammo_nails = cvar("g_lms_start_ammo_nails");
- start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
- start_ammo_cells = warmup_start_ammo_cells = cvar("g_lms_start_ammo_cells");
- start_ammo_plasma = warmup_start_ammo_plasma = cvar("g_lms_start_ammo_plasma");
- start_ammo_fuel = warmup_start_ammo_fuel = cvar("g_lms_start_ammo_fuel");
-}
-
-MUTATOR_HOOKFUNCTION(ca, Damage_Calculate)
-{
- entity frag_attacker = M_ARGV(1, entity);
- entity frag_target = M_ARGV(2, entity);
- float frag_deathtype = M_ARGV(3, float);
- float frag_damage = M_ARGV(4, float);
- float frag_mirrordamage = M_ARGV(5, float);
-
- if (IS_PLAYER(frag_target))
- if (!IS_DEAD(frag_target))
- if (frag_target == frag_attacker || SAME_TEAM(frag_target, frag_attacker) || frag_deathtype == DEATH_FALL.m_id)
- frag_damage = 0;
-
- frag_mirrordamage = 0;
-
- M_ARGV(4, float) = frag_damage;
- M_ARGV(5, float) = frag_mirrordamage;
-}
-
-MUTATOR_HOOKFUNCTION(ca, FilterItem)
-{
- entity item = M_ARGV(0, entity);
-
- if (autocvar_g_powerups <= 0)
- if (item.flags & FL_POWERUP)
- return true;
-
- if (autocvar_g_pickup_items <= 0)
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, PlayerDamage_SplitHealthArmor)
-{
- entity frag_attacker = M_ARGV(1, entity);
- entity frag_target = M_ARGV(2, entity);
- float frag_damage = M_ARGV(7, float);
- float damage_take = M_ARGV(4, float);
- float damage_save = M_ARGV(5, float);
-
- float excess = max(0, frag_damage - damage_take - damage_save);
-
- if (frag_target != frag_attacker && IS_PLAYER(frag_attacker))
- GameRules_scoring_add_team(frag_attacker, SCORE, (frag_damage - excess) * autocvar_g_ca_damage2score_multiplier);
-}
-
-MUTATOR_HOOKFUNCTION(ca, CalculateRespawnTime)
-{
- // no respawn calculations needed, player is forced to spectate anyway
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, PlayerRegen)
-{
- // no regeneration in CA
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, Scores_CountFragsRemaining)
-{
- // announce remaining frags
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, SpectateSet)
-{
- entity client = M_ARGV(0, entity);
- entity targ = M_ARGV(1, entity);
-
- if (!autocvar_g_ca_spectate_enemies && client.caplayer)
- if (DIFF_TEAM(targ, client))
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, SpectateNext)
-{
- entity client = M_ARGV(0, entity);
-
- if (!autocvar_g_ca_spectate_enemies && client.caplayer)
- {
- entity targ = M_ARGV(1, entity);
- M_ARGV(1, entity) = CA_SpectateNext(client, targ);
- return true;
- }
-}
-
-MUTATOR_HOOKFUNCTION(ca, SpectatePrev)
-{
- entity client = M_ARGV(0, entity);
- entity targ = M_ARGV(1, entity);
- entity first = M_ARGV(2, entity);
-
- if (!autocvar_g_ca_spectate_enemies && client.caplayer)
- {
- do { targ = targ.chain; }
- while(targ && DIFF_TEAM(targ, client));
-
- if (!targ)
- {
- for (targ = first; targ && DIFF_TEAM(targ, client); targ = targ.chain);
-
- if (targ == client.enemy)
- return MUT_SPECPREV_RETURN;
- }
- }
-
- M_ARGV(1, entity) = targ;
-
- return MUT_SPECPREV_FOUND;
-}
-
-MUTATOR_HOOKFUNCTION(ca, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
-{
- FOREACH_CLIENT(IS_REAL_CLIENT(it), {
- if (IS_PLAYER(it) || it.caplayer == 1)
- ++M_ARGV(0, int);
- ++M_ARGV(1, int);
- });
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, ClientCommand_Spectate)
-{
- entity player = M_ARGV(0, entity);
-
- if (player.caplayer)
- {
- // they're going to spec, we can do other checks
- if (autocvar_sv_spectate && (IS_SPEC(player) || IS_OBSERVER(player)))
- Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_LEAVE);
- return MUT_SPECCMD_FORCE;
- }
-
- return MUT_SPECCMD_CONTINUE;
-}
-
-MUTATOR_HOOKFUNCTION(ca, WantWeapon)
-{
- M_ARGV(2, bool) = true; // all weapons
-}
-
-MUTATOR_HOOKFUNCTION(ca, HideTeamNagger)
-{
- return true; // doesn't work well with the whole spectator as player thing
-}
-
-MUTATOR_HOOKFUNCTION(ca, GetPlayerStatus)
-{
- entity player = M_ARGV(0, entity);
-
- return player.caplayer == 1;
-}
-
-MUTATOR_HOOKFUNCTION(ca, SetWeaponArena)
-{
- // most weapons arena
- if (M_ARGV(0, string) == "0" || M_ARGV(0, string) == "") M_ARGV(0, string) = "most";
-}
-#endif
+++ /dev/null
-#pragma once
-
-#ifdef SVQC
-#include <common/mutators/base.qh>
-#include <server/round_handler.qh>
-#include <server/miscfunctions.qh>
-
-int autocvar_g_ca_point_limit;
-int autocvar_g_ca_point_leadlimit;
-float autocvar_g_ca_round_timelimit;
-bool autocvar_g_ca_team_spawns;
-//int autocvar_g_ca_teams;
-int autocvar_g_ca_teams_override;
-float autocvar_g_ca_warmup;
-
-
-int ca_teams;
-bool allowed_to_spawn;
-
-const int ST_CA_ROUNDS = 1;
-
-bool CA_CheckTeams();
-bool CA_CheckWinner();
-void CA_RoundStart();
-bool ca_isEliminated(entity e);
-
-REGISTER_MUTATOR(ca, false)
-{
- MUTATOR_STATIC();
- MUTATOR_ONADD
- {
- GameRules_teams(true);
- GameRules_spawning_teams(autocvar_g_ca_team_spawns);
- GameRules_limit_score(autocvar_g_ca_point_limit);
- GameRules_limit_lead(autocvar_g_ca_point_leadlimit);
-
- ca_teams = autocvar_g_ca_teams_override;
- if (ca_teams < 2)
- ca_teams = cvar("g_ca_teams"); // read the cvar directly as it gets written earlier in the same frame
-
- ca_teams = BITS(bound(2, ca_teams, 4));
- GameRules_scoring(ca_teams, SFL_SORT_PRIO_PRIMARY, 0, {
- field_team(ST_CA_ROUNDS, "rounds", SFL_SORT_PRIO_PRIMARY);
- });
-
- allowed_to_spawn = true;
- round_handler_Spawn(CA_CheckTeams, CA_CheckWinner, CA_RoundStart);
- round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
- EliminatedPlayers_Init(ca_isEliminated);
- }
- return 0;
-}
-
-// should be removed in the future, as other code should not have to care
-.float caplayer; // 0.5 if scheduled to join the next round
-#endif
--- /dev/null
+#include "sv_clanarena.qh"
+
+float autocvar_g_ca_damage2score_multiplier;
+bool autocvar_g_ca_spectate_enemies;
+
+void CA_count_alive_players()
+{
+ total_players = 0;
+ for (int i = 1; i <= NUM_TEAMS; ++i)
+ {
+ Team_SetNumberOfAlivePlayers(Team_GetTeamFromIndex(i), 0);
+ }
+ FOREACH_CLIENT(IS_PLAYER(it) && Entity_HasValidTeam(it),
+ {
+ ++total_players;
+ if (IS_DEAD(it))
+ {
+ continue;
+ }
+ entity team_ = Entity_GetTeam(it);
+ int num_alive = Team_GetNumberOfAlivePlayers(team_);
+ ++num_alive;
+ Team_SetNumberOfAlivePlayers(team_, num_alive);
+ });
+ FOREACH_CLIENT(IS_REAL_CLIENT(it),
+ {
+ STAT(REDALIVE, it) = Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(
+ 1));
+ STAT(BLUEALIVE, it) = Team_GetNumberOfAlivePlayers(
+ Team_GetTeamFromIndex(2));
+ STAT(YELLOWALIVE, it) = Team_GetNumberOfAlivePlayers(
+ Team_GetTeamFromIndex(3));
+ STAT(PINKALIVE, it) = Team_GetNumberOfAlivePlayers(
+ Team_GetTeamFromIndex(4));
+ });
+}
+
+int CA_GetWinnerTeam()
+{
+ int winner_team = 0;
+ if (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(1)) >= 1)
+ {
+ winner_team = NUM_TEAM_1;
+ }
+ for (int i = 2; i <= NUM_TEAMS; ++i)
+ {
+ if (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) >= 1)
+ {
+ if (winner_team != 0)
+ {
+ return 0;
+ }
+ winner_team = Team_IndexToTeam(i);
+ }
+ }
+ if (winner_team)
+ {
+ return winner_team;
+ }
+ return -1; // no player left
+}
+
+void nades_Clear(entity player);
+
+#define CA_ALIVE_TEAMS_OK() (Team_GetNumberOfAliveTeams() == NumTeams(ca_teams))
+float CA_CheckWinner()
+{
+ if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
+ FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); });
+
+ allowed_to_spawn = false;
+ game_stopped = true;
+ round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
+ return 1;
+ }
+
+ CA_count_alive_players();
+ if (Team_GetNumberOfAliveTeams() > 1)
+ {
+ return 0;
+ }
+
+ int winner_team = CA_GetWinnerTeam();
+ if(winner_team > 0)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
+ TeamScore_AddToTeam(winner_team, ST_CA_ROUNDS, +1);
+ }
+ else if(winner_team == -1)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
+ }
+
+ allowed_to_spawn = false;
+ game_stopped = true;
+ round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
+
+ FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); });
+
+ return 1;
+}
+
+void CA_RoundStart()
+{
+ allowed_to_spawn = boolean(warmup_stage);
+}
+
+bool CA_CheckTeams()
+{
+ static int prev_missing_teams_mask;
+ allowed_to_spawn = true;
+ CA_count_alive_players();
+ if(CA_ALIVE_TEAMS_OK())
+ {
+ if(prev_missing_teams_mask > 0)
+ Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+ prev_missing_teams_mask = -1;
+ return true;
+ }
+ if(total_players == 0)
+ {
+ if(prev_missing_teams_mask > 0)
+ Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+ prev_missing_teams_mask = -1;
+ return false;
+ }
+ int missing_teams_mask = 0;
+ for (int i = 1; i <= NUM_TEAMS; ++i)
+ {
+ if ((ca_teams & Team_IndexToBit(i)) &&
+ (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) == 0))
+ {
+ missing_teams_mask |= Team_IndexToBit(i);
+ }
+ }
+ if(prev_missing_teams_mask != missing_teams_mask)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
+ prev_missing_teams_mask = missing_teams_mask;
+ }
+ return false;
+}
+
+bool ca_isEliminated(entity e)
+{
+ if(e.caplayer == 1 && (IS_DEAD(e) || e.frags == FRAGS_LMS_LOSER))
+ return true;
+ if(e.caplayer == 0.5)
+ return true;
+ return false;
+}
+
+/** Returns next available player to spectate if g_ca_spectate_enemies == 0 */
+entity CA_SpectateNext(entity player, entity start)
+{
+ if (SAME_TEAM(start, player)) return start;
+ // continue from current player
+ for (entity e = start; (e = find(e, classname, STR_PLAYER)); )
+ {
+ if (SAME_TEAM(player, e)) return e;
+ }
+ // restart from begining
+ for (entity e = NULL; (e = find(e, classname, STR_PLAYER)); )
+ {
+ if (SAME_TEAM(player, e)) return e;
+ }
+ return start;
+}
+
+
+MUTATOR_HOOKFUNCTION(ca, PlayerSpawn)
+{
+ entity player = M_ARGV(0, entity);
+
+ player.caplayer = 1;
+ if (!warmup_stage)
+ eliminatedPlayers.SendFlags |= 1;
+}
+
+MUTATOR_HOOKFUNCTION(ca, ForbidSpawn)
+{
+ entity player = M_ARGV(0, entity);
+
+ // spectators / observers that weren't playing can join; they are
+ // immediately forced to observe in the PutClientInServer hook
+ // this way they are put in a team and can play in the next round
+ if (!allowed_to_spawn && player.caplayer)
+ return true;
+ return false;
+}
+
+MUTATOR_HOOKFUNCTION(ca, PutClientInServer)
+{
+ entity player = M_ARGV(0, entity);
+
+ if (!allowed_to_spawn && IS_PLAYER(player)) // this is true even when player is trying to join
+ {
+ TRANSMUTE(Observer, player);
+ if (CS(player).jointime != time && !player.caplayer) // not when connecting
+ {
+ player.caplayer = 0.5;
+ Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ca, reset_map_players)
+{
+ FOREACH_CLIENT(true, {
+ CS(it).killcount = 0;
+ if (!it.caplayer && IS_BOT_CLIENT(it))
+ {
+ it.team = -1;
+ it.caplayer = 1;
+ }
+ if (it.caplayer)
+ {
+ TRANSMUTE(Player, it);
+ it.caplayer = 1;
+ PutClientInServer(it);
+ }
+ });
+ bot_relinkplayerlist();
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, ClientConnect)
+{
+ entity player = M_ARGV(0, entity);
+
+ TRANSMUTE(Observer, player);
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, reset_map_global)
+{
+ allowed_to_spawn = true;
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+ M_ARGV(0, float) = ca_teams;
+ return true;
+}
+
+entity ca_LastPlayerForTeam(entity this)
+{
+ entity last_pl = NULL;
+ FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
+ if (!IS_DEAD(it))
+ if (SAME_TEAM(this, it))
+ if (!last_pl)
+ last_pl = it;
+ else
+ return NULL;
+ });
+ return last_pl;
+}
+
+void ca_LastPlayerForTeam_Notify(entity this)
+{
+ if (!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
+ {
+ entity pl = ca_LastPlayerForTeam(this);
+ if (pl)
+ Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ca, PlayerDies)
+{
+ entity frag_target = M_ARGV(2, entity);
+
+ ca_LastPlayerForTeam_Notify(frag_target);
+ if (!allowed_to_spawn)
+ {
+ frag_target.respawn_flags = RESPAWN_SILENT;
+ // prevent unwanted sudden rejoin as spectator and movement of spectator camera
+ frag_target.respawn_time = time + 2;
+ }
+ frag_target.respawn_flags |= RESPAWN_FORCE;
+ if (!warmup_stage)
+ {
+ eliminatedPlayers.SendFlags |= 1;
+ if (IS_BOT_CLIENT(frag_target))
+ bot_clear(frag_target);
+ }
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, ClientDisconnect)
+{
+ entity player = M_ARGV(0, entity);
+
+ if (IS_PLAYER(player) && !IS_DEAD(player))
+ ca_LastPlayerForTeam_Notify(player);
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, MakePlayerObserver)
+{
+ entity player = M_ARGV(0, entity);
+
+ if (IS_PLAYER(player) && !IS_DEAD(player))
+ ca_LastPlayerForTeam_Notify(player);
+ if (player.killindicator_teamchange == -2) // player wants to spectate
+ player.caplayer = 0;
+ if (player.caplayer)
+ player.frags = FRAGS_LMS_LOSER;
+ if (!warmup_stage)
+ eliminatedPlayers.SendFlags |= 1;
+ if (!player.caplayer)
+ return false; // allow team reset
+ return true; // prevent team reset
+}
+
+MUTATOR_HOOKFUNCTION(ca, ForbidThrowCurrentWeapon)
+{
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, GiveFragsForKill, CBC_ORDER_FIRST)
+{
+ M_ARGV(2, float) = 0; // score will be given to the winner team when the round ends
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, SetStartItems)
+{
+ start_items &= ~IT_UNLIMITED_AMMO;
+ start_health = warmup_start_health = cvar("g_lms_start_health");
+ start_armorvalue = warmup_start_armorvalue = cvar("g_lms_start_armor");
+ start_ammo_shells = warmup_start_ammo_shells = cvar("g_lms_start_ammo_shells");
+ start_ammo_nails = warmup_start_ammo_nails = cvar("g_lms_start_ammo_nails");
+ start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
+ start_ammo_cells = warmup_start_ammo_cells = cvar("g_lms_start_ammo_cells");
+ start_ammo_plasma = warmup_start_ammo_plasma = cvar("g_lms_start_ammo_plasma");
+ start_ammo_fuel = warmup_start_ammo_fuel = cvar("g_lms_start_ammo_fuel");
+}
+
+MUTATOR_HOOKFUNCTION(ca, Damage_Calculate)
+{
+ entity frag_attacker = M_ARGV(1, entity);
+ entity frag_target = M_ARGV(2, entity);
+ float frag_deathtype = M_ARGV(3, float);
+ float frag_damage = M_ARGV(4, float);
+ float frag_mirrordamage = M_ARGV(5, float);
+
+ if (IS_PLAYER(frag_target))
+ if (!IS_DEAD(frag_target))
+ if (frag_target == frag_attacker || SAME_TEAM(frag_target, frag_attacker) || frag_deathtype == DEATH_FALL.m_id)
+ frag_damage = 0;
+
+ frag_mirrordamage = 0;
+
+ M_ARGV(4, float) = frag_damage;
+ M_ARGV(5, float) = frag_mirrordamage;
+}
+
+MUTATOR_HOOKFUNCTION(ca, FilterItem)
+{
+ entity item = M_ARGV(0, entity);
+
+ if (autocvar_g_powerups <= 0)
+ if (item.flags & FL_POWERUP)
+ return true;
+
+ if (autocvar_g_pickup_items <= 0)
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, PlayerDamage_SplitHealthArmor)
+{
+ entity frag_attacker = M_ARGV(1, entity);
+ entity frag_target = M_ARGV(2, entity);
+ float frag_damage = M_ARGV(7, float);
+ float damage_take = bound(0, M_ARGV(4, float), GetResourceAmount(frag_target, RESOURCE_HEALTH));
+ float damage_save = bound(0, M_ARGV(5, float), GetResourceAmount(frag_target, RESOURCE_ARMOR));
+
+ float excess = max(0, frag_damage - damage_take - damage_save);
+
+ if (frag_target != frag_attacker && IS_PLAYER(frag_attacker) && DIFF_TEAM(frag_target, frag_attacker))
+ GameRules_scoring_add_team(frag_attacker, SCORE, (frag_damage - excess) * autocvar_g_ca_damage2score_multiplier);
+}
+
+MUTATOR_HOOKFUNCTION(ca, CalculateRespawnTime)
+{
+ // no respawn calculations needed, player is forced to spectate anyway
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, PlayerRegen)
+{
+ // no regeneration in CA
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, Scores_CountFragsRemaining)
+{
+ // announce remaining frags
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, SpectateSet)
+{
+ entity client = M_ARGV(0, entity);
+ entity targ = M_ARGV(1, entity);
+
+ if (!autocvar_g_ca_spectate_enemies && client.caplayer)
+ if (DIFF_TEAM(targ, client))
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, SpectateNext)
+{
+ entity client = M_ARGV(0, entity);
+
+ if (!autocvar_g_ca_spectate_enemies && client.caplayer)
+ {
+ entity targ = M_ARGV(1, entity);
+ M_ARGV(1, entity) = CA_SpectateNext(client, targ);
+ return true;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ca, SpectatePrev)
+{
+ entity client = M_ARGV(0, entity);
+ entity targ = M_ARGV(1, entity);
+ entity first = M_ARGV(2, entity);
+
+ if (!autocvar_g_ca_spectate_enemies && client.caplayer)
+ {
+ do { targ = targ.chain; }
+ while(targ && DIFF_TEAM(targ, client));
+
+ if (!targ)
+ {
+ for (targ = first; targ && DIFF_TEAM(targ, client); targ = targ.chain);
+
+ if (targ == client.enemy)
+ return MUT_SPECPREV_RETURN;
+ }
+ }
+
+ M_ARGV(1, entity) = targ;
+
+ return MUT_SPECPREV_FOUND;
+}
+
+MUTATOR_HOOKFUNCTION(ca, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
+{
+ FOREACH_CLIENT(IS_REAL_CLIENT(it), {
+ if (IS_PLAYER(it) || it.caplayer == 1)
+ ++M_ARGV(0, int);
+ ++M_ARGV(1, int);
+ });
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, ClientCommand_Spectate)
+{
+ entity player = M_ARGV(0, entity);
+
+ if (player.caplayer)
+ {
+ // they're going to spec, we can do other checks
+ if (autocvar_sv_spectate && (IS_SPEC(player) || IS_OBSERVER(player)))
+ Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_LEAVE);
+ return MUT_SPECCMD_FORCE;
+ }
+
+ return MUT_SPECCMD_CONTINUE;
+}
+
+MUTATOR_HOOKFUNCTION(ca, WantWeapon)
+{
+ M_ARGV(2, bool) = true; // all weapons
+}
+
+MUTATOR_HOOKFUNCTION(ca, HideTeamNagger)
+{
+ return true; // doesn't work well with the whole spectator as player thing
+}
+
+MUTATOR_HOOKFUNCTION(ca, GetPlayerStatus)
+{
+ entity player = M_ARGV(0, entity);
+
+ return player.caplayer == 1;
+}
+
+MUTATOR_HOOKFUNCTION(ca, SetWeaponArena)
+{
+ // most weapons arena
+ if (M_ARGV(0, string) == "0" || M_ARGV(0, string) == "") M_ARGV(0, string) = "most";
+}
--- /dev/null
+#pragma once
+
+#include <common/mutators/base.qh>
+#include <server/round_handler.qh>
+#include <server/miscfunctions.qh>
+
+int autocvar_g_ca_point_limit;
+int autocvar_g_ca_point_leadlimit;
+float autocvar_g_ca_round_timelimit;
+bool autocvar_g_ca_team_spawns;
+//int autocvar_g_ca_teams;
+int autocvar_g_ca_teams_override;
+float autocvar_g_ca_warmup;
+
+
+int ca_teams;
+bool allowed_to_spawn;
+
+const int ST_CA_ROUNDS = 1;
+
+bool CA_CheckTeams();
+bool CA_CheckWinner();
+void CA_RoundStart();
+bool ca_isEliminated(entity e);
+
+REGISTER_MUTATOR(ca, false)
+{
+ MUTATOR_STATIC();
+ MUTATOR_ONADD
+ {
+ GameRules_teams(true);
+ GameRules_spawning_teams(autocvar_g_ca_team_spawns);
+ GameRules_limit_score(autocvar_g_ca_point_limit);
+ GameRules_limit_lead(autocvar_g_ca_point_leadlimit);
+
+ ca_teams = autocvar_g_ca_teams_override;
+ if (ca_teams < 2)
+ ca_teams = cvar("g_ca_teams"); // read the cvar directly as it gets written earlier in the same frame
+
+ ca_teams = BITS(bound(2, ca_teams, 4));
+ GameRules_scoring(ca_teams, SFL_SORT_PRIO_PRIMARY, 0, {
+ field_team(ST_CA_ROUNDS, "rounds", SFL_SORT_PRIO_PRIMARY);
+ });
+
+ allowed_to_spawn = true;
+ round_handler_Spawn(CA_CheckTeams, CA_CheckWinner, CA_RoundStart);
+ round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
+ EliminatedPlayers_Init(ca_isEliminated);
+ }
+ return 0;
+}
+
+// should be removed in the future, as other code should not have to care
+.float caplayer; // 0.5 if scheduled to join the next round
// generated file; do not modify
-#include <common/gamemodes/gamemode/ctf/ctf.qc>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/ctf/sv_ctf.qc>
+#endif
// generated file; do not modify
#include <common/gamemodes/gamemode/ctf/ctf.qh>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/ctf/sv_ctf.qh>
+#endif
+++ /dev/null
-#include "ctf.qh"
-
-// TODO: split into sv_ctf
-#ifdef SVQC
-#include <common/effects/all.qh>
-#include <common/vehicles/all.qh>
-#include <server/teamplay.qh>
-
-#include <lib/warpzone/common.qh>
-
-bool autocvar_g_ctf_allow_vehicle_carry;
-bool autocvar_g_ctf_allow_vehicle_touch;
-bool autocvar_g_ctf_allow_monster_touch;
-bool autocvar_g_ctf_throw;
-float autocvar_g_ctf_throw_angle_max;
-float autocvar_g_ctf_throw_angle_min;
-int autocvar_g_ctf_throw_punish_count;
-float autocvar_g_ctf_throw_punish_delay;
-float autocvar_g_ctf_throw_punish_time;
-float autocvar_g_ctf_throw_strengthmultiplier;
-float autocvar_g_ctf_throw_velocity_forward;
-float autocvar_g_ctf_throw_velocity_up;
-float autocvar_g_ctf_drop_velocity_up;
-float autocvar_g_ctf_drop_velocity_side;
-bool autocvar_g_ctf_oneflag_reverse;
-bool autocvar_g_ctf_portalteleport;
-bool autocvar_g_ctf_pass;
-float autocvar_g_ctf_pass_arc;
-float autocvar_g_ctf_pass_arc_max;
-float autocvar_g_ctf_pass_directional_max;
-float autocvar_g_ctf_pass_directional_min;
-float autocvar_g_ctf_pass_radius;
-float autocvar_g_ctf_pass_wait;
-bool autocvar_g_ctf_pass_request;
-float autocvar_g_ctf_pass_turnrate;
-float autocvar_g_ctf_pass_timelimit;
-float autocvar_g_ctf_pass_velocity;
-bool autocvar_g_ctf_dynamiclights;
-float autocvar_g_ctf_flag_collect_delay;
-float autocvar_g_ctf_flag_damageforcescale;
-bool autocvar_g_ctf_flag_dropped_waypoint;
-bool autocvar_g_ctf_flag_dropped_floatinwater;
-bool autocvar_g_ctf_flag_glowtrails;
-int autocvar_g_ctf_flag_health;
-bool autocvar_g_ctf_flag_return;
-bool autocvar_g_ctf_flag_return_carrying;
-float autocvar_g_ctf_flag_return_carried_radius;
-float autocvar_g_ctf_flag_return_time;
-bool autocvar_g_ctf_flag_return_when_unreachable;
-float autocvar_g_ctf_flag_return_damage;
-float autocvar_g_ctf_flag_return_damage_delay;
-float autocvar_g_ctf_flag_return_dropped;
-float autocvar_g_ctf_flagcarrier_auto_helpme_damage;
-float autocvar_g_ctf_flagcarrier_auto_helpme_time;
-float autocvar_g_ctf_flagcarrier_selfdamagefactor;
-float autocvar_g_ctf_flagcarrier_selfforcefactor;
-float autocvar_g_ctf_flagcarrier_damagefactor;
-float autocvar_g_ctf_flagcarrier_forcefactor;
-//float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting;
-bool autocvar_g_ctf_fullbrightflags;
-bool autocvar_g_ctf_ignore_frags;
-bool autocvar_g_ctf_score_ignore_fields;
-int autocvar_g_ctf_score_capture;
-int autocvar_g_ctf_score_capture_assist;
-int autocvar_g_ctf_score_kill;
-int autocvar_g_ctf_score_penalty_drop;
-int autocvar_g_ctf_score_penalty_returned;
-int autocvar_g_ctf_score_pickup_base;
-int autocvar_g_ctf_score_pickup_dropped_early;
-int autocvar_g_ctf_score_pickup_dropped_late;
-int autocvar_g_ctf_score_return;
-float autocvar_g_ctf_shield_force;
-float autocvar_g_ctf_shield_max_ratio;
-int autocvar_g_ctf_shield_min_negscore;
-bool autocvar_g_ctf_stalemate;
-int autocvar_g_ctf_stalemate_endcondition;
-float autocvar_g_ctf_stalemate_time;
-bool autocvar_g_ctf_reverse;
-float autocvar_g_ctf_dropped_capture_delay;
-float autocvar_g_ctf_dropped_capture_radius;
-
-void ctf_FakeTimeLimit(entity e, float t)
-{
- msg_entity = e;
- WriteByte(MSG_ONE, 3); // svc_updatestat
- WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
- if(t < 0)
- WriteCoord(MSG_ONE, autocvar_timelimit);
- else
- WriteCoord(MSG_ONE, (t + 1) / 60);
-}
-
-void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
-{
- if(autocvar_sv_eventlog)
- GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != NULL) ? ftos(actor.playerid) : "")));
- //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
-}
-
-void ctf_CaptureRecord(entity flag, entity player)
-{
- float cap_record = ctf_captimerecord;
- float cap_time = (time - flag.ctf_pickuptime);
- string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
-
- // notify about shit
- if(ctf_oneflag)
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname);
- else if(!ctf_captimerecord)
- Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_TIME), player.netname, TIME_ENCODE(cap_time));
- else if(cap_time < cap_record)
- Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_BROKEN), player.netname, refername, TIME_ENCODE(cap_time), TIME_ENCODE(cap_record));
- else
- Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_UNBROKEN), player.netname, refername, TIME_ENCODE(cap_time), TIME_ENCODE(cap_record));
-
- // write that shit in the database
- if(!ctf_oneflag) // but not in 1-flag mode
- if((!ctf_captimerecord) || (cap_time < cap_record))
- {
- ctf_captimerecord = cap_time;
- db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
- db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
- write_recordmarker(player, flag.ctf_pickuptime, cap_time);
- }
-
- if(autocvar_g_ctf_leaderboard && !ctf_oneflag)
- race_setTime(GetMapname(), TIME_ENCODE(cap_time), player.crypto_idfp, player.netname, player, false);
-}
-
-bool ctf_Immediate_Return_Allowed(entity flag, entity toucher)
-{
- int num_perteam = 0;
- FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), { ++num_perteam; });
-
- // automatically return if there's only 1 player on the team
- return ((autocvar_g_ctf_flag_return || num_perteam <= 1 || (autocvar_g_ctf_flag_return_carrying && toucher.flagcarried))
- && flag.team);
-}
-
-bool ctf_Return_Customize(entity this, entity client)
-{
- // only to the carrier
- return boolean(client == this.owner);
-}
-
-void ctf_FlagcarrierWaypoints(entity player)
-{
- WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
- WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
- WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(GetResourceAmount(player, RESOURCE_HEALTH), GetResourceAmount(player, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
- WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
-
- if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried))
- {
- if(!player.wps_enemyflagcarrier)
- {
- entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 0, player, wps_enemyflagcarrier, true, RADARICON_FLAG);
- wp.colormod = WPCOLOR_ENEMYFC(player.team);
- setcefc(wp, ctf_Stalemate_Customize);
-
- if(IS_REAL_CLIENT(player) && !ctf_stalemate)
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_VISIBLE);
- }
-
- if(!player.wps_flagreturn)
- {
- entity owp = WaypointSprite_SpawnFixed(WP_FlagReturn, player.flagcarried.ctf_spawnorigin + FLAG_WAYPOINT_OFFSET, player, wps_flagreturn, RADARICON_FLAG);
- owp.colormod = '0 0.8 0.8';
- //WaypointSprite_UpdateTeamRadar(player.wps_flagreturn, RADARICON_FLAG, ((player.team) ? colormapPaletteColor(player.team - 1, false) : '1 1 1'));
- setcefc(owp, ctf_Return_Customize);
- }
- }
-}
-
-void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
-{
- float current_distance = vlen((('1 0 0' * to.x) + ('0 1 0' * to.y)) - (('1 0 0' * from.x) + ('0 1 0' * from.y))); // for the sake of this check, exclude Z axis
- float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
- float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
- //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
-
- vector targpos;
- if(current_height) // make sure we can actually do this arcing path
- {
- targpos = (to + ('0 0 1' * current_height));
- WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
- if(trace_fraction < 1)
- {
- //print("normal arc line failed, trying to find new pos...");
- WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
- targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
- WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
- if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
- /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
- }
- }
- else { targpos = to; }
-
- //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
-
- vector desired_direction = normalize(targpos - from);
- if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
- else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
-}
-
-bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
-{
- if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
- {
- // directional tracing only
- float spreadlimit;
- makevectors(passer_angle);
-
- // find the closest point on the enemy to the center of the attack
- float h; // hypotenuse, which is the distance between attacker to head
- float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
-
- h = vlen(head_center - passer_center);
- a = h * (normalize(head_center - passer_center) * v_forward);
-
- vector nearest_on_line = (passer_center + a * v_forward);
- float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
-
- spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
- spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
-
- if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
- { return true; }
- else
- { return false; }
- }
- else { return true; }
-}
-
-
-// =======================
-// CaptureShield Functions
-// =======================
-
-bool ctf_CaptureShield_CheckStatus(entity p)
-{
- int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
- int players_worseeq, players_total;
-
- if(ctf_captureshield_max_ratio <= 0)
- return false;
-
- s = GameRules_scoring_add(p, CTF_CAPS, 0);
- s2 = GameRules_scoring_add(p, CTF_PICKUPS, 0);
- s3 = GameRules_scoring_add(p, CTF_RETURNS, 0);
- s4 = GameRules_scoring_add(p, CTF_FCKILLS, 0);
-
- sr = ((s - s2) + (s3 + s4));
-
- if(sr >= -ctf_captureshield_min_negscore)
- return false;
-
- players_total = players_worseeq = 0;
- FOREACH_CLIENT(IS_PLAYER(it), {
- if(DIFF_TEAM(it, p))
- continue;
- se = GameRules_scoring_add(it, CTF_CAPS, 0);
- se2 = GameRules_scoring_add(it, CTF_PICKUPS, 0);
- se3 = GameRules_scoring_add(it, CTF_RETURNS, 0);
- se4 = GameRules_scoring_add(it, CTF_FCKILLS, 0);
-
- ser = ((se - se2) + (se3 + se4));
-
- if(ser <= sr)
- ++players_worseeq;
- ++players_total;
- });
-
- // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
- // use this rule here
-
- if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
- return false;
-
- return true;
-}
-
-void ctf_CaptureShield_Update(entity player, bool wanted_status)
-{
- bool updated_status = ctf_CaptureShield_CheckStatus(player);
- if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
- {
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
- player.ctf_captureshielded = updated_status;
- }
-}
-
-bool ctf_CaptureShield_Customize(entity this, entity client)
-{
- if(!client.ctf_captureshielded) { return false; }
- if(CTF_SAMETEAM(this, client)) { return false; }
-
- return true;
-}
-
-void ctf_CaptureShield_Touch(entity this, entity toucher)
-{
- if(!toucher.ctf_captureshielded) { return; }
- if(CTF_SAMETEAM(this, toucher)) { return; }
-
- vector mymid = (this.absmin + this.absmax) * 0.5;
- vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
-
- Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, mymid, normalize(theirmid - mymid) * ctf_captureshield_force);
- if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
-}
-
-void ctf_CaptureShield_Spawn(entity flag)
-{
- entity shield = new(ctf_captureshield);
-
- shield.enemy = flag;
- shield.team = flag.team;
- settouch(shield, ctf_CaptureShield_Touch);
- setcefc(shield, ctf_CaptureShield_Customize);
- shield.effects = EF_ADDITIVE;
- set_movetype(shield, MOVETYPE_NOCLIP);
- shield.solid = SOLID_TRIGGER;
- shield.avelocity = '7 0 11';
- shield.scale = 0.5;
-
- setorigin(shield, flag.origin);
- setmodel(shield, MDL_CTF_SHIELD);
- setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
-}
-
-
-// ====================
-// Drop/Pass/Throw Code
-// ====================
-
-void ctf_Handle_Drop(entity flag, entity player, int droptype)
-{
- // declarations
- player = (player ? player : flag.pass_sender);
-
- // main
- set_movetype(flag, MOVETYPE_TOSS);
- flag.takedamage = DAMAGE_YES;
- flag.angles = '0 0 0';
- SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health);
- flag.ctf_droptime = time;
- flag.ctf_dropper = player;
- flag.ctf_status = FLAG_DROPPED;
-
- // messages and sounds
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_LOST), player.netname);
- _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
- ctf_EventLog("dropped", player.team, player);
-
- // scoring
- GameRules_scoring_add_team(player, SCORE, -((flag.score_drop) ? flag.score_drop : autocvar_g_ctf_score_penalty_drop));
- GameRules_scoring_add(player, CTF_DROPS, 1);
-
- // waypoints
- if(autocvar_g_ctf_flag_dropped_waypoint) {
- entity wp = WaypointSprite_Spawn(WP_FlagDropped, 0, 0, flag, FLAG_WAYPOINT_OFFSET, NULL, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, true, RADARICON_FLAG);
- wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
- }
-
- if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
- {
- WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
- WaypointSprite_UpdateHealth(flag.wps_flagdropped, GetResourceAmount(flag, RESOURCE_HEALTH));
- }
-
- player.throw_antispam = time + autocvar_g_ctf_pass_wait;
-
- if(droptype == DROP_PASS)
- {
- flag.pass_distance = 0;
- flag.pass_sender = NULL;
- flag.pass_target = NULL;
- }
-}
-
-void ctf_Handle_Retrieve(entity flag, entity player)
-{
- entity sender = flag.pass_sender;
-
- // transfer flag to player
- flag.owner = player;
- flag.owner.flagcarried = flag;
- GameRules_scoring_vip(player, true);
-
- // reset flag
- if(player.vehicle)
- {
- setattachment(flag, player.vehicle, "");
- setorigin(flag, VEHICLE_FLAG_OFFSET);
- flag.scale = VEHICLE_FLAG_SCALE;
- }
- else
- {
- setattachment(flag, player, "");
- setorigin(flag, FLAG_CARRY_OFFSET);
- }
- set_movetype(flag, MOVETYPE_NONE);
- flag.takedamage = DAMAGE_NO;
- flag.solid = SOLID_NOT;
- flag.angles = '0 0 0';
- flag.ctf_status = FLAG_CARRY;
-
- // messages and sounds
- _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
- ctf_EventLog("receive", flag.team, player);
-
- FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
- if(it == sender)
- Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_SENT), player.netname);
- else if(it == player)
- Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_RECEIVED), sender.netname);
- else if(SAME_TEAM(it, sender))
- Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_OTHER), sender.netname, player.netname);
- });
-
- // create new waypoint
- ctf_FlagcarrierWaypoints(player);
-
- sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
- player.throw_antispam = sender.throw_antispam;
-
- flag.pass_distance = 0;
- flag.pass_sender = NULL;
- flag.pass_target = NULL;
-}
-
-void ctf_Handle_Throw(entity player, entity receiver, int droptype)
-{
- entity flag = player.flagcarried;
- vector targ_origin, flag_velocity;
-
- if(!flag) { return; }
- if((droptype == DROP_PASS) && !receiver) { return; }
-
- if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
-
- // reset the flag
- setattachment(flag, NULL, "");
- setorigin(flag, player.origin + FLAG_DROP_OFFSET);
- flag.owner.flagcarried = NULL;
- GameRules_scoring_vip(flag.owner, false);
- flag.owner = NULL;
- flag.solid = SOLID_TRIGGER;
- flag.ctf_dropper = player;
- flag.ctf_droptime = time;
- navigation_dynamicgoal_set(flag);
-
- flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
-
- switch(droptype)
- {
- case DROP_PASS:
- {
- // warpzone support:
- // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
- // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
- WarpZone_RefSys_Copy(flag, receiver);
- WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
- targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
-
- flag.pass_distance = vlen((('1 0 0' * targ_origin.x) + ('0 1 0' * targ_origin.y)) - (('1 0 0' * player.origin.x) + ('0 1 0' * player.origin.y))); // for the sake of this check, exclude Z axis
- ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
-
- // main
- set_movetype(flag, MOVETYPE_FLY);
- flag.takedamage = DAMAGE_NO;
- flag.pass_sender = player;
- flag.pass_target = receiver;
- flag.ctf_status = FLAG_PASSING;
-
- // other
- _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
- WarpZone_TrailParticles(NULL, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
- ctf_EventLog("pass", flag.team, player);
- break;
- }
-
- case DROP_THROW:
- {
- makevectors((player.v_angle.y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle.x, autocvar_g_ctf_throw_angle_max) * '1 0 0'));
-
- flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & ITEM_Strength.m_itemid) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
- flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false);
- ctf_Handle_Drop(flag, player, droptype);
- break;
- }
-
- case DROP_RESET:
- {
- flag.velocity = '0 0 0'; // do nothing
- break;
- }
-
- default:
- case DROP_NORMAL:
- {
- flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), false);
- ctf_Handle_Drop(flag, player, droptype);
- break;
- }
- }
-
- // kill old waypointsprite
- WaypointSprite_Ping(player.wps_flagcarrier);
- WaypointSprite_Kill(player.wps_flagcarrier);
-
- if(player.wps_enemyflagcarrier)
- WaypointSprite_Kill(player.wps_enemyflagcarrier);
-
- if(player.wps_flagreturn)
- WaypointSprite_Kill(player.wps_flagreturn);
-
- // captureshield
- ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
-}
-
-void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
-{
- return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
-}
-
-// ==============
-// Event Handlers
-// ==============
-
-void nades_GiveBonus(entity player, float score);
-
-void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
-{
- entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
- entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
- entity player_team_flag = NULL, tmp_entity;
- float old_time, new_time;
-
- if(!player) { return; } // without someone to give the reward to, we can't possibly cap
- 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))
- {
- player_team_flag = tmp_entity;
- break;
- }
-
- nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
-
- player.throw_prevtime = time;
- player.throw_count = 0;
-
- // messages and sounds
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_NUM(enemy_flag.team, CENTER_CTF_CAPTURE));
- ctf_CaptureRecord(enemy_flag, player);
- _sound(player, CH_TRIGGER, ((ctf_oneflag) ? player_team_flag.snd_flag_capture : ((DIFF_TEAM(player, flag)) ? enemy_flag.snd_flag_capture : flag.snd_flag_capture)), VOL_BASE, ATTEN_NONE);
-
- switch(capturetype)
- {
- case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
- case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
- default: break;
- }
-
- // scoring
- float pscore = 0;
- if(enemy_flag.score_capture || flag.score_capture)
- pscore = floor((max(1, enemy_flag.score_capture) + max(1, flag.score_capture)) * 0.5);
- GameRules_scoring_add_team(player, SCORE, ((pscore) ? pscore : autocvar_g_ctf_score_capture));
- float capscore = 0;
- if(enemy_flag.score_team_capture || flag.score_team_capture)
- capscore = floor((max(1, enemy_flag.score_team_capture) + max(1, flag.score_team_capture)) * 0.5);
- GameRules_scoring_add_team(player, CTF_CAPS, ((capscore) ? capscore : 1));
-
- old_time = GameRules_scoring_add(player, CTF_CAPTIME, 0);
- new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
- if(!old_time || new_time < old_time)
- GameRules_scoring_add(player, CTF_CAPTIME, new_time - old_time);
-
- // effects
- Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
- //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
-
- // other
- if(capturetype == CAPTURE_NORMAL)
- {
- WaypointSprite_Kill(player.wps_flagcarrier);
- if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
-
- if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
- { 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);
-}
-
-void ctf_Handle_Return(entity flag, entity player)
-{
- // messages and sounds
- if(IS_MONSTER(player))
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN_MONSTER), player.monster_name);
- }
- else if(flag.team)
- {
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_RETURN));
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN), player.netname);
- }
- _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
- ctf_EventLog("return", flag.team, player);
-
- // scoring
- if(IS_PLAYER(player))
- {
- GameRules_scoring_add_team(player, SCORE, ((flag.score_return) ? flag.score_return : autocvar_g_ctf_score_return)); // reward for return
- GameRules_scoring_add(player, CTF_RETURNS, 1); // add to count of returns
-
- nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
- }
-
- TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
-
- if(flag.ctf_dropper)
- {
- GameRules_scoring_add(flag.ctf_dropper, SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
- ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
- flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
- }
-
- // other
- if(player.flagcarried == flag)
- WaypointSprite_Kill(player.wps_flagcarrier);
-
- flag.enemy = player;
-
- // reset the flag
- ctf_RespawnFlag(flag);
-}
-
-void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
-{
- // declarations
- float pickup_dropped_score; // used to calculate dropped pickup score
-
- // attach the flag to the player
- flag.owner = player;
- player.flagcarried = flag;
- GameRules_scoring_vip(player, true);
- if(player.vehicle)
- {
- setattachment(flag, player.vehicle, "");
- setorigin(flag, VEHICLE_FLAG_OFFSET);
- flag.scale = VEHICLE_FLAG_SCALE;
- }
- else
- {
- setattachment(flag, player, "");
- setorigin(flag, FLAG_CARRY_OFFSET);
- }
-
- // flag setup
- set_movetype(flag, MOVETYPE_NONE);
- flag.takedamage = DAMAGE_NO;
- flag.solid = SOLID_NOT;
- flag.angles = '0 0 0';
- flag.ctf_status = FLAG_CARRY;
-
- switch(pickuptype)
- {
- case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
- case PICKUP_DROPPED: SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health); break; // reset health/return timelimit
- default: break;
- }
-
- // messages and sounds
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_PICKUP), player.netname);
- if(ctf_stalemate)
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER);
- if(!flag.team)
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL);
- else if(CTF_DIFFTEAM(player, flag))
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_PICKUP));
- else
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_RETURN : CENTER_CTF_PICKUP_RETURN_ENEMY), Team_ColorCode(flag.team));
-
- Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
-
- if(!flag.team)
- FOREACH_CLIENT(IS_PLAYER(it) && it != player && DIFF_TEAM(it, player), { Send_Notification(NOTIF_ONE, it, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname); });
-
- if(flag.team)
- FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
- if(CTF_SAMETEAM(flag, it))
- if(SAME_TEAM(player, it))
- Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
- else
- Send_Notification(NOTIF_ONE, it, MSG_CHOICE, ((SAME_TEAM(flag, player)) ? CHOICE_CTF_PICKUP_ENEMY_TEAM : CHOICE_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), player.netname);
- });
-
- _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
-
- // scoring
- GameRules_scoring_add(player, CTF_PICKUPS, 1);
- nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
- switch(pickuptype)
- {
- case PICKUP_BASE:
- {
- GameRules_scoring_add_team(player, SCORE, ((flag.score_pickup) ? flag.score_pickup : autocvar_g_ctf_score_pickup_base));
- ctf_EventLog("steal", flag.team, player);
- break;
- }
-
- case PICKUP_DROPPED:
- {
- pickup_dropped_score = (autocvar_g_ctf_flag_return_time ? bound(0, ((flag.ctf_droptime + autocvar_g_ctf_flag_return_time) - time) / autocvar_g_ctf_flag_return_time, 1) : 1);
- pickup_dropped_score = floor((autocvar_g_ctf_score_pickup_dropped_late * (1 - pickup_dropped_score) + autocvar_g_ctf_score_pickup_dropped_early * pickup_dropped_score) + 0.5);
- LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score));
- GameRules_scoring_add_team(player, SCORE, pickup_dropped_score);
- ctf_EventLog("pickup", flag.team, player);
- break;
- }
-
- default: break;
- }
-
- // speedrunning
- if(pickuptype == PICKUP_BASE)
- {
- flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
- if((player.speedrunning) && (ctf_captimerecord))
- ctf_FakeTimeLimit(player, time + ctf_captimerecord);
- }
-
- // effects
- Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
-
- // waypoints
- if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
- ctf_FlagcarrierWaypoints(player);
- WaypointSprite_Ping(player.wps_flagcarrier);
-}
-
-
-// ===================
-// Main Flag Functions
-// ===================
-
-void ctf_CheckFlagReturn(entity flag, int returntype)
-{
- if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
- {
- if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, GetResourceAmount(flag, RESOURCE_HEALTH)); }
-
- if((GetResourceAmount(flag, RESOURCE_HEALTH) <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
- {
- switch(returntype)
- {
- case RETURN_DROPPED:
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DROPPED)); break;
- case RETURN_DAMAGE:
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DAMAGED)); break;
- case RETURN_SPEEDRUN:
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_SPEEDRUN), TIME_ENCODE(ctf_captimerecord)); break;
- case RETURN_NEEDKILL:
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_NEEDKILL)); break;
- default:
- case RETURN_TIMEOUT:
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_TIMEOUT)); break;
- }
- _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
- ctf_EventLog("returned", flag.team, NULL);
- flag.enemy = NULL;
- ctf_RespawnFlag(flag);
- }
- }
-}
-
-bool ctf_Stalemate_Customize(entity this, entity client)
-{
- // make spectators see what the player would see
- entity e = WaypointSprite_getviewentity(client);
- entity wp_owner = this.owner;
-
- // team waypoints
- //if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
- if(SAME_TEAM(wp_owner, e)) { return false; }
- if(!IS_PLAYER(e)) { return false; }
-
- return true;
-}
-
-void ctf_CheckStalemate()
-{
- // declarations
- int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
- entity tmp_entity;
-
- entity ctf_staleflaglist = NULL; // reset the list, we need to build the list each time this function runs
-
- // build list of stale flags
- for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
- {
- if(autocvar_g_ctf_stalemate)
- if(tmp_entity.ctf_status != FLAG_BASE)
- if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
- {
- tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
- ctf_staleflaglist = tmp_entity;
-
- switch(tmp_entity.team)
- {
- case NUM_TEAM_1: ++stale_red_flags; break;
- case NUM_TEAM_2: ++stale_blue_flags; break;
- case NUM_TEAM_3: ++stale_yellow_flags; break;
- case NUM_TEAM_4: ++stale_pink_flags; break;
- default: ++stale_neutral_flags; break;
- }
- }
- }
-
- if(ctf_oneflag)
- stale_flags = (stale_neutral_flags >= 1);
- else
- stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
-
- if(ctf_oneflag && stale_flags == 1)
- ctf_stalemate = true;
- else if(stale_flags >= 2)
- ctf_stalemate = true;
- else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
- { ctf_stalemate = false; wpforenemy_announced = false; }
- else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
- { ctf_stalemate = false; wpforenemy_announced = false; }
-
- // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
- if(ctf_stalemate)
- {
- for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
- {
- if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
- {
- entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, NULL, 0, tmp_entity.owner, wps_enemyflagcarrier, true, RADARICON_FLAG);
- wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
- setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize);
- }
- }
-
- if (!wpforenemy_announced)
- {
- FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { Send_Notification(NOTIF_ONE, it, MSG_CENTER, ((it.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER)); });
-
- wpforenemy_announced = true;
- }
- }
-}
-
-void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
-{
- if(ITEM_DAMAGE_NEEDKILL(deathtype))
- {
- if(autocvar_g_ctf_flag_return_damage_delay)
- this.ctf_flagdamaged_byworld = true;
- else
- {
- SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
- ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
- }
- return;
- }
- if(autocvar_g_ctf_flag_return_damage)
- {
- // reduce health and check if it should be returned
- TakeResource(this, RESOURCE_HEALTH, damage);
- ctf_CheckFlagReturn(this, RETURN_DAMAGE);
- return;
- }
-}
-
-void ctf_FlagThink(entity this)
-{
- // declarations
- entity tmp_entity;
-
- this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
-
- // captureshield
- if(this == ctf_worldflaglist) // only for the first flag
- FOREACH_CLIENT(true, { ctf_CaptureShield_Update(it, 1); }); // release shield only
-
- // sanity checks
- if(this.mins != this.m_mins || this.maxs != this.m_maxs) { // reset the flag boundaries in case it got squished
- LOG_TRACE("wtf the flag got squashed?");
- tracebox(this.origin, this.m_mins, this.m_maxs, this.origin, MOVE_NOMONSTERS, this);
- if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
- setsize(this, this.m_mins, this.m_maxs);
- }
-
- // main think method
- switch(this.ctf_status)
- {
- case FLAG_BASE:
- {
- if(autocvar_g_ctf_dropped_capture_radius)
- {
- for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
- if(tmp_entity.ctf_status == FLAG_DROPPED)
- if(vdist(this.origin - tmp_entity.origin, <, autocvar_g_ctf_dropped_capture_radius))
- if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
- ctf_Handle_Capture(this, tmp_entity, CAPTURE_DROPPED);
- }
- return;
- }
-
- case FLAG_DROPPED:
- {
- this.angles = '0 0 0'; // reset flag angles in case warpzones adjust it
-
- if(autocvar_g_ctf_flag_dropped_floatinwater)
- {
- vector midpoint = ((this.absmin + this.absmax) * 0.5);
- if(pointcontents(midpoint) == CONTENT_WATER)
- {
- this.velocity = this.velocity * 0.5;
-
- if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
- { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
- else
- { set_movetype(this, MOVETYPE_FLY); }
- }
- else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
- }
- if(autocvar_g_ctf_flag_return_dropped)
- {
- if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
- {
- SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
- ctf_CheckFlagReturn(this, RETURN_DROPPED);
- return;
- }
- }
- if(this.ctf_flagdamaged_byworld)
- {
- TakeResource(this, RESOURCE_HEALTH, ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE));
- ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
- return;
- }
- else if(autocvar_g_ctf_flag_return_time)
- {
- TakeResource(this, RESOURCE_HEALTH, ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE));
- ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
- return;
- }
- return;
- }
-
- case FLAG_CARRY:
- {
- if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
- {
- SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
- ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
-
- CS(this.owner).impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
- ImpulseCommands(this.owner);
- }
- if(autocvar_g_ctf_stalemate)
- {
- if(time >= wpforenemy_nextthink)
- {
- ctf_CheckStalemate();
- wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
- }
- }
- if(CTF_SAMETEAM(this, this.owner) && this.team)
- {
- if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
- ctf_Handle_Throw(this.owner, NULL, DROP_THROW);
- else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
- ctf_Handle_Return(this, this.owner);
- }
- return;
- }
-
- case FLAG_PASSING:
- {
- vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5);
- targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
- WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
-
- if((this.pass_target == NULL)
- || (IS_DEAD(this.pass_target))
- || (this.pass_target.flagcarried)
- || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
- || ((trace_fraction < 1) && (trace_ent != this.pass_target))
- || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit))
- {
- // give up, pass failed
- ctf_Handle_Drop(this, NULL, DROP_PASS);
- }
- else
- {
- // still a viable target, go for it
- ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
- }
- return;
- }
-
- default: // this should never happen
- {
- LOG_TRACE("ctf_FlagThink(): Flag exists with no status?");
- return;
- }
- }
-}
-
-METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
-{
- return = false;
- if(game_stopped) return;
- if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
-
- bool is_not_monster = (!IS_MONSTER(toucher));
-
- // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
- if(ITEM_TOUCH_NEEDKILL())
- {
- if(!autocvar_g_ctf_flag_return_damage_delay)
- {
- SetResourceAmountExplicit(flag, RESOURCE_HEALTH, 0);
- ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
- }
- if(!flag.ctf_flagdamaged_byworld) { return; }
- }
-
- // special touch behaviors
- if(STAT(FROZEN, toucher)) { return; }
- else if(IS_VEHICLE(toucher))
- {
- if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
- toucher = toucher.owner; // the player is actually the vehicle owner, not other
- else
- return; // do nothing
- }
- else if(IS_MONSTER(toucher))
- {
- if(!autocvar_g_ctf_allow_monster_touch)
- return; // do nothing
- }
- else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
- {
- if(time > flag.wait) // if we haven't in a while, play a sound/effect
- {
- Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
- _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
- flag.wait = time + FLAG_TOUCHRATE;
- }
- return;
- }
- else if(IS_DEAD(toucher)) { return; }
-
- switch(flag.ctf_status)
- {
- case FLAG_BASE:
- {
- if(ctf_oneflag)
- {
- if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
- ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
- else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
- ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
- }
- else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
- ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
- else if(CTF_DIFFTEAM(toucher, flag) && (toucher.flagcarried) && CTF_SAMETEAM(toucher.flagcarried, toucher) && (!toucher.ctf_captureshielded) && autocvar_g_ctf_flag_return_carrying && (time > toucher.next_take_time) && is_not_monster)
- {
- ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
- ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
- }
- else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
- ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
- break;
- }
-
- case FLAG_DROPPED:
- {
- if(CTF_SAMETEAM(toucher, flag) && ctf_Immediate_Return_Allowed(flag, toucher))
- ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
- else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
- ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
- break;
- }
-
- case FLAG_CARRY:
- {
- LOG_TRACE("Someone touched a flag even though it was being carried?");
- break;
- }
-
- case FLAG_PASSING:
- {
- if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
- {
- if(DIFF_TEAM(toucher, flag.pass_sender))
- {
- if(ctf_Immediate_Return_Allowed(flag, toucher))
- ctf_Handle_Return(flag, toucher);
- else if(is_not_monster && (!toucher.flagcarried))
- ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED);
- }
- else if(!toucher.flagcarried)
- ctf_Handle_Retrieve(flag, toucher);
- }
- break;
- }
- }
-}
-
-.float last_respawn;
-void ctf_RespawnFlag(entity flag)
-{
- // check for flag respawn being called twice in a row
- if(flag.last_respawn > time - 0.5)
- { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
-
- flag.last_respawn = time;
-
- // reset the player (if there is one)
- if((flag.owner) && (flag.owner.flagcarried == flag))
- {
- WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
- WaypointSprite_Kill(flag.owner.wps_flagreturn);
- WaypointSprite_Kill(flag.wps_flagcarrier);
-
- flag.owner.flagcarried = NULL;
- GameRules_scoring_vip(flag.owner, false);
-
- if(flag.speedrunning)
- ctf_FakeTimeLimit(flag.owner, -1);
- }
-
- if((flag.owner) && (flag.owner.vehicle))
- flag.scale = FLAG_SCALE;
-
- if(flag.ctf_status == FLAG_DROPPED)
- { WaypointSprite_Kill(flag.wps_flagdropped); }
-
- // reset the flag
- setattachment(flag, NULL, "");
- setorigin(flag, flag.ctf_spawnorigin);
-
- set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
- flag.takedamage = DAMAGE_NO;
- SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health);
- flag.solid = SOLID_TRIGGER;
- flag.velocity = '0 0 0';
- flag.angles = flag.mangle;
- flag.flags = FL_ITEM | FL_NOTARGET;
-
- flag.ctf_status = FLAG_BASE;
- flag.owner = NULL;
- flag.pass_distance = 0;
- flag.pass_sender = NULL;
- flag.pass_target = NULL;
- flag.ctf_dropper = NULL;
- flag.ctf_pickuptime = 0;
- flag.ctf_droptime = 0;
- flag.ctf_flagdamaged_byworld = false;
- navigation_dynamicgoal_unset(flag);
-
- ctf_CheckStalemate();
-}
-
-void ctf_Reset(entity this)
-{
- if(this.owner && IS_PLAYER(this.owner))
- ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
-
- this.enemy = NULL;
- ctf_RespawnFlag(this);
-}
-
-bool ctf_FlagBase_Customize(entity this, entity client)
-{
- entity e = WaypointSprite_getviewentity(client);
- entity wp_owner = this.owner;
- entity flag = e.flagcarried;
- if(flag && CTF_SAMETEAM(e, flag))
- return false;
- if(flag && (flag.cnt || wp_owner.cnt) && wp_owner.cnt != flag.cnt)
- return false;
- return true;
-}
-
-void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
-{
- // bot waypoints
- waypoint_spawnforitem_force(this, this.origin);
- navigation_dynamicgoal_init(this, true);
-
- // waypointsprites
- entity basename;
- switch (this.team)
- {
- case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
- case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
- case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
- case NUM_TEAM_4: basename = WP_FlagBasePink; break;
- default: basename = WP_FlagBaseNeutral; break;
- }
-
- entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
- wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
- WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
- setcefc(wp, ctf_FlagBase_Customize);
-
- // captureshield setup
- ctf_CaptureShield_Spawn(this);
-}
-
-.bool pushable;
-
-void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
-{
- // main setup
- flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
- ctf_worldflaglist = flag;
-
- setattachment(flag, NULL, "");
-
- flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
- flag.team = teamnumber;
- flag.classname = "item_flag_team";
- flag.target = "###item###"; // wut?
- flag.flags = FL_ITEM | FL_NOTARGET;
- IL_PUSH(g_items, flag);
- flag.solid = SOLID_TRIGGER;
- flag.takedamage = DAMAGE_NO;
- flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
- flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
- SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health);
- flag.event_damage = ctf_FlagDamage;
- flag.pushable = true;
- flag.teleportable = TELEPORT_NORMAL;
- flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP;
- flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
- flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
- if(flag.damagedbycontents)
- IL_PUSH(g_damagedbycontents, flag);
- flag.velocity = '0 0 0';
- flag.mangle = flag.angles;
- flag.reset = ctf_Reset;
- settouch(flag, ctf_FlagTouch);
- setthink(flag, ctf_FlagThink);
- flag.nextthink = time + FLAG_THINKRATE;
- flag.ctf_status = FLAG_BASE;
-
- // crudely force them all to 0
- if(autocvar_g_ctf_score_ignore_fields)
- flag.cnt = flag.score_assist = flag.score_team_capture = flag.score_capture = flag.score_drop = flag.score_pickup = flag.score_return = 0;
-
- string teamname = Static_Team_ColorName_Lower(teamnumber);
- // appearence
- if(!flag.scale) { flag.scale = FLAG_SCALE; }
- if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
- if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
- if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
- if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
- if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
-
- // sounds
-#define X(s,b) \
- if(flag.s == "") flag.s = b; \
- precache_sound(flag.s);
-
- X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnumber))))
- X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnumber))))
- X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnumber))))
- X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnumber))))
- X(snd_flag_respawn, strzone(SND(CTF_RESPAWN)))
- X(snd_flag_touch, strzone(SND(CTF_TOUCH)))
- X(snd_flag_pass, strzone(SND(CTF_PASS)))
-#undef X
-
- // precache
- precache_model(flag.model);
-
- // appearence
- _setmodel(flag, flag.model); // precision set below
- setsize(flag, CTF_FLAG.m_mins * flag.scale, CTF_FLAG.m_maxs * flag.scale);
- flag.m_mins = flag.mins; // store these for squash checks
- flag.m_maxs = flag.maxs;
- setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
-
- if(autocvar_g_ctf_flag_glowtrails)
- {
- switch(teamnumber)
- {
- case NUM_TEAM_1: flag.glow_color = 251; break;
- case NUM_TEAM_2: flag.glow_color = 210; break;
- case NUM_TEAM_3: flag.glow_color = 110; break;
- case NUM_TEAM_4: flag.glow_color = 145; break;
- default: flag.glow_color = 254; break;
- }
- flag.glow_size = 25;
- flag.glow_trail = 1;
- }
-
- flag.effects |= EF_LOWPRECISION;
- if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
- if(autocvar_g_ctf_dynamiclights)
- {
- switch(teamnumber)
- {
- case NUM_TEAM_1: flag.effects |= EF_RED; break;
- case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
- case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
- case NUM_TEAM_4: flag.effects |= EF_RED; break;
- default: flag.effects |= EF_DIMLIGHT; break;
- }
- }
-
- // flag placement
- if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
- {
- flag.dropped_origin = flag.origin;
- flag.noalign = true;
- set_movetype(flag, MOVETYPE_NONE);
- }
- else // drop to floor, automatically find a platform and set that as spawn origin
- {
- flag.noalign = false;
- droptofloor(flag);
- set_movetype(flag, MOVETYPE_NONE);
- }
-
- InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
-}
-
-
-// ================
-// Bot player logic
-// ================
-
-// NOTE: LEGACY CODE, needs to be re-written!
-
-void havocbot_ctf_calculate_middlepoint()
-{
- entity f;
- vector s = '0 0 0';
- vector fo = '0 0 0';
- int n = 0;
-
- f = ctf_worldflaglist;
- while (f)
- {
- fo = f.origin;
- s = s + fo;
- f = f.ctf_worldflagnext;
- n++;
- }
- if(!n)
- return;
-
- havocbot_middlepoint = s / n;
- havocbot_middlepoint_radius = vlen(fo - havocbot_middlepoint);
-
- havocbot_symmetry_axis_m = 0;
- havocbot_symmetry_axis_q = 0;
- if(n == 2)
- {
- // for symmetrical editing of waypoints
- entity f1 = ctf_worldflaglist;
- entity f2 = f1.ctf_worldflagnext;
- float m = -(f1.origin.y - f2.origin.y) / (f1.origin.x - f2.origin.x);
- float q = havocbot_middlepoint.y - m * havocbot_middlepoint.x;
- havocbot_symmetry_axis_m = m;
- havocbot_symmetry_axis_q = q;
- }
- havocbot_symmetry_origin_order = n;
-}
-
-
-entity havocbot_ctf_find_flag(entity bot)
-{
- entity f;
- f = ctf_worldflaglist;
- while (f)
- {
- if (CTF_SAMETEAM(bot, f))
- return f;
- f = f.ctf_worldflagnext;
- }
- return NULL;
-}
-
-entity havocbot_ctf_find_enemy_flag(entity bot)
-{
- entity f;
- f = ctf_worldflaglist;
- while (f)
- {
- if(ctf_oneflag)
- {
- if(CTF_DIFFTEAM(bot, f))
- {
- if(f.team)
- {
- if(bot.flagcarried)
- return f;
- }
- else if(!bot.flagcarried)
- return f;
- }
- }
- else if (CTF_DIFFTEAM(bot, f))
- return f;
- f = f.ctf_worldflagnext;
- }
- return NULL;
-}
-
-int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
-{
- if (!teamplay)
- return 0;
-
- int c = 0;
-
- FOREACH_CLIENT(IS_PLAYER(it), {
- if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
- continue;
-
- if(vdist(it.origin - org, <, tc_radius))
- ++c;
- });
-
- return c;
-}
-
-// unused
-#if 0
-void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
-{
- entity head;
- head = ctf_worldflaglist;
- while (head)
- {
- if (CTF_SAMETEAM(this, head))
- break;
- head = head.ctf_worldflagnext;
- }
- if (head)
- navigation_routerating(this, head, ratingscale, 10000);
-}
-#endif
-
-void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
-{
- entity head;
- head = ctf_worldflaglist;
- while (head)
- {
- if (CTF_SAMETEAM(this, head))
- {
- if (this.flagcarried)
- if ((this.flagcarried.cnt || head.cnt) && this.flagcarried.cnt != head.cnt)
- {
- head = head.ctf_worldflagnext; // skip base if it has a different group
- continue;
- }
- break;
- }
- head = head.ctf_worldflagnext;
- }
- if (!head)
- return;
-
- navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
-}
-
-void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
-{
- entity head;
- head = ctf_worldflaglist;
- while (head)
- {
- if(ctf_oneflag)
- {
- if(CTF_DIFFTEAM(this, head))
- {
- if(head.team)
- {
- if(this.flagcarried)
- break;
- }
- else if(!this.flagcarried)
- break;
- }
- }
- else if(CTF_DIFFTEAM(this, head))
- break;
- head = head.ctf_worldflagnext;
- }
- if (head)
- navigation_routerating(this, head, ratingscale, 10000);
-}
-
-void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
-{
- if (!bot_waypoints_for_items)
- {
- havocbot_goalrating_ctf_enemyflag(this, ratingscale);
- return;
- }
-
- entity head;
-
- head = havocbot_ctf_find_enemy_flag(this);
-
- if (!head)
- return;
-
- navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
-}
-
-void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
-{
- entity mf;
-
- mf = havocbot_ctf_find_flag(this);
-
- if(mf.ctf_status == FLAG_BASE)
- return;
-
- if(mf.tag_entity)
- navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
-}
-
-void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
-{
- entity head;
- head = ctf_worldflaglist;
- while (head)
- {
- // flag is out in the field
- if(head.ctf_status != FLAG_BASE)
- if(head.tag_entity==NULL) // dropped
- {
- if(df_radius)
- {
- if(vdist(org - head.origin, <, df_radius))
- navigation_routerating(this, head, ratingscale, 10000);
- }
- else
- navigation_routerating(this, head, ratingscale, 10000);
- }
-
- head = head.ctf_worldflagnext;
- }
-}
-
-void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
-{
- IL_EACH(g_items, it.bot_pickup,
- {
- // gather health and armor only
- if (it.solid)
- if (GetResourceAmount(it, RESOURCE_HEALTH) || GetResourceAmount(it, RESOURCE_ARMOR))
- if (vdist(it.origin - org, <, sradius))
- {
- // get the value of the item
- float t = it.bot_pickupevalfunc(this, it) * 0.0001;
- if (t > 0)
- navigation_routerating(this, it, t * ratingscale, 500);
- }
- });
-}
-
-void havocbot_ctf_reset_role(entity this)
-{
- float cdefense, cmiddle, coffense;
- entity mf, ef;
- float c;
-
- if(IS_DEAD(this))
- return;
-
- // Check ctf flags
- if (this.flagcarried)
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
- return;
- }
-
- mf = havocbot_ctf_find_flag(this);
- ef = havocbot_ctf_find_enemy_flag(this);
-
- // Retrieve stolen flag
- if(mf.ctf_status!=FLAG_BASE)
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
- return;
- }
-
- // If enemy flag is taken go to the middle to intercept pursuers
- if(ef.ctf_status!=FLAG_BASE)
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
- return;
- }
-
- // if there is only me on the team switch to offense
- c = 0;
- FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), { ++c; });
-
- if(c==1)
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
- return;
- }
-
- // Evaluate best position to take
- // Count mates on middle position
- cmiddle = havocbot_ctf_teamcount(this, havocbot_middlepoint, havocbot_middlepoint_radius * 0.5);
-
- // Count mates on defense position
- cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_middlepoint_radius * 0.5);
-
- // Count mates on offense position
- coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_middlepoint_radius);
-
- if(cdefense<=coffense)
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
- else if(coffense<=cmiddle)
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
- else
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
-}
-
-void havocbot_role_ctf_carrier(entity this)
-{
- if(IS_DEAD(this))
- {
- havocbot_ctf_reset_role(this);
- return;
- }
-
- if (this.flagcarried == NULL)
- {
- havocbot_ctf_reset_role(this);
- return;
- }
-
- if (navigation_goalrating_timeout(this))
- {
- navigation_goalrating_start(this);
-
- if(ctf_oneflag)
- havocbot_goalrating_ctf_enemybase(this, 50000);
- else
- havocbot_goalrating_ctf_ourbase(this, 50000);
-
- if(GetResourceAmount(this, RESOURCE_HEALTH) < 100)
- havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
-
- 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)
- {
- // Can't navigate to my own base, suicide!
- // TODO: drop it and wander around
- Damage(this, this, this, 100000, DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0');
- return;
- }
- }
-}
-
-void havocbot_role_ctf_escort(entity this)
-{
- entity mf, ef;
-
- if(IS_DEAD(this))
- {
- havocbot_ctf_reset_role(this);
- return;
- }
-
- if (this.flagcarried)
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
- return;
- }
-
- // If enemy flag is back on the base switch to previous role
- ef = havocbot_ctf_find_enemy_flag(this);
- if(ef.ctf_status==FLAG_BASE)
- {
- this.havocbot_role = this.havocbot_previous_role;
- this.havocbot_role_timeout = 0;
- return;
- }
-
- // If the flag carrier reached the base switch to defense
- mf = havocbot_ctf_find_flag(this);
- if(mf.ctf_status!=FLAG_BASE)
- if(vdist(ef.origin - mf.dropped_origin, <, 300))
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
- return;
- }
-
- // Set the role timeout if necessary
- if (!this.havocbot_role_timeout)
- {
- this.havocbot_role_timeout = time + random() * 30 + 60;
- }
-
- // If nothing happened just switch to previous role
- if (time > this.havocbot_role_timeout)
- {
- this.havocbot_role = this.havocbot_previous_role;
- this.havocbot_role_timeout = 0;
- return;
- }
-
- // Chase the flag carrier
- if (navigation_goalrating_timeout(this))
- {
- 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);
- }
-}
-
-void havocbot_role_ctf_offense(entity this)
-{
- entity mf, ef;
- vector pos;
-
- if(IS_DEAD(this))
- {
- havocbot_ctf_reset_role(this);
- return;
- }
-
- if (this.flagcarried)
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
- return;
- }
-
- // Check flags
- mf = havocbot_ctf_find_flag(this);
- ef = havocbot_ctf_find_enemy_flag(this);
-
- // Own flag stolen
- if(mf.ctf_status!=FLAG_BASE)
- {
- if(mf.tag_entity)
- pos = mf.tag_entity.origin;
- else
- pos = mf.origin;
-
- // Try to get it if closer than the enemy base
- if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
- return;
- }
- }
-
- // Escort flag carrier
- if(ef.ctf_status!=FLAG_BASE)
- {
- if(ef.tag_entity)
- pos = ef.tag_entity.origin;
- else
- pos = ef.origin;
-
- if(vdist(pos - mf.dropped_origin, >, 700))
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
- return;
- }
- }
-
- // About to fail, switch to middlefield
- if(GetResourceAmount(this, RESOURCE_HEALTH) < 50)
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
- return;
- }
-
- // Set the role timeout if necessary
- if (!this.havocbot_role_timeout)
- this.havocbot_role_timeout = time + 120;
-
- if (time > this.havocbot_role_timeout)
- {
- havocbot_ctf_reset_role(this);
- return;
- }
-
- if (navigation_goalrating_timeout(this))
- {
- 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);
- }
-}
-
-// Retriever (temporary role):
-void havocbot_role_ctf_retriever(entity this)
-{
- entity mf;
-
- if(IS_DEAD(this))
- {
- havocbot_ctf_reset_role(this);
- return;
- }
-
- if (this.flagcarried)
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
- return;
- }
-
- // If flag is back on the base switch to previous role
- mf = havocbot_ctf_find_flag(this);
- if(mf.ctf_status==FLAG_BASE)
- {
- if (mf.enemy == this) // did this bot return the flag?
- navigation_goalrating_timeout_force(this);
- havocbot_ctf_reset_role(this);
- return;
- }
-
- if (!this.havocbot_role_timeout)
- this.havocbot_role_timeout = time + 20;
-
- if (time > this.havocbot_role_timeout)
- {
- havocbot_ctf_reset_role(this);
- return;
- }
-
- if (navigation_goalrating_timeout(this))
- {
- float rt_radius;
- rt_radius = 10000;
-
- 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);
- }
-}
-
-void havocbot_role_ctf_middle(entity this)
-{
- entity mf;
-
- if(IS_DEAD(this))
- {
- havocbot_ctf_reset_role(this);
- return;
- }
-
- if (this.flagcarried)
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
- return;
- }
-
- mf = havocbot_ctf_find_flag(this);
- if(mf.ctf_status!=FLAG_BASE)
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
- return;
- }
-
- if (!this.havocbot_role_timeout)
- this.havocbot_role_timeout = time + 10;
-
- if (time > this.havocbot_role_timeout)
- {
- havocbot_ctf_reset_role(this);
- return;
- }
-
- if (navigation_goalrating_timeout(this))
- {
- vector org;
-
- org = havocbot_middlepoint;
- org.z = this.origin.z;
-
- 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);
- }
-}
-
-void havocbot_role_ctf_defense(entity this)
-{
- entity mf;
-
- if(IS_DEAD(this))
- {
- havocbot_ctf_reset_role(this);
- return;
- }
-
- if (this.flagcarried)
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
- return;
- }
-
- // If own flag was captured
- mf = havocbot_ctf_find_flag(this);
- if(mf.ctf_status!=FLAG_BASE)
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
- return;
- }
-
- if (!this.havocbot_role_timeout)
- this.havocbot_role_timeout = time + 30;
-
- if (time > this.havocbot_role_timeout)
- {
- havocbot_ctf_reset_role(this);
- return;
- }
- if (navigation_goalrating_timeout(this))
- {
- vector org = mf.dropped_origin;
-
- navigation_goalrating_start(this);
-
- // if enemies are closer to our base, go there
- entity closestplayer = NULL;
- float distance, bestdistance = 10000;
- FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), {
- distance = vlen(org - it.origin);
- if(distance<bestdistance)
- {
- closestplayer = it;
- bestdistance = distance;
- }
- });
-
- if(closestplayer)
- if(DIFF_TEAM(closestplayer, this))
- if(vdist(org - this.origin, >, 1000))
- if(checkpvs(this.origin,closestplayer)||random()<0.5)
- havocbot_goalrating_ctf_ourbase(this, 30000);
-
- havocbot_goalrating_ctf_ourstolenflag(this, 20000);
- havocbot_goalrating_ctf_droppedflags(this, 20000, org, havocbot_middlepoint_radius);
- 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);
- }
-}
-
-void havocbot_role_ctf_setrole(entity bot, int role)
-{
- string s = "(null)";
- switch(role)
- {
- case HAVOCBOT_CTF_ROLE_CARRIER:
- s = "carrier";
- bot.havocbot_role = havocbot_role_ctf_carrier;
- bot.havocbot_role_timeout = 0;
- bot.havocbot_cantfindflag = time + 10;
- if (bot.havocbot_previous_role != bot.havocbot_role)
- navigation_goalrating_timeout_force(bot);
- break;
- case HAVOCBOT_CTF_ROLE_DEFENSE:
- s = "defense";
- bot.havocbot_role = havocbot_role_ctf_defense;
- bot.havocbot_role_timeout = 0;
- break;
- case HAVOCBOT_CTF_ROLE_MIDDLE:
- s = "middle";
- bot.havocbot_role = havocbot_role_ctf_middle;
- bot.havocbot_role_timeout = 0;
- break;
- case HAVOCBOT_CTF_ROLE_OFFENSE:
- s = "offense";
- bot.havocbot_role = havocbot_role_ctf_offense;
- bot.havocbot_role_timeout = 0;
- break;
- case HAVOCBOT_CTF_ROLE_RETRIEVER:
- s = "retriever";
- bot.havocbot_previous_role = bot.havocbot_role;
- bot.havocbot_role = havocbot_role_ctf_retriever;
- bot.havocbot_role_timeout = time + 10;
- 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;
- if (bot.havocbot_previous_role != bot.havocbot_role)
- navigation_goalrating_timeout_expire(bot, 2);
- break;
- }
- LOG_TRACE(bot.netname, " switched to ", s);
-}
-
-
-// ==============
-// Hook Functions
-// ==============
-
-MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
-{
- entity player = M_ARGV(0, entity);
-
- int t = 0, t2 = 0, t3 = 0;
- bool b1 = false, b2 = false, b3 = false, b4 = false, b5 = false; // TODO: kill this, we WANT to show the other flags, somehow! (note: also means you don't see if you're FC)
-
- // initially clear items so they can be set as necessary later.
- STAT(CTF_FLAGSTATUS, player) &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
- | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
- | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
- | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
- | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
- | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
-
- // scan through all the flags and notify the client about them
- for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
- {
- if(flag.team == NUM_TEAM_1 && !b1) { b1 = true; t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
- if(flag.team == NUM_TEAM_2 && !b2) { b2 = true; t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
- if(flag.team == NUM_TEAM_3 && !b3) { b3 = true; t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
- if(flag.team == NUM_TEAM_4 && !b4) { b4 = true; t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
- if(flag.team == 0 && !b5) { b5 = true; t = CTF_NEUTRAL_FLAG_CARRYING; t2 = CTF_NEUTRAL_FLAG_TAKEN; t3 = CTF_NEUTRAL_FLAG_LOST; STAT(CTF_FLAGSTATUS, player) |= CTF_FLAG_NEUTRAL; }
-
- switch(flag.ctf_status)
- {
- case FLAG_PASSING:
- case FLAG_CARRY:
- {
- if((flag.owner == player) || (flag.pass_sender == player))
- STAT(CTF_FLAGSTATUS, player) |= t; // carrying: player is currently carrying the flag
- else
- STAT(CTF_FLAGSTATUS, player) |= t2; // taken: someone else is carrying the flag
- break;
- }
- case FLAG_DROPPED:
- {
- STAT(CTF_FLAGSTATUS, player) |= t3; // lost: the flag is dropped somewhere on the map
- break;
- }
- }
- }
-
- // item for stopping players from capturing the flag too often
- if(player.ctf_captureshielded)
- STAT(CTF_FLAGSTATUS, player) |= CTF_SHIELDED;
-
- if(ctf_stalemate)
- STAT(CTF_FLAGSTATUS, player) |= CTF_STALEMATE;
-
- // update the health of the flag carrier waypointsprite
- if(player.wps_flagcarrier)
- WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(GetResourceAmount(player, RESOURCE_HEALTH), GetResourceAmount(player, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
-}
-
-MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
-{
- entity frag_attacker = M_ARGV(1, entity);
- entity frag_target = M_ARGV(2, entity);
- float frag_damage = M_ARGV(4, float);
- vector frag_force = M_ARGV(6, vector);
-
- if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
- {
- if(frag_target == frag_attacker) // damage done to yourself
- {
- frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
- frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
- }
- else // damage done to everyone else
- {
- frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
- frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
- }
-
- M_ARGV(4, float) = frag_damage;
- M_ARGV(6, vector) = frag_force;
- }
- else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
- {
- if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(GetResourceAmount(frag_target, RESOURCE_HEALTH), GetResourceAmount(frag_target, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id)))
- if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
- {
- frag_target.wps_helpme_time = time;
- WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
- }
- // todo: add notification for when flag carrier needs help?
- }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
-{
- entity frag_attacker = M_ARGV(1, entity);
- entity frag_target = M_ARGV(2, entity);
-
- if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
- {
- GameRules_scoring_add_team(frag_attacker, SCORE, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
- GameRules_scoring_add(frag_attacker, CTF_FCKILLS, 1);
- }
-
- if(frag_target.flagcarried)
- {
- entity tmp_entity = frag_target.flagcarried;
- ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
- tmp_entity.ctf_dropper = NULL;
- }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
-{
- M_ARGV(2, float) = 0; // frag score
- return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
-}
-
-void ctf_RemovePlayer(entity player)
-{
- if(player.flagcarried)
- { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
-
- for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
- {
- if(flag.pass_sender == player) { flag.pass_sender = NULL; }
- if(flag.pass_target == player) { flag.pass_target = NULL; }
- if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
- }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
-{
- entity player = M_ARGV(0, entity);
-
- ctf_RemovePlayer(player);
-}
-
-MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
-{
- entity player = M_ARGV(0, entity);
-
- ctf_RemovePlayer(player);
-}
-
-MUTATOR_HOOKFUNCTION(ctf, ClientConnect)
-{
- if(!autocvar_g_ctf_leaderboard)
- return;
-
- entity player = M_ARGV(0, entity);
-
- if(IS_REAL_CLIENT(player))
- {
- int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
- race_send_rankings_cnt(MSG_ONE);
- for (int i = 1; i <= m; ++i)
- {
- race_SendRankings(i, 0, 0, MSG_ONE);
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, GetPressedKeys)
-{
- if(!autocvar_g_ctf_leaderboard)
- return;
-
- entity player = M_ARGV(0, entity);
-
- if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
- {
- if (!player.stored_netname)
- player.stored_netname = strzone(uid2name(player.crypto_idfp));
- if(player.stored_netname != player.netname)
- {
- db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
- strcpy(player.stored_netname, player.netname);
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
-{
- entity player = M_ARGV(0, entity);
-
- if(player.flagcarried)
- if(!autocvar_g_ctf_portalteleport)
- { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
-{
- if(MUTATOR_RETURNVALUE || game_stopped) return;
-
- entity player = M_ARGV(0, entity);
-
- if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
- {
- // pass the flag to a team mate
- if(autocvar_g_ctf_pass)
- {
- entity head, closest_target = NULL;
- head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
-
- while(head) // find the closest acceptable target to pass to
- {
- if(IS_PLAYER(head) && !IS_DEAD(head))
- if(head != player && SAME_TEAM(head, player))
- if(!head.speedrunning && !head.vehicle)
- {
- // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
- vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
- vector passer_center = CENTER_OR_VIEWOFS(player);
-
- if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
- {
- if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
- {
- if(IS_BOT_CLIENT(head))
- {
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
- ctf_Handle_Throw(head, player, DROP_PASS);
- }
- else
- {
- Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
- }
- player.throw_antispam = time + autocvar_g_ctf_pass_wait;
- return true;
- }
- else if(player.flagcarried && !head.flagcarried)
- {
- if(closest_target)
- {
- vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
- if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
- { closest_target = head; }
- }
- else { closest_target = head; }
- }
- }
- }
- head = head.chain;
- }
-
- if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
- }
-
- // throw the flag in front of you
- if(autocvar_g_ctf_throw && player.flagcarried)
- {
- if(player.throw_count == -1)
- {
- if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
- {
- player.throw_prevtime = time;
- player.throw_count = 1;
- ctf_Handle_Throw(player, NULL, DROP_THROW);
- return true;
- }
- else
- {
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
- return false;
- }
- }
- else
- {
- if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
- else { player.throw_count += 1; }
- if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
-
- player.throw_prevtime = time;
- ctf_Handle_Throw(player, NULL, DROP_THROW);
- return true;
- }
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
-{
- entity player = M_ARGV(0, entity);
-
- if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
- {
- player.wps_helpme_time = time;
- WaypointSprite_HelpMePing(player.wps_flagcarrier);
- }
- else // create a normal help me waypointsprite
- {
- WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
- WaypointSprite_Ping(player.wps_helpme);
- }
-
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
-{
- entity player = M_ARGV(0, entity);
- entity veh = M_ARGV(1, entity);
-
- if(player.flagcarried)
- {
- if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
- {
- ctf_Handle_Throw(player, NULL, DROP_NORMAL);
- }
- else
- {
- player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
- setattachment(player.flagcarried, veh, "");
- setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
- player.flagcarried.scale = VEHICLE_FLAG_SCALE;
- //player.flagcarried.angles = '0 0 0';
- }
- return true;
- }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
-{
- entity player = M_ARGV(0, entity);
-
- if(player.flagcarried)
- {
- setattachment(player.flagcarried, player, "");
- setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
- player.flagcarried.scale = FLAG_SCALE;
- player.flagcarried.angles = '0 0 0';
- player.flagcarried.nodrawtoclient = NULL;
- return true;
- }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
-{
- entity player = M_ARGV(0, entity);
-
- if(player.flagcarried)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
- ctf_RespawnFlag(player.flagcarried);
- return true;
- }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
-{
- entity flag; // temporary entity for the search method
-
- for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
- {
- switch(flag.ctf_status)
- {
- case FLAG_DROPPED:
- case FLAG_PASSING:
- {
- // lock the flag, game is over
- set_movetype(flag, MOVETYPE_NONE);
- flag.takedamage = DAMAGE_NO;
- flag.solid = SOLID_NOT;
- flag.nextthink = false; // stop thinking
-
- //dprint("stopping the ", flag.netname, " from moving.\n");
- break;
- }
-
- default:
- case FLAG_BASE:
- case FLAG_CARRY:
- {
- // do nothing for these flags
- break;
- }
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
-{
- entity bot = M_ARGV(0, entity);
-
- havocbot_ctf_reset_role(bot);
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, CheckAllowedTeams)
-{
- //M_ARGV(0, float) = ctf_teams;
- M_ARGV(1, string) = "ctf_team";
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
-{
- entity spectatee = M_ARGV(0, entity);
- entity client = M_ARGV(1, entity);
-
- STAT(CTF_FLAGSTATUS, client) = STAT(CTF_FLAGSTATUS, spectatee);
-}
-
-MUTATOR_HOOKFUNCTION(ctf, GetRecords)
-{
- int record_page = M_ARGV(0, int);
- string ret_string = M_ARGV(1, string);
-
- for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
- {
- if (MapInfo_Get_ByID(i))
- {
- float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
-
- if(!r)
- continue;
-
- // TODO: uid2name
- string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
- ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
- }
- }
-
- M_ARGV(1, string) = ret_string;
-}
-
-bool superspec_Spectate(entity this, entity targ); // TODO
-void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
-MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
-{
- entity player = M_ARGV(0, entity);
- string cmd_name = M_ARGV(1, string);
- int cmd_argc = M_ARGV(2, int);
-
- if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
-
- if(cmd_name == "followfc")
- {
- if(!g_ctf)
- return true;
-
- int _team = 0;
- bool found = false;
-
- if(cmd_argc == 2)
- {
- switch(argv(1))
- {
- case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
- case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
- case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
- case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
- }
- }
-
- FOREACH_CLIENT(IS_PLAYER(it), {
- if(it.flagcarried && (it.team == _team || _team == 0))
- {
- found = true;
- if(_team == 0 && IS_SPEC(player) && player.enemy == it)
- continue; // already spectating this fc, try another
- return superspec_Spectate(player, it);
- }
- });
-
- if(!found)
- superspec_msg("", "", player, "No active flag carrier\n", 1);
- return true;
- }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
-{
- entity frag_target = M_ARGV(0, entity);
-
- if(frag_target.flagcarried)
- ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
-}
-
-
-// ==========
-// Spawnfuncs
-// ==========
-
-/*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
-CTF flag for team one (Red).
-Keys:
-"angle" Angle the flag will point (minus 90 degrees)...
-"model" model to use, note this needs red and blue as skins 0 and 1...
-"noise" sound played when flag is picked up...
-"noise1" sound played when flag is returned by a teammate...
-"noise2" sound played when flag is captured...
-"noise3" sound played when flag is lost in the field and respawns itself...
-"noise4" sound played when flag is dropped by a player...
-"noise5" sound played when flag touches the ground... */
-spawnfunc(item_flag_team1)
-{
- if(!g_ctf) { delete(this); return; }
-
- ctf_FlagSetup(NUM_TEAM_1, this);
-}
-
-/*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
-CTF flag for team two (Blue).
-Keys:
-"angle" Angle the flag will point (minus 90 degrees)...
-"model" model to use, note this needs red and blue as skins 0 and 1...
-"noise" sound played when flag is picked up...
-"noise1" sound played when flag is returned by a teammate...
-"noise2" sound played when flag is captured...
-"noise3" sound played when flag is lost in the field and respawns itself...
-"noise4" sound played when flag is dropped by a player...
-"noise5" sound played when flag touches the ground... */
-spawnfunc(item_flag_team2)
-{
- if(!g_ctf) { delete(this); return; }
-
- ctf_FlagSetup(NUM_TEAM_2, this);
-}
-
-/*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
-CTF flag for team three (Yellow).
-Keys:
-"angle" Angle the flag will point (minus 90 degrees)...
-"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
-"noise" sound played when flag is picked up...
-"noise1" sound played when flag is returned by a teammate...
-"noise2" sound played when flag is captured...
-"noise3" sound played when flag is lost in the field and respawns itself...
-"noise4" sound played when flag is dropped by a player...
-"noise5" sound played when flag touches the ground... */
-spawnfunc(item_flag_team3)
-{
- if(!g_ctf) { delete(this); return; }
-
- ctf_FlagSetup(NUM_TEAM_3, this);
-}
-
-/*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
-CTF flag for team four (Pink).
-Keys:
-"angle" Angle the flag will point (minus 90 degrees)...
-"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
-"noise" sound played when flag is picked up...
-"noise1" sound played when flag is returned by a teammate...
-"noise2" sound played when flag is captured...
-"noise3" sound played when flag is lost in the field and respawns itself...
-"noise4" sound played when flag is dropped by a player...
-"noise5" sound played when flag touches the ground... */
-spawnfunc(item_flag_team4)
-{
- if(!g_ctf) { delete(this); return; }
-
- ctf_FlagSetup(NUM_TEAM_4, this);
-}
-
-/*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
-CTF flag (Neutral).
-Keys:
-"angle" Angle the flag will point (minus 90 degrees)...
-"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
-"noise" sound played when flag is picked up...
-"noise1" sound played when flag is returned by a teammate...
-"noise2" sound played when flag is captured...
-"noise3" sound played when flag is lost in the field and respawns itself...
-"noise4" sound played when flag is dropped by a player...
-"noise5" sound played when flag touches the ground... */
-spawnfunc(item_flag_neutral)
-{
- if(!g_ctf) { delete(this); return; }
- if(!cvar("g_ctf_oneflag")) { delete(this); return; }
-
- ctf_FlagSetup(0, this);
-}
-
-/*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
-Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
-Note: If you use spawnfunc_ctf_team entities you must define at least 2! However, unlike domination, you don't need to make a blank one too.
-Keys:
-"netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
-"cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
-spawnfunc(ctf_team)
-{
- if(!g_ctf) { delete(this); return; }
-
- this.classname = "ctf_team";
- this.team = this.cnt + 1;
-}
-
-// compatibility for quake maps
-spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
-spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
-spawnfunc(info_player_team1);
-spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
-spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
-spawnfunc(info_player_team2);
-spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
-spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
-
-spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
-spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
-
-// compatibility for wop maps
-spawnfunc(team_redplayer) { spawnfunc_info_player_team1(this); }
-spawnfunc(team_blueplayer) { spawnfunc_info_player_team2(this); }
-spawnfunc(team_ctl_redlolly) { spawnfunc_item_flag_team1(this); }
-spawnfunc(team_CTL_redlolly) { spawnfunc_item_flag_team1(this); }
-spawnfunc(team_ctl_bluelolly) { spawnfunc_item_flag_team2(this); }
-spawnfunc(team_CTL_bluelolly) { spawnfunc_item_flag_team2(this); }
-
-
-// ==============
-// Initialization
-// ==============
-
-// scoreboard setup
-void ctf_ScoreRules(int teams)
-{
- CheckAllowedTeams(NULL);
- GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
- field_team(ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
- field(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
- field(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
- field(SP_CTF_PICKUPS, "pickups", 0);
- field(SP_CTF_FCKILLS, "fckills", 0);
- field(SP_CTF_RETURNS, "returns", 0);
- field(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
- });
-}
-
-// code from here on is just to support maps that don't have flag and team entities
-void ctf_SpawnTeam (string teamname, int teamcolor)
-{
- entity this = new_pure(ctf_team);
- this.netname = teamname;
- this.cnt = teamcolor - 1;
- this.spawnfunc_checked = true;
- this.team = teamcolor;
-}
-
-void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
-{
- ctf_teams = 0;
-
- entity tmp_entity;
- for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
- {
- //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
- //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
-
- switch(tmp_entity.team)
- {
- case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
- case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
- case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
- case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
- }
- if(tmp_entity.team == 0) { ctf_oneflag = true; }
- }
-
- havocbot_ctf_calculate_middlepoint();
-
- if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
- {
- ctf_teams = 0; // so set the default red and blue teams
- BITSET_ASSIGN(ctf_teams, BIT(0));
- BITSET_ASSIGN(ctf_teams, BIT(1));
- }
-
- //ctf_teams = bound(2, ctf_teams, 4);
-
- // if no teams are found, spawn defaults
- if(find(NULL, classname, "ctf_team") == NULL)
- {
- LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
- if(ctf_teams & BIT(0))
- ctf_SpawnTeam("Red", NUM_TEAM_1);
- if(ctf_teams & BIT(1))
- ctf_SpawnTeam("Blue", NUM_TEAM_2);
- if(ctf_teams & BIT(2))
- ctf_SpawnTeam("Yellow", NUM_TEAM_3);
- if(ctf_teams & BIT(3))
- ctf_SpawnTeam("Pink", NUM_TEAM_4);
- }
-
- ctf_ScoreRules(ctf_teams);
-}
-
-void ctf_Initialize()
-{
- ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
-
- ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
- ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
- ctf_captureshield_force = autocvar_g_ctf_shield_force;
-
- InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);
-}
-#endif
#pragma once
-#ifdef SVQC
-
-void ctf_Initialize();
-
-REGISTER_MUTATOR(ctf, false)
-{
- MUTATOR_STATIC();
- MUTATOR_ONADD
- {
- GameRules_teams(true);
- GameRules_limit_score(autocvar_capturelimit_override);
- GameRules_limit_lead(autocvar_captureleadlimit_override);
-
- ctf_Initialize();
- }
- return 0;
-}
-
-// used in cheats.qc
-void ctf_RespawnFlag(entity flag);
-
-// score rule declarations
-const int ST_CTF_CAPS = 1;
-
-CLASS(Flag, Pickup)
- ATTRIB(Flag, m_mins, vector, (PL_MIN_CONST + '0 0 -13') * 1.4); // scaling be damned
- ATTRIB(Flag, m_maxs, vector, (PL_MAX_CONST + '0 0 -13') * 1.4);
-ENDCLASS(Flag)
-Flag CTF_FLAG; STATIC_INIT(Flag) { CTF_FLAG = NEW(Flag); }
-void ctf_FlagTouch(entity this, entity toucher) { ITEM_HANDLE(Pickup, CTF_FLAG, this, toucher); }
-
-// flag constants // for most of these, there is just one question to be asked: WHYYYYY?
-
-const float FLAG_SCALE = 0.6;
-
-const float FLAG_THINKRATE = 0.2;
-const float FLAG_TOUCHRATE = 0.5;
-const float WPFE_THINKRATE = 0.5;
-
-const vector FLAG_DROP_OFFSET = ('0 0 32');
-const vector FLAG_CARRY_OFFSET = ('-16 0 8');
-#define FLAG_SPAWN_OFFSET ('0 0 1' * (PL_MAX_CONST.z - 13))
-const vector FLAG_WAYPOINT_OFFSET = ('0 0 64');
-const vector FLAG_FLOAT_OFFSET = ('0 0 32');
-const vector FLAG_PASS_ARC_OFFSET = ('0 0 -10');
-
-const vector VEHICLE_FLAG_OFFSET = ('0 0 96');
-const float VEHICLE_FLAG_SCALE = 1.0;
-
-// waypoint colors
-#define WPCOLOR_ENEMYFC(t) ((t) ? colormapPaletteColor(t - 1, false) * 0.75 : '1 1 1')
-#define WPCOLOR_FLAGCARRIER(t) ((t) ? colormapPaletteColor(t - 1, false) * 0.75 : '1 1 1')
-//#define WPCOLOR_FLAGCARRIER(t) (WP_FlagCarrier.m_color)
-#define WPCOLOR_DROPPEDFLAG(t) ((t) ? ('0.25 0.25 0.25' + colormapPaletteColor(t - 1, false)) * 0.5 : '1 1 1')
-
-// sounds
-#define snd_flag_taken noise
-#define snd_flag_returned noise1
-#define snd_flag_capture noise2
-#define snd_flag_respawn noise3
-.string snd_flag_dropped;
-.string snd_flag_touch;
-.string snd_flag_pass;
-
-// score fields
-.float score_assist;
-.float score_capture;
-.float score_drop; // note: negated
-.float score_pickup;
-.float score_return;
-.float score_team_capture; // shouldn't be too high
-
-// effects
-.string toucheffect;
-.string passeffect;
-.string capeffect;
-
-// list of flags on the map
-entity ctf_worldflaglist;
-.entity ctf_worldflagnext;
-.entity ctf_staleflagnext;
-
-// waypoint sprites
-.entity wps_helpme;
-.entity wps_flagbase;
-.entity wps_flagcarrier;
-.entity wps_flagdropped;
-.entity wps_flagreturn;
-.entity wps_enemyflagcarrier;
-.float wps_helpme_time;
-bool wpforenemy_announced;
-float wpforenemy_nextthink;
-
-// statuses
-const int FLAG_BASE = 1;
-const int FLAG_DROPPED = 2;
-const int FLAG_CARRY = 3;
-const int FLAG_PASSING = 4;
-
-const int DROP_NORMAL = 1;
-const int DROP_THROW = 2;
-const int DROP_PASS = 3;
-const int DROP_RESET = 4;
-
-const int PICKUP_BASE = 1;
-const int PICKUP_DROPPED = 2;
-
-const int CAPTURE_NORMAL = 1;
-const int CAPTURE_DROPPED = 2;
-
-const int RETURN_TIMEOUT = 1;
-const int RETURN_DROPPED = 2;
-const int RETURN_DAMAGE = 3;
-const int RETURN_SPEEDRUN = 4;
-const int RETURN_NEEDKILL = 5;
-
-bool ctf_Stalemate_Customize(entity this, entity client);
-
-void ctf_Handle_Throw(entity player, entity receiver, float droptype);
-
-// flag properties
-#define ctf_spawnorigin dropped_origin
-bool ctf_stalemate; // indicates that a stalemate is active
-float ctf_captimerecord; // record time for capturing the flag
-.float ctf_pickuptime;
-.float ctf_droptime;
-.int ctf_status; // status of the flag (FLAG_BASE, FLAG_DROPPED, FLAG_CARRY declared globally)
-.entity ctf_dropper; // don't allow spam of dropping the flag
-.int max_flag_health;
-.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;
-.entity pass_sender;
-.entity pass_target;
-.float throw_antispam;
-.float throw_prevtime;
-.int throw_count;
-
-// CaptureShield: If the player is too bad to be allowed to capture, shield them from taking the flag.
-.bool ctf_captureshielded; // set to 1 if the player is too bad to be allowed to capture
-float ctf_captureshield_min_negscore; // punish at -20 points
-float ctf_captureshield_max_ratio; // punish at most 30% of each team
-float ctf_captureshield_force; // push force of the shield
-
-// 1 flag ctf
-bool ctf_oneflag; // indicates whether or not a neutral flag has been found
-
-// bot player logic
-const int HAVOCBOT_CTF_ROLE_NONE = 0;
-const int HAVOCBOT_CTF_ROLE_DEFENSE = 2;
-const int HAVOCBOT_CTF_ROLE_MIDDLE = 4;
-const int HAVOCBOT_CTF_ROLE_OFFENSE = 8;
-const int HAVOCBOT_CTF_ROLE_CARRIER = 16;
-const int HAVOCBOT_CTF_ROLE_RETRIEVER = 32;
-const int HAVOCBOT_CTF_ROLE_ESCORT = 64;
-
-.bool havocbot_cantfindflag;
-
-void havocbot_role_ctf_setrole(entity bot, int role);
-
-// team checking
-#define CTF_SAMETEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? DIFF_TEAM(a,b) : SAME_TEAM(a,b))
-#define CTF_DIFFTEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? SAME_TEAM(a,b) : DIFF_TEAM(a,b))
-#endif
-
const int CTF_RED_FLAG_TAKEN = 1;
const int CTF_RED_FLAG_LOST = 2;
const int CTF_RED_FLAG_CARRYING = 3;
--- /dev/null
+#include "sv_ctf.qh"
+
+#include <common/effects/all.qh>
+#include <common/vehicles/all.qh>
+#include <server/teamplay.qh>
+
+#include <lib/warpzone/common.qh>
+
+bool autocvar_g_ctf_allow_vehicle_carry;
+bool autocvar_g_ctf_allow_vehicle_touch;
+bool autocvar_g_ctf_allow_monster_touch;
+bool autocvar_g_ctf_throw;
+float autocvar_g_ctf_throw_angle_max;
+float autocvar_g_ctf_throw_angle_min;
+int autocvar_g_ctf_throw_punish_count;
+float autocvar_g_ctf_throw_punish_delay;
+float autocvar_g_ctf_throw_punish_time;
+float autocvar_g_ctf_throw_strengthmultiplier;
+float autocvar_g_ctf_throw_velocity_forward;
+float autocvar_g_ctf_throw_velocity_up;
+float autocvar_g_ctf_drop_velocity_up;
+float autocvar_g_ctf_drop_velocity_side;
+bool autocvar_g_ctf_oneflag_reverse;
+bool autocvar_g_ctf_portalteleport;
+bool autocvar_g_ctf_pass;
+float autocvar_g_ctf_pass_arc;
+float autocvar_g_ctf_pass_arc_max;
+float autocvar_g_ctf_pass_directional_max;
+float autocvar_g_ctf_pass_directional_min;
+float autocvar_g_ctf_pass_radius;
+float autocvar_g_ctf_pass_wait;
+bool autocvar_g_ctf_pass_request;
+float autocvar_g_ctf_pass_turnrate;
+float autocvar_g_ctf_pass_timelimit;
+float autocvar_g_ctf_pass_velocity;
+bool autocvar_g_ctf_dynamiclights;
+float autocvar_g_ctf_flag_collect_delay;
+float autocvar_g_ctf_flag_damageforcescale;
+bool autocvar_g_ctf_flag_dropped_waypoint;
+bool autocvar_g_ctf_flag_dropped_floatinwater;
+bool autocvar_g_ctf_flag_glowtrails;
+int autocvar_g_ctf_flag_health;
+bool autocvar_g_ctf_flag_return;
+bool autocvar_g_ctf_flag_return_carrying;
+float autocvar_g_ctf_flag_return_carried_radius;
+float autocvar_g_ctf_flag_return_time;
+bool autocvar_g_ctf_flag_return_when_unreachable;
+float autocvar_g_ctf_flag_return_damage;
+float autocvar_g_ctf_flag_return_damage_delay;
+float autocvar_g_ctf_flag_return_dropped;
+float autocvar_g_ctf_flagcarrier_auto_helpme_damage;
+float autocvar_g_ctf_flagcarrier_auto_helpme_time;
+float autocvar_g_ctf_flagcarrier_selfdamagefactor;
+float autocvar_g_ctf_flagcarrier_selfforcefactor;
+float autocvar_g_ctf_flagcarrier_damagefactor;
+float autocvar_g_ctf_flagcarrier_forcefactor;
+//float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting;
+bool autocvar_g_ctf_fullbrightflags;
+bool autocvar_g_ctf_ignore_frags;
+bool autocvar_g_ctf_score_ignore_fields;
+int autocvar_g_ctf_score_capture;
+int autocvar_g_ctf_score_capture_assist;
+int autocvar_g_ctf_score_kill;
+int autocvar_g_ctf_score_penalty_drop;
+int autocvar_g_ctf_score_penalty_returned;
+int autocvar_g_ctf_score_pickup_base;
+int autocvar_g_ctf_score_pickup_dropped_early;
+int autocvar_g_ctf_score_pickup_dropped_late;
+int autocvar_g_ctf_score_return;
+float autocvar_g_ctf_shield_force;
+float autocvar_g_ctf_shield_max_ratio;
+int autocvar_g_ctf_shield_min_negscore;
+bool autocvar_g_ctf_stalemate;
+int autocvar_g_ctf_stalemate_endcondition;
+float autocvar_g_ctf_stalemate_time;
+bool autocvar_g_ctf_reverse;
+float autocvar_g_ctf_dropped_capture_delay;
+float autocvar_g_ctf_dropped_capture_radius;
+
+void ctf_FakeTimeLimit(entity e, float t)
+{
+ msg_entity = e;
+ WriteByte(MSG_ONE, 3); // svc_updatestat
+ WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
+ if(t < 0)
+ WriteCoord(MSG_ONE, autocvar_timelimit);
+ else
+ WriteCoord(MSG_ONE, (t + 1) / 60);
+}
+
+void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
+{
+ if(autocvar_sv_eventlog)
+ GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != NULL) ? ftos(actor.playerid) : "")));
+ //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
+}
+
+void ctf_CaptureRecord(entity flag, entity player)
+{
+ float cap_record = ctf_captimerecord;
+ float cap_time = (time - flag.ctf_pickuptime);
+ string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
+
+ // notify about shit
+ if(ctf_oneflag)
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname);
+ else if(!ctf_captimerecord)
+ Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_TIME), player.netname, TIME_ENCODE(cap_time));
+ else if(cap_time < cap_record)
+ Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_BROKEN), player.netname, refername, TIME_ENCODE(cap_time), TIME_ENCODE(cap_record));
+ else
+ Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_UNBROKEN), player.netname, refername, TIME_ENCODE(cap_time), TIME_ENCODE(cap_record));
+
+ // write that shit in the database
+ if(!ctf_oneflag) // but not in 1-flag mode
+ if((!ctf_captimerecord) || (cap_time < cap_record))
+ {
+ ctf_captimerecord = cap_time;
+ db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
+ db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
+ write_recordmarker(player, flag.ctf_pickuptime, cap_time);
+ }
+
+ if(autocvar_g_ctf_leaderboard && !ctf_oneflag)
+ race_setTime(GetMapname(), TIME_ENCODE(cap_time), player.crypto_idfp, player.netname, player, false);
+}
+
+bool ctf_Immediate_Return_Allowed(entity flag, entity toucher)
+{
+ int num_perteam = 0;
+ FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), { ++num_perteam; });
+
+ // automatically return if there's only 1 player on the team
+ return ((autocvar_g_ctf_flag_return || num_perteam <= 1 || (autocvar_g_ctf_flag_return_carrying && toucher.flagcarried))
+ && flag.team);
+}
+
+bool ctf_Return_Customize(entity this, entity client)
+{
+ // only to the carrier
+ return boolean(client == this.owner);
+}
+
+void ctf_FlagcarrierWaypoints(entity player)
+{
+ WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
+ WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
+ WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(GetResourceAmount(player, RESOURCE_HEALTH), GetResourceAmount(player, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
+ WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
+
+ if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried))
+ {
+ if(!player.wps_enemyflagcarrier)
+ {
+ entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 0, player, wps_enemyflagcarrier, true, RADARICON_FLAG);
+ wp.colormod = WPCOLOR_ENEMYFC(player.team);
+ setcefc(wp, ctf_Stalemate_Customize);
+
+ if(IS_REAL_CLIENT(player) && !ctf_stalemate)
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_VISIBLE);
+ }
+
+ if(!player.wps_flagreturn)
+ {
+ entity owp = WaypointSprite_SpawnFixed(WP_FlagReturn, player.flagcarried.ctf_spawnorigin + FLAG_WAYPOINT_OFFSET, player, wps_flagreturn, RADARICON_FLAG);
+ owp.colormod = '0 0.8 0.8';
+ //WaypointSprite_UpdateTeamRadar(player.wps_flagreturn, RADARICON_FLAG, ((player.team) ? colormapPaletteColor(player.team - 1, false) : '1 1 1'));
+ setcefc(owp, ctf_Return_Customize);
+ }
+ }
+}
+
+void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
+{
+ float current_distance = vlen((('1 0 0' * to.x) + ('0 1 0' * to.y)) - (('1 0 0' * from.x) + ('0 1 0' * from.y))); // for the sake of this check, exclude Z axis
+ float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
+ float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
+ //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
+
+ vector targpos;
+ if(current_height) // make sure we can actually do this arcing path
+ {
+ targpos = (to + ('0 0 1' * current_height));
+ WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
+ if(trace_fraction < 1)
+ {
+ //print("normal arc line failed, trying to find new pos...");
+ WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
+ targpos = (trace_endpos + eZ * FLAG_PASS_ARC_OFFSET_Z);
+ WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
+ if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
+ /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
+ }
+ }
+ else { targpos = to; }
+
+ //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
+
+ vector desired_direction = normalize(targpos - from);
+ if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
+ else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
+}
+
+bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
+{
+ if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
+ {
+ // directional tracing only
+ float spreadlimit;
+ makevectors(passer_angle);
+
+ // find the closest point on the enemy to the center of the attack
+ float h; // hypotenuse, which is the distance between attacker to head
+ float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
+
+ h = vlen(head_center - passer_center);
+ a = h * (normalize(head_center - passer_center) * v_forward);
+
+ vector nearest_on_line = (passer_center + a * v_forward);
+ float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
+
+ spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
+ spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
+
+ if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
+ { return true; }
+ else
+ { return false; }
+ }
+ else { return true; }
+}
+
+
+// =======================
+// CaptureShield Functions
+// =======================
+
+bool ctf_CaptureShield_CheckStatus(entity p)
+{
+ int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
+ int players_worseeq, players_total;
+
+ if(ctf_captureshield_max_ratio <= 0)
+ return false;
+
+ s = GameRules_scoring_add(p, CTF_CAPS, 0);
+ s2 = GameRules_scoring_add(p, CTF_PICKUPS, 0);
+ s3 = GameRules_scoring_add(p, CTF_RETURNS, 0);
+ s4 = GameRules_scoring_add(p, CTF_FCKILLS, 0);
+
+ sr = ((s - s2) + (s3 + s4));
+
+ if(sr >= -ctf_captureshield_min_negscore)
+ return false;
+
+ players_total = players_worseeq = 0;
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ if(DIFF_TEAM(it, p))
+ continue;
+ se = GameRules_scoring_add(it, CTF_CAPS, 0);
+ se2 = GameRules_scoring_add(it, CTF_PICKUPS, 0);
+ se3 = GameRules_scoring_add(it, CTF_RETURNS, 0);
+ se4 = GameRules_scoring_add(it, CTF_FCKILLS, 0);
+
+ ser = ((se - se2) + (se3 + se4));
+
+ if(ser <= sr)
+ ++players_worseeq;
+ ++players_total;
+ });
+
+ // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
+ // use this rule here
+
+ if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
+ return false;
+
+ return true;
+}
+
+void ctf_CaptureShield_Update(entity player, bool wanted_status)
+{
+ bool updated_status = ctf_CaptureShield_CheckStatus(player);
+ if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
+ {
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
+ player.ctf_captureshielded = updated_status;
+ }
+}
+
+bool ctf_CaptureShield_Customize(entity this, entity client)
+{
+ if(!client.ctf_captureshielded) { return false; }
+ if(CTF_SAMETEAM(this, client)) { return false; }
+
+ return true;
+}
+
+void ctf_CaptureShield_Touch(entity this, entity toucher)
+{
+ if(!toucher.ctf_captureshielded) { return; }
+ if(CTF_SAMETEAM(this, toucher)) { return; }
+
+ vector mymid = (this.absmin + this.absmax) * 0.5;
+ vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
+
+ Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, mymid, normalize(theirmid - mymid) * ctf_captureshield_force);
+ if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
+}
+
+void ctf_CaptureShield_Spawn(entity flag)
+{
+ entity shield = new(ctf_captureshield);
+
+ shield.enemy = flag;
+ shield.team = flag.team;
+ settouch(shield, ctf_CaptureShield_Touch);
+ setcefc(shield, ctf_CaptureShield_Customize);
+ shield.effects = EF_ADDITIVE;
+ set_movetype(shield, MOVETYPE_NOCLIP);
+ shield.solid = SOLID_TRIGGER;
+ shield.avelocity = '7 0 11';
+ shield.scale = 0.5;
+
+ setorigin(shield, flag.origin);
+ setmodel(shield, MDL_CTF_SHIELD);
+ setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
+}
+
+
+// ====================
+// Drop/Pass/Throw Code
+// ====================
+
+void ctf_Handle_Drop(entity flag, entity player, int droptype)
+{
+ // declarations
+ player = (player ? player : flag.pass_sender);
+
+ // main
+ set_movetype(flag, MOVETYPE_TOSS);
+ flag.takedamage = DAMAGE_YES;
+ flag.angles = '0 0 0';
+ SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health);
+ flag.ctf_droptime = time;
+ flag.ctf_dropper = player;
+ flag.ctf_status = FLAG_DROPPED;
+
+ // messages and sounds
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_LOST), player.netname);
+ _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
+ ctf_EventLog("dropped", player.team, player);
+
+ // scoring
+ GameRules_scoring_add_team(player, SCORE, -((flag.score_drop) ? flag.score_drop : autocvar_g_ctf_score_penalty_drop));
+ GameRules_scoring_add(player, CTF_DROPS, 1);
+
+ // waypoints
+ if(autocvar_g_ctf_flag_dropped_waypoint) {
+ entity wp = WaypointSprite_Spawn(WP_FlagDropped, 0, 0, flag, FLAG_WAYPOINT_OFFSET, NULL, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, true, RADARICON_FLAG);
+ wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
+ }
+
+ if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
+ {
+ WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
+ WaypointSprite_UpdateHealth(flag.wps_flagdropped, GetResourceAmount(flag, RESOURCE_HEALTH));
+ }
+
+ player.throw_antispam = time + autocvar_g_ctf_pass_wait;
+
+ if(droptype == DROP_PASS)
+ {
+ flag.pass_distance = 0;
+ flag.pass_sender = NULL;
+ flag.pass_target = NULL;
+ }
+}
+
+void ctf_Handle_Retrieve(entity flag, entity player)
+{
+ entity sender = flag.pass_sender;
+
+ // transfer flag to player
+ flag.owner = player;
+ flag.owner.flagcarried = flag;
+ GameRules_scoring_vip(player, true);
+
+ // reset flag
+ if(player.vehicle)
+ {
+ setattachment(flag, player.vehicle, "");
+ setorigin(flag, VEHICLE_FLAG_OFFSET);
+ flag.scale = VEHICLE_FLAG_SCALE;
+ }
+ else
+ {
+ setattachment(flag, player, "");
+ setorigin(flag, FLAG_CARRY_OFFSET);
+ }
+ set_movetype(flag, MOVETYPE_NONE);
+ flag.takedamage = DAMAGE_NO;
+ flag.solid = SOLID_NOT;
+ flag.angles = '0 0 0';
+ flag.ctf_status = FLAG_CARRY;
+
+ // messages and sounds
+ _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
+ ctf_EventLog("receive", flag.team, player);
+
+ FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
+ if(it == sender)
+ Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_SENT), player.netname);
+ else if(it == player)
+ Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_RECEIVED), sender.netname);
+ else if(SAME_TEAM(it, sender))
+ Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_OTHER), sender.netname, player.netname);
+ });
+
+ // create new waypoint
+ ctf_FlagcarrierWaypoints(player);
+
+ sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
+ player.throw_antispam = sender.throw_antispam;
+
+ flag.pass_distance = 0;
+ flag.pass_sender = NULL;
+ flag.pass_target = NULL;
+}
+
+void ctf_Handle_Throw(entity player, entity receiver, int droptype)
+{
+ entity flag = player.flagcarried;
+ vector targ_origin, flag_velocity;
+
+ if(!flag) { return; }
+ if((droptype == DROP_PASS) && !receiver) { return; }
+
+ if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
+
+ // reset the flag
+ setattachment(flag, NULL, "");
+ setorigin(flag, player.origin + FLAG_DROP_OFFSET);
+ flag.owner.flagcarried = NULL;
+ GameRules_scoring_vip(flag.owner, false);
+ flag.owner = NULL;
+ flag.solid = SOLID_TRIGGER;
+ flag.ctf_dropper = player;
+ flag.ctf_droptime = time;
+
+ flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
+
+ switch(droptype)
+ {
+ case DROP_PASS:
+ {
+ // warpzone support:
+ // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
+ // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
+ WarpZone_RefSys_Copy(flag, receiver);
+ WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
+ targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
+
+ flag.pass_distance = vlen((('1 0 0' * targ_origin.x) + ('0 1 0' * targ_origin.y)) - (('1 0 0' * player.origin.x) + ('0 1 0' * player.origin.y))); // for the sake of this check, exclude Z axis
+ ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
+
+ // main
+ set_movetype(flag, MOVETYPE_FLY);
+ flag.takedamage = DAMAGE_NO;
+ flag.pass_sender = player;
+ flag.pass_target = receiver;
+ flag.ctf_status = FLAG_PASSING;
+
+ // other
+ _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
+ WarpZone_TrailParticles(NULL, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
+ ctf_EventLog("pass", flag.team, player);
+ break;
+ }
+
+ case DROP_THROW:
+ {
+ makevectors((player.v_angle.y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle.x, autocvar_g_ctf_throw_angle_max) * '1 0 0'));
+
+ flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & ITEM_Strength.m_itemid) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
+ flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false);
+ ctf_Handle_Drop(flag, player, droptype);
+ navigation_dynamicgoal_set(flag, player);
+ break;
+ }
+
+ case DROP_RESET:
+ {
+ flag.velocity = '0 0 0'; // do nothing
+ break;
+ }
+
+ default:
+ case DROP_NORMAL:
+ {
+ flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), false);
+ ctf_Handle_Drop(flag, player, droptype);
+ navigation_dynamicgoal_set(flag, player);
+ break;
+ }
+ }
+
+ // kill old waypointsprite
+ WaypointSprite_Ping(player.wps_flagcarrier);
+ WaypointSprite_Kill(player.wps_flagcarrier);
+
+ if(player.wps_enemyflagcarrier)
+ WaypointSprite_Kill(player.wps_enemyflagcarrier);
+
+ if(player.wps_flagreturn)
+ WaypointSprite_Kill(player.wps_flagreturn);
+
+ // captureshield
+ ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
+}
+
+void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
+{
+ return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
+}
+
+// ==============
+// Event Handlers
+// ==============
+
+void nades_GiveBonus(entity player, float score);
+
+void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
+{
+ entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
+ entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
+ entity player_team_flag = NULL, tmp_entity;
+ float old_time, new_time;
+
+ if(!player) { return; } // without someone to give the reward to, we can't possibly cap
+ 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))
+ {
+ player_team_flag = tmp_entity;
+ break;
+ }
+
+ nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
+
+ player.throw_prevtime = time;
+ player.throw_count = 0;
+
+ // messages and sounds
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_NUM(enemy_flag.team, CENTER_CTF_CAPTURE));
+ ctf_CaptureRecord(enemy_flag, player);
+ _sound(player, CH_TRIGGER, ((ctf_oneflag) ? player_team_flag.snd_flag_capture : ((DIFF_TEAM(player, flag)) ? enemy_flag.snd_flag_capture : flag.snd_flag_capture)), VOL_BASE, ATTEN_NONE);
+
+ switch(capturetype)
+ {
+ case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
+ case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
+ default: break;
+ }
+
+ // scoring
+ float pscore = 0;
+ if(enemy_flag.score_capture || flag.score_capture)
+ pscore = floor((max(1, enemy_flag.score_capture) + max(1, flag.score_capture)) * 0.5);
+ GameRules_scoring_add_team(player, SCORE, ((pscore) ? pscore : autocvar_g_ctf_score_capture));
+ float capscore = 0;
+ if(enemy_flag.score_team_capture || flag.score_team_capture)
+ capscore = floor((max(1, enemy_flag.score_team_capture) + max(1, flag.score_team_capture)) * 0.5);
+ GameRules_scoring_add_team(player, CTF_CAPS, ((capscore) ? capscore : 1));
+
+ old_time = GameRules_scoring_add(player, CTF_CAPTIME, 0);
+ new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
+ if(!old_time || new_time < old_time)
+ GameRules_scoring_add(player, CTF_CAPTIME, new_time - old_time);
+
+ // effects
+ Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
+ //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
+
+ // other
+ if(capturetype == CAPTURE_NORMAL)
+ {
+ WaypointSprite_Kill(player.wps_flagcarrier);
+ if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
+
+ if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
+ { 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);
+}
+
+void ctf_Handle_Return(entity flag, entity player)
+{
+ // messages and sounds
+ if(IS_MONSTER(player))
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN_MONSTER), player.monster_name);
+ }
+ else if(flag.team)
+ {
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_RETURN));
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN), player.netname);
+ }
+ _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
+ ctf_EventLog("return", flag.team, player);
+
+ // scoring
+ if(IS_PLAYER(player))
+ {
+ GameRules_scoring_add_team(player, SCORE, ((flag.score_return) ? flag.score_return : autocvar_g_ctf_score_return)); // reward for return
+ GameRules_scoring_add(player, CTF_RETURNS, 1); // add to count of returns
+
+ nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
+ }
+
+ TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
+
+ if(flag.ctf_dropper)
+ {
+ GameRules_scoring_add(flag.ctf_dropper, SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
+ ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
+ flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
+ }
+
+ // other
+ if(player.flagcarried == flag)
+ WaypointSprite_Kill(player.wps_flagcarrier);
+
+ flag.enemy = player;
+
+ // reset the flag
+ ctf_RespawnFlag(flag);
+}
+
+void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
+{
+ // declarations
+ float pickup_dropped_score; // used to calculate dropped pickup score
+
+ // attach the flag to the player
+ flag.owner = player;
+ player.flagcarried = flag;
+ GameRules_scoring_vip(player, true);
+ if(player.vehicle)
+ {
+ setattachment(flag, player.vehicle, "");
+ setorigin(flag, VEHICLE_FLAG_OFFSET);
+ flag.scale = VEHICLE_FLAG_SCALE;
+ }
+ else
+ {
+ setattachment(flag, player, "");
+ setorigin(flag, FLAG_CARRY_OFFSET);
+ }
+
+ // flag setup
+ set_movetype(flag, MOVETYPE_NONE);
+ flag.takedamage = DAMAGE_NO;
+ flag.solid = SOLID_NOT;
+ flag.angles = '0 0 0';
+ flag.ctf_status = FLAG_CARRY;
+
+ switch(pickuptype)
+ {
+ case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
+ case PICKUP_DROPPED: SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health); break; // reset health/return timelimit
+ default: break;
+ }
+
+ // messages and sounds
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_PICKUP), player.netname);
+ if(ctf_stalemate)
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER);
+ if(!flag.team)
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL);
+ else if(CTF_DIFFTEAM(player, flag))
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_PICKUP));
+ else
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_RETURN : CENTER_CTF_PICKUP_RETURN_ENEMY), Team_ColorCode(flag.team));
+
+ Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
+
+ if(!flag.team)
+ FOREACH_CLIENT(IS_PLAYER(it) && it != player && DIFF_TEAM(it, player), { Send_Notification(NOTIF_ONE, it, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname); });
+
+ if(flag.team)
+ FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
+ if(CTF_SAMETEAM(flag, it))
+ if(SAME_TEAM(player, it))
+ Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
+ else
+ Send_Notification(NOTIF_ONE, it, MSG_CHOICE, ((SAME_TEAM(flag, player)) ? CHOICE_CTF_PICKUP_ENEMY_TEAM : CHOICE_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), player.netname);
+ });
+
+ _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
+
+ // scoring
+ GameRules_scoring_add(player, CTF_PICKUPS, 1);
+ nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
+ switch(pickuptype)
+ {
+ case PICKUP_BASE:
+ {
+ GameRules_scoring_add_team(player, SCORE, ((flag.score_pickup) ? flag.score_pickup : autocvar_g_ctf_score_pickup_base));
+ ctf_EventLog("steal", flag.team, player);
+ break;
+ }
+
+ case PICKUP_DROPPED:
+ {
+ pickup_dropped_score = (autocvar_g_ctf_flag_return_time ? bound(0, ((flag.ctf_droptime + autocvar_g_ctf_flag_return_time) - time) / autocvar_g_ctf_flag_return_time, 1) : 1);
+ pickup_dropped_score = floor((autocvar_g_ctf_score_pickup_dropped_late * (1 - pickup_dropped_score) + autocvar_g_ctf_score_pickup_dropped_early * pickup_dropped_score) + 0.5);
+ LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score));
+ GameRules_scoring_add_team(player, SCORE, pickup_dropped_score);
+ ctf_EventLog("pickup", flag.team, player);
+ break;
+ }
+
+ default: break;
+ }
+
+ // speedrunning
+ if(pickuptype == PICKUP_BASE)
+ {
+ flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
+ if((player.speedrunning) && (ctf_captimerecord))
+ ctf_FakeTimeLimit(player, time + ctf_captimerecord);
+ }
+
+ // effects
+ Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
+
+ // waypoints
+ if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
+ ctf_FlagcarrierWaypoints(player);
+ WaypointSprite_Ping(player.wps_flagcarrier);
+}
+
+
+// ===================
+// Main Flag Functions
+// ===================
+
+void ctf_CheckFlagReturn(entity flag, int returntype)
+{
+ if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
+ {
+ if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, GetResourceAmount(flag, RESOURCE_HEALTH)); }
+
+ if((GetResourceAmount(flag, RESOURCE_HEALTH) <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
+ {
+ switch(returntype)
+ {
+ case RETURN_DROPPED:
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DROPPED)); break;
+ case RETURN_DAMAGE:
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DAMAGED)); break;
+ case RETURN_SPEEDRUN:
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_SPEEDRUN), TIME_ENCODE(ctf_captimerecord)); break;
+ case RETURN_NEEDKILL:
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_NEEDKILL)); break;
+ default:
+ case RETURN_TIMEOUT:
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_TIMEOUT)); break;
+ }
+ _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
+ ctf_EventLog("returned", flag.team, NULL);
+ flag.enemy = NULL;
+ ctf_RespawnFlag(flag);
+ }
+ }
+}
+
+bool ctf_Stalemate_Customize(entity this, entity client)
+{
+ // make spectators see what the player would see
+ entity e = WaypointSprite_getviewentity(client);
+ entity wp_owner = this.owner;
+
+ // team waypoints
+ //if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
+ if(SAME_TEAM(wp_owner, e)) { return false; }
+ if(!IS_PLAYER(e)) { return false; }
+
+ return true;
+}
+
+void ctf_CheckStalemate()
+{
+ // declarations
+ int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
+ entity tmp_entity;
+
+ entity ctf_staleflaglist = NULL; // reset the list, we need to build the list each time this function runs
+
+ // build list of stale flags
+ for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
+ {
+ if(autocvar_g_ctf_stalemate)
+ if(tmp_entity.ctf_status != FLAG_BASE)
+ if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
+ {
+ tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
+ ctf_staleflaglist = tmp_entity;
+
+ switch(tmp_entity.team)
+ {
+ case NUM_TEAM_1: ++stale_red_flags; break;
+ case NUM_TEAM_2: ++stale_blue_flags; break;
+ case NUM_TEAM_3: ++stale_yellow_flags; break;
+ case NUM_TEAM_4: ++stale_pink_flags; break;
+ default: ++stale_neutral_flags; break;
+ }
+ }
+ }
+
+ if(ctf_oneflag)
+ stale_flags = (stale_neutral_flags >= 1);
+ else
+ stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
+
+ if(ctf_oneflag && stale_flags == 1)
+ ctf_stalemate = true;
+ else if(stale_flags >= 2)
+ ctf_stalemate = true;
+ else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
+ { ctf_stalemate = false; wpforenemy_announced = false; }
+ else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
+ { ctf_stalemate = false; wpforenemy_announced = false; }
+
+ // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
+ if(ctf_stalemate)
+ {
+ for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
+ {
+ if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
+ {
+ entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, NULL, 0, tmp_entity.owner, wps_enemyflagcarrier, true, RADARICON_FLAG);
+ wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
+ setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize);
+ }
+ }
+
+ if (!wpforenemy_announced)
+ {
+ FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { Send_Notification(NOTIF_ONE, it, MSG_CENTER, ((it.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER)); });
+
+ wpforenemy_announced = true;
+ }
+ }
+}
+
+void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
+{
+ if(ITEM_DAMAGE_NEEDKILL(deathtype))
+ {
+ if(autocvar_g_ctf_flag_return_damage_delay)
+ this.ctf_flagdamaged_byworld = true;
+ else
+ {
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
+ ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
+ }
+ return;
+ }
+ if(autocvar_g_ctf_flag_return_damage)
+ {
+ // reduce health and check if it should be returned
+ TakeResource(this, RESOURCE_HEALTH, damage);
+ ctf_CheckFlagReturn(this, RETURN_DAMAGE);
+ return;
+ }
+}
+
+void ctf_FlagThink(entity this)
+{
+ // declarations
+ entity tmp_entity;
+
+ this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
+
+ // captureshield
+ if(this == ctf_worldflaglist) // only for the first flag
+ FOREACH_CLIENT(true, { ctf_CaptureShield_Update(it, 1); }); // release shield only
+
+ // sanity checks
+ if(this.mins != this.m_mins || this.maxs != this.m_maxs) { // reset the flag boundaries in case it got squished
+ LOG_TRACE("wtf the flag got squashed?");
+ tracebox(this.origin, this.m_mins, this.m_maxs, this.origin, MOVE_NOMONSTERS, this);
+ if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
+ setsize(this, this.m_mins, this.m_maxs);
+ }
+
+ // main think method
+ switch(this.ctf_status)
+ {
+ case FLAG_BASE:
+ {
+ if(autocvar_g_ctf_dropped_capture_radius)
+ {
+ for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
+ if(tmp_entity.ctf_status == FLAG_DROPPED)
+ if(vdist(this.origin - tmp_entity.origin, <, autocvar_g_ctf_dropped_capture_radius))
+ if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
+ ctf_Handle_Capture(this, tmp_entity, CAPTURE_DROPPED);
+ }
+ return;
+ }
+
+ case FLAG_DROPPED:
+ {
+ this.angles = '0 0 0'; // reset flag angles in case warpzones adjust it
+
+ if(autocvar_g_ctf_flag_dropped_floatinwater)
+ {
+ vector midpoint = ((this.absmin + this.absmax) * 0.5);
+ if(pointcontents(midpoint) == CONTENT_WATER)
+ {
+ this.velocity = this.velocity * 0.5;
+
+ if (pointcontents(midpoint + eZ * FLAG_FLOAT_OFFSET_Z) == CONTENT_WATER)
+ { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
+ else
+ { set_movetype(this, MOVETYPE_FLY); }
+ }
+ else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
+ }
+ if(autocvar_g_ctf_flag_return_dropped)
+ {
+ if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
+ {
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
+ ctf_CheckFlagReturn(this, RETURN_DROPPED);
+ return;
+ }
+ }
+ if(this.ctf_flagdamaged_byworld)
+ {
+ TakeResource(this, RESOURCE_HEALTH, ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE));
+ ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
+ return;
+ }
+ else if(autocvar_g_ctf_flag_return_time)
+ {
+ TakeResource(this, RESOURCE_HEALTH, ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE));
+ ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
+ return;
+ }
+ return;
+ }
+
+ case FLAG_CARRY:
+ {
+ if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
+ {
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
+ ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
+
+ CS(this.owner).impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
+ ImpulseCommands(this.owner);
+ }
+ if(autocvar_g_ctf_stalemate)
+ {
+ if(time >= wpforenemy_nextthink)
+ {
+ ctf_CheckStalemate();
+ wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
+ }
+ }
+ if(CTF_SAMETEAM(this, this.owner) && this.team)
+ {
+ if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
+ ctf_Handle_Throw(this.owner, NULL, DROP_THROW);
+ else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
+ ctf_Handle_Return(this, this.owner);
+ }
+ return;
+ }
+
+ case FLAG_PASSING:
+ {
+ vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5);
+ targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
+ WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
+
+ if((this.pass_target == NULL)
+ || (IS_DEAD(this.pass_target))
+ || (this.pass_target.flagcarried)
+ || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
+ || ((trace_fraction < 1) && (trace_ent != this.pass_target))
+ || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit))
+ {
+ // give up, pass failed
+ ctf_Handle_Drop(this, NULL, DROP_PASS);
+ }
+ else
+ {
+ // still a viable target, go for it
+ ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
+ }
+ return;
+ }
+
+ default: // this should never happen
+ {
+ LOG_TRACE("ctf_FlagThink(): Flag exists with no status?");
+ return;
+ }
+ }
+}
+
+METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
+{
+ return = false;
+ if(game_stopped) return;
+ if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
+
+ bool is_not_monster = (!IS_MONSTER(toucher));
+
+ // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
+ if(ITEM_TOUCH_NEEDKILL())
+ {
+ if(!autocvar_g_ctf_flag_return_damage_delay)
+ {
+ SetResourceAmountExplicit(flag, RESOURCE_HEALTH, 0);
+ ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
+ }
+ if(!flag.ctf_flagdamaged_byworld) { return; }
+ }
+
+ // special touch behaviors
+ if(STAT(FROZEN, toucher)) { return; }
+ else if(IS_VEHICLE(toucher))
+ {
+ if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
+ toucher = toucher.owner; // the player is actually the vehicle owner, not other
+ else
+ return; // do nothing
+ }
+ else if(IS_MONSTER(toucher))
+ {
+ if(!autocvar_g_ctf_allow_monster_touch)
+ return; // do nothing
+ }
+ else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
+ {
+ if(time > flag.wait) // if we haven't in a while, play a sound/effect
+ {
+ Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
+ _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
+ flag.wait = time + FLAG_TOUCHRATE;
+ }
+ return;
+ }
+ else if(IS_DEAD(toucher)) { return; }
+
+ switch(flag.ctf_status)
+ {
+ case FLAG_BASE:
+ {
+ if(ctf_oneflag)
+ {
+ if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
+ ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
+ else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
+ ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
+ }
+ else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
+ ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
+ else if(CTF_DIFFTEAM(toucher, flag) && (toucher.flagcarried) && CTF_SAMETEAM(toucher.flagcarried, toucher) && (!toucher.ctf_captureshielded) && autocvar_g_ctf_flag_return_carrying && (time > toucher.next_take_time) && is_not_monster)
+ {
+ ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
+ ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
+ }
+ else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
+ ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
+ break;
+ }
+
+ case FLAG_DROPPED:
+ {
+ if(CTF_SAMETEAM(toucher, flag) && ctf_Immediate_Return_Allowed(flag, toucher))
+ ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
+ else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
+ ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
+ break;
+ }
+
+ case FLAG_CARRY:
+ {
+ LOG_TRACE("Someone touched a flag even though it was being carried?");
+ break;
+ }
+
+ case FLAG_PASSING:
+ {
+ if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
+ {
+ if(DIFF_TEAM(toucher, flag.pass_sender))
+ {
+ if(ctf_Immediate_Return_Allowed(flag, toucher))
+ ctf_Handle_Return(flag, toucher);
+ else if(is_not_monster && (!toucher.flagcarried))
+ ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED);
+ }
+ else if(!toucher.flagcarried)
+ ctf_Handle_Retrieve(flag, toucher);
+ }
+ break;
+ }
+ }
+}
+
+.float last_respawn;
+void ctf_RespawnFlag(entity flag)
+{
+ // check for flag respawn being called twice in a row
+ if(flag.last_respawn > time - 0.5)
+ { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
+
+ flag.last_respawn = time;
+
+ // reset the player (if there is one)
+ if((flag.owner) && (flag.owner.flagcarried == flag))
+ {
+ WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
+ WaypointSprite_Kill(flag.owner.wps_flagreturn);
+ WaypointSprite_Kill(flag.wps_flagcarrier);
+
+ flag.owner.flagcarried = NULL;
+ GameRules_scoring_vip(flag.owner, false);
+
+ if(flag.speedrunning)
+ ctf_FakeTimeLimit(flag.owner, -1);
+ }
+
+ if((flag.owner) && (flag.owner.vehicle))
+ flag.scale = FLAG_SCALE;
+
+ if(flag.ctf_status == FLAG_DROPPED)
+ { WaypointSprite_Kill(flag.wps_flagdropped); }
+
+ // reset the flag
+ setattachment(flag, NULL, "");
+ setorigin(flag, flag.ctf_spawnorigin);
+
+ set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
+ flag.takedamage = DAMAGE_NO;
+ SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health);
+ flag.solid = SOLID_TRIGGER;
+ flag.velocity = '0 0 0';
+ flag.angles = flag.mangle;
+ flag.flags = FL_ITEM | FL_NOTARGET;
+
+ flag.ctf_status = FLAG_BASE;
+ flag.owner = NULL;
+ flag.pass_distance = 0;
+ flag.pass_sender = NULL;
+ flag.pass_target = NULL;
+ flag.ctf_dropper = NULL;
+ flag.ctf_pickuptime = 0;
+ flag.ctf_droptime = 0;
+ flag.ctf_flagdamaged_byworld = false;
+ navigation_dynamicgoal_unset(flag);
+
+ ctf_CheckStalemate();
+}
+
+void ctf_Reset(entity this)
+{
+ if(this.owner && IS_PLAYER(this.owner))
+ ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
+
+ this.enemy = NULL;
+ ctf_RespawnFlag(this);
+}
+
+bool ctf_FlagBase_Customize(entity this, entity client)
+{
+ entity e = WaypointSprite_getviewentity(client);
+ entity wp_owner = this.owner;
+ entity flag = e.flagcarried;
+ if(flag && CTF_SAMETEAM(e, flag))
+ return false;
+ if(flag && (flag.cnt || wp_owner.cnt) && wp_owner.cnt != flag.cnt)
+ return false;
+ return true;
+}
+
+void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
+{
+ // bot waypoints
+ waypoint_spawnforitem_force(this, this.origin);
+ navigation_dynamicgoal_init(this, true);
+
+ // waypointsprites
+ entity basename;
+ switch (this.team)
+ {
+ case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
+ case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
+ case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
+ case NUM_TEAM_4: basename = WP_FlagBasePink; break;
+ default: basename = WP_FlagBaseNeutral; break;
+ }
+
+ entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
+ wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
+ WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
+ setcefc(wp, ctf_FlagBase_Customize);
+
+ // captureshield setup
+ ctf_CaptureShield_Spawn(this);
+}
+
+.bool pushable;
+
+void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
+{
+ // main setup
+ flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
+ ctf_worldflaglist = flag;
+
+ setattachment(flag, NULL, "");
+
+ flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
+ flag.team = teamnumber;
+ flag.classname = "item_flag_team";
+ flag.target = "###item###"; // for finding the nearest item using findnearest
+ flag.flags = FL_ITEM | FL_NOTARGET;
+ IL_PUSH(g_items, flag);
+ flag.solid = SOLID_TRIGGER;
+ flag.takedamage = DAMAGE_NO;
+ flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
+ flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
+ SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health);
+ flag.event_damage = ctf_FlagDamage;
+ flag.pushable = true;
+ flag.teleportable = TELEPORT_NORMAL;
+ flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP;
+ flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
+ flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
+ if(flag.damagedbycontents)
+ IL_PUSH(g_damagedbycontents, flag);
+ flag.velocity = '0 0 0';
+ flag.mangle = flag.angles;
+ flag.reset = ctf_Reset;
+ settouch(flag, ctf_FlagTouch);
+ setthink(flag, ctf_FlagThink);
+ flag.nextthink = time + FLAG_THINKRATE;
+ flag.ctf_status = FLAG_BASE;
+
+ // crudely force them all to 0
+ if(autocvar_g_ctf_score_ignore_fields)
+ flag.cnt = flag.score_assist = flag.score_team_capture = flag.score_capture = flag.score_drop = flag.score_pickup = flag.score_return = 0;
+
+ string teamname = Static_Team_ColorName_Lower(teamnumber);
+ // appearence
+ if(!flag.scale) { flag.scale = FLAG_SCALE; }
+ if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
+ if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
+ if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
+ if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
+ if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
+
+ // sounds
+#define X(s,b) \
+ if(flag.s == "") flag.s = b; \
+ precache_sound(flag.s);
+
+ X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnumber))))
+ X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnumber))))
+ X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnumber))))
+ X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnumber))))
+ X(snd_flag_respawn, strzone(SND(CTF_RESPAWN)))
+ X(snd_flag_touch, strzone(SND(CTF_TOUCH)))
+ X(snd_flag_pass, strzone(SND(CTF_PASS)))
+#undef X
+
+ // precache
+ precache_model(flag.model);
+
+ // appearence
+ _setmodel(flag, flag.model); // precision set below
+ setsize(flag, CTF_FLAG.m_mins * flag.scale, CTF_FLAG.m_maxs * flag.scale);
+ flag.m_mins = flag.mins; // store these for squash checks
+ flag.m_maxs = flag.maxs;
+ setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
+
+ if(autocvar_g_ctf_flag_glowtrails)
+ {
+ switch(teamnumber)
+ {
+ case NUM_TEAM_1: flag.glow_color = 251; break;
+ case NUM_TEAM_2: flag.glow_color = 210; break;
+ case NUM_TEAM_3: flag.glow_color = 110; break;
+ case NUM_TEAM_4: flag.glow_color = 145; break;
+ default: flag.glow_color = 254; break;
+ }
+ flag.glow_size = 25;
+ flag.glow_trail = 1;
+ }
+
+ flag.effects |= EF_LOWPRECISION;
+ if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
+ if(autocvar_g_ctf_dynamiclights)
+ {
+ switch(teamnumber)
+ {
+ case NUM_TEAM_1: flag.effects |= EF_RED; break;
+ case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
+ case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
+ case NUM_TEAM_4: flag.effects |= EF_RED; break;
+ default: flag.effects |= EF_DIMLIGHT; break;
+ }
+ }
+
+ // flag placement
+ if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
+ {
+ flag.dropped_origin = flag.origin;
+ flag.noalign = true;
+ set_movetype(flag, MOVETYPE_NONE);
+ }
+ else // drop to floor, automatically find a platform and set that as spawn origin
+ {
+ flag.noalign = false;
+ droptofloor(flag);
+ set_movetype(flag, MOVETYPE_NONE);
+ }
+
+ InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
+}
+
+
+// ================
+// Bot player logic
+// ================
+
+// NOTE: LEGACY CODE, needs to be re-written!
+
+void havocbot_ctf_calculate_middlepoint()
+{
+ entity f;
+ vector s = '0 0 0';
+ vector fo = '0 0 0';
+ int n = 0;
+
+ f = ctf_worldflaglist;
+ while (f)
+ {
+ fo = f.origin;
+ s = s + fo;
+ f = f.ctf_worldflagnext;
+ n++;
+ }
+ if(!n)
+ return;
+
+ havocbot_middlepoint = s / n;
+ havocbot_middlepoint_radius = vlen(fo - havocbot_middlepoint);
+
+ havocbot_symmetry_axis_m = 0;
+ havocbot_symmetry_axis_q = 0;
+ if(n == 2)
+ {
+ // for symmetrical editing of waypoints
+ entity f1 = ctf_worldflaglist;
+ entity f2 = f1.ctf_worldflagnext;
+ float m = -(f1.origin.y - f2.origin.y) / (f1.origin.x - f2.origin.x);
+ float q = havocbot_middlepoint.y - m * havocbot_middlepoint.x;
+ havocbot_symmetry_axis_m = m;
+ havocbot_symmetry_axis_q = q;
+ }
+ havocbot_symmetry_origin_order = n;
+}
+
+
+entity havocbot_ctf_find_flag(entity bot)
+{
+ entity f;
+ f = ctf_worldflaglist;
+ while (f)
+ {
+ if (CTF_SAMETEAM(bot, f))
+ return f;
+ f = f.ctf_worldflagnext;
+ }
+ return NULL;
+}
+
+entity havocbot_ctf_find_enemy_flag(entity bot)
+{
+ entity f;
+ f = ctf_worldflaglist;
+ while (f)
+ {
+ if(ctf_oneflag)
+ {
+ if(CTF_DIFFTEAM(bot, f))
+ {
+ if(f.team)
+ {
+ if(bot.flagcarried)
+ return f;
+ }
+ else if(!bot.flagcarried)
+ return f;
+ }
+ }
+ else if (CTF_DIFFTEAM(bot, f))
+ return f;
+ f = f.ctf_worldflagnext;
+ }
+ return NULL;
+}
+
+int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
+{
+ if (!teamplay)
+ return 0;
+
+ int c = 0;
+
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
+ continue;
+
+ if(vdist(it.origin - org, <, tc_radius))
+ ++c;
+ });
+
+ return c;
+}
+
+// unused
+#if 0
+void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
+{
+ entity head;
+ head = ctf_worldflaglist;
+ while (head)
+ {
+ if (CTF_SAMETEAM(this, head))
+ break;
+ head = head.ctf_worldflagnext;
+ }
+ if (head)
+ navigation_routerating(this, head, ratingscale, 10000);
+}
+#endif
+
+void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
+{
+ entity head;
+ head = ctf_worldflaglist;
+ while (head)
+ {
+ if (CTF_SAMETEAM(this, head))
+ {
+ if (this.flagcarried)
+ if ((this.flagcarried.cnt || head.cnt) && this.flagcarried.cnt != head.cnt)
+ {
+ head = head.ctf_worldflagnext; // skip base if it has a different group
+ continue;
+ }
+ break;
+ }
+ head = head.ctf_worldflagnext;
+ }
+ if (!head)
+ return;
+
+ navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
+{
+ entity head;
+ head = ctf_worldflaglist;
+ while (head)
+ {
+ if(ctf_oneflag)
+ {
+ if(CTF_DIFFTEAM(this, head))
+ {
+ if(head.team)
+ {
+ if(this.flagcarried)
+ break;
+ }
+ else if(!this.flagcarried)
+ break;
+ }
+ }
+ else if(CTF_DIFFTEAM(this, head))
+ break;
+ head = head.ctf_worldflagnext;
+ }
+ if (head)
+ {
+ if (head.ctf_status == FLAG_CARRY)
+ {
+ // adjust rating of our flag carrier depending on his health
+ head = head.tag_entity;
+ float f = bound(0, (GetResourceAmount(head, RESOURCE_HEALTH) + GetResourceAmount(head, RESOURCE_ARMOR)) / 100, 2) - 1;
+ ratingscale += ratingscale * f * 0.1;
+ }
+ navigation_routerating(this, head, ratingscale, 10000);
+ }
+}
+
+void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
+{
+ // disabled because we always spawn waypoints for flags with waypoint_spawnforitem_force
+ /*
+ if (!bot_waypoints_for_items)
+ {
+ havocbot_goalrating_ctf_enemyflag(this, ratingscale);
+ return;
+ }
+ */
+ entity head;
+
+ head = havocbot_ctf_find_enemy_flag(this);
+
+ if (!head)
+ return;
+
+ navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
+{
+ entity mf;
+
+ mf = havocbot_ctf_find_flag(this);
+
+ if(mf.ctf_status == FLAG_BASE)
+ return;
+
+ if(mf.tag_entity)
+ navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
+{
+ entity head;
+ head = ctf_worldflaglist;
+ while (head)
+ {
+ // flag is out in the field
+ if(head.ctf_status != FLAG_BASE)
+ if(head.tag_entity==NULL) // dropped
+ {
+ if(df_radius)
+ {
+ if(vdist(org - head.origin, <, df_radius))
+ navigation_routerating(this, head, ratingscale, 10000);
+ }
+ else
+ navigation_routerating(this, head, ratingscale, 10000);
+ }
+
+ head = head.ctf_worldflagnext;
+ }
+}
+
+void havocbot_ctf_reset_role(entity this)
+{
+ float cdefense, cmiddle, coffense;
+ entity mf, ef;
+
+ if(IS_DEAD(this))
+ return;
+
+ // Check ctf flags
+ if (this.flagcarried)
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
+ return;
+ }
+
+ mf = havocbot_ctf_find_flag(this);
+ ef = havocbot_ctf_find_enemy_flag(this);
+
+ // Retrieve stolen flag
+ if(mf.ctf_status!=FLAG_BASE)
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
+ return;
+ }
+
+ // If enemy flag is taken go to the middle to intercept pursuers
+ if(ef.ctf_status!=FLAG_BASE)
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
+ return;
+ }
+
+ // if there is no one else on the team switch to offense
+ int count = 0;
+ // don't check if this bot is a player since it isn't true when the bot is added to the server
+ FOREACH_CLIENT(it != this && IS_PLAYER(it) && SAME_TEAM(it, this), { ++count; });
+
+ if (count == 0)
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
+ return;
+ }
+ else if (time < CS(this).jointime + 1)
+ {
+ // if bots spawn all at once set good default roles
+ if (count == 1)
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
+ return;
+ }
+ else if (count == 2)
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
+ return;
+ }
+ }
+
+ // Evaluate best position to take
+ // Count mates on middle position
+ cmiddle = havocbot_ctf_teamcount(this, havocbot_middlepoint, havocbot_middlepoint_radius * 0.5);
+
+ // Count mates on defense position
+ cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_middlepoint_radius * 0.5);
+
+ // Count mates on offense position
+ coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_middlepoint_radius);
+
+ if(cdefense<=coffense)
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
+ else if(coffense<=cmiddle)
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
+ else
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
+
+ // if bots spawn all at once assign them a more appropriated role after a while
+ if (time < CS(this).jointime + 1 && count > 2)
+ this.havocbot_role_timeout = time + 10 + random() * 10;
+}
+
+bool havocbot_ctf_is_basewaypoint(entity item)
+{
+ if (item.classname != "waypoint")
+ return false;
+
+ entity head = ctf_worldflaglist;
+ while (head)
+ {
+ if (item == head.bot_basewaypoint)
+ return true;
+ head = head.ctf_worldflagnext;
+ }
+ return false;
+}
+
+void havocbot_role_ctf_carrier(entity this)
+{
+ if(IS_DEAD(this))
+ {
+ havocbot_ctf_reset_role(this);
+ return;
+ }
+
+ if (this.flagcarried == NULL)
+ {
+ havocbot_ctf_reset_role(this);
+ return;
+ }
+
+ if (navigation_goalrating_timeout(this))
+ {
+ navigation_goalrating_start(this);
+
+ // role: carrier
+ entity mf = havocbot_ctf_find_flag(this);
+ vector base_org = mf.dropped_origin;
+ float base_rating = (mf.ctf_status == FLAG_BASE) ? 10000 : (vdist(this.origin - base_org, >, 100) ? 2000 : 1000);
+ if(ctf_oneflag)
+ havocbot_goalrating_ctf_enemybase(this, base_rating);
+ else
+ havocbot_goalrating_ctf_ourbase(this, base_rating);
+
+ // start collecting items very close to the bot but only inside of own base radius
+ if (vdist(this.origin - base_org, <, havocbot_middlepoint_radius))
+ havocbot_goalrating_items(this, 15000, this.origin, min(500, havocbot_middlepoint_radius * 0.5));
+
+ havocbot_goalrating_items(this, 10000, base_org, havocbot_middlepoint_radius * 0.5);
+
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+
+ entity goal = this.goalentity;
+ if (havocbot_ctf_is_basewaypoint(goal) && vdist(goal.origin - this.origin, <, 100))
+ this.goalentity_lock_timeout = time + ((this.bot_aimtarg) ? 2 : 3);
+
+ if (goal)
+ this.havocbot_cantfindflag = time + 10;
+ else if (time > this.havocbot_cantfindflag)
+ {
+ // Can't navigate to my own base, suicide!
+ // TODO: drop it and wander around
+ Damage(this, this, this, 100000, DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0');
+ return;
+ }
+ }
+}
+
+void havocbot_role_ctf_escort(entity this)
+{
+ entity mf, ef;
+
+ if(IS_DEAD(this))
+ {
+ havocbot_ctf_reset_role(this);
+ return;
+ }
+
+ if (this.flagcarried)
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
+ return;
+ }
+
+ // If enemy flag is back on the base switch to previous role
+ ef = havocbot_ctf_find_enemy_flag(this);
+ if(ef.ctf_status==FLAG_BASE)
+ {
+ this.havocbot_role = this.havocbot_previous_role;
+ this.havocbot_role_timeout = 0;
+ return;
+ }
+ if (ef.ctf_status == FLAG_DROPPED)
+ {
+ navigation_goalrating_timeout_expire(this, 1);
+ return;
+ }
+
+ // If the flag carrier reached the base switch to defense
+ mf = havocbot_ctf_find_flag(this);
+ if (mf.ctf_status != FLAG_BASE && vdist(ef.origin - mf.dropped_origin, <, 900))
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
+ return;
+ }
+
+ // Set the role timeout if necessary
+ if (!this.havocbot_role_timeout)
+ {
+ this.havocbot_role_timeout = time + random() * 30 + 60;
+ }
+
+ // If nothing happened just switch to previous role
+ if (time > this.havocbot_role_timeout)
+ {
+ this.havocbot_role = this.havocbot_previous_role;
+ this.havocbot_role_timeout = 0;
+ return;
+ }
+
+ // Chase the flag carrier
+ if (navigation_goalrating_timeout(this))
+ {
+ navigation_goalrating_start(this);
+
+ // role: escort
+ havocbot_goalrating_ctf_enemyflag(this, 10000);
+ havocbot_goalrating_ctf_ourstolenflag(this, 6000);
+ havocbot_goalrating_items(this, 21000, this.origin, 10000);
+
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+void havocbot_role_ctf_offense(entity this)
+{
+ entity mf, ef;
+ vector pos;
+
+ if(IS_DEAD(this))
+ {
+ havocbot_ctf_reset_role(this);
+ return;
+ }
+
+ if (this.flagcarried)
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
+ return;
+ }
+
+ // Check flags
+ mf = havocbot_ctf_find_flag(this);
+ ef = havocbot_ctf_find_enemy_flag(this);
+
+ // Own flag stolen
+ if(mf.ctf_status!=FLAG_BASE)
+ {
+ if(mf.tag_entity)
+ pos = mf.tag_entity.origin;
+ else
+ pos = mf.origin;
+
+ // Try to get it if closer than the enemy base
+ if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
+ return;
+ }
+ }
+
+ // Escort flag carrier
+ if(ef.ctf_status!=FLAG_BASE)
+ {
+ if(ef.tag_entity)
+ pos = ef.tag_entity.origin;
+ else
+ pos = ef.origin;
+
+ if(vdist(pos - mf.dropped_origin, >, 700))
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
+ return;
+ }
+ }
+
+ // Set the role timeout if necessary
+ if (!this.havocbot_role_timeout)
+ this.havocbot_role_timeout = time + 120;
+
+ if (time > this.havocbot_role_timeout)
+ {
+ havocbot_ctf_reset_role(this);
+ return;
+ }
+
+ if (navigation_goalrating_timeout(this))
+ {
+ navigation_goalrating_start(this);
+
+ // role: offense
+ havocbot_goalrating_ctf_ourstolenflag(this, 10000);
+ havocbot_goalrating_ctf_enemybase(this, 10000);
+ havocbot_goalrating_items(this, 22000, this.origin, 10000);
+
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+// Retriever (temporary role):
+void havocbot_role_ctf_retriever(entity this)
+{
+ entity mf;
+
+ if(IS_DEAD(this))
+ {
+ havocbot_ctf_reset_role(this);
+ return;
+ }
+
+ if (this.flagcarried)
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
+ return;
+ }
+
+ // If flag is back on the base switch to previous role
+ mf = havocbot_ctf_find_flag(this);
+ if(mf.ctf_status==FLAG_BASE)
+ {
+ if (mf.enemy == this) // did this bot return the flag?
+ navigation_goalrating_timeout_force(this);
+ havocbot_ctf_reset_role(this);
+ return;
+ }
+
+ if (!this.havocbot_role_timeout)
+ this.havocbot_role_timeout = time + 20;
+
+ if (time > this.havocbot_role_timeout)
+ {
+ havocbot_ctf_reset_role(this);
+ return;
+ }
+
+ if (navigation_goalrating_timeout(this))
+ {
+ const float RT_RADIUS = 10000;
+
+ navigation_goalrating_start(this);
+
+ // role: retriever
+ havocbot_goalrating_ctf_ourstolenflag(this, 10000);
+ havocbot_goalrating_ctf_droppedflags(this, 12000, this.origin, RT_RADIUS);
+ havocbot_goalrating_ctf_enemybase(this, 8000);
+ entity ef = havocbot_ctf_find_enemy_flag(this);
+ vector enemy_base_org = ef.dropped_origin;
+ // start collecting items very close to the bot but only inside of enemy base radius
+ if (vdist(this.origin - enemy_base_org, <, havocbot_middlepoint_radius))
+ havocbot_goalrating_items(this, 27000, this.origin, min(500, havocbot_middlepoint_radius * 0.5));
+ havocbot_goalrating_items(this, 18000, this.origin, havocbot_middlepoint_radius);
+
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+void havocbot_role_ctf_middle(entity this)
+{
+ entity mf;
+
+ if(IS_DEAD(this))
+ {
+ havocbot_ctf_reset_role(this);
+ return;
+ }
+
+ if (this.flagcarried)
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
+ return;
+ }
+
+ mf = havocbot_ctf_find_flag(this);
+ if(mf.ctf_status!=FLAG_BASE)
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
+ return;
+ }
+
+ if (!this.havocbot_role_timeout)
+ this.havocbot_role_timeout = time + 10;
+
+ if (time > this.havocbot_role_timeout)
+ {
+ havocbot_ctf_reset_role(this);
+ return;
+ }
+
+ if (navigation_goalrating_timeout(this))
+ {
+ vector org;
+
+ org = havocbot_middlepoint;
+ org.z = this.origin.z;
+
+ navigation_goalrating_start(this);
+
+ // role: middle
+ havocbot_goalrating_ctf_ourstolenflag(this, 8000);
+ havocbot_goalrating_ctf_droppedflags(this, 9000, this.origin, 10000);
+ havocbot_goalrating_enemyplayers(this, 25000, org, havocbot_middlepoint_radius * 0.5);
+ havocbot_goalrating_items(this, 25000, org, havocbot_middlepoint_radius * 0.5);
+ havocbot_goalrating_items(this, 18000, this.origin, 10000);
+ havocbot_goalrating_ctf_enemybase(this, 3000);
+
+ navigation_goalrating_end(this);
+
+ entity goal = this.goalentity;
+ if (havocbot_ctf_is_basewaypoint(goal) && vdist(goal.origin - this.origin, <, 100))
+ this.goalentity_lock_timeout = time + 2;
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+void havocbot_role_ctf_defense(entity this)
+{
+ entity mf;
+
+ if(IS_DEAD(this))
+ {
+ havocbot_ctf_reset_role(this);
+ return;
+ }
+
+ if (this.flagcarried)
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
+ return;
+ }
+
+ // If own flag was captured
+ mf = havocbot_ctf_find_flag(this);
+ if(mf.ctf_status!=FLAG_BASE)
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
+ return;
+ }
+
+ if (!this.havocbot_role_timeout)
+ this.havocbot_role_timeout = time + 30;
+
+ if (time > this.havocbot_role_timeout)
+ {
+ havocbot_ctf_reset_role(this);
+ return;
+ }
+ if (navigation_goalrating_timeout(this))
+ {
+ vector org = mf.dropped_origin;
+
+ navigation_goalrating_start(this);
+
+ // if enemies are closer to our base, go there
+ entity closestplayer = NULL;
+ float distance, bestdistance = 10000;
+ FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), {
+ distance = vlen(org - it.origin);
+ if(distance<bestdistance)
+ {
+ closestplayer = it;
+ bestdistance = distance;
+ }
+ });
+
+ // role: defense
+ if(closestplayer)
+ if(DIFF_TEAM(closestplayer, this))
+ if(vdist(org - this.origin, >, 1000))
+ if(checkpvs(this.origin,closestplayer)||random()<0.5)
+ havocbot_goalrating_ctf_ourbase(this, 10000);
+
+ havocbot_goalrating_ctf_ourstolenflag(this, 5000);
+ havocbot_goalrating_ctf_droppedflags(this, 6000, org, havocbot_middlepoint_radius);
+ havocbot_goalrating_enemyplayers(this, 25000, org, havocbot_middlepoint_radius);
+ havocbot_goalrating_items(this, 25000, org, havocbot_middlepoint_radius);
+ havocbot_goalrating_items(this, 18000, this.origin, 10000);
+
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+void havocbot_role_ctf_setrole(entity bot, int role)
+{
+ string s = "(null)";
+ switch(role)
+ {
+ case HAVOCBOT_CTF_ROLE_CARRIER:
+ s = "carrier";
+ bot.havocbot_role = havocbot_role_ctf_carrier;
+ bot.havocbot_role_timeout = 0;
+ bot.havocbot_cantfindflag = time + 10;
+ if (bot.havocbot_previous_role != bot.havocbot_role)
+ navigation_goalrating_timeout_force(bot);
+ break;
+ case HAVOCBOT_CTF_ROLE_DEFENSE:
+ s = "defense";
+ bot.havocbot_role = havocbot_role_ctf_defense;
+ bot.havocbot_role_timeout = 0;
+ break;
+ case HAVOCBOT_CTF_ROLE_MIDDLE:
+ s = "middle";
+ bot.havocbot_role = havocbot_role_ctf_middle;
+ bot.havocbot_role_timeout = 0;
+ break;
+ case HAVOCBOT_CTF_ROLE_OFFENSE:
+ s = "offense";
+ bot.havocbot_role = havocbot_role_ctf_offense;
+ bot.havocbot_role_timeout = 0;
+ break;
+ case HAVOCBOT_CTF_ROLE_RETRIEVER:
+ s = "retriever";
+ bot.havocbot_previous_role = bot.havocbot_role;
+ bot.havocbot_role = havocbot_role_ctf_retriever;
+ bot.havocbot_role_timeout = time + 10;
+ 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;
+ if (bot.havocbot_previous_role != bot.havocbot_role)
+ navigation_goalrating_timeout_expire(bot, 2);
+ break;
+ }
+ LOG_TRACE(bot.netname, " switched to ", s);
+}
+
+
+// ==============
+// Hook Functions
+// ==============
+
+MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
+{
+ entity player = M_ARGV(0, entity);
+
+ int t = 0, t2 = 0, t3 = 0;
+ bool b1 = false, b2 = false, b3 = false, b4 = false, b5 = false; // TODO: kill this, we WANT to show the other flags, somehow! (note: also means you don't see if you're FC)
+
+ // initially clear items so they can be set as necessary later.
+ STAT(CTF_FLAGSTATUS, player) &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
+ | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
+ | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
+ | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
+ | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
+ | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
+
+ // scan through all the flags and notify the client about them
+ for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
+ {
+ if(flag.team == NUM_TEAM_1 && !b1) { b1 = true; t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
+ if(flag.team == NUM_TEAM_2 && !b2) { b2 = true; t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
+ if(flag.team == NUM_TEAM_3 && !b3) { b3 = true; t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
+ if(flag.team == NUM_TEAM_4 && !b4) { b4 = true; t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
+ if(flag.team == 0 && !b5) { b5 = true; t = CTF_NEUTRAL_FLAG_CARRYING; t2 = CTF_NEUTRAL_FLAG_TAKEN; t3 = CTF_NEUTRAL_FLAG_LOST; STAT(CTF_FLAGSTATUS, player) |= CTF_FLAG_NEUTRAL; }
+
+ switch(flag.ctf_status)
+ {
+ case FLAG_PASSING:
+ case FLAG_CARRY:
+ {
+ if((flag.owner == player) || (flag.pass_sender == player))
+ STAT(CTF_FLAGSTATUS, player) |= t; // carrying: player is currently carrying the flag
+ else
+ STAT(CTF_FLAGSTATUS, player) |= t2; // taken: someone else is carrying the flag
+ break;
+ }
+ case FLAG_DROPPED:
+ {
+ STAT(CTF_FLAGSTATUS, player) |= t3; // lost: the flag is dropped somewhere on the map
+ break;
+ }
+ }
+ }
+
+ // item for stopping players from capturing the flag too often
+ if(player.ctf_captureshielded)
+ STAT(CTF_FLAGSTATUS, player) |= CTF_SHIELDED;
+
+ if(ctf_stalemate)
+ STAT(CTF_FLAGSTATUS, player) |= CTF_STALEMATE;
+
+ // update the health of the flag carrier waypointsprite
+ if(player.wps_flagcarrier)
+ WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(GetResourceAmount(player, RESOURCE_HEALTH), GetResourceAmount(player, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
+}
+
+MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
+{
+ entity frag_attacker = M_ARGV(1, entity);
+ entity frag_target = M_ARGV(2, entity);
+ float frag_damage = M_ARGV(4, float);
+ vector frag_force = M_ARGV(6, vector);
+
+ if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
+ {
+ if(frag_target == frag_attacker) // damage done to yourself
+ {
+ frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
+ frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
+ }
+ else // damage done to everyone else
+ {
+ frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
+ frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
+ }
+
+ M_ARGV(4, float) = frag_damage;
+ M_ARGV(6, vector) = frag_force;
+ }
+ else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
+ {
+ if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(GetResourceAmount(frag_target, RESOURCE_HEALTH), GetResourceAmount(frag_target, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id)))
+ if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
+ {
+ frag_target.wps_helpme_time = time;
+ WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
+ }
+ // todo: add notification for when flag carrier needs help?
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
+{
+ entity frag_attacker = M_ARGV(1, entity);
+ entity frag_target = M_ARGV(2, entity);
+
+ if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
+ {
+ GameRules_scoring_add_team(frag_attacker, SCORE, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
+ GameRules_scoring_add(frag_attacker, CTF_FCKILLS, 1);
+ }
+
+ if(frag_target.flagcarried)
+ {
+ entity tmp_entity = frag_target.flagcarried;
+ ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
+ tmp_entity.ctf_dropper = NULL;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
+{
+ M_ARGV(2, float) = 0; // frag score
+ return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
+}
+
+void ctf_RemovePlayer(entity player)
+{
+ if(player.flagcarried)
+ { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
+
+ for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
+ {
+ if(flag.pass_sender == player) { flag.pass_sender = NULL; }
+ if(flag.pass_target == player) { flag.pass_target = NULL; }
+ if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
+{
+ entity player = M_ARGV(0, entity);
+
+ ctf_RemovePlayer(player);
+}
+
+MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
+{
+ entity player = M_ARGV(0, entity);
+
+ ctf_RemovePlayer(player);
+}
+
+MUTATOR_HOOKFUNCTION(ctf, ClientConnect)
+{
+ if(!autocvar_g_ctf_leaderboard)
+ return;
+
+ entity player = M_ARGV(0, entity);
+
+ if(IS_REAL_CLIENT(player))
+ {
+ int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
+ race_send_rankings_cnt(MSG_ONE);
+ for (int i = 1; i <= m; ++i)
+ {
+ race_SendRankings(i, 0, 0, MSG_ONE);
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, GetPressedKeys)
+{
+ if(!autocvar_g_ctf_leaderboard)
+ return;
+
+ entity player = M_ARGV(0, entity);
+
+ if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
+ {
+ if (!player.stored_netname)
+ player.stored_netname = strzone(uid2name(player.crypto_idfp));
+ if(player.stored_netname != player.netname)
+ {
+ db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
+ strcpy(player.stored_netname, player.netname);
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(player.flagcarried)
+ if(!autocvar_g_ctf_portalteleport)
+ { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
+{
+ if(MUTATOR_RETURNVALUE || game_stopped) return;
+
+ entity player = M_ARGV(0, entity);
+
+ if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
+ {
+ // pass the flag to a team mate
+ if(autocvar_g_ctf_pass)
+ {
+ entity head, closest_target = NULL;
+ head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
+
+ while(head) // find the closest acceptable target to pass to
+ {
+ if(IS_PLAYER(head) && !IS_DEAD(head))
+ if(head != player && SAME_TEAM(head, player))
+ if(!head.speedrunning && !head.vehicle)
+ {
+ // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
+ vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
+ vector passer_center = CENTER_OR_VIEWOFS(player);
+
+ if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
+ {
+ if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
+ {
+ if(IS_BOT_CLIENT(head))
+ {
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
+ ctf_Handle_Throw(head, player, DROP_PASS);
+ }
+ else
+ {
+ Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
+ }
+ player.throw_antispam = time + autocvar_g_ctf_pass_wait;
+ return true;
+ }
+ else if(player.flagcarried && !head.flagcarried)
+ {
+ if(closest_target)
+ {
+ vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
+ if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
+ { closest_target = head; }
+ }
+ else { closest_target = head; }
+ }
+ }
+ }
+ head = head.chain;
+ }
+
+ if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
+ }
+
+ // throw the flag in front of you
+ if(autocvar_g_ctf_throw && player.flagcarried)
+ {
+ if(player.throw_count == -1)
+ {
+ if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
+ {
+ player.throw_prevtime = time;
+ player.throw_count = 1;
+ ctf_Handle_Throw(player, NULL, DROP_THROW);
+ return true;
+ }
+ else
+ {
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
+ return false;
+ }
+ }
+ else
+ {
+ if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
+ else { player.throw_count += 1; }
+ if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
+
+ player.throw_prevtime = time;
+ ctf_Handle_Throw(player, NULL, DROP_THROW);
+ return true;
+ }
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
+ {
+ player.wps_helpme_time = time;
+ WaypointSprite_HelpMePing(player.wps_flagcarrier);
+ }
+ else // create a normal help me waypointsprite
+ {
+ WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
+ WaypointSprite_Ping(player.wps_helpme);
+ }
+
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
+{
+ entity player = M_ARGV(0, entity);
+ entity veh = M_ARGV(1, entity);
+
+ if(player.flagcarried)
+ {
+ if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
+ {
+ ctf_Handle_Throw(player, NULL, DROP_NORMAL);
+ }
+ else
+ {
+ player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
+ setattachment(player.flagcarried, veh, "");
+ setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
+ player.flagcarried.scale = VEHICLE_FLAG_SCALE;
+ //player.flagcarried.angles = '0 0 0';
+ }
+ return true;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(player.flagcarried)
+ {
+ setattachment(player.flagcarried, player, "");
+ setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
+ player.flagcarried.scale = FLAG_SCALE;
+ player.flagcarried.angles = '0 0 0';
+ player.flagcarried.nodrawtoclient = NULL;
+ return true;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(player.flagcarried)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
+ ctf_RespawnFlag(player.flagcarried);
+ return true;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
+{
+ entity flag; // temporary entity for the search method
+
+ for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
+ {
+ switch(flag.ctf_status)
+ {
+ case FLAG_DROPPED:
+ case FLAG_PASSING:
+ {
+ // lock the flag, game is over
+ set_movetype(flag, MOVETYPE_NONE);
+ flag.takedamage = DAMAGE_NO;
+ flag.solid = SOLID_NOT;
+ flag.nextthink = false; // stop thinking
+
+ //dprint("stopping the ", flag.netname, " from moving.\n");
+ break;
+ }
+
+ default:
+ case FLAG_BASE:
+ case FLAG_CARRY:
+ {
+ // do nothing for these flags
+ break;
+ }
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
+{
+ entity bot = M_ARGV(0, entity);
+
+ havocbot_ctf_reset_role(bot);
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, TeamBalance_CheckAllowedTeams)
+{
+ M_ARGV(1, string) = "ctf_team";
+}
+
+MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
+{
+ entity spectatee = M_ARGV(0, entity);
+ entity client = M_ARGV(1, entity);
+
+ STAT(CTF_FLAGSTATUS, client) = STAT(CTF_FLAGSTATUS, spectatee);
+}
+
+MUTATOR_HOOKFUNCTION(ctf, GetRecords)
+{
+ int record_page = M_ARGV(0, int);
+ string ret_string = M_ARGV(1, string);
+
+ for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
+ {
+ if (MapInfo_Get_ByID(i))
+ {
+ float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
+
+ if(!r)
+ continue;
+
+ // TODO: uid2name
+ string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
+ ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
+ }
+ }
+
+ M_ARGV(1, string) = ret_string;
+}
+
+bool superspec_Spectate(entity this, entity targ); // TODO
+void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
+MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
+{
+ entity player = M_ARGV(0, entity);
+ string cmd_name = M_ARGV(1, string);
+ int cmd_argc = M_ARGV(2, int);
+
+ if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
+
+ if(cmd_name == "followfc")
+ {
+ if(!g_ctf)
+ return true;
+
+ int _team = 0;
+ bool found = false;
+
+ if(cmd_argc == 2)
+ {
+ switch(argv(1))
+ {
+ case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
+ case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
+ case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
+ case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
+ }
+ }
+
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ if(it.flagcarried && (it.team == _team || _team == 0))
+ {
+ found = true;
+ if(_team == 0 && IS_SPEC(player) && player.enemy == it)
+ continue; // already spectating this fc, try another
+ return superspec_Spectate(player, it);
+ }
+ });
+
+ if(!found)
+ superspec_msg("", "", player, "No active flag carrier\n", 1);
+ return true;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
+{
+ entity frag_target = M_ARGV(0, entity);
+
+ if(frag_target.flagcarried)
+ ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
+}
+
+
+// ==========
+// Spawnfuncs
+// ==========
+
+/*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
+CTF flag for team one (Red).
+Keys:
+"angle" Angle the flag will point (minus 90 degrees)...
+"model" model to use, note this needs red and blue as skins 0 and 1...
+"noise" sound played when flag is picked up...
+"noise1" sound played when flag is returned by a teammate...
+"noise2" sound played when flag is captured...
+"noise3" sound played when flag is lost in the field and respawns itself...
+"noise4" sound played when flag is dropped by a player...
+"noise5" sound played when flag touches the ground... */
+spawnfunc(item_flag_team1)
+{
+ if(!g_ctf) { delete(this); return; }
+
+ ctf_FlagSetup(NUM_TEAM_1, this);
+}
+
+/*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
+CTF flag for team two (Blue).
+Keys:
+"angle" Angle the flag will point (minus 90 degrees)...
+"model" model to use, note this needs red and blue as skins 0 and 1...
+"noise" sound played when flag is picked up...
+"noise1" sound played when flag is returned by a teammate...
+"noise2" sound played when flag is captured...
+"noise3" sound played when flag is lost in the field and respawns itself...
+"noise4" sound played when flag is dropped by a player...
+"noise5" sound played when flag touches the ground... */
+spawnfunc(item_flag_team2)
+{
+ if(!g_ctf) { delete(this); return; }
+
+ ctf_FlagSetup(NUM_TEAM_2, this);
+}
+
+/*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
+CTF flag for team three (Yellow).
+Keys:
+"angle" Angle the flag will point (minus 90 degrees)...
+"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
+"noise" sound played when flag is picked up...
+"noise1" sound played when flag is returned by a teammate...
+"noise2" sound played when flag is captured...
+"noise3" sound played when flag is lost in the field and respawns itself...
+"noise4" sound played when flag is dropped by a player...
+"noise5" sound played when flag touches the ground... */
+spawnfunc(item_flag_team3)
+{
+ if(!g_ctf) { delete(this); return; }
+
+ ctf_FlagSetup(NUM_TEAM_3, this);
+}
+
+/*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
+CTF flag for team four (Pink).
+Keys:
+"angle" Angle the flag will point (minus 90 degrees)...
+"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
+"noise" sound played when flag is picked up...
+"noise1" sound played when flag is returned by a teammate...
+"noise2" sound played when flag is captured...
+"noise3" sound played when flag is lost in the field and respawns itself...
+"noise4" sound played when flag is dropped by a player...
+"noise5" sound played when flag touches the ground... */
+spawnfunc(item_flag_team4)
+{
+ if(!g_ctf) { delete(this); return; }
+
+ ctf_FlagSetup(NUM_TEAM_4, this);
+}
+
+/*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
+CTF flag (Neutral).
+Keys:
+"angle" Angle the flag will point (minus 90 degrees)...
+"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
+"noise" sound played when flag is picked up...
+"noise1" sound played when flag is returned by a teammate...
+"noise2" sound played when flag is captured...
+"noise3" sound played when flag is lost in the field and respawns itself...
+"noise4" sound played when flag is dropped by a player...
+"noise5" sound played when flag touches the ground... */
+spawnfunc(item_flag_neutral)
+{
+ if(!g_ctf) { delete(this); return; }
+ if(!cvar("g_ctf_oneflag")) { delete(this); return; }
+
+ ctf_FlagSetup(0, this);
+}
+
+/*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
+Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
+Note: If you use spawnfunc_ctf_team entities you must define at least 2! However, unlike domination, you don't need to make a blank one too.
+Keys:
+"netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
+"cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
+spawnfunc(ctf_team)
+{
+ if(!g_ctf) { delete(this); return; }
+
+ this.classname = "ctf_team";
+ this.team = this.cnt + 1;
+}
+
+// compatibility for quake maps
+spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
+spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
+spawnfunc(info_player_team1);
+spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
+spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
+spawnfunc(info_player_team2);
+spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
+spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
+
+spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
+spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
+
+// compatibility for wop maps
+spawnfunc(team_redplayer) { spawnfunc_info_player_team1(this); }
+spawnfunc(team_blueplayer) { spawnfunc_info_player_team2(this); }
+spawnfunc(team_ctl_redlolly) { spawnfunc_item_flag_team1(this); }
+spawnfunc(team_CTL_redlolly) { spawnfunc_item_flag_team1(this); }
+spawnfunc(team_ctl_bluelolly) { spawnfunc_item_flag_team2(this); }
+spawnfunc(team_CTL_bluelolly) { spawnfunc_item_flag_team2(this); }
+
+
+// ==============
+// Initialization
+// ==============
+
+// scoreboard setup
+void ctf_ScoreRules(int teams)
+{
+ GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
+ field_team(ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
+ field(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
+ field(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
+ field(SP_CTF_PICKUPS, "pickups", 0);
+ field(SP_CTF_FCKILLS, "fckills", 0);
+ field(SP_CTF_RETURNS, "returns", 0);
+ field(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
+ });
+}
+
+// code from here on is just to support maps that don't have flag and team entities
+void ctf_SpawnTeam (string teamname, int teamcolor)
+{
+ entity this = new_pure(ctf_team);
+ this.netname = teamname;
+ this.cnt = teamcolor - 1;
+ this.spawnfunc_checked = true;
+ this.team = teamcolor;
+}
+
+void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
+{
+ ctf_teams = 0;
+
+ entity tmp_entity;
+ for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
+ {
+ //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
+ //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
+
+ switch(tmp_entity.team)
+ {
+ case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
+ case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
+ case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
+ case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
+ }
+ if(tmp_entity.team == 0) { ctf_oneflag = true; }
+ }
+
+ havocbot_ctf_calculate_middlepoint();
+
+ if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
+ {
+ ctf_teams = 0; // so set the default red and blue teams
+ BITSET_ASSIGN(ctf_teams, BIT(0));
+ BITSET_ASSIGN(ctf_teams, BIT(1));
+ }
+
+ //ctf_teams = bound(2, ctf_teams, 4);
+
+ // if no teams are found, spawn defaults
+ if(find(NULL, classname, "ctf_team") == NULL)
+ {
+ LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
+ if(ctf_teams & BIT(0))
+ ctf_SpawnTeam("Red", NUM_TEAM_1);
+ if(ctf_teams & BIT(1))
+ ctf_SpawnTeam("Blue", NUM_TEAM_2);
+ if(ctf_teams & BIT(2))
+ ctf_SpawnTeam("Yellow", NUM_TEAM_3);
+ if(ctf_teams & BIT(3))
+ ctf_SpawnTeam("Pink", NUM_TEAM_4);
+ }
+
+ ctf_ScoreRules(ctf_teams);
+}
+
+void ctf_Initialize()
+{
+ ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
+
+ ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
+ ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
+ ctf_captureshield_force = autocvar_g_ctf_shield_force;
+
+ InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);
+}
--- /dev/null
+#pragma once
+
+#include "ctf.qh"
+
+void ctf_Initialize();
+
+REGISTER_MUTATOR(ctf, false)
+{
+ MUTATOR_STATIC();
+ MUTATOR_ONADD
+ {
+ GameRules_teams(true);
+ GameRules_limit_score(autocvar_capturelimit_override);
+ GameRules_limit_lead(autocvar_captureleadlimit_override);
+
+ ctf_Initialize();
+ }
+ return 0;
+}
+
+// used in cheats.qc
+void ctf_RespawnFlag(entity flag);
+
+// score rule declarations
+const int ST_CTF_CAPS = 1;
+
+CLASS(Flag, Pickup)
+ ATTRIB(Flag, m_mins, vector, (PL_MIN_CONST + '0 0 -13') * 1.4); // scaling be damned
+ ATTRIB(Flag, m_maxs, vector, (PL_MAX_CONST + '0 0 -13') * 1.4);
+ENDCLASS(Flag)
+Flag CTF_FLAG; STATIC_INIT(Flag) { CTF_FLAG = NEW(Flag); }
+void ctf_FlagTouch(entity this, entity toucher) { ITEM_HANDLE(Pickup, CTF_FLAG, this, toucher); }
+
+// flag constants // for most of these, there is just one question to be asked: WHYYYYY?
+
+const float FLAG_SCALE = 0.6;
+
+const float FLAG_THINKRATE = 0.2;
+const float FLAG_TOUCHRATE = 0.5;
+const float WPFE_THINKRATE = 0.5;
+
+const vector FLAG_DROP_OFFSET = ('0 0 32');
+const vector FLAG_CARRY_OFFSET = ('-16 0 8');
+#define FLAG_SPAWN_OFFSET ('0 0 1' * (PL_MAX_CONST.z - 13))
+const vector FLAG_WAYPOINT_OFFSET = ('0 0 64');
+const int FLAG_FLOAT_OFFSET_Z = 32;
+const int FLAG_PASS_ARC_OFFSET_Z = -10;
+
+const vector VEHICLE_FLAG_OFFSET = ('0 0 96');
+const float VEHICLE_FLAG_SCALE = 1.0;
+
+// waypoint colors
+#define WPCOLOR_ENEMYFC(t) ((t) ? colormapPaletteColor(t - 1, false) * 0.75 : '1 1 1')
+#define WPCOLOR_FLAGCARRIER(t) ((t) ? colormapPaletteColor(t - 1, false) * 0.75 : '1 1 1')
+//#define WPCOLOR_FLAGCARRIER(t) (WP_FlagCarrier.m_color)
+#define WPCOLOR_DROPPEDFLAG(t) ((t) ? ('0.25 0.25 0.25' + colormapPaletteColor(t - 1, false)) * 0.5 : '1 1 1')
+
+// sounds
+#define snd_flag_taken noise
+#define snd_flag_returned noise1
+#define snd_flag_capture noise2
+#define snd_flag_respawn noise3
+.string snd_flag_dropped;
+.string snd_flag_touch;
+.string snd_flag_pass;
+
+// score fields
+.float score_assist;
+.float score_capture;
+.float score_drop; // note: negated
+.float score_pickup;
+.float score_return;
+.float score_team_capture; // shouldn't be too high
+
+// effects
+.string toucheffect;
+.string passeffect;
+.string capeffect;
+
+// list of flags on the map
+entity ctf_worldflaglist;
+.entity ctf_worldflagnext;
+.entity ctf_staleflagnext;
+
+// waypoint sprites
+.entity wps_helpme;
+.entity wps_flagbase;
+.entity wps_flagcarrier;
+.entity wps_flagdropped;
+.entity wps_flagreturn;
+.entity wps_enemyflagcarrier;
+.float wps_helpme_time;
+bool wpforenemy_announced;
+float wpforenemy_nextthink;
+
+// statuses
+const int FLAG_BASE = 1;
+const int FLAG_DROPPED = 2;
+const int FLAG_CARRY = 3;
+const int FLAG_PASSING = 4;
+
+const int DROP_NORMAL = 1;
+const int DROP_THROW = 2;
+const int DROP_PASS = 3;
+const int DROP_RESET = 4;
+
+const int PICKUP_BASE = 1;
+const int PICKUP_DROPPED = 2;
+
+const int CAPTURE_NORMAL = 1;
+const int CAPTURE_DROPPED = 2;
+
+const int RETURN_TIMEOUT = 1;
+const int RETURN_DROPPED = 2;
+const int RETURN_DAMAGE = 3;
+const int RETURN_SPEEDRUN = 4;
+const int RETURN_NEEDKILL = 5;
+
+bool ctf_Stalemate_Customize(entity this, entity client);
+
+void ctf_Handle_Throw(entity player, entity receiver, float droptype);
+
+// flag properties
+#define ctf_spawnorigin dropped_origin
+bool ctf_stalemate; // indicates that a stalemate is active
+float ctf_captimerecord; // record time for capturing the flag
+.float ctf_pickuptime;
+.float ctf_droptime;
+.int ctf_status; // status of the flag (FLAG_BASE, FLAG_DROPPED, FLAG_CARRY declared globally)
+.entity ctf_dropper; // don't allow spam of dropping the flag
+.int max_flag_health;
+.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;
+.entity pass_sender;
+.entity pass_target;
+.float throw_antispam;
+.float throw_prevtime;
+.int throw_count;
+
+// CaptureShield: If the player is too bad to be allowed to capture, shield them from taking the flag.
+.bool ctf_captureshielded; // set to 1 if the player is too bad to be allowed to capture
+float ctf_captureshield_min_negscore; // punish at -20 points
+float ctf_captureshield_max_ratio; // punish at most 30% of each team
+float ctf_captureshield_force; // push force of the shield
+
+// 1 flag ctf
+bool ctf_oneflag; // indicates whether or not a neutral flag has been found
+
+// bot player logic
+const int HAVOCBOT_CTF_ROLE_NONE = 0;
+const int HAVOCBOT_CTF_ROLE_DEFENSE = 2;
+const int HAVOCBOT_CTF_ROLE_MIDDLE = 4;
+const int HAVOCBOT_CTF_ROLE_OFFENSE = 8;
+const int HAVOCBOT_CTF_ROLE_CARRIER = 16;
+const int HAVOCBOT_CTF_ROLE_RETRIEVER = 32;
+const int HAVOCBOT_CTF_ROLE_ESCORT = 64;
+
+.bool havocbot_cantfindflag;
+
+void havocbot_role_ctf_setrole(entity bot, int role);
+
+// team checking
+#define CTF_SAMETEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? DIFF_TEAM(a,b) : SAME_TEAM(a,b))
+#define CTF_DIFFTEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? SAME_TEAM(a,b) : DIFF_TEAM(a,b))
// generated file; do not modify
-#include <common/gamemodes/gamemode/cts/cts.qc>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/cts/sv_cts.qc>
+#endif
// generated file; do not modify
-#include <common/gamemodes/gamemode/cts/cts.qh>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/cts/sv_cts.qh>
+#endif
+++ /dev/null
-#include "cts.qh"
-
-// TODO: split into sv_cts
-#ifdef SVQC
-#include <server/race.qh>
-#include <server/items.qh>
-
-float autocvar_g_cts_finish_kill_delay;
-bool autocvar_g_cts_selfdamage;
-
-// legacy bot roles
-.float race_checkpoint;
-void havocbot_role_cts(entity this)
-{
- if(IS_DEAD(this))
- return;
-
- if (navigation_goalrating_timeout(this))
- {
- 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 == 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);
- }
-}
-
-void cts_ScoreRules()
-{
- GameRules_score_enabled(false);
- GameRules_scoring(0, 0, 0, {
- if (g_race_qualifying) {
- field(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
- } else {
- field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
- field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
- field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
- }
- });
-}
-
-void cts_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later
-{
- if(autocvar_sv_eventlog)
- GameLogEcho(strcat(":cts:", mode, ":", ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
-}
-
-void KillIndicator_Think(entity this);
-void CTS_ClientKill(entity e) // silent version of ClientKill, used when player finishes a CTS run. Useful to prevent cheating by running back to the start line and starting out with more speed
-{
- e.killindicator = spawn();
- e.killindicator.owner = e;
- setthink(e.killindicator, KillIndicator_Think);
- e.killindicator.nextthink = time + (e.lip) * 0.05;
- e.killindicator.cnt = ceil(autocvar_g_cts_finish_kill_delay);
- e.killindicator.count = 1; // this is used to indicate that it should be silent
- e.lip = 0;
-}
-
-MUTATOR_HOOKFUNCTION(cts, PlayerPhysics)
-{
- entity player = M_ARGV(0, entity);
- float dt = M_ARGV(1, float);
-
- player.race_movetime_frac += dt;
- float f = floor(player.race_movetime_frac);
- player.race_movetime_frac -= f;
- player.race_movetime_count += f;
- player.race_movetime = player.race_movetime_frac + player.race_movetime_count;
-
-#ifdef SVQC
- if(IS_PLAYER(player))
- {
- if (player.race_penalty)
- if (time > player.race_penalty)
- player.race_penalty = 0;
- if(player.race_penalty)
- {
- player.velocity = '0 0 0';
- set_movetype(player, MOVETYPE_NONE);
- player.disableclientprediction = 2;
- }
- }
-#endif
-
- // force kbd movement for fairness
- float wishspeed;
- vector wishvel;
-
- // if record times matter
- // ensure nothing EVIL is being done (i.e. div0_evade)
- // this hinders joystick users though
- // but it still gives SOME analog control
- wishvel.x = fabs(CS(player).movement.x);
- wishvel.y = fabs(CS(player).movement.y);
- if(wishvel.x != 0 && wishvel.y != 0 && wishvel.x != wishvel.y)
- {
- wishvel.z = 0;
- wishspeed = vlen(wishvel);
- if(wishvel.x >= 2 * wishvel.y)
- {
- // pure X motion
- if(CS(player).movement.x > 0)
- CS(player).movement_x = wishspeed;
- else
- CS(player).movement_x = -wishspeed;
- CS(player).movement_y = 0;
- }
- else if(wishvel.y >= 2 * wishvel.x)
- {
- // pure Y motion
- CS(player).movement_x = 0;
- if(CS(player).movement.y > 0)
- CS(player).movement_y = wishspeed;
- else
- CS(player).movement_y = -wishspeed;
- }
- else
- {
- // diagonal
- if(CS(player).movement.x > 0)
- CS(player).movement_x = M_SQRT1_2 * wishspeed;
- else
- CS(player).movement_x = -M_SQRT1_2 * wishspeed;
- if(CS(player).movement.y > 0)
- CS(player).movement_y = M_SQRT1_2 * wishspeed;
- else
- CS(player).movement_y = -M_SQRT1_2 * wishspeed;
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(cts, reset_map_global)
-{
- float s;
-
- Score_NicePrint(NULL);
-
- race_ClearRecords();
- PlayerScore_Sort(race_place, 0, 1, 0);
-
- FOREACH_CLIENT(true, {
- if(it.race_place)
- {
- s = GameRules_scoring_add(it, RACE_FASTEST, 0);
- if(!s)
- it.race_place = 0;
- }
- cts_EventLog(ftos(it.race_place), it);
- });
-
- if(g_race_qualifying == 2)
- {
- g_race_qualifying = 0;
- independent_players = 0;
- cvar_set("fraglimit", ftos(race_fraglimit));
- cvar_set("leadlimit", ftos(race_leadlimit));
- cvar_set("timelimit", ftos(race_timelimit));
- cts_ScoreRules();
- }
-}
-
-MUTATOR_HOOKFUNCTION(cts, ClientConnect)
-{
- entity player = M_ARGV(0, entity);
-
- race_PreparePlayer(player);
- player.race_checkpoint = -1;
-
- if(IS_REAL_CLIENT(player))
- {
- string rr = CTS_RECORD;
-
- msg_entity = player;
- race_send_recordtime(MSG_ONE);
- race_send_speedaward(MSG_ONE);
-
- speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed")));
- speedaward_alltimebest_holder = uid2name(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp")));
- race_send_speedaward_alltimebest(MSG_ONE);
-
- float i;
- int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
- race_send_rankings_cnt(MSG_ONE);
- for (i = 1; i <= m; ++i)
- {
- race_SendRankings(i, 0, 0, MSG_ONE);
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(cts, AbortSpeedrun)
-{
- entity player = M_ARGV(0, entity);
-
- if(autocvar_g_allow_checkpoints)
- race_PreparePlayer(player); // nice try
-}
-
-MUTATOR_HOOKFUNCTION(cts, MakePlayerObserver)
-{
- entity player = M_ARGV(0, entity);
-
- if(GameRules_scoring_add(player, RACE_FASTEST, 0))
- player.frags = FRAGS_LMS_LOSER;
- else
- player.frags = FRAGS_SPECTATOR;
-
- race_PreparePlayer(player);
- player.race_checkpoint = -1;
-}
-
-MUTATOR_HOOKFUNCTION(cts, PlayerSpawn)
-{
- entity player = M_ARGV(0, entity);
- entity spawn_spot = M_ARGV(1, entity);
-
- if(spawn_spot.target == "")
- // Emergency: this wasn't a real spawnpoint. Can this ever happen?
- race_PreparePlayer(player);
-
- // if we need to respawn, do it right
- player.race_respawn_checkpoint = player.race_checkpoint;
- player.race_respawn_spotref = spawn_spot;
-
- player.race_place = 0;
-}
-
-MUTATOR_HOOKFUNCTION(cts, PutClientInServer)
-{
- entity player = M_ARGV(0, entity);
-
- if(IS_PLAYER(player))
- if(!game_stopped)
- {
- if(CS(player).killcount == FRAGS_SPECTATOR /* initial spawn */ || g_race_qualifying) // spawn
- race_PreparePlayer(player);
- else // respawn
- race_RetractPlayer(player);
-
- race_AbandonRaceCheck(player);
- }
-}
-
-MUTATOR_HOOKFUNCTION(cts, PlayerDies)
-{
- entity frag_target = M_ARGV(2, entity);
-
- frag_target.respawn_flags |= RESPAWN_FORCE;
- race_AbandonRaceCheck(frag_target);
-}
-
-MUTATOR_HOOKFUNCTION(cts, HavocBot_ChooseRole)
-{
- entity bot = M_ARGV(0, entity);
-
- bot.havocbot_role = havocbot_role_cts;
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(cts, GetPressedKeys)
-{
- entity player = M_ARGV(0, entity);
-
- if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
- {
- if (!player.stored_netname)
- player.stored_netname = strzone(uid2name(player.crypto_idfp));
- if(player.stored_netname != player.netname)
- {
- db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
- strcpy(player.stored_netname, player.netname);
- }
- }
-
- if (!IS_OBSERVER(player))
- {
- if(vdist(player.velocity - player.velocity_z * '0 0 1', >, speedaward_speed))
- {
- speedaward_speed = vlen(player.velocity - player.velocity_z * '0 0 1');
- speedaward_holder = player.netname;
- speedaward_uid = player.crypto_idfp;
- speedaward_lastupdate = time;
- }
- if (speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1)
- {
- string rr = CTS_RECORD;
- race_send_speedaward(MSG_ALL);
- speedaward_lastsent = speedaward_speed;
- if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "")
- {
- speedaward_alltimebest = speedaward_speed;
- speedaward_alltimebest_holder = speedaward_holder;
- speedaward_alltimebest_uid = speedaward_uid;
- db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest));
- db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid);
- race_send_speedaward_alltimebest(MSG_ALL);
- }
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(cts, ForbidThrowCurrentWeapon)
-{
- // no weapon dropping in CTS
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(cts, FilterItem)
-{
- entity item = M_ARGV(0, entity);
-
- if (Item_IsLoot(item))
- {
- return true;
- }
-}
-
-MUTATOR_HOOKFUNCTION(cts, Damage_Calculate)
-{
- entity frag_attacker = M_ARGV(1, entity);
- entity frag_target = M_ARGV(2, entity);
- float frag_deathtype = M_ARGV(3, float);
- float frag_damage = M_ARGV(4, float);
-
- if(frag_target == frag_attacker || frag_deathtype == DEATH_FALL.m_id)
- if(!autocvar_g_cts_selfdamage)
- {
- frag_damage = 0;
- M_ARGV(4, float) = frag_damage;
- }
-}
-
-MUTATOR_HOOKFUNCTION(cts, ForbidPlayerScore_Clear)
-{
- return true; // in CTS, you don't lose score by observing
-}
-
-MUTATOR_HOOKFUNCTION(cts, GetRecords)
-{
- int record_page = M_ARGV(0, int);
- string ret_string = M_ARGV(1, string);
-
- for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
- {
- if(MapInfo_Get_ByID(i))
- {
- float r = race_readTime(MapInfo_Map_bspname, 1);
-
- if(!r)
- continue;
-
- string h = race_readName(MapInfo_Map_bspname, 1);
- ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r)), " ", h, "\n");
- }
- }
-
- M_ARGV(1, string) = ret_string;
-}
-
-void ClientKill_Now(entity this);
-MUTATOR_HOOKFUNCTION(cts, ClientKill)
-{
- entity player = M_ARGV(0, entity);
-
- M_ARGV(1, float) = 0; // kill delay
-
- if(player.killindicator && player.killindicator.count == 1) // player.killindicator.count == 1 means that the kill indicator was spawned by CTS_ClientKill
- {
- delete(player.killindicator);
- player.killindicator = NULL;
-
- ClientKill_Now(player); // allow instant kill in this case
- return;
- }
-}
-
-MUTATOR_HOOKFUNCTION(cts, Race_FinalCheckpoint)
-{
- entity player = M_ARGV(0, entity);
-
- if(autocvar_g_cts_finish_kill_delay)
- CTS_ClientKill(player);
-}
-
-MUTATOR_HOOKFUNCTION(cts, HideTeamNagger)
-{
- return true; // doesn't work so well (but isn't cts a teamless mode?)
-}
-
-MUTATOR_HOOKFUNCTION(cts, FixClientCvars)
-{
- entity player = M_ARGV(0, entity);
-
- stuffcmd(player, "cl_cmd settemp cl_movecliptokeyboard 2\n");
-}
-
-MUTATOR_HOOKFUNCTION(cts, WantWeapon)
-{
- M_ARGV(1, float) = (M_ARGV(0, entity) == WEP_SHOTGUN); // want weapon = weapon info
- M_ARGV(3, bool) = true; // want mutator blocked
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(cts, ForbidDropCurrentWeapon)
-{
- return true;
-}
-
-void cts_Initialize()
-{
- cts_ScoreRules();
-}
-#endif
+++ /dev/null
-#pragma once
-
-#ifdef SVQC
-#include <common/mutators/base.qh>
-#include <server/race.qh>
-
-void cts_Initialize();
-
-REGISTER_MUTATOR(cts, false)
-{
- MUTATOR_STATIC();
- MUTATOR_ONADD
- {
- g_race_qualifying = true;
- independent_players = 1;
- GameRules_limit_score(0);
- GameRules_limit_lead(0);
-
- cts_Initialize();
- }
- return 0;
-}
-
-// scores
-const float ST_CTS_LAPS = 1;
-#endif
--- /dev/null
+#include "sv_cts.qh"
+
+#include <server/race.qh>
+#include <server/items.qh>
+
+float autocvar_g_cts_finish_kill_delay;
+bool autocvar_g_cts_selfdamage;
+bool autocvar_g_cts_removeprojectiles;
+
+// legacy bot roles
+.float race_checkpoint;
+void havocbot_role_cts(entity this)
+{
+ if(IS_DEAD(this))
+ return;
+
+ if (navigation_goalrating_timeout(this))
+ {
+ 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 == 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);
+ }
+}
+
+void cts_ScoreRules()
+{
+ GameRules_score_enabled(false);
+ GameRules_scoring(0, 0, 0, {
+ if (g_race_qualifying) {
+ field(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
+ } else {
+ field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
+ field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
+ field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
+ }
+ });
+}
+
+void cts_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later
+{
+ if(autocvar_sv_eventlog)
+ GameLogEcho(strcat(":cts:", mode, ":", ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
+}
+
+MUTATOR_HOOKFUNCTION(cts, PlayerPhysics)
+{
+ entity player = M_ARGV(0, entity);
+ float dt = M_ARGV(1, float);
+
+ player.race_movetime_frac += dt;
+ float f = floor(player.race_movetime_frac);
+ player.race_movetime_frac -= f;
+ player.race_movetime_count += f;
+ player.race_movetime = player.race_movetime_frac + player.race_movetime_count;
+
+ if(IS_PLAYER(player))
+ {
+ if (player.race_penalty)
+ if (time > player.race_penalty)
+ player.race_penalty = 0;
+ if(player.race_penalty)
+ {
+ player.velocity = '0 0 0';
+ set_movetype(player, MOVETYPE_NONE);
+ player.disableclientprediction = 2;
+ }
+ }
+
+ // force kbd movement for fairness
+ float wishspeed;
+ vector wishvel;
+
+ // if record times matter
+ // ensure nothing EVIL is being done (i.e. div0_evade)
+ // this hinders joystick users though
+ // but it still gives SOME analog control
+ wishvel.x = fabs(CS(player).movement.x);
+ wishvel.y = fabs(CS(player).movement.y);
+ if(wishvel.x != 0 && wishvel.y != 0 && wishvel.x != wishvel.y)
+ {
+ wishvel.z = 0;
+ wishspeed = vlen(wishvel);
+ if(wishvel.x >= 2 * wishvel.y)
+ {
+ // pure X motion
+ if(CS(player).movement.x > 0)
+ CS(player).movement_x = wishspeed;
+ else
+ CS(player).movement_x = -wishspeed;
+ CS(player).movement_y = 0;
+ }
+ else if(wishvel.y >= 2 * wishvel.x)
+ {
+ // pure Y motion
+ CS(player).movement_x = 0;
+ if(CS(player).movement.y > 0)
+ CS(player).movement_y = wishspeed;
+ else
+ CS(player).movement_y = -wishspeed;
+ }
+ else
+ {
+ // diagonal
+ if(CS(player).movement.x > 0)
+ CS(player).movement_x = M_SQRT1_2 * wishspeed;
+ else
+ CS(player).movement_x = -M_SQRT1_2 * wishspeed;
+ if(CS(player).movement.y > 0)
+ CS(player).movement_y = M_SQRT1_2 * wishspeed;
+ else
+ CS(player).movement_y = -M_SQRT1_2 * wishspeed;
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(cts, reset_map_global)
+{
+ float s;
+
+ Score_NicePrint(NULL);
+
+ race_ClearRecords();
+ PlayerScore_Sort(race_place, 0, 1, 0);
+
+ FOREACH_CLIENT(true, {
+ if(it.race_place)
+ {
+ s = GameRules_scoring_add(it, RACE_FASTEST, 0);
+ if(!s)
+ it.race_place = 0;
+ }
+ cts_EventLog(ftos(it.race_place), it);
+ });
+
+ if(g_race_qualifying == 2)
+ {
+ g_race_qualifying = 0;
+ independent_players = 0;
+ cvar_set("fraglimit", ftos(race_fraglimit));
+ cvar_set("leadlimit", ftos(race_leadlimit));
+ cvar_set("timelimit", ftos(race_timelimit));
+ cts_ScoreRules();
+ }
+}
+
+MUTATOR_HOOKFUNCTION(cts, ClientConnect)
+{
+ entity player = M_ARGV(0, entity);
+
+ race_PreparePlayer(player);
+ player.race_checkpoint = -1;
+
+ if(IS_REAL_CLIENT(player))
+ {
+ string rr = CTS_RECORD;
+
+ msg_entity = player;
+ race_send_recordtime(MSG_ONE);
+ race_send_speedaward(MSG_ONE);
+
+ speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed")));
+ speedaward_alltimebest_holder = uid2name(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp")));
+ race_send_speedaward_alltimebest(MSG_ONE);
+
+ float i;
+ int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
+ race_send_rankings_cnt(MSG_ONE);
+ for (i = 1; i <= m; ++i)
+ {
+ race_SendRankings(i, 0, 0, MSG_ONE);
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(cts, AbortSpeedrun)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(autocvar_g_allow_checkpoints)
+ race_PreparePlayer(player); // nice try
+}
+
+MUTATOR_HOOKFUNCTION(cts, MakePlayerObserver)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(GameRules_scoring_add(player, RACE_FASTEST, 0))
+ player.frags = FRAGS_LMS_LOSER;
+ else
+ player.frags = FRAGS_SPECTATOR;
+
+ race_PreparePlayer(player);
+ player.race_checkpoint = -1;
+}
+
+MUTATOR_HOOKFUNCTION(cts, PlayerSpawn)
+{
+ entity player = M_ARGV(0, entity);
+ entity spawn_spot = M_ARGV(1, entity);
+
+ if(spawn_spot.target == "")
+ // Emergency: this wasn't a real spawnpoint. Can this ever happen?
+ race_PreparePlayer(player);
+
+ // if we need to respawn, do it right
+ player.race_respawn_checkpoint = player.race_checkpoint;
+ player.race_respawn_spotref = spawn_spot;
+
+ player.race_place = 0;
+}
+
+MUTATOR_HOOKFUNCTION(cts, PutClientInServer)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(IS_PLAYER(player))
+ if(!game_stopped)
+ {
+ if(CS(player).killcount == FRAGS_SPECTATOR /* initial spawn */ || g_race_qualifying) // spawn
+ race_PreparePlayer(player);
+ else // respawn
+ race_RetractPlayer(player);
+
+ race_AbandonRaceCheck(player);
+ }
+}
+
+MUTATOR_HOOKFUNCTION(cts, PlayerDies)
+{
+ entity frag_target = M_ARGV(2, entity);
+
+ frag_target.respawn_flags |= RESPAWN_FORCE;
+ race_AbandonRaceCheck(frag_target);
+
+ if(autocvar_g_cts_removeprojectiles)
+ {
+ IL_EACH(g_projectiles, it.owner == frag_target && (it.flags & FL_PROJECTILE),
+ {
+ delete(it);
+ });
+ }
+}
+
+MUTATOR_HOOKFUNCTION(cts, HavocBot_ChooseRole)
+{
+ entity bot = M_ARGV(0, entity);
+
+ bot.havocbot_role = havocbot_role_cts;
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(cts, GetPressedKeys)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
+ {
+ if (!player.stored_netname)
+ player.stored_netname = strzone(uid2name(player.crypto_idfp));
+ if(player.stored_netname != player.netname)
+ {
+ db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
+ strcpy(player.stored_netname, player.netname);
+ }
+ }
+
+ if (!IS_OBSERVER(player))
+ {
+ if(vdist(player.velocity - player.velocity_z * '0 0 1', >, speedaward_speed))
+ {
+ speedaward_speed = vlen(player.velocity - player.velocity_z * '0 0 1');
+ speedaward_holder = player.netname;
+ speedaward_uid = player.crypto_idfp;
+ speedaward_lastupdate = time;
+ }
+ if (speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1)
+ {
+ string rr = CTS_RECORD;
+ race_send_speedaward(MSG_ALL);
+ speedaward_lastsent = speedaward_speed;
+ if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "")
+ {
+ speedaward_alltimebest = speedaward_speed;
+ speedaward_alltimebest_holder = speedaward_holder;
+ speedaward_alltimebest_uid = speedaward_uid;
+ db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest));
+ db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid);
+ race_send_speedaward_alltimebest(MSG_ALL);
+ }
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(cts, ForbidThrowCurrentWeapon)
+{
+ // no weapon dropping in CTS
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(cts, FilterItem)
+{
+ entity item = M_ARGV(0, entity);
+
+ if (Item_IsLoot(item))
+ {
+ return true;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(cts, Damage_Calculate)
+{
+ entity frag_attacker = M_ARGV(1, entity);
+ entity frag_target = M_ARGV(2, entity);
+ float frag_deathtype = M_ARGV(3, float);
+ float frag_damage = M_ARGV(4, float);
+
+ if(frag_target == frag_attacker || frag_deathtype == DEATH_FALL.m_id)
+ if(!autocvar_g_cts_selfdamage)
+ {
+ frag_damage = 0;
+ M_ARGV(4, float) = frag_damage;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(cts, ForbidPlayerScore_Clear)
+{
+ return true; // in CTS, you don't lose score by observing
+}
+
+MUTATOR_HOOKFUNCTION(cts, GetRecords)
+{
+ int record_page = M_ARGV(0, int);
+ string ret_string = M_ARGV(1, string);
+
+ for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
+ {
+ if(MapInfo_Get_ByID(i))
+ {
+ float r = race_readTime(MapInfo_Map_bspname, 1);
+
+ if(!r)
+ continue;
+
+ string h = race_readName(MapInfo_Map_bspname, 1);
+ ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r)), " ", h, "\n");
+ }
+ }
+
+ M_ARGV(1, string) = ret_string;
+}
+
+MUTATOR_HOOKFUNCTION(cts, ClientKill)
+{
+ M_ARGV(1, float) = 0; // kill delay
+}
+
+MUTATOR_HOOKFUNCTION(cts, Race_FinalCheckpoint)
+{
+ entity player = M_ARGV(0, entity);
+
+ // useful to prevent cheating by running back to the start line and starting out with more speed
+ if(autocvar_g_cts_finish_kill_delay)
+ ClientKill_Silent(player, autocvar_g_cts_finish_kill_delay);
+}
+
+MUTATOR_HOOKFUNCTION(cts, HideTeamNagger)
+{
+ return true; // doesn't work so well (but isn't cts a teamless mode?)
+}
+
+MUTATOR_HOOKFUNCTION(cts, FixClientCvars)
+{
+ entity player = M_ARGV(0, entity);
+
+ stuffcmd(player, "cl_cmd settemp cl_movecliptokeyboard 2\n");
+}
+
+MUTATOR_HOOKFUNCTION(cts, WantWeapon)
+{
+ M_ARGV(1, float) = (M_ARGV(0, entity) == WEP_SHOTGUN); // want weapon = weapon info
+ M_ARGV(3, bool) = true; // want mutator blocked
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(cts, ForbidDropCurrentWeapon)
+{
+ return true;
+}
+
+void cts_Initialize()
+{
+ cts_ScoreRules();
+}
--- /dev/null
+#pragma once
+
+#include <common/mutators/base.qh>
+#include <server/race.qh>
+
+void cts_Initialize();
+
+REGISTER_MUTATOR(cts, false)
+{
+ MUTATOR_STATIC();
+ MUTATOR_ONADD
+ {
+ g_race_qualifying = true;
+ independent_players = 1;
+ GameRules_limit_score(0);
+ GameRules_limit_lead(0);
+
+ cts_Initialize();
+ }
+ return 0;
+}
+
+// scores
+const float ST_CTS_LAPS = 1;
// generated file; do not modify
-#include <common/gamemodes/gamemode/deathmatch/deathmatch.qc>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/deathmatch/sv_deathmatch.qc>
+#endif
// generated file; do not modify
-#include <common/gamemodes/gamemode/deathmatch/deathmatch.qh>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/deathmatch/sv_deathmatch.qh>
+#endif
+++ /dev/null
-#include "deathmatch.qh"
-
-// TODO: sv_deathmatch?
-#ifdef SVQC
-MUTATOR_HOOKFUNCTION(dm, Scores_CountFragsRemaining)
-{
- // announce remaining frags
- return true;
-}
-#endif
+++ /dev/null
-#pragma once
-
-#ifdef SVQC
-#include <common/mutators/base.qh>
-REGISTER_MUTATOR(dm, false)
-{
- MUTATOR_STATIC();
- return 0;
-}
-#endif
--- /dev/null
+#include "sv_deathmatch.qh"
+
+MUTATOR_HOOKFUNCTION(dm, Scores_CountFragsRemaining)
+{
+ // announce remaining frags
+ return true;
+}
--- /dev/null
+#pragma once
+
+#include <common/mutators/base.qh>
+REGISTER_MUTATOR(dm, false)
+{
+ MUTATOR_STATIC();
+ return 0;
+}
// generated file; do not modify
-#include <common/gamemodes/gamemode/domination/domination.qc>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/domination/sv_domination.qc>
+#endif
// generated file; do not modify
-#include <common/gamemodes/gamemode/domination/domination.qh>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/domination/sv_domination.qh>
+#endif
+++ /dev/null
-#include "domination.qh"
-
-// TODO: sv_domination
-#ifdef SVQC
-#include <server/teamplay.qh>
-
-bool g_domination;
-
-int autocvar_g_domination_default_teams;
-bool autocvar_g_domination_disable_frags;
-int autocvar_g_domination_point_amt;
-bool autocvar_g_domination_point_fullbright;
-float autocvar_g_domination_round_timelimit;
-float autocvar_g_domination_warmup;
-float autocvar_g_domination_point_rate;
-int autocvar_g_domination_teams_override;
-
-void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
-{
- if(autocvar_sv_eventlog)
- GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
-}
-
-void set_dom_state(entity e)
-{
- STAT(DOM_TOTAL_PPS, e) = total_pps;
- STAT(DOM_PPS_RED, e) = pps_red;
- STAT(DOM_PPS_BLUE, e) = pps_blue;
- if(domination_teams >= 3)
- STAT(DOM_PPS_YELLOW, e) = pps_yellow;
- if(domination_teams >= 4)
- STAT(DOM_PPS_PINK, e) = pps_pink;
-}
-
-void dompoint_captured(entity this)
-{
- float old_delay, old_team, real_team;
-
- // now that the delay has expired, switch to the latest team to lay claim to this point
- entity head = this.owner;
-
- real_team = this.cnt;
- this.cnt = -1;
-
- dom_EventLog("taken", this.team, this.dmg_inflictor);
- this.dmg_inflictor = NULL;
-
- this.goalentity = head;
- this.model = head.mdl;
- this.modelindex = head.dmg;
- this.skin = head.skin;
-
- float points, wait_time;
- if (autocvar_g_domination_point_amt)
- points = autocvar_g_domination_point_amt;
- else
- points = this.frags;
- if (autocvar_g_domination_point_rate)
- wait_time = autocvar_g_domination_point_rate;
- else
- wait_time = this.wait;
-
- if(domination_roundbased)
- bprint(sprintf("^3%s^3%s\n", head.netname, this.message));
- else
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, this.message, points, wait_time);
-
- if(this.enemy.playerid == this.enemy_playerid)
- GameRules_scoring_add(this.enemy, DOM_TAKES, 1);
- else
- this.enemy = NULL;
-
- if (head.noise != "")
- if(this.enemy)
- _sound(this.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
- else
- _sound(this, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
- if (head.noise1 != "")
- play2all(head.noise1);
-
- this.delay = time + wait_time;
-
- // do trigger work
- old_delay = this.delay;
- old_team = this.team;
- this.team = real_team;
- this.delay = 0;
- SUB_UseTargets (this, this, NULL);
- this.delay = old_delay;
- this.team = old_team;
-
- entity msg = WP_DomNeut;
- switch(real_team)
- {
- case NUM_TEAM_1: msg = WP_DomRed; break;
- case NUM_TEAM_2: msg = WP_DomBlue; break;
- case NUM_TEAM_3: msg = WP_DomYellow; break;
- case NUM_TEAM_4: msg = WP_DomPink; break;
- }
-
- WaypointSprite_UpdateSprites(this.sprite, msg, WP_Null, WP_Null);
-
- total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
- IL_EACH(g_dompoints, true,
- {
- if (autocvar_g_domination_point_amt)
- points = autocvar_g_domination_point_amt;
- else
- points = it.frags;
- if (autocvar_g_domination_point_rate)
- wait_time = autocvar_g_domination_point_rate;
- else
- wait_time = it.wait;
- switch(it.goalentity.team)
- {
- case NUM_TEAM_1: pps_red += points/wait_time; break;
- case NUM_TEAM_2: pps_blue += points/wait_time; break;
- case NUM_TEAM_3: pps_yellow += points/wait_time; break;
- case NUM_TEAM_4: pps_pink += points/wait_time; break;
- }
- total_pps += points/wait_time;
- });
-
- WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, colormapPaletteColor(this.goalentity.team - 1, 0));
- WaypointSprite_Ping(this.sprite);
-
- this.captime = time;
-
- FOREACH_CLIENT(IS_REAL_CLIENT(it), { set_dom_state(it); });
-}
-
-void AnimateDomPoint(entity this)
-{
- if(this.pain_finished > time)
- return;
- this.pain_finished = time + this.t_width;
- if(this.nextthink > this.pain_finished)
- this.nextthink = this.pain_finished;
-
- this.frame = this.frame + 1;
- if(this.frame > this.t_length)
- this.frame = 0;
-}
-
-void dompointthink(entity this)
-{
- float fragamt;
-
- this.nextthink = time + 0.1;
-
- //this.frame = this.frame + 1;
- //if(this.frame > 119)
- // this.frame = 0;
- AnimateDomPoint(this);
-
- // give points
-
- if (game_stopped || this.delay > time || time < game_starttime) // game has ended, don't keep giving points
- return;
-
- if(autocvar_g_domination_point_rate)
- this.delay = time + autocvar_g_domination_point_rate;
- else
- this.delay = time + this.wait;
-
- // give credit to the team
- // NOTE: this defaults to 0
- if (!domination_roundbased)
- if (this.goalentity.netname != "")
- {
- if(autocvar_g_domination_point_amt)
- fragamt = autocvar_g_domination_point_amt;
- else
- fragamt = this.frags;
- TeamScore_AddToTeam(this.goalentity.team, ST_SCORE, fragamt);
- TeamScore_AddToTeam(this.goalentity.team, ST_DOM_TICKS, fragamt);
-
- // give credit to the individual player, if he is still there
- if (this.enemy.playerid == this.enemy_playerid)
- {
- GameRules_scoring_add(this.enemy, SCORE, fragamt);
- GameRules_scoring_add(this.enemy, DOM_TICKS, fragamt);
- }
- else
- this.enemy = NULL;
- }
-}
-
-void dompointtouch(entity this, entity toucher)
-{
- if(!IS_PLAYER(toucher))
- return;
- if(GetResourceAmount(toucher, RESOURCE_HEALTH) < 1)
- return;
-
- if(round_handler_IsActive() && !round_handler_IsRoundStarted())
- return;
-
- if(time < this.captime + 0.3)
- return;
-
- // only valid teams can claim it
- entity head = find(NULL, classname, "dom_team");
- while (head && head.team != toucher.team)
- head = find(head, classname, "dom_team");
- if (!head || head.netname == "" || head == this.goalentity)
- return;
-
- // delay capture
-
- this.team = this.goalentity.team; // this stores the PREVIOUS team!
-
- this.cnt = toucher.team;
- this.owner = head; // team to switch to after the delay
- this.dmg_inflictor = toucher;
-
- // this.state = 1;
- // this.delay = time + cvar("g_domination_point_capturetime");
- //this.nextthink = time + cvar("g_domination_point_capturetime");
- //this.think = dompoint_captured;
-
- // go to neutral team in the mean time
- head = find(NULL, classname, "dom_team");
- while (head && head.netname != "")
- head = find(head, classname, "dom_team");
- if(head == NULL)
- return;
-
- WaypointSprite_UpdateSprites(this.sprite, WP_DomNeut, WP_Null, WP_Null);
- WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, '0 1 1');
- WaypointSprite_Ping(this.sprite);
-
- this.goalentity = head;
- this.model = head.mdl;
- this.modelindex = head.dmg;
- this.skin = head.skin;
-
- this.enemy = toucher; // individual player scoring
- this.enemy_playerid = toucher.playerid;
- dompoint_captured(this);
-}
-
-void dom_controlpoint_setup(entity this)
-{
- entity head;
- // find the spawnfunc_dom_team representing unclaimed points
- head = find(NULL, classname, "dom_team");
- while(head && head.netname != "")
- head = find(head, classname, "dom_team");
- if (!head)
- objerror(this, "no spawnfunc_dom_team with netname \"\" found\n");
-
- // copy important properties from spawnfunc_dom_team entity
- this.goalentity = head;
- _setmodel(this, head.mdl); // precision already set
- this.skin = head.skin;
-
- this.cnt = -1;
-
- if(this.message == "")
- this.message = " has captured a control point";
-
- if(this.frags <= 0)
- this.frags = 1;
- if(this.wait <= 0)
- this.wait = 5;
-
- float points, waittime;
- if (autocvar_g_domination_point_amt)
- points = autocvar_g_domination_point_amt;
- else
- points = this.frags;
- if (autocvar_g_domination_point_rate)
- waittime = autocvar_g_domination_point_rate;
- else
- waittime = this.wait;
-
- total_pps += points/waittime;
-
- if(!this.t_width)
- this.t_width = 0.02; // frame animation rate
- if(!this.t_length)
- this.t_length = 239; // maximum frame
-
- setthink(this, dompointthink);
- this.nextthink = time;
- settouch(this, dompointtouch);
- this.solid = SOLID_TRIGGER;
- if(!this.flags & FL_ITEM)
- IL_PUSH(g_items, this);
- this.flags = FL_ITEM;
- setsize(this, '-32 -32 -32', '32 32 32');
- setorigin(this, this.origin + '0 0 20');
- droptofloor(this);
-
- waypoint_spawnforitem(this);
- WaypointSprite_SpawnFixed(WP_DomNeut, this.origin + '0 0 32', this, sprite, RADARICON_DOMPOINT);
-}
-
-float total_controlpoints;
-void Domination_count_controlpoints()
-{
- total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
- IL_EACH(g_dompoints, true,
- {
- ++total_controlpoints;
- redowned += (it.goalentity.team == NUM_TEAM_1);
- blueowned += (it.goalentity.team == NUM_TEAM_2);
- yellowowned += (it.goalentity.team == NUM_TEAM_3);
- pinkowned += (it.goalentity.team == NUM_TEAM_4);
- });
-}
-
-float Domination_GetWinnerTeam()
-{
- float winner_team = 0;
- if(redowned == total_controlpoints)
- winner_team = NUM_TEAM_1;
- if(blueowned == total_controlpoints)
- {
- if(winner_team) return 0;
- winner_team = NUM_TEAM_2;
- }
- if(yellowowned == total_controlpoints)
- {
- if(winner_team) return 0;
- winner_team = NUM_TEAM_3;
- }
- if(pinkowned == total_controlpoints)
- {
- if(winner_team) return 0;
- winner_team = NUM_TEAM_4;
- }
- if(winner_team)
- return winner_team;
- return -1; // no control points left?
-}
-
-#define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
-#define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
-float Domination_CheckWinner()
-{
- if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
-
- game_stopped = true;
- round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
- return 1;
- }
-
- Domination_count_controlpoints();
-
- float winner_team = Domination_GetWinnerTeam();
-
- if(winner_team == -1)
- return 0;
-
- if(winner_team > 0)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
- TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
- }
- else if(winner_team == -1)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
- }
-
- game_stopped = true;
- round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
-
- return 1;
-}
-
-float Domination_CheckPlayers()
-{
- return 1;
-}
-
-void Domination_RoundStart()
-{
- FOREACH_CLIENT(IS_PLAYER(it), { it.player_blocked = false; });
-}
-
-//go to best items, or control points you don't own
-void havocbot_goalrating_controlpoints(entity this, float ratingscale, vector org, float sradius)
-{
- IL_EACH(g_dompoints, vdist((((it.absmin + it.absmax) * 0.5) - org), <, sradius),
- {
- if(it.cnt > -1) // this is just being fought
- navigation_routerating(this, it, ratingscale, 5000);
- else if(it.goalentity.cnt == 0) // unclaimed
- navigation_routerating(this, it, ratingscale * 0.5, 5000);
- else if(it.goalentity.team != this.team) // other team's point
- navigation_routerating(this, it, ratingscale * 0.2, 5000);
- });
-}
-
-void havocbot_role_dom(entity this)
-{
- if(IS_DEAD(this))
- return;
-
- if (navigation_goalrating_timeout(this))
- {
- 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);
- }
-}
-
-MUTATOR_HOOKFUNCTION(dom, CheckAllowedTeams)
-{
- // fallback?
- M_ARGV(0, float) = domination_teams;
- string ret_string = "dom_team";
-
- entity head = find(NULL, classname, ret_string);
- while(head)
- {
- if(head.netname != "")
- {
- switch(head.team)
- {
- case NUM_TEAM_1: c1 = 0; break;
- case NUM_TEAM_2: c2 = 0; break;
- case NUM_TEAM_3: c3 = 0; break;
- case NUM_TEAM_4: c4 = 0; break;
- }
- }
-
- head = find(head, classname, ret_string);
- }
-
- M_ARGV(1, string) = string_null;
-
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(dom, reset_map_players)
-{
- total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
- FOREACH_CLIENT(IS_PLAYER(it), {
- PutClientInServer(it);
- if(domination_roundbased)
- it.player_blocked = 1;
- if(IS_REAL_CLIENT(it))
- set_dom_state(it);
- });
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
-{
- entity player = M_ARGV(0, entity);
-
- if(domination_roundbased)
- if(!round_handler_IsRoundStarted())
- player.player_blocked = 1;
- else
- player.player_blocked = 0;
-}
-
-MUTATOR_HOOKFUNCTION(dom, ClientConnect)
-{
- entity player = M_ARGV(0, entity);
-
- set_dom_state(player);
-}
-
-MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
-{
- entity bot = M_ARGV(0, entity);
-
- bot.havocbot_role = havocbot_role_dom;
- return true;
-}
-
-/*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
-Control point for Domination gameplay.
-*/
-spawnfunc(dom_controlpoint)
-{
- if(!g_domination)
- {
- delete(this);
- return;
- }
- setthink(this, dom_controlpoint_setup);
- this.nextthink = time + 0.1;
- this.reset = dom_controlpoint_setup;
-
- if(!this.scale)
- this.scale = 0.6;
-
- this.effects = this.effects | EF_LOWPRECISION;
- if (autocvar_g_domination_point_fullbright)
- this.effects |= EF_FULLBRIGHT;
-
- IL_PUSH(g_dompoints, this);
-}
-
-/*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
-Team declaration for Domination gameplay, this allows you to decide what team
-names and control point models are used in your map.
-
-Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
-can have netname set! The nameless team owns all control points at start.
-
-Keys:
-"netname"
- Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
-"cnt"
- Scoreboard color of the team (for example 4 is red and 13 is blue)
-"model"
- Model to use for control points owned by this team (for example
- "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
- keycard)
-"skin"
- Skin of the model to use (for team skins on a single model)
-"noise"
- Sound to play when this team captures a point.
- (this is a localized sound, like a small alarm or other effect)
-"noise1"
- Narrator speech to play when this team captures a point.
- (this is a global sound, like "Red team has captured a control point")
-*/
-
-spawnfunc(dom_team)
-{
- if(!g_domination || autocvar_g_domination_teams_override >= 2)
- {
- delete(this);
- return;
- }
- precache_model(this.model);
- if (this.noise != "")
- precache_sound(this.noise);
- if (this.noise1 != "")
- precache_sound(this.noise1);
- this.classname = "dom_team";
- _setmodel(this, this.model); // precision not needed
- this.mdl = this.model;
- this.dmg = this.modelindex;
- this.model = "";
- this.modelindex = 0;
- // this would have to be changed if used in quakeworld
- if(this.cnt)
- this.team = this.cnt + 1; // WHY are these different anyway?
-}
-
-// scoreboard setup
-void ScoreRules_dom(int teams)
-{
- if(domination_roundbased)
- {
- GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
- field_team(ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
- field(SP_DOM_TAKES, "takes", 0);
- });
- }
- else
- {
- float sp_domticks, sp_score;
- sp_score = sp_domticks = 0;
- if(autocvar_g_domination_disable_frags)
- sp_domticks = SFL_SORT_PRIO_PRIMARY;
- else
- sp_score = SFL_SORT_PRIO_PRIMARY;
- GameRules_scoring(teams, sp_score, sp_score, {
- field_team(ST_DOM_TICKS, "ticks", sp_domticks);
- field(SP_DOM_TICKS, "ticks", sp_domticks);
- field(SP_DOM_TAKES, "takes", 0);
- });
- }
-}
-
-// code from here on is just to support maps that don't have control point and team entities
-void dom_spawnteam(string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
-{
- TC(Sound, capsound);
- entity e = new_pure(dom_team);
- e.netname = strzone(teamname);
- e.cnt = teamcolor;
- e.model = pointmodel;
- e.skin = pointskin;
- e.noise = strzone(Sound_fixpath(capsound));
- e.noise1 = strzone(capnarration);
- e.message = strzone(capmessage);
-
- // this code is identical to spawnfunc_dom_team
- _setmodel(e, e.model); // precision not needed
- e.mdl = e.model;
- e.dmg = e.modelindex;
- e.model = "";
- e.modelindex = 0;
- // this would have to be changed if used in quakeworld
- e.team = e.cnt + 1;
-
- //eprint(e);
-}
-
-void dom_spawnpoint(vector org)
-{
- entity e = spawn();
- e.classname = "dom_controlpoint";
- setthink(e, spawnfunc_dom_controlpoint);
- e.nextthink = time;
- setorigin(e, org);
- spawnfunc_dom_controlpoint(e);
-}
-
-// spawn some default teams if the map is not set up for domination
-void dom_spawnteams(int teams)
-{
- TC(int, teams);
- dom_spawnteam(Team_ColoredFullName(NUM_TEAM_1), NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, SND_DOM_CLAIM, "", "Red team has captured a control point");
- dom_spawnteam(Team_ColoredFullName(NUM_TEAM_2), NUM_TEAM_2-1, "models/domination/dom_blue.md3", 0, SND_DOM_CLAIM, "", "Blue team has captured a control point");
- if(teams >= 3)
- dom_spawnteam(Team_ColoredFullName(NUM_TEAM_3), NUM_TEAM_3-1, "models/domination/dom_yellow.md3", 0, SND_DOM_CLAIM, "", "Yellow team has captured a control point");
- if(teams >= 4)
- dom_spawnteam(Team_ColoredFullName(NUM_TEAM_4), NUM_TEAM_4-1, "models/domination/dom_pink.md3", 0, SND_DOM_CLAIM, "", "Pink team has captured a control point");
- dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
-}
-
-void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
-{
- // if no teams are found, spawn defaults
- if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
- {
- LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.");
- domination_teams = autocvar_g_domination_teams_override;
- if (domination_teams < 2)
- domination_teams = autocvar_g_domination_default_teams;
- domination_teams = bound(2, domination_teams, 4);
- dom_spawnteams(domination_teams);
- }
-
- CheckAllowedTeams(NULL);
- //domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
-
- int teams = 0;
- if(c1 >= 0) teams |= BIT(0);
- if(c2 >= 0) teams |= BIT(1);
- if(c3 >= 0) teams |= BIT(2);
- if(c4 >= 0) teams |= BIT(3);
- domination_teams = teams;
-
- domination_roundbased = autocvar_g_domination_roundbased;
-
- ScoreRules_dom(domination_teams);
-
- if(domination_roundbased)
- {
- round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
- round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
- }
-}
-
-void dom_Initialize()
-{
- g_domination = true;
- InitializeEntity(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);
-}
-#endif
+++ /dev/null
-#pragma once
-
-#ifdef SVQC
-#include <common/mutators/base.qh>
-#define autocvar_g_domination_point_limit cvar("g_domination_point_limit")
-bool autocvar_g_domination_roundbased;
-int autocvar_g_domination_roundbased_point_limit;
-int autocvar_g_domination_point_leadlimit;
-
-void dom_Initialize();
-
-REGISTER_MUTATOR(dom, false)
-{
- MUTATOR_STATIC();
- MUTATOR_ONADD
- {
- int fraglimit_override = autocvar_g_domination_point_limit;
- if (autocvar_g_domination_roundbased && autocvar_g_domination_roundbased_point_limit)
- fraglimit_override = autocvar_g_domination_roundbased_point_limit;
-
- GameRules_teams(true);
- GameRules_limit_score(fraglimit_override);
- GameRules_limit_lead(autocvar_g_domination_point_leadlimit);
-
- dom_Initialize();
- }
- return 0;
-}
-
-// score rule declarations
-const float ST_DOM_TICKS = 1;
-const float ST_DOM_CAPS = 1;
-
-// pps: points per second
-float total_pps;
-float pps_red;
-float pps_blue;
-float pps_yellow;
-float pps_pink;
-
-// capture declarations
-.float enemy_playerid;
-.entity sprite;
-.float captime;
-
-// misc globals
-float domination_roundbased;
-float domination_teams;
-
-void AnimateDomPoint(entity this);
-
-IntrusiveList g_dompoints;
-STATIC_INIT(g_dompoints) { g_dompoints = IL_NEW(); }
-#endif
--- /dev/null
+#include "sv_domination.qh"
+
+#include <server/teamplay.qh>
+
+bool g_domination;
+
+int autocvar_g_domination_default_teams;
+bool autocvar_g_domination_disable_frags;
+int autocvar_g_domination_point_amt;
+bool autocvar_g_domination_point_fullbright;
+float autocvar_g_domination_round_timelimit;
+float autocvar_g_domination_warmup;
+float autocvar_g_domination_point_rate;
+int autocvar_g_domination_teams_override;
+
+void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
+{
+ if(autocvar_sv_eventlog)
+ GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
+}
+
+void set_dom_state(entity e)
+{
+ STAT(DOM_TOTAL_PPS, e) = total_pps;
+ STAT(DOM_PPS_RED, e) = pps_red;
+ STAT(DOM_PPS_BLUE, e) = pps_blue;
+ if(domination_teams >= 3)
+ STAT(DOM_PPS_YELLOW, e) = pps_yellow;
+ if(domination_teams >= 4)
+ STAT(DOM_PPS_PINK, e) = pps_pink;
+}
+
+void dompoint_captured(entity this)
+{
+ float old_delay, old_team, real_team;
+
+ // now that the delay has expired, switch to the latest team to lay claim to this point
+ entity head = this.owner;
+
+ real_team = this.cnt;
+ this.cnt = -1;
+
+ dom_EventLog("taken", this.team, this.dmg_inflictor);
+ this.dmg_inflictor = NULL;
+
+ this.goalentity = head;
+ this.model = head.mdl;
+ this.modelindex = head.dmg;
+ this.skin = head.skin;
+
+ float points, wait_time;
+ if (autocvar_g_domination_point_amt)
+ points = autocvar_g_domination_point_amt;
+ else
+ points = this.frags;
+ if (autocvar_g_domination_point_rate)
+ wait_time = autocvar_g_domination_point_rate;
+ else
+ wait_time = this.wait;
+
+ if(domination_roundbased)
+ bprint(sprintf("^3%s^3%s\n", head.netname, this.message));
+ else
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, this.message, points, wait_time);
+
+ if(this.enemy.playerid == this.enemy_playerid)
+ GameRules_scoring_add(this.enemy, DOM_TAKES, 1);
+ else
+ this.enemy = NULL;
+
+ if (head.noise != "")
+ if(this.enemy)
+ _sound(this.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
+ else
+ _sound(this, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
+ if (head.noise1 != "")
+ play2all(head.noise1);
+
+ this.delay = time + wait_time;
+
+ // do trigger work
+ old_delay = this.delay;
+ old_team = this.team;
+ this.team = real_team;
+ this.delay = 0;
+ SUB_UseTargets (this, this, NULL);
+ this.delay = old_delay;
+ this.team = old_team;
+
+ entity msg = WP_DomNeut;
+ switch(real_team)
+ {
+ case NUM_TEAM_1: msg = WP_DomRed; break;
+ case NUM_TEAM_2: msg = WP_DomBlue; break;
+ case NUM_TEAM_3: msg = WP_DomYellow; break;
+ case NUM_TEAM_4: msg = WP_DomPink; break;
+ }
+
+ WaypointSprite_UpdateSprites(this.sprite, msg, WP_Null, WP_Null);
+
+ total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
+ IL_EACH(g_dompoints, true,
+ {
+ if (autocvar_g_domination_point_amt)
+ points = autocvar_g_domination_point_amt;
+ else
+ points = it.frags;
+ if (autocvar_g_domination_point_rate)
+ wait_time = autocvar_g_domination_point_rate;
+ else
+ wait_time = it.wait;
+ switch(it.goalentity.team)
+ {
+ case NUM_TEAM_1: pps_red += points/wait_time; break;
+ case NUM_TEAM_2: pps_blue += points/wait_time; break;
+ case NUM_TEAM_3: pps_yellow += points/wait_time; break;
+ case NUM_TEAM_4: pps_pink += points/wait_time; break;
+ }
+ total_pps += points/wait_time;
+ });
+
+ WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, colormapPaletteColor(this.goalentity.team - 1, 0));
+ WaypointSprite_Ping(this.sprite);
+
+ this.captime = time;
+
+ FOREACH_CLIENT(IS_REAL_CLIENT(it), { set_dom_state(it); });
+}
+
+void AnimateDomPoint(entity this)
+{
+ if(this.pain_finished > time)
+ return;
+ this.pain_finished = time + this.t_width;
+ if(this.nextthink > this.pain_finished)
+ this.nextthink = this.pain_finished;
+
+ this.frame = this.frame + 1;
+ if(this.frame > this.t_length)
+ this.frame = 0;
+}
+
+void dompointthink(entity this)
+{
+ float fragamt;
+
+ this.nextthink = time + 0.1;
+
+ //this.frame = this.frame + 1;
+ //if(this.frame > 119)
+ // this.frame = 0;
+ AnimateDomPoint(this);
+
+ // give points
+
+ if (game_stopped || this.delay > time || time < game_starttime) // game has ended, don't keep giving points
+ return;
+
+ if(autocvar_g_domination_point_rate)
+ this.delay = time + autocvar_g_domination_point_rate;
+ else
+ this.delay = time + this.wait;
+
+ // give credit to the team
+ // NOTE: this defaults to 0
+ if (!domination_roundbased)
+ if (this.goalentity.netname != "")
+ {
+ if(autocvar_g_domination_point_amt)
+ fragamt = autocvar_g_domination_point_amt;
+ else
+ fragamt = this.frags;
+ TeamScore_AddToTeam(this.goalentity.team, ST_SCORE, fragamt);
+ TeamScore_AddToTeam(this.goalentity.team, ST_DOM_TICKS, fragamt);
+
+ // give credit to the individual player, if he is still there
+ if (this.enemy.playerid == this.enemy_playerid)
+ {
+ GameRules_scoring_add(this.enemy, SCORE, fragamt);
+ GameRules_scoring_add(this.enemy, DOM_TICKS, fragamt);
+ }
+ else
+ this.enemy = NULL;
+ }
+}
+
+void dompointtouch(entity this, entity toucher)
+{
+ if(!IS_PLAYER(toucher))
+ return;
+ if(GetResourceAmount(toucher, RESOURCE_HEALTH) < 1)
+ return;
+
+ if(round_handler_IsActive() && !round_handler_IsRoundStarted())
+ return;
+
+ if(time < this.captime + 0.3)
+ return;
+
+ // only valid teams can claim it
+ entity head = find(NULL, classname, "dom_team");
+ while (head && head.team != toucher.team)
+ head = find(head, classname, "dom_team");
+ if (!head || head.netname == "" || head == this.goalentity)
+ return;
+
+ // delay capture
+
+ this.team = this.goalentity.team; // this stores the PREVIOUS team!
+
+ this.cnt = toucher.team;
+ this.owner = head; // team to switch to after the delay
+ this.dmg_inflictor = toucher;
+
+ // this.state = 1;
+ // this.delay = time + cvar("g_domination_point_capturetime");
+ //this.nextthink = time + cvar("g_domination_point_capturetime");
+ //this.think = dompoint_captured;
+
+ // go to neutral team in the mean time
+ head = find(NULL, classname, "dom_team");
+ while (head && head.netname != "")
+ head = find(head, classname, "dom_team");
+ if(head == NULL)
+ return;
+
+ WaypointSprite_UpdateSprites(this.sprite, WP_DomNeut, WP_Null, WP_Null);
+ WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, '0 1 1');
+ WaypointSprite_Ping(this.sprite);
+
+ this.goalentity = head;
+ this.model = head.mdl;
+ this.modelindex = head.dmg;
+ this.skin = head.skin;
+
+ this.enemy = toucher; // individual player scoring
+ this.enemy_playerid = toucher.playerid;
+ dompoint_captured(this);
+}
+
+void dom_controlpoint_setup(entity this)
+{
+ entity head;
+ // find the spawnfunc_dom_team representing unclaimed points
+ head = find(NULL, classname, "dom_team");
+ while(head && head.netname != "")
+ head = find(head, classname, "dom_team");
+ if (!head)
+ objerror(this, "no spawnfunc_dom_team with netname \"\" found\n");
+
+ // copy important properties from spawnfunc_dom_team entity
+ this.goalentity = head;
+ _setmodel(this, head.mdl); // precision already set
+ this.skin = head.skin;
+
+ this.cnt = -1;
+
+ if(this.message == "")
+ this.message = " has captured a control point";
+
+ if(this.frags <= 0)
+ this.frags = 1;
+ if(this.wait <= 0)
+ this.wait = 5;
+
+ float points, waittime;
+ if (autocvar_g_domination_point_amt)
+ points = autocvar_g_domination_point_amt;
+ else
+ points = this.frags;
+ if (autocvar_g_domination_point_rate)
+ waittime = autocvar_g_domination_point_rate;
+ else
+ waittime = this.wait;
+
+ total_pps += points/waittime;
+
+ if(!this.t_width)
+ this.t_width = 0.02; // frame animation rate
+ if(!this.t_length)
+ this.t_length = 239; // maximum frame
+
+ setthink(this, dompointthink);
+ this.nextthink = time;
+ settouch(this, dompointtouch);
+ this.solid = SOLID_TRIGGER;
+ if(!this.flags & FL_ITEM)
+ IL_PUSH(g_items, this);
+ this.flags = FL_ITEM;
+ setsize(this, '-32 -32 -32', '32 32 32');
+ setorigin(this, this.origin + '0 0 20');
+ droptofloor(this);
+
+ waypoint_spawnforitem(this);
+ WaypointSprite_SpawnFixed(WP_DomNeut, this.origin + '0 0 32', this, sprite, RADARICON_DOMPOINT);
+}
+
+int total_control_points;
+void Domination_count_controlpoints()
+{
+ total_control_points = 0;
+ for (int i = 1; i <= NUM_TEAMS; ++i)
+ {
+ Team_SetNumberOfControlPoints(Team_GetTeamFromIndex(i), 0);
+ }
+ IL_EACH(g_dompoints, true,
+ {
+ ++total_control_points;
+ if (!Entity_HasValidTeam(it.goalentity))
+ {
+ continue;
+ }
+ entity team_ = Entity_GetTeam(it.goalentity);
+ int num_control_points = Team_GetNumberOfControlPoints(team_);
+ ++num_control_points;
+ Team_SetNumberOfControlPoints(team_, num_control_points);
+ });
+}
+
+int Domination_GetWinnerTeam()
+{
+ int winner_team = 0;
+ if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(1)) ==
+ total_control_points)
+ {
+ winner_team = NUM_TEAM_1;
+ }
+ for (int i = 2; i <= NUM_TEAMS; ++i)
+ {
+ if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(i)) ==
+ total_control_points)
+ {
+ if (winner_team != 0)
+ {
+ return 0;
+ }
+ winner_team = Team_IndexToTeam(i);
+ }
+ }
+ if (winner_team)
+ {
+ return winner_team;
+ }
+ return -1; // no control points left?
+}
+
+bool Domination_CheckWinner()
+{
+ if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
+
+ game_stopped = true;
+ round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
+ return true;
+ }
+
+ Domination_count_controlpoints();
+
+ float winner_team = Domination_GetWinnerTeam();
+
+ if(winner_team == -1)
+ return false;
+
+ if(winner_team > 0)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
+ TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
+ }
+ else if(winner_team == -1)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
+ }
+
+ game_stopped = true;
+ round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
+
+ return true;
+}
+
+bool Domination_CheckPlayers()
+{
+ return true;
+}
+
+void Domination_RoundStart()
+{
+ FOREACH_CLIENT(IS_PLAYER(it), { it.player_blocked = false; });
+}
+
+//go to best items, or control points you don't own
+void havocbot_goalrating_controlpoints(entity this, float ratingscale, vector org, float sradius)
+{
+ IL_EACH(g_dompoints, vdist((((it.absmin + it.absmax) * 0.5) - org), <, sradius),
+ {
+ if(it.cnt > -1) // this is just being fought
+ navigation_routerating(this, it, ratingscale, 5000);
+ else if(it.goalentity.cnt == 0) // unclaimed
+ navigation_routerating(this, it, ratingscale, 5000);
+ else if(it.goalentity.team != this.team) // other team's point
+ navigation_routerating(this, it, ratingscale, 5000);
+ });
+}
+
+void havocbot_role_dom(entity this)
+{
+ if(IS_DEAD(this))
+ return;
+
+ if (navigation_goalrating_timeout(this))
+ {
+ navigation_goalrating_start(this);
+ havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
+ havocbot_goalrating_items(this, 20000, this.origin, 8000);
+ //havocbot_goalrating_enemyplayers(this, 1500, this.origin, 2000);
+ havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+MUTATOR_HOOKFUNCTION(dom, TeamBalance_CheckAllowedTeams)
+{
+ // fallback?
+ M_ARGV(0, float) = domination_teams;
+ string ret_string = "dom_team";
+
+ entity head = find(NULL, classname, ret_string);
+ while(head)
+ {
+ if(head.netname != "")
+ {
+ if (Team_IsValidTeam(head.team))
+ {
+ M_ARGV(0, float) |= Team_TeamToBit(head.team);
+ }
+ }
+
+ head = find(head, classname, ret_string);
+ }
+
+ M_ARGV(1, string) = string_null;
+
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(dom, reset_map_players)
+{
+ total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ PutClientInServer(it);
+ if(domination_roundbased)
+ it.player_blocked = 1;
+ if(IS_REAL_CLIENT(it))
+ set_dom_state(it);
+ });
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(domination_roundbased)
+ if(!round_handler_IsRoundStarted())
+ player.player_blocked = 1;
+ else
+ player.player_blocked = 0;
+}
+
+MUTATOR_HOOKFUNCTION(dom, ClientConnect)
+{
+ entity player = M_ARGV(0, entity);
+
+ set_dom_state(player);
+}
+
+MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
+{
+ entity bot = M_ARGV(0, entity);
+
+ bot.havocbot_role = havocbot_role_dom;
+ return true;
+}
+
+/*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
+Control point for Domination gameplay.
+*/
+spawnfunc(dom_controlpoint)
+{
+ if(!g_domination)
+ {
+ delete(this);
+ return;
+ }
+ setthink(this, dom_controlpoint_setup);
+ this.nextthink = time + 0.1;
+ this.reset = dom_controlpoint_setup;
+
+ if(!this.scale)
+ this.scale = 0.6;
+
+ this.effects = this.effects | EF_LOWPRECISION;
+ if (autocvar_g_domination_point_fullbright)
+ this.effects |= EF_FULLBRIGHT;
+
+ IL_PUSH(g_dompoints, this);
+}
+
+/*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
+Team declaration for Domination gameplay, this allows you to decide what team
+names and control point models are used in your map.
+
+Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
+can have netname set! The nameless team owns all control points at start.
+
+Keys:
+"netname"
+ Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
+"cnt"
+ Scoreboard color of the team (for example 4 is red and 13 is blue)
+"model"
+ Model to use for control points owned by this team (for example
+ "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
+ keycard)
+"skin"
+ Skin of the model to use (for team skins on a single model)
+"noise"
+ Sound to play when this team captures a point.
+ (this is a localized sound, like a small alarm or other effect)
+"noise1"
+ Narrator speech to play when this team captures a point.
+ (this is a global sound, like "Red team has captured a control point")
+*/
+
+spawnfunc(dom_team)
+{
+ if(!g_domination || autocvar_g_domination_teams_override >= 2)
+ {
+ delete(this);
+ return;
+ }
+ precache_model(this.model);
+ if (this.noise != "")
+ precache_sound(this.noise);
+ if (this.noise1 != "")
+ precache_sound(this.noise1);
+ this.classname = "dom_team";
+ _setmodel(this, this.model); // precision not needed
+ this.mdl = this.model;
+ this.dmg = this.modelindex;
+ this.model = "";
+ this.modelindex = 0;
+ // this would have to be changed if used in quakeworld
+ if(this.cnt)
+ this.team = this.cnt + 1; // WHY are these different anyway?
+}
+
+// scoreboard setup
+void ScoreRules_dom(int teams)
+{
+ if(domination_roundbased)
+ {
+ GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
+ field_team(ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
+ field(SP_DOM_TAKES, "takes", 0);
+ });
+ }
+ else
+ {
+ float sp_domticks, sp_score;
+ sp_score = sp_domticks = 0;
+ if(autocvar_g_domination_disable_frags)
+ sp_domticks = SFL_SORT_PRIO_PRIMARY;
+ else
+ sp_score = SFL_SORT_PRIO_PRIMARY;
+ GameRules_scoring(teams, sp_score, sp_score, {
+ field_team(ST_DOM_TICKS, "ticks", sp_domticks);
+ field(SP_DOM_TICKS, "ticks", sp_domticks);
+ field(SP_DOM_TAKES, "takes", 0);
+ });
+ }
+}
+
+// code from here on is just to support maps that don't have control point and team entities
+void dom_spawnteam(string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
+{
+ TC(Sound, capsound);
+ entity e = new_pure(dom_team);
+ e.netname = strzone(teamname);
+ e.cnt = teamcolor;
+ e.model = pointmodel;
+ e.skin = pointskin;
+ e.noise = strzone(Sound_fixpath(capsound));
+ e.noise1 = strzone(capnarration);
+ e.message = strzone(capmessage);
+
+ // this code is identical to spawnfunc_dom_team
+ _setmodel(e, e.model); // precision not needed
+ e.mdl = e.model;
+ e.dmg = e.modelindex;
+ e.model = "";
+ e.modelindex = 0;
+ // this would have to be changed if used in quakeworld
+ e.team = e.cnt + 1;
+
+ //eprint(e);
+}
+
+void dom_spawnpoint(vector org)
+{
+ entity e = spawn();
+ e.classname = "dom_controlpoint";
+ setthink(e, spawnfunc_dom_controlpoint);
+ e.nextthink = time;
+ setorigin(e, org);
+ spawnfunc_dom_controlpoint(e);
+}
+
+// spawn some default teams if the map is not set up for domination
+void dom_spawnteams(int teams)
+{
+ TC(int, teams);
+ dom_spawnteam(Team_ColoredFullName(NUM_TEAM_1), NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, SND_DOM_CLAIM, "", "Red team has captured a control point");
+ dom_spawnteam(Team_ColoredFullName(NUM_TEAM_2), NUM_TEAM_2-1, "models/domination/dom_blue.md3", 0, SND_DOM_CLAIM, "", "Blue team has captured a control point");
+ if(teams >= 3)
+ dom_spawnteam(Team_ColoredFullName(NUM_TEAM_3), NUM_TEAM_3-1, "models/domination/dom_yellow.md3", 0, SND_DOM_CLAIM, "", "Yellow team has captured a control point");
+ if(teams >= 4)
+ dom_spawnteam(Team_ColoredFullName(NUM_TEAM_4), NUM_TEAM_4-1, "models/domination/dom_pink.md3", 0, SND_DOM_CLAIM, "", "Pink team has captured a control point");
+ dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
+}
+
+void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
+{
+ // if no teams are found, spawn defaults
+ if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
+ {
+ LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.");
+ domination_teams = autocvar_g_domination_teams_override;
+ if (domination_teams < 2)
+ domination_teams = autocvar_g_domination_default_teams;
+ domination_teams = bound(2, domination_teams, 4);
+ dom_spawnteams(domination_teams);
+ }
+
+ entity balance = TeamBalance_CheckAllowedTeams(NULL);
+ int teams = TeamBalance_GetAllowedTeams(balance);
+ TeamBalance_Destroy(balance);
+ domination_teams = teams;
+
+ domination_roundbased = autocvar_g_domination_roundbased;
+
+ ScoreRules_dom(domination_teams);
+
+ if(domination_roundbased)
+ {
+ round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
+ round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
+ }
+}
+
+void dom_Initialize()
+{
+ g_domination = true;
+ InitializeEntity(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);
+}
--- /dev/null
+#pragma once
+
+#include <common/mutators/base.qh>
+#define autocvar_g_domination_point_limit cvar("g_domination_point_limit")
+bool autocvar_g_domination_roundbased;
+int autocvar_g_domination_roundbased_point_limit;
+int autocvar_g_domination_point_leadlimit;
+
+void dom_Initialize();
+
+REGISTER_MUTATOR(dom, false)
+{
+ MUTATOR_STATIC();
+ MUTATOR_ONADD
+ {
+ int fraglimit_override = autocvar_g_domination_point_limit;
+ if (autocvar_g_domination_roundbased && autocvar_g_domination_roundbased_point_limit)
+ fraglimit_override = autocvar_g_domination_roundbased_point_limit;
+
+ GameRules_teams(true);
+ GameRules_limit_score(fraglimit_override);
+ GameRules_limit_lead(autocvar_g_domination_point_leadlimit);
+
+ dom_Initialize();
+ }
+ return 0;
+}
+
+// score rule declarations
+const float ST_DOM_TICKS = 1;
+const float ST_DOM_CAPS = 1;
+
+// pps: points per second
+float total_pps;
+float pps_red;
+float pps_blue;
+float pps_yellow;
+float pps_pink;
+
+// capture declarations
+.float enemy_playerid;
+.entity sprite;
+.float captime;
+
+// misc globals
+float domination_roundbased;
+float domination_teams;
+
+void AnimateDomPoint(entity this);
+
+IntrusiveList g_dompoints;
+STATIC_INIT(g_dompoints) { g_dompoints = IL_NEW(); }
--- /dev/null
+// generated file; do not modify
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/duel/sv_duel.qc>
+#endif
--- /dev/null
+// generated file; do not modify
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/duel/sv_duel.qh>
+#endif
--- /dev/null
+#include "sv_duel.qh"
+
+MUTATOR_HOOKFUNCTION(duel, GetPlayerLimit)
+{
+ M_ARGV(0, int) = 2; // duel is always 1v1!
+}
+
+MUTATOR_HOOKFUNCTION(duel, Scores_CountFragsRemaining)
+{
+ // announce remaining frags?
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(duel, FilterItemDefinition)
+{
+ entity definition = M_ARGV(0, entity);
+
+ if(definition.instanceOfPowerup)
+ {
+ return !autocvar_g_duel_with_powerups;
+ }
+}
--- /dev/null
+#pragma once
+
+#include <common/mutators/base.qh>
+REGISTER_MUTATOR(duel, false)
+{
+ MUTATOR_STATIC();
+ return 0;
+}
+
+bool autocvar_g_duel_with_powerups;
// generated file; do not modify
-#include <common/gamemodes/gamemode/freezetag/freezetag.qc>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/freezetag/sv_freezetag.qc>
+#endif
// generated file; do not modify
-#include <common/gamemodes/gamemode/freezetag/freezetag.qh>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/freezetag/sv_freezetag.qh>
+#endif
+++ /dev/null
-#include "freezetag.qh"
-
-// TODO: sv_freezetag
-#ifdef SVQC
-#include <server/resources.qh>
-
-float autocvar_g_freezetag_frozen_maxtime;
-float autocvar_g_freezetag_revive_clearspeed;
-float autocvar_g_freezetag_round_timelimit;
-//int autocvar_g_freezetag_teams;
-int autocvar_g_freezetag_teams_override;
-float autocvar_g_freezetag_warmup;
-
-void freezetag_count_alive_players()
-{
- total_players = redalive = bluealive = yellowalive = pinkalive = 0;
- FOREACH_CLIENT(IS_PLAYER(it), {
- switch(it.team)
- {
- case NUM_TEAM_1: ++total_players; if(STAT(FROZEN, it) != 1 && GetResourceAmount(it, RESOURCE_HEALTH) >= 1) ++redalive; break;
- case NUM_TEAM_2: ++total_players; if(STAT(FROZEN, it) != 1 && GetResourceAmount(it, RESOURCE_HEALTH) >= 1) ++bluealive; break;
- case NUM_TEAM_3: ++total_players; if(STAT(FROZEN, it) != 1 && GetResourceAmount(it, RESOURCE_HEALTH) >= 1) ++yellowalive; break;
- case NUM_TEAM_4: ++total_players; if(STAT(FROZEN, it) != 1 && GetResourceAmount(it, RESOURCE_HEALTH) >= 1) ++pinkalive; break;
- }
- });
- FOREACH_CLIENT(IS_REAL_CLIENT(it), {
- STAT(REDALIVE, it) = redalive;
- STAT(BLUEALIVE, it) = bluealive;
- STAT(YELLOWALIVE, it) = yellowalive;
- STAT(PINKALIVE, it) = pinkalive;
- });
-
- eliminatedPlayers.SendFlags |= 1;
-}
-#define FREEZETAG_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0))
-#define FREEZETAG_ALIVE_TEAMS_OK() (FREEZETAG_ALIVE_TEAMS() == NumTeams(freezetag_teams))
-
-float freezetag_CheckTeams()
-{
- static float prev_missing_teams_mask;
- if(FREEZETAG_ALIVE_TEAMS_OK())
- {
- if(prev_missing_teams_mask > 0)
- Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
- prev_missing_teams_mask = -1;
- return 1;
- }
- if(total_players == 0)
- {
- if(prev_missing_teams_mask > 0)
- Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
- prev_missing_teams_mask = -1;
- return 0;
- }
- int missing_teams_mask = 0;
- if(freezetag_teams & BIT(0))
- missing_teams_mask += (!redalive) * 1;
- if(freezetag_teams & BIT(1))
- missing_teams_mask += (!bluealive) * 2;
- if(freezetag_teams & BIT(2))
- missing_teams_mask += (!yellowalive) * 4;
- if(freezetag_teams & BIT(3))
- missing_teams_mask += (!pinkalive) * 8;
- if(prev_missing_teams_mask != missing_teams_mask)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
- prev_missing_teams_mask = missing_teams_mask;
- }
- return 0;
-}
-
-float freezetag_getWinnerTeam()
-{
- float winner_team = 0;
- if(redalive >= 1)
- winner_team = NUM_TEAM_1;
- if(bluealive >= 1)
- {
- if(winner_team) return 0;
- winner_team = NUM_TEAM_2;
- }
- if(yellowalive >= 1)
- {
- if(winner_team) return 0;
- winner_team = NUM_TEAM_3;
- }
- if(pinkalive >= 1)
- {
- if(winner_team) return 0;
- winner_team = NUM_TEAM_4;
- }
- if(winner_team)
- return winner_team;
- return -1; // no player left
-}
-
-void nades_Clear(entity);
-void nades_GiveBonus(entity player, float score);
-
-float freezetag_CheckWinner()
-{
- if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
- FOREACH_CLIENT(IS_PLAYER(it), {
- it.freezetag_frozen_timeout = 0;
- nades_Clear(it);
- });
- game_stopped = true;
- round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
- return 1;
- }
-
- if(FREEZETAG_ALIVE_TEAMS() > 1)
- return 0;
-
- int winner_team = freezetag_getWinnerTeam();
- if(winner_team > 0)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
- TeamScore_AddToTeam(winner_team, ST_SCORE, +1);
- }
- else if(winner_team == -1)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
- }
-
- FOREACH_CLIENT(IS_PLAYER(it), {
- it.freezetag_frozen_timeout = 0;
- nades_Clear(it);
- });
-
- game_stopped = true;
- round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
- return 1;
-}
-
-entity freezetag_LastPlayerForTeam(entity this)
-{
- entity last_pl = NULL;
- FOREACH_CLIENT(IS_PLAYER(it) && it != this && SAME_TEAM(it, this), {
- if (!STAT(FROZEN, it) && GetResourceAmount(it, RESOURCE_HEALTH) >= 1)
- {
- if (!last_pl)
- last_pl = it;
- else
- return NULL;
- }
- });
- return last_pl;
-}
-
-void freezetag_LastPlayerForTeam_Notify(entity this)
-{
- if(round_handler_IsActive())
- if(round_handler_IsRoundStarted())
- {
- entity pl = freezetag_LastPlayerForTeam(this);
- if(pl)
- Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
- }
-}
-
-void freezetag_Add_Score(entity targ, entity attacker)
-{
- if(attacker == targ)
- {
- // you froze your own dumb targ
- // counted as "suicide" already
- GameRules_scoring_add(targ, SCORE, -1);
- }
- else if(IS_PLAYER(attacker))
- {
- // got frozen by an enemy
- // counted as "kill" and "death" already
- GameRules_scoring_add(targ, SCORE, -1);
- GameRules_scoring_add(attacker, SCORE, +1);
- }
- // else nothing - got frozen by the game type rules themselves
-}
-
-// to be called when the player is frozen by freezetag (on death, spectator join etc), gives the score
-void freezetag_Freeze(entity targ, entity attacker)
-{
- if(STAT(FROZEN, targ))
- return;
-
- if(autocvar_g_freezetag_frozen_maxtime > 0)
- targ.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime;
-
- Freeze(targ, 0, 1, true);
-
- freezetag_count_alive_players();
-
- freezetag_Add_Score(targ, attacker);
-}
-
-float freezetag_isEliminated(entity e)
-{
- if(IS_PLAYER(e) && (STAT(FROZEN, e) == 1 || IS_DEAD(e)))
- return true;
- return false;
-}
-
-
-// ================
-// Bot player logic
-// ================
-
-void(entity this) havocbot_role_ft_freeing;
-void(entity this) havocbot_role_ft_offense;
-
-void havocbot_goalrating_freeplayers(entity this, float ratingscale, vector org, float sradius)
-{
- float t;
- FOREACH_CLIENT(IS_PLAYER(it) && it != this && SAME_TEAM(it, this), {
- if (STAT(FROZEN, it) == 1)
- {
- if(vdist(it.origin - org, >, sradius))
- continue;
- navigation_routerating(this, it, ratingscale, 2000);
- }
- else if(vdist(it.origin - org, >, 400)) // avoid gathering all teammates in one place
- {
- // If teamate is not frozen still seek them out as fight better
- // in a group.
- t = 0.2 * 150 / (GetResourceAmount(this, RESOURCE_HEALTH) + GetResourceAmount(this, RESOURCE_ARMOR));
- navigation_routerating(this, it, t * ratingscale, 2000);
- }
- });
-}
-
-void havocbot_role_ft_offense(entity this)
-{
- if(IS_DEAD(this))
- return;
-
- if (!this.havocbot_role_timeout)
- this.havocbot_role_timeout = time + random() * 10 + 20;
-
- // Count how many players on team are unfrozen.
- int unfrozen = 0;
- FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this) && !(STAT(FROZEN, it) != 1), { unfrozen++; });
-
- // If only one left on team or if role has timed out then start trying to free players.
- if (((unfrozen == 0) && (!STAT(FROZEN, this))) || (time > this.havocbot_role_timeout))
- {
- LOG_TRACE("changing role to freeing");
- this.havocbot_role = havocbot_role_ft_freeing;
- this.havocbot_role_timeout = 0;
- return;
- }
-
- if (navigation_goalrating_timeout(this))
- {
- 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);
- }
-}
-
-void havocbot_role_ft_freeing(entity this)
-{
- if(IS_DEAD(this))
- return;
-
- if (!this.havocbot_role_timeout)
- this.havocbot_role_timeout = time + random() * 10 + 20;
-
- if (time > this.havocbot_role_timeout)
- {
- LOG_TRACE("changing role to offense");
- this.havocbot_role = havocbot_role_ft_offense;
- this.havocbot_role_timeout = 0;
- return;
- }
-
- if (navigation_goalrating_timeout(this))
- {
- 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);
- }
-}
-
-
-// ==============
-// Hook Functions
-// ==============
-
-void ft_RemovePlayer(entity this)
-{
- SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0); // neccessary to update correctly alive stats
- if(!STAT(FROZEN, this))
- freezetag_LastPlayerForTeam_Notify(this);
- Unfreeze(this);
- freezetag_count_alive_players();
-}
-
-MUTATOR_HOOKFUNCTION(ft, ClientDisconnect)
-{
- entity player = M_ARGV(0, entity);
-
- ft_RemovePlayer(player);
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, MakePlayerObserver)
-{
- entity player = M_ARGV(0, entity);
-
- ft_RemovePlayer(player);
-}
-
-MUTATOR_HOOKFUNCTION(ft, PlayerDies)
-{
- entity frag_attacker = M_ARGV(1, entity);
- entity frag_target = M_ARGV(2, entity);
- float frag_deathtype = M_ARGV(3, float);
-
- if(round_handler_IsActive())
- if(round_handler_CountdownRunning())
- {
- if(STAT(FROZEN, frag_target))
- Unfreeze(frag_target);
- freezetag_count_alive_players();
- return true; // let the player die so that he can respawn whenever he wants
- }
-
- // Cases DEATH_TEAMCHANGE and DEATH_AUTOTEAMCHANGE are needed to fix a bug whe
- // you succeed changing team through the menu: you both really die (gibbing) and get frozen
- if(ITEM_DAMAGE_NEEDKILL(frag_deathtype)
- || frag_deathtype == DEATH_TEAMCHANGE.m_id || frag_deathtype == DEATH_AUTOTEAMCHANGE.m_id)
- {
- // let the player die, he will be automatically frozen when he respawns
- if(STAT(FROZEN, frag_target) != 1)
- {
- freezetag_Add_Score(frag_target, frag_attacker);
- freezetag_count_alive_players();
- freezetag_LastPlayerForTeam_Notify(frag_target);
- }
- else
- Unfreeze(frag_target); // remove ice
- SetResourceAmountExplicit(frag_target, RESOURCE_HEALTH, 0); // Unfreeze resets health
- frag_target.freezetag_frozen_timeout = -2; // freeze on respawn
- return true;
- }
-
- if(STAT(FROZEN, frag_target))
- return true;
-
- freezetag_Freeze(frag_target, frag_attacker);
- freezetag_LastPlayerForTeam_Notify(frag_target);
-
- if(frag_attacker == frag_target || frag_attacker == NULL)
- {
- if(IS_PLAYER(frag_target))
- Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_SELF);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_SELF, frag_target.netname);
- }
- else
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname);
- }
-
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, PlayerSpawn)
-{
- entity player = M_ARGV(0, entity);
-
- if(player.freezetag_frozen_timeout == -1) // if PlayerSpawn is called by reset_map_players
- return true; // do nothing, round is starting right now
-
- if(player.freezetag_frozen_timeout == -2) // player was dead
- {
- freezetag_Freeze(player, NULL);
- return true;
- }
-
- freezetag_count_alive_players();
-
- if(round_handler_IsActive())
- if(round_handler_IsRoundStarted())
- {
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_SPAWN_LATE);
- freezetag_Freeze(player, NULL);
- }
-
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, reset_map_players)
-{
- FOREACH_CLIENT(IS_PLAYER(it), {
- CS(it).killcount = 0;
- it.freezetag_frozen_timeout = -1;
- PutClientInServer(it);
- it.freezetag_frozen_timeout = 0;
- });
- freezetag_count_alive_players();
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, GiveFragsForKill, CBC_ORDER_FIRST)
-{
- M_ARGV(2, float) = 0; // no frags counted in Freeze Tag
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, Unfreeze)
-{
- entity targ = M_ARGV(0, entity);
- targ.freezetag_frozen_time = 0;
- targ.freezetag_frozen_timeout = 0;
-
- freezetag_count_alive_players();
-}
-
-MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST)
-{
- if(game_stopped)
- return true;
-
- if(round_handler_IsActive())
- if(!round_handler_IsRoundStarted())
- return true;
-
- int n;
- entity o = NULL;
- entity player = M_ARGV(0, entity);
- //if(STAT(FROZEN, player))
- //if(player.freezetag_frozen_timeout > 0 && time < player.freezetag_frozen_timeout)
- //player.iceblock.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (player.freezetag_frozen_timeout - time) / (player.freezetag_frozen_timeout - player.freezetag_frozen_time);
-
- if(player.freezetag_frozen_timeout > 0 && time >= player.freezetag_frozen_timeout)
- n = -1;
- else
- {
- vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
- n = 0;
- FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
- if(STAT(FROZEN, it) == 0)
- if(!IS_DEAD(it))
- if(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;
- ++n;
- }
- });
-
- }
-
- if(n && STAT(FROZEN, player) == 1) // OK, there is at least one teammate reviving us
- {
- STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
- SetResourceAmountExplicit(player, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health)));
-
- if(STAT(REVIVE_PROGRESS, player) >= 1)
- {
- Unfreeze(player);
- freezetag_count_alive_players();
-
- if(n == -1)
- {
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_AUTO_REVIVED, autocvar_g_freezetag_frozen_maxtime);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_AUTO_REVIVED, player.netname, autocvar_g_freezetag_frozen_maxtime);
- return true;
- }
-
- // EVERY team mate nearby gets a point (even if multiple!)
- FOREACH_CLIENT(IS_PLAYER(it) && it.reviving, {
- GameRules_scoring_add(it, FREEZETAG_REVIVALS, +1);
- GameRules_scoring_add(it, SCORE, +1);
- nades_GiveBonus(it,autocvar_g_nades_bonus_score_low);
- });
-
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname);
- Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, player.netname);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED, player.netname, o.netname);
- }
-
- FOREACH_CLIENT(IS_PLAYER(it) && it.reviving, {
- STAT(REVIVE_PROGRESS, it) = STAT(REVIVE_PROGRESS, player);
- it.reviving = false;
- });
- }
- else if(!n && STAT(FROZEN, player) == 1) // only if no teammate is nearby will we reset
- {
- STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) - frametime * autocvar_g_freezetag_revive_clearspeed, 1);
- SetResourceAmountExplicit(player, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health)));
- }
- else if(!n && !STAT(FROZEN, player))
- {
- STAT(REVIVE_PROGRESS, player) = 0; // thawing nobody
- }
-
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, SetStartItems)
-{
- start_items &= ~IT_UNLIMITED_AMMO;
- //start_health = warmup_start_health = cvar("g_lms_start_health");
- //start_armorvalue = warmup_start_armorvalue = cvar("g_lms_start_armor");
- start_ammo_shells = warmup_start_ammo_shells = cvar("g_lms_start_ammo_shells");
- start_ammo_nails = warmup_start_ammo_nails = cvar("g_lms_start_ammo_nails");
- start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
- start_ammo_cells = warmup_start_ammo_cells = cvar("g_lms_start_ammo_cells");
- start_ammo_plasma = warmup_start_ammo_plasma = cvar("g_lms_start_ammo_plasma");
- start_ammo_fuel = warmup_start_ammo_fuel = cvar("g_lms_start_ammo_fuel");
-}
-
-MUTATOR_HOOKFUNCTION(ft, HavocBot_ChooseRole)
-{
- entity bot = M_ARGV(0, entity);
-
- if (!IS_DEAD(bot))
- {
- if (random() < 0.5)
- bot.havocbot_role = havocbot_role_ft_freeing;
- else
- bot.havocbot_role = havocbot_role_ft_offense;
- }
-
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
-{
- M_ARGV(0, float) = freezetag_teams;
-}
-
-MUTATOR_HOOKFUNCTION(ft, SetWeaponArena)
-{
- // most weapons arena
- if(M_ARGV(0, string) == "0" || M_ARGV(0, string) == "")
- M_ARGV(0, string) = "most";
-}
-
-MUTATOR_HOOKFUNCTION(ft, FragCenterMessage)
-{
- entity frag_attacker = M_ARGV(0, entity);
- entity frag_target = M_ARGV(1, entity);
- //float frag_deathtype = M_ARGV(2, float);
- int kill_count_to_attacker = M_ARGV(3, int);
- int kill_count_to_target = M_ARGV(4, int);
-
- if(STAT(FROZEN, frag_target))
- return; // target was already frozen, so this is just pushing them off the cliff
-
- Send_Notification(NOTIF_ONE, frag_attacker, MSG_CHOICE, CHOICE_FRAG_FREEZE, frag_target.netname, kill_count_to_attacker, (IS_BOT_CLIENT(frag_target) ? -1 : CS(frag_target).ping));
- Send_Notification(NOTIF_ONE, frag_target, MSG_CHOICE, CHOICE_FRAGGED_FREEZE, frag_attacker.netname, kill_count_to_target,
- GetResourceAmount(frag_attacker, RESOURCE_HEALTH), GetResourceAmount(frag_attacker, RESOURCE_ARMOR), (IS_BOT_CLIENT(frag_attacker) ? -1 : CS(frag_attacker).ping));
-
- return true;
-}
-
-void freezetag_Initialize()
-{
- freezetag_teams = autocvar_g_freezetag_teams_override;
- if(freezetag_teams < 2)
- freezetag_teams = cvar("g_freezetag_teams"); // read the cvar directly as it gets written earlier in the same frame
-
- freezetag_teams = BITS(bound(2, freezetag_teams, 4));
- GameRules_scoring(freezetag_teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, {
- field(SP_FREEZETAG_REVIVALS, "revivals", 0);
- });
-
- round_handler_Spawn(freezetag_CheckTeams, freezetag_CheckWinner, func_null);
- round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
-
- EliminatedPlayers_Init(freezetag_isEliminated);
-}
-#endif
+++ /dev/null
-#pragma once
-
-#ifdef SVQC
-#include <common/mutators/base.qh>
-int autocvar_g_freezetag_point_limit;
-int autocvar_g_freezetag_point_leadlimit;
-bool autocvar_g_freezetag_team_spawns;
-void freezetag_Initialize();
-
-REGISTER_MUTATOR(ft, false)
-{
- MUTATOR_STATIC();
- MUTATOR_ONADD
- {
- GameRules_teams(true);
- GameRules_spawning_teams(autocvar_g_freezetag_team_spawns);
- GameRules_limit_score(autocvar_g_freezetag_point_limit);
- GameRules_limit_lead(autocvar_g_freezetag_point_leadlimit);
-
- freezetag_Initialize();
- }
- return 0;
-}
-
-.float freezetag_frozen_time;
-.float freezetag_frozen_timeout;
-const float ICE_MAX_ALPHA = 1;
-const float ICE_MIN_ALPHA = 0.1;
-float freezetag_teams;
-
-.float reviving; // temp var
-
-float autocvar_g_freezetag_revive_extra_size;
-float autocvar_g_freezetag_revive_speed;
-bool autocvar_g_freezetag_revive_nade;
-float autocvar_g_freezetag_revive_nade_health;
-#endif
--- /dev/null
+#include "sv_freezetag.qh"
+
+#include <server/resources.qh>
+
+float autocvar_g_freezetag_frozen_maxtime;
+float autocvar_g_freezetag_revive_clearspeed;
+float autocvar_g_freezetag_round_timelimit;
+//int autocvar_g_freezetag_teams;
+int autocvar_g_freezetag_teams_override;
+float autocvar_g_freezetag_warmup;
+
+void freezetag_count_alive_players()
+{
+ total_players = 0;
+ for (int i = 1; i <= NUM_TEAMS; ++i)
+ {
+ Team_SetNumberOfAlivePlayers(Team_GetTeamFromIndex(i), 0);
+ }
+ FOREACH_CLIENT(IS_PLAYER(it) && Entity_HasValidTeam(it),
+ {
+ ++total_players;
+ if (GetResourceAmount(it, RESOURCE_HEALTH) < 1 || STAT(FROZEN, it) == FROZEN_NORMAL)
+ {
+ continue;
+ }
+ entity team_ = Entity_GetTeam(it);
+ int num_alive = Team_GetNumberOfAlivePlayers(team_);
+ ++num_alive;
+ Team_SetNumberOfAlivePlayers(team_, num_alive);
+ });
+ FOREACH_CLIENT(IS_REAL_CLIENT(it),
+ {
+ STAT(REDALIVE, it) = Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(
+ 1));
+ STAT(BLUEALIVE, it) = Team_GetNumberOfAlivePlayers(
+ Team_GetTeamFromIndex(2));
+ STAT(YELLOWALIVE, it) = Team_GetNumberOfAlivePlayers(
+ Team_GetTeamFromIndex(3));
+ STAT(PINKALIVE, it) = Team_GetNumberOfAlivePlayers(
+ Team_GetTeamFromIndex(4));
+ });
+
+ eliminatedPlayers.SendFlags |= 1;
+}
+
+#define FREEZETAG_ALIVE_TEAMS_OK() (Team_GetNumberOfAliveTeams() == NumTeams(freezetag_teams))
+
+bool freezetag_CheckTeams()
+{
+ static float prev_missing_teams_mask;
+ if(FREEZETAG_ALIVE_TEAMS_OK())
+ {
+ if(prev_missing_teams_mask > 0)
+ Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+ prev_missing_teams_mask = -1;
+ return true;
+ }
+ if(total_players == 0)
+ {
+ if(prev_missing_teams_mask > 0)
+ Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+ prev_missing_teams_mask = -1;
+ return false;
+ }
+ int missing_teams_mask = 0;
+ for (int i = 1; i <= NUM_TEAMS; ++i)
+ {
+ if ((freezetag_teams & Team_IndexToBit(i)) &&
+ (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) == 0))
+ {
+ missing_teams_mask |= Team_IndexToBit(i);
+ }
+ }
+ if(prev_missing_teams_mask != missing_teams_mask)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
+ prev_missing_teams_mask = missing_teams_mask;
+ }
+ return false;
+}
+
+int freezetag_getWinnerTeam()
+{
+ int winner_team = 0;
+ if (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(1)) >= 1)
+ {
+ winner_team = NUM_TEAM_1;
+ }
+ for (int i = 2; i <= NUM_TEAMS; ++i)
+ {
+ if (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) >= 1)
+ {
+ if (winner_team != 0)
+ {
+ return 0;
+ }
+ winner_team = Team_IndexToTeam(i);
+ }
+ }
+ if (winner_team)
+ {
+ return winner_team;
+ }
+ return -1; // no player left
+}
+
+void nades_Clear(entity);
+void nades_GiveBonus(entity player, float score);
+
+bool freezetag_CheckWinner()
+{
+ if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ it.freezetag_frozen_timeout = 0;
+ nades_Clear(it);
+ });
+ game_stopped = true;
+ round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
+ return true;
+ }
+
+ if (Team_GetNumberOfAliveTeams() > 1)
+ {
+ return false;
+ }
+
+ int winner_team = freezetag_getWinnerTeam();
+ if(winner_team > 0)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
+ TeamScore_AddToTeam(winner_team, ST_SCORE, +1);
+ }
+ else if(winner_team == -1)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
+ }
+
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ it.freezetag_frozen_timeout = 0;
+ nades_Clear(it);
+ });
+
+ game_stopped = true;
+ round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
+ return true;
+}
+
+entity freezetag_LastPlayerForTeam(entity this)
+{
+ entity last_pl = NULL;
+ FOREACH_CLIENT(IS_PLAYER(it) && it != this && SAME_TEAM(it, this), {
+ if (STAT(FROZEN, it) != FROZEN_NORMAL && GetResourceAmount(it, RESOURCE_HEALTH) >= 1)
+ {
+ if (!last_pl)
+ last_pl = it;
+ else
+ return NULL;
+ }
+ });
+ return last_pl;
+}
+
+void freezetag_LastPlayerForTeam_Notify(entity this)
+{
+ if(round_handler_IsActive())
+ if(round_handler_IsRoundStarted())
+ {
+ entity pl = freezetag_LastPlayerForTeam(this);
+ if(pl)
+ Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
+ }
+}
+
+void freezetag_Add_Score(entity targ, entity attacker)
+{
+ if(attacker == targ)
+ {
+ // you froze your own dumb targ
+ // counted as "suicide" already
+ GameRules_scoring_add(targ, SCORE, -1);
+ }
+ else if(IS_PLAYER(attacker))
+ {
+ // got frozen by an enemy
+ // counted as "kill" and "death" already
+ GameRules_scoring_add(targ, SCORE, -1);
+ GameRules_scoring_add(attacker, SCORE, +1);
+ }
+ // else nothing - got frozen by the game type rules themselves
+}
+
+// to be called when the player is frozen by freezetag (on death, spectator join etc), gives the score
+void freezetag_Freeze(entity targ, entity attacker)
+{
+ if(STAT(FROZEN, targ))
+ return;
+
+ if(autocvar_g_freezetag_frozen_maxtime > 0)
+ targ.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime;
+
+ Freeze(targ, 0, FROZEN_NORMAL, true);
+
+ freezetag_count_alive_players();
+
+ freezetag_Add_Score(targ, attacker);
+}
+
+bool freezetag_isEliminated(entity e)
+{
+ if(IS_PLAYER(e) && (STAT(FROZEN, e) == FROZEN_NORMAL || IS_DEAD(e)))
+ return true;
+ return false;
+}
+
+
+// ================
+// Bot player logic
+// ================
+
+void(entity this) havocbot_role_ft_freeing;
+void(entity this) havocbot_role_ft_offense;
+
+void havocbot_goalrating_ft_freeplayers(entity this, float ratingscale, vector org, float sradius)
+{
+ entity best_pl = NULL;
+ float best_dist2 = FLOAT_MAX;
+ FOREACH_CLIENT(IS_PLAYER(it) && it != this && SAME_TEAM(it, this), {
+ if (STAT(FROZEN, it) == FROZEN_NORMAL)
+ {
+ if(vdist(it.origin - org, >, sradius))
+ continue;
+ navigation_routerating(this, it, ratingscale, 2000);
+ }
+ else if (best_dist2
+ && GetResourceAmount(it, RESOURCE_HEALTH) < GetResourceAmount(this, RESOURCE_HEALTH) + 30
+ && vlen2(it.origin - org) < best_dist2)
+ {
+ // If teamate is not frozen still seek them out as fight better
+ // in a group.
+ best_dist2 = vlen2(it.origin - org);
+ if (best_dist2 < 700 ** 2)
+ {
+ best_pl = NULL;
+ best_dist2 = 0; // already close to a teammate
+ }
+ else
+ best_pl = it;
+ }
+ });
+ if (best_pl)
+ navigation_routerating(this, best_pl, ratingscale / 2, 2000);
+}
+
+void havocbot_role_ft_offense(entity this)
+{
+ if(IS_DEAD(this))
+ return;
+
+ if (!this.havocbot_role_timeout)
+ this.havocbot_role_timeout = time + random() * 10 + 20;
+
+ // Count how many players on team are unfrozen.
+ int unfrozen = 0;
+ FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this) && STAT(FROZEN, it) != FROZEN_NORMAL, {
+ unfrozen++;
+ });
+
+ // If only one left on team or if role has timed out then start trying to free players.
+ if ((!unfrozen && STAT(FROZEN, this) != FROZEN_NORMAL) || time > this.havocbot_role_timeout)
+ {
+ LOG_TRACE("changing role to freeing");
+ this.havocbot_role = havocbot_role_ft_freeing;
+ this.havocbot_role_timeout = 0;
+ return;
+ }
+
+ if (navigation_goalrating_timeout(this))
+ {
+ navigation_goalrating_start(this);
+ havocbot_goalrating_items(this, 12000, this.origin, 10000);
+ havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000);
+ havocbot_goalrating_ft_freeplayers(this, 9000, this.origin, 10000);
+ havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+void havocbot_role_ft_freeing(entity this)
+{
+ if(IS_DEAD(this))
+ return;
+
+ if (!this.havocbot_role_timeout)
+ this.havocbot_role_timeout = time + random() * 10 + 20;
+
+ if (time > this.havocbot_role_timeout)
+ {
+ LOG_TRACE("changing role to offense");
+ this.havocbot_role = havocbot_role_ft_offense;
+ this.havocbot_role_timeout = 0;
+ return;
+ }
+
+ if (navigation_goalrating_timeout(this))
+ {
+ navigation_goalrating_start(this);
+ havocbot_goalrating_items(this, 10000, this.origin, 10000);
+ havocbot_goalrating_enemyplayers(this, 5000, this.origin, 10000);
+ havocbot_goalrating_ft_freeplayers(this, 20000, this.origin, 10000);
+ havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+
+// ==============
+// Hook Functions
+// ==============
+
+void ft_RemovePlayer(entity this)
+{
+ if (STAT(FROZEN, this) != FROZEN_NORMAL)
+ freezetag_LastPlayerForTeam_Notify(this);
+ Unfreeze(this, false);
+
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0); // neccessary to correctly count alive players
+ freezetag_count_alive_players();
+}
+
+MUTATOR_HOOKFUNCTION(ft, ClientDisconnect)
+{
+ entity player = M_ARGV(0, entity);
+
+ ft_RemovePlayer(player);
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, MakePlayerObserver)
+{
+ entity player = M_ARGV(0, entity);
+
+ ft_RemovePlayer(player);
+}
+
+MUTATOR_HOOKFUNCTION(ft, PlayerDies)
+{
+ entity frag_attacker = M_ARGV(1, entity);
+ entity frag_target = M_ARGV(2, entity);
+ float frag_deathtype = M_ARGV(3, float);
+
+ if(round_handler_IsActive())
+ if(round_handler_CountdownRunning())
+ {
+ if (STAT(FROZEN, frag_target) == FROZEN_NORMAL)
+ Unfreeze(frag_target, true);
+ freezetag_count_alive_players();
+ return true; // let the player die so that he can respawn whenever he wants
+ }
+
+ // Cases DEATH_TEAMCHANGE and DEATH_AUTOTEAMCHANGE are needed to fix a bug whe
+ // you succeed changing team through the menu: you both really die (gibbing) and get frozen
+ if(ITEM_DAMAGE_NEEDKILL(frag_deathtype)
+ || frag_deathtype == DEATH_TEAMCHANGE.m_id || frag_deathtype == DEATH_AUTOTEAMCHANGE.m_id)
+ {
+ // let the player die, he will be automatically frozen when he respawns
+ if (STAT(FROZEN, frag_target) != FROZEN_NORMAL)
+ {
+ freezetag_Add_Score(frag_target, frag_attacker);
+ freezetag_count_alive_players();
+ freezetag_LastPlayerForTeam_Notify(frag_target);
+ }
+ else
+ Unfreeze(frag_target, false); // remove ice
+ frag_target.freezetag_frozen_timeout = -2; // freeze on respawn
+ return true;
+ }
+
+ if (STAT(FROZEN, frag_target) == FROZEN_NORMAL)
+ return true;
+
+ freezetag_Freeze(frag_target, frag_attacker);
+ freezetag_LastPlayerForTeam_Notify(frag_target);
+
+ if(frag_attacker == frag_target || frag_attacker == NULL)
+ {
+ if(IS_PLAYER(frag_target))
+ Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_SELF);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_SELF, frag_target.netname);
+ }
+ else
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname);
+ }
+
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, PlayerSpawn)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(player.freezetag_frozen_timeout == -1) // if PlayerSpawn is called by reset_map_players
+ return true; // do nothing, round is starting right now
+
+ if(player.freezetag_frozen_timeout == -2) // player was dead
+ {
+ freezetag_Freeze(player, NULL);
+ return true;
+ }
+
+ freezetag_count_alive_players();
+
+ if(round_handler_IsActive())
+ if(round_handler_IsRoundStarted())
+ {
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_SPAWN_LATE);
+ freezetag_Freeze(player, NULL);
+ }
+
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, reset_map_players)
+{
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ CS(it).killcount = 0;
+ it.freezetag_frozen_timeout = -1;
+ PutClientInServer(it);
+ it.freezetag_frozen_timeout = 0;
+ });
+ freezetag_count_alive_players();
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, GiveFragsForKill, CBC_ORDER_FIRST)
+{
+ M_ARGV(2, float) = 0; // no frags counted in Freeze Tag
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, Unfreeze)
+{
+ entity targ = M_ARGV(0, entity);
+ targ.freezetag_frozen_time = 0;
+ targ.freezetag_frozen_timeout = 0;
+}
+
+#ifdef IS_REVIVING
+ #undef IS_REVIVING
+#endif
+
+// returns true if player is reviving it
+#define IS_REVIVING(player, it, revive_extra_size) \
+ (it != player && !STAT(FROZEN, it) && !IS_DEAD(it) && SAME_TEAM(it, player) \
+ && boxesoverlap(player.absmin - revive_extra_size, player.absmax + revive_extra_size, it.absmin, it.absmax))
+
+MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST)
+{
+ if(game_stopped)
+ return true;
+
+ if(round_handler_IsActive())
+ if(!round_handler_IsRoundStarted())
+ return true;
+
+ int n;
+ entity player = M_ARGV(0, entity);
+ //if (STAT(FROZEN, player) == FROZEN_NORMAL)
+ //if(player.freezetag_frozen_timeout > 0 && time < player.freezetag_frozen_timeout)
+ //player.iceblock.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (player.freezetag_frozen_timeout - time) / (player.freezetag_frozen_timeout - player.freezetag_frozen_time);
+
+ IntrusiveList reviving_players = NULL;
+
+ if(player.freezetag_frozen_timeout > 0 && time >= player.freezetag_frozen_timeout)
+ n = -1;
+ else
+ {
+ n = 0;
+ vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
+ FOREACH_CLIENT(IS_PLAYER(it) && IS_REVIVING(player, it, revive_extra_size), {
+ if (!reviving_players)
+ reviving_players = IL_NEW();
+ IL_PUSH(reviving_players, it);
+ ++n;
+ });
+ }
+
+ if (!n) // no teammate nearby
+ {
+ if (STAT(FROZEN, player) == FROZEN_NORMAL)
+ {
+ STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) - frametime * autocvar_g_freezetag_revive_clearspeed, 1);
+ SetResourceAmountExplicit(player, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health)));
+ }
+ else if (!STAT(FROZEN, player))
+ STAT(REVIVE_PROGRESS, player) = 0; // thawing nobody
+ }
+ else if (STAT(FROZEN, player) == FROZEN_NORMAL) // OK, there is at least one teammate reviving us
+ {
+ STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
+ SetResourceAmountExplicit(player, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health)));
+
+ if(STAT(REVIVE_PROGRESS, player) >= 1)
+ {
+ Unfreeze(player, false);
+ freezetag_count_alive_players();
+
+ if(n == -1)
+ {
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_AUTO_REVIVED, autocvar_g_freezetag_frozen_maxtime);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_AUTO_REVIVED, player.netname, autocvar_g_freezetag_frozen_maxtime);
+ return true;
+ }
+
+ // EVERY team mate nearby gets a point (even if multiple!)
+ IL_EACH(reviving_players, true, {
+ GameRules_scoring_add(it, FREEZETAG_REVIVALS, +1);
+ GameRules_scoring_add(it, SCORE, +1);
+ nades_GiveBonus(it, autocvar_g_nades_bonus_score_low);
+ });
+
+ entity first = IL_FIRST(reviving_players);
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_REVIVED, first.netname);
+ Send_Notification(NOTIF_ONE, first, MSG_CENTER, CENTER_FREEZETAG_REVIVE, player.netname);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED, player.netname, first.netname);
+ }
+
+ IL_EACH(reviving_players, true, {
+ STAT(REVIVE_PROGRESS, it) = STAT(REVIVE_PROGRESS, player);
+ });
+ }
+
+ if (reviving_players)
+ IL_DELETE(reviving_players);
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, SetStartItems)
+{
+ start_items &= ~IT_UNLIMITED_AMMO;
+ //start_health = warmup_start_health = cvar("g_lms_start_health");
+ //start_armorvalue = warmup_start_armorvalue = cvar("g_lms_start_armor");
+ start_ammo_shells = warmup_start_ammo_shells = cvar("g_lms_start_ammo_shells");
+ start_ammo_nails = warmup_start_ammo_nails = cvar("g_lms_start_ammo_nails");
+ start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
+ start_ammo_cells = warmup_start_ammo_cells = cvar("g_lms_start_ammo_cells");
+ start_ammo_plasma = warmup_start_ammo_plasma = cvar("g_lms_start_ammo_plasma");
+ start_ammo_fuel = warmup_start_ammo_fuel = cvar("g_lms_start_ammo_fuel");
+}
+
+MUTATOR_HOOKFUNCTION(ft, HavocBot_ChooseRole)
+{
+ entity bot = M_ARGV(0, entity);
+
+ if (!IS_DEAD(bot))
+ {
+ if (random() < 0.5)
+ bot.havocbot_role = havocbot_role_ft_freeing;
+ else
+ bot.havocbot_role = havocbot_role_ft_offense;
+ }
+
+ // if bots spawn all at once assign them a more appropriated role after a while
+ if (time < CS(bot).jointime + 1)
+ bot.havocbot_role_timeout = time + 10 + random() * 10;
+
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+ M_ARGV(0, float) = freezetag_teams;
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, SetWeaponArena)
+{
+ // most weapons arena
+ if(M_ARGV(0, string) == "0" || M_ARGV(0, string) == "")
+ M_ARGV(0, string) = "most";
+}
+
+MUTATOR_HOOKFUNCTION(ft, FragCenterMessage)
+{
+ entity frag_attacker = M_ARGV(0, entity);
+ entity frag_target = M_ARGV(1, entity);
+ //float frag_deathtype = M_ARGV(2, float);
+ int kill_count_to_attacker = M_ARGV(3, int);
+ int kill_count_to_target = M_ARGV(4, int);
+
+ if(STAT(FROZEN, frag_target) == FROZEN_NORMAL)
+ return; // target was already frozen, so this is just pushing them off the cliff
+
+ Send_Notification(NOTIF_ONE, frag_attacker, MSG_CHOICE, CHOICE_FRAG_FREEZE, frag_target.netname, kill_count_to_attacker, (IS_BOT_CLIENT(frag_target) ? -1 : CS(frag_target).ping));
+ Send_Notification(NOTIF_ONE, frag_target, MSG_CHOICE, CHOICE_FRAGGED_FREEZE, frag_attacker.netname, kill_count_to_target,
+ GetResourceAmount(frag_attacker, RESOURCE_HEALTH), GetResourceAmount(frag_attacker, RESOURCE_ARMOR), (IS_BOT_CLIENT(frag_attacker) ? -1 : CS(frag_attacker).ping));
+
+ return true;
+}
+
+void freezetag_Initialize()
+{
+ freezetag_teams = autocvar_g_freezetag_teams_override;
+ if(freezetag_teams < 2)
+ freezetag_teams = cvar("g_freezetag_teams"); // read the cvar directly as it gets written earlier in the same frame
+
+ freezetag_teams = BITS(bound(2, freezetag_teams, 4));
+ GameRules_scoring(freezetag_teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, {
+ field(SP_FREEZETAG_REVIVALS, "revivals", 0);
+ });
+
+ round_handler_Spawn(freezetag_CheckTeams, freezetag_CheckWinner, func_null);
+ round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
+
+ EliminatedPlayers_Init(freezetag_isEliminated);
+}
--- /dev/null
+#pragma once
+
+#include <common/mutators/base.qh>
+int autocvar_g_freezetag_point_limit;
+int autocvar_g_freezetag_point_leadlimit;
+bool autocvar_g_freezetag_team_spawns;
+void freezetag_Initialize();
+
+REGISTER_MUTATOR(ft, false)
+{
+ MUTATOR_STATIC();
+ MUTATOR_ONADD
+ {
+ GameRules_teams(true);
+ GameRules_spawning_teams(autocvar_g_freezetag_team_spawns);
+ GameRules_limit_score(autocvar_g_freezetag_point_limit);
+ GameRules_limit_lead(autocvar_g_freezetag_point_leadlimit);
+
+ freezetag_Initialize();
+ }
+ return 0;
+}
+
+.float freezetag_frozen_time;
+.float freezetag_frozen_timeout;
+const float ICE_MAX_ALPHA = 1;
+const float ICE_MIN_ALPHA = 0.1;
+float freezetag_teams;
+
+float autocvar_g_freezetag_revive_extra_size;
+float autocvar_g_freezetag_revive_speed;
+bool autocvar_g_freezetag_revive_nade;
+float autocvar_g_freezetag_revive_nade_health;
// generated file; do not modify
-#include <common/gamemodes/gamemode/invasion/invasion.qc>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/invasion/sv_invasion.qc>
+#endif
// generated file; do not modify
-#include <common/gamemodes/gamemode/invasion/invasion.qh>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/invasion/sv_invasion.qh>
+#endif
+++ /dev/null
-#include "invasion.qh"
-
-// TODO: sv_invasion
-#ifdef SVQC
-#include <common/monsters/sv_spawn.qh>
-#include <common/monsters/sv_spawner.qh>
-#include <common/monsters/sv_monsters.qh>
-
-#include <server/teamplay.qh>
-
-IntrusiveList g_invasion_roundends;
-IntrusiveList g_invasion_waves;
-IntrusiveList g_invasion_spawns;
-STATIC_INIT(g_invasion)
-{
- g_invasion_roundends = IL_NEW();
- g_invasion_waves = IL_NEW();
- g_invasion_spawns = IL_NEW();
-}
-
-float autocvar_g_invasion_round_timelimit;
-float autocvar_g_invasion_spawnpoint_spawn_delay;
-float autocvar_g_invasion_warmup;
-int autocvar_g_invasion_monster_count;
-bool autocvar_g_invasion_zombies_only;
-float autocvar_g_invasion_spawn_delay;
-
-bool victent_present;
-.bool inv_endreached;
-
-bool inv_warning_shown; // spammy
-
-void target_invasion_roundend_use(entity this, entity actor, entity trigger)
-{
- if(!IS_PLAYER(actor)) { return; }
-
- actor.inv_endreached = true;
-
- int plnum = 0;
- int realplnum = 0;
- // let's not count bots
- FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
- ++realplnum;
- if(it.inv_endreached)
- ++plnum;
- });
- if(plnum < ceil(realplnum * min(1, this.count))) // 70% of players
- return;
-
- this.winning = true;
-}
-
-spawnfunc(target_invasion_roundend)
-{
- if(!g_invasion) { delete(this); return; }
-
- victent_present = true; // a victory entity is present, we don't need to rely on monster count TODO: merge this with the intrusive list (can check empty)
-
- if(!this.count) { this.count = 0.7; } // require at least 70% of the players to reach the end before triggering victory
-
- this.use = target_invasion_roundend_use;
-
- IL_PUSH(g_invasion_roundends, this);
-}
-
-spawnfunc(invasion_wave)
-{
- if(!g_invasion) { delete(this); return; }
-
- IL_PUSH(g_invasion_waves, this);
-}
-
-spawnfunc(invasion_spawnpoint)
-{
- if(!g_invasion) { delete(this); return; }
-
- this.classname = "invasion_spawnpoint";
- IL_PUSH(g_invasion_spawns, this);
-}
-
-void ClearWinners();
-
-// Invasion stage mode winning condition: If the attackers triggered a round end (by fulfilling all objectives)
-// they win.
-int WinningCondition_Invasion()
-{
- WinningConditionHelper(NULL); // set worldstatus
-
- int status = WINNING_NO;
-
- if(autocvar_g_invasion_type == INV_TYPE_STAGE)
- {
- SetWinners(inv_endreached, true);
-
- int found = 0;
- IL_EACH(g_invasion_roundends, true,
- {
- ++found;
- if(it.winning)
- {
- bprint("Invasion: round completed.\n");
- // winners already set (TODO: teamplay support)
-
- status = WINNING_YES;
- break;
- }
- });
-
- if(!found)
- status = WINNING_YES; // just end it? TODO: should warn mapper!
- }
- else if(autocvar_g_invasion_type == INV_TYPE_HUNT)
- {
- ClearWinners();
-
- int found = 0; // NOTE: this ends the round if no monsters are placed
- IL_EACH(g_monsters, !(it.spawnflags & MONSTERFLAG_RESPAWNED),
- {
- ++found;
- });
-
- if(found <= 0)
- {
- FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
- {
- it.winning = true;
- });
- status = WINNING_YES;
- }
- }
-
- return status;
-}
-
-Monster invasion_PickMonster(int supermonster_count)
-{
- RandomSelection_Init();
-
- FOREACH(Monsters, it != MON_Null,
- {
- if((it.spawnflags & MON_FLAG_HIDDEN) || (it.spawnflags & MONSTER_TYPE_PASSIVE) || (it.spawnflags & MONSTER_TYPE_FLY) || (it.spawnflags & MONSTER_TYPE_SWIM) ||
- (it.spawnflags & MONSTER_SIZE_QUAKE) || ((it.spawnflags & MON_FLAG_SUPERMONSTER) && supermonster_count >= 1))
- continue;
- if(autocvar_g_invasion_zombies_only && !(it.spawnflags & MONSTER_TYPE_UNDEAD))
- continue;
- RandomSelection_AddEnt(it, 1, 1);
- });
-
- return RandomSelection_chosen_ent;
-}
-
-entity invasion_PickSpawn()
-{
- RandomSelection_Init();
-
- IL_EACH(g_invasion_spawns, true,
- {
- RandomSelection_AddEnt(it, 1, ((time < it.spawnshieldtime) ? 0.2 : 1)); // give recently used spawnpoints a very low rating
- it.spawnshieldtime = time + autocvar_g_invasion_spawnpoint_spawn_delay;
- });
-
- return RandomSelection_chosen_ent;
-}
-
-entity invasion_GetWaveEntity(int wavenum)
-{
- IL_EACH(g_invasion_waves, it.cnt == wavenum,
- {
- return it; // found one
- });
-
- // if no specific one is found, find the last existing wave ent
- entity best = NULL;
- IL_EACH(g_invasion_waves, it.cnt <= wavenum,
- {
- if(!best || it.cnt > best.cnt)
- best = it;
- });
-
- return best;
-}
-
-void invasion_SpawnChosenMonster(Monster mon)
-{
- entity monster;
- entity spawn_point = invasion_PickSpawn();
- entity wave_ent = invasion_GetWaveEntity(inv_roundcnt);
-
- string tospawn = "";
- if(wave_ent && wave_ent.spawnmob && wave_ent.spawnmob != "")
- {
- RandomSelection_Init();
- FOREACH_WORD(wave_ent.spawnmob, true,
- {
- RandomSelection_AddString(it, 1, 1);
- });
-
- tospawn = RandomSelection_chosen_string;
- }
-
- if(spawn_point == NULL)
- {
- if(!inv_warning_shown)
- {
- inv_warning_shown = true;
- LOG_TRACE("Warning: couldn't find any invasion_spawnpoint spawnpoints, attempting to spawn monsters in random locations");
- }
- entity e = spawn();
- setsize(e, mon.m_mins, mon.m_maxs);
-
- if(MoveToRandomMapLocation(e, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
- monster = spawnmonster(e, tospawn, mon.monsterid, NULL, NULL, e.origin, false, false, 2);
- else
- {
- delete(e);
- return;
- }
- }
- else // if spawnmob field falls through (unset), fallback to mon (relying on spawnmonster for that behaviour)
- monster = spawnmonster(spawn(), ((spawn_point.spawnmob && spawn_point.spawnmob != "") ? spawn_point.spawnmob : tospawn), mon.monsterid, spawn_point, spawn_point, spawn_point.origin, false, false, 2);
-
- if(!monster)
- return;
-
- monster.spawnshieldtime = time;
-
- if(spawn_point)
- {
- if(spawn_point.target_range)
- monster.target_range = spawn_point.target_range;
- monster.target2 = spawn_point.target2;
- }
-
- if(teamplay)
- {
- if(spawn_point && spawn_point.team && inv_monsters_perteam[spawn_point.team] > 0)
- monster.team = spawn_point.team;
- else
- {
- RandomSelection_Init();
- if(inv_monsters_perteam[NUM_TEAM_1] > 0) RandomSelection_AddFloat(NUM_TEAM_1, 1, 1);
- if(inv_monsters_perteam[NUM_TEAM_2] > 0) RandomSelection_AddFloat(NUM_TEAM_2, 1, 1);
- if(invasion_teams >= 3) if(inv_monsters_perteam[NUM_TEAM_3] > 0) { RandomSelection_AddFloat(NUM_TEAM_3, 1, 1); }
- if(invasion_teams >= 4) if(inv_monsters_perteam[NUM_TEAM_4] > 0) { RandomSelection_AddFloat(NUM_TEAM_4, 1, 1); }
-
- monster.team = RandomSelection_chosen_float;
- }
-
- monster_setupcolors(monster);
-
- if(monster.sprite)
- {
- WaypointSprite_UpdateTeamRadar(monster.sprite, RADARICON_DANGER, ((monster.team) ? Team_ColorRGB(monster.team) : '1 0 0'));
-
- monster.sprite.team = 0;
- monster.sprite.SendFlags |= 1;
- }
- }
-
- if(monster.monster_attack)
- IL_REMOVE(g_monster_targets, monster);
- monster.monster_attack = false; // it's the player's job to kill all the monsters
-
- if(inv_roundcnt >= inv_maxrounds)
- monster.spawnflags |= MONSTERFLAG_MINIBOSS; // last round spawns minibosses
-}
-
-void invasion_SpawnMonsters(int supermonster_count)
-{
- Monster chosen_monster = invasion_PickMonster(supermonster_count);
-
- invasion_SpawnChosenMonster(chosen_monster);
-}
-
-bool Invasion_CheckWinner()
-{
- if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
- {
- IL_EACH(g_monsters, true,
- {
- Monster_Remove(it);
- });
- IL_CLEAR(g_monsters);
-
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
- round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
- return 1;
- }
-
- float total_alive_monsters = 0, supermonster_count = 0, red_alive = 0, blue_alive = 0, yellow_alive = 0, pink_alive = 0;
-
- IL_EACH(g_monsters, GetResourceAmount(it, RESOURCE_HEALTH) > 0,
- {
- if((get_monsterinfo(it.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
- ++supermonster_count;
- ++total_alive_monsters;
-
- if(teamplay)
- switch(it.team)
- {
- case NUM_TEAM_1: ++red_alive; break;
- case NUM_TEAM_2: ++blue_alive; break;
- case NUM_TEAM_3: ++yellow_alive; break;
- case NUM_TEAM_4: ++pink_alive; break;
- }
- });
-
- if((total_alive_monsters + inv_numkilled) < inv_maxspawned && inv_maxcurrent < inv_maxspawned)
- {
- if(time >= inv_lastcheck)
- {
- invasion_SpawnMonsters(supermonster_count);
- inv_lastcheck = time + autocvar_g_invasion_spawn_delay;
- }
-
- return 0;
- }
-
- if(inv_numspawned < 1)
- return 0; // nothing has spawned yet
-
- if(teamplay)
- {
- if(((red_alive > 0) + (blue_alive > 0) + (yellow_alive > 0) + (pink_alive > 0)) > 1)
- return 0;
- }
- else if(inv_numkilled < inv_maxspawned)
- return 0;
-
- entity winner = NULL;
- float winning_score = 0, winner_team = 0;
-
-
- if(teamplay)
- {
- if(red_alive > 0) { winner_team = NUM_TEAM_1; }
- if(blue_alive > 0)
- if(winner_team) { winner_team = 0; }
- else { winner_team = NUM_TEAM_2; }
- if(yellow_alive > 0)
- if(winner_team) { winner_team = 0; }
- else { winner_team = NUM_TEAM_3; }
- if(pink_alive > 0)
- if(winner_team) { winner_team = 0; }
- else { winner_team = NUM_TEAM_4; }
- }
- else
- {
- FOREACH_CLIENT(IS_PLAYER(it), {
- float cs = GameRules_scoring_add(it, KILLS, 0);
- if(cs > winning_score)
- {
- winning_score = cs;
- winner = it;
- }
- });
- }
-
- IL_EACH(g_monsters, true,
- {
- Monster_Remove(it);
- });
- IL_CLEAR(g_monsters);
-
- if(teamplay)
- {
- if(winner_team)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
- }
- }
- else if(winner)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, winner.netname);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_PLAYER_WIN, winner.netname);
- }
-
- round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
-
- return 1;
-}
-
-bool Invasion_CheckPlayers()
-{
- return true;
-}
-
-void Invasion_RoundStart()
-{
- int numplayers = 0;
- FOREACH_CLIENT(IS_PLAYER(it), {
- it.player_blocked = false;
- ++numplayers;
- });
-
- if(inv_roundcnt < inv_maxrounds)
- inv_roundcnt += 1; // a limiter to stop crazy counts
-
- inv_monsterskill = inv_roundcnt + max(1, numplayers * 0.3);
-
- inv_maxcurrent = 0;
- inv_numspawned = 0;
- inv_numkilled = 0;
-
- inv_maxspawned = rint(max(autocvar_g_invasion_monster_count, autocvar_g_invasion_monster_count * (inv_roundcnt * 0.5)));
-
- if(teamplay)
- {
- DistributeEvenly_Init(inv_maxspawned, invasion_teams);
- inv_monsters_perteam[NUM_TEAM_1] = DistributeEvenly_Get(1);
- inv_monsters_perteam[NUM_TEAM_2] = DistributeEvenly_Get(1);
- if(invasion_teams >= 3) inv_monsters_perteam[NUM_TEAM_3] = DistributeEvenly_Get(1);
- if(invasion_teams >= 4) inv_monsters_perteam[NUM_TEAM_4] = DistributeEvenly_Get(1);
- }
-}
-
-MUTATOR_HOOKFUNCTION(inv, MonsterDies)
-{
- entity frag_target = M_ARGV(0, entity);
- entity frag_attacker = M_ARGV(1, entity);
-
- if(!(frag_target.spawnflags & MONSTERFLAG_RESPAWNED))
- {
- if(autocvar_g_invasion_type == INV_TYPE_ROUND)
- {
- inv_numkilled += 1;
- inv_maxcurrent -= 1;
- }
- if(teamplay) { inv_monsters_perteam[frag_target.team] -= 1; }
-
- if(IS_PLAYER(frag_attacker))
- if(SAME_TEAM(frag_attacker, frag_target)) // in non-teamplay modes, same team = same player, so this works
- GameRules_scoring_add(frag_attacker, KILLS, -1);
- else
- {
- GameRules_scoring_add(frag_attacker, KILLS, +1);
- if(teamplay)
- TeamScore_AddToTeam(frag_attacker.team, ST_INV_KILLS, +1);
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(inv, MonsterSpawn)
-{
- entity mon = M_ARGV(0, entity);
- mon.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
-
- if(autocvar_g_invasion_type == INV_TYPE_HUNT)
- return false; // allowed
-
- if(!(mon.spawnflags & MONSTERFLAG_SPAWNED))
- return true;
-
- if(!(mon.spawnflags & MONSTERFLAG_RESPAWNED))
- {
- inv_numspawned += 1;
- inv_maxcurrent += 1;
- }
-
- mon.monster_skill = inv_monsterskill;
-
- if((get_monsterinfo(mon.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_INVASION_SUPERMONSTER, mon.monster_name);
-}
-
-MUTATOR_HOOKFUNCTION(inv, SV_StartFrame)
-{
- if(autocvar_g_invasion_type != INV_TYPE_ROUND)
- return; // uses map spawned monsters
-
- monsters_total = inv_maxspawned; // TODO: make sure numspawned never exceeds maxspawned
- monsters_killed = inv_numkilled;
-}
-
-MUTATOR_HOOKFUNCTION(inv, PlayerRegen)
-{
- // no regeneration in invasion, regardless of the game type
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(inv, PlayerSpawn)
-{
- entity player = M_ARGV(0, entity);
-
- if(player.bot_attack)
- IL_REMOVE(g_bot_targets, player);
- player.bot_attack = false;
-}
-
-MUTATOR_HOOKFUNCTION(inv, Damage_Calculate)
-{
- entity frag_attacker = M_ARGV(1, entity);
- entity frag_target = M_ARGV(2, entity);
- float frag_damage = M_ARGV(4, float);
- vector frag_force = M_ARGV(6, vector);
-
- if(IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target) && frag_attacker != frag_target)
- {
- frag_damage = 0;
- frag_force = '0 0 0';
-
- M_ARGV(4, float) = frag_damage;
- M_ARGV(6, vector) = frag_force;
- }
-}
-
-MUTATOR_HOOKFUNCTION(inv, BotShouldAttack)
-{
- entity targ = M_ARGV(1, entity);
-
- if(!IS_MONSTER(targ))
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(inv, SetStartItems)
-{
- if(autocvar_g_invasion_type == INV_TYPE_ROUND)
- {
- start_health = 200;
- start_armorvalue = 200;
- }
-}
-
-MUTATOR_HOOKFUNCTION(inv, AccuracyTargetValid)
-{
- entity frag_target = M_ARGV(1, entity);
-
- if(IS_MONSTER(frag_target))
- return MUT_ACCADD_INVALID;
- return MUT_ACCADD_INDIFFERENT;
-}
-
-MUTATOR_HOOKFUNCTION(inv, AllowMobSpawning)
-{
- // monster spawning disabled during an invasion
- M_ARGV(1, string) = "You cannot spawn monsters during an invasion!";
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(inv, CheckRules_World)
-{
- if(autocvar_g_invasion_type == INV_TYPE_ROUND)
- return false;
-
- M_ARGV(0, float) = WinningCondition_Invasion();
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(inv, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
-{
- M_ARGV(0, float) = invasion_teams;
-}
-
-MUTATOR_HOOKFUNCTION(inv, AllowMobButcher)
-{
- M_ARGV(0, string) = "This command does not work during an invasion!";
- return true;
-}
-
-void invasion_ScoreRules(int inv_teams)
-{
- if(inv_teams) { CheckAllowedTeams(NULL); }
- GameRules_score_enabled(false);
- GameRules_scoring(inv_teams, 0, 0, {
- if (inv_teams) {
- field_team(ST_INV_KILLS, "frags", SFL_SORT_PRIO_PRIMARY);
- }
- field(SP_KILLS, "frags", ((inv_teams) ? SFL_SORT_PRIO_SECONDARY : SFL_SORT_PRIO_PRIMARY));
- });
-}
-
-void invasion_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
-{
- if(autocvar_g_invasion_type == INV_TYPE_HUNT || autocvar_g_invasion_type == INV_TYPE_STAGE)
- cvar_set("fraglimit", "0");
-
- if(autocvar_g_invasion_teams)
- {
- invasion_teams = BITS(bound(2, autocvar_g_invasion_teams, 4));
- }
- else
- invasion_teams = 0;
-
- independent_players = 1; // to disable extra useless scores
-
- invasion_ScoreRules(invasion_teams);
-
- independent_players = 0;
-
- if(autocvar_g_invasion_type == INV_TYPE_ROUND)
- {
- round_handler_Spawn(Invasion_CheckPlayers, Invasion_CheckWinner, Invasion_RoundStart);
- round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
-
- inv_roundcnt = 0;
- inv_maxrounds = 15; // 15?
- }
-}
-
-void invasion_Initialize()
-{
- InitializeEntity(NULL, invasion_DelayedInit, INITPRIO_GAMETYPE);
-}
-#endif
+++ /dev/null
-#pragma once
-
-#ifdef SVQC
-#include <common/mutators/base.qh>
-#define autocvar_g_invasion_point_limit cvar("g_invasion_point_limit")
-int autocvar_g_invasion_teams;
-int autocvar_g_invasion_type;
-bool autocvar_g_invasion_team_spawns;
-bool g_invasion;
-void invasion_Initialize();
-
-REGISTER_MUTATOR(inv, false)
-{
- MUTATOR_STATIC();
- MUTATOR_ONADD
- {
- if (autocvar_g_invasion_teams >= 2) {
- GameRules_teams(true);
- GameRules_spawning_teams(autocvar_g_invasion_team_spawns);
- }
- GameRules_limit_score(autocvar_g_invasion_point_limit);
-
- g_invasion = true;
- cvar_settemp("g_monsters", "1");
- invasion_Initialize();
- }
- return 0;
-}
-
-float inv_numspawned;
-float inv_maxspawned;
-float inv_roundcnt;
-float inv_maxrounds;
-float inv_numkilled;
-float inv_lastcheck;
-float inv_maxcurrent;
-
-float invasion_teams;
-float inv_monsters_perteam[17];
-
-float inv_monsterskill;
-
-const float ST_INV_KILLS = 1;
-
-const int INV_TYPE_ROUND = 0; // round-based waves of enemies
-const int INV_TYPE_HUNT = 1; // clear the map of placed enemies
-const int INV_TYPE_STAGE = 2; // reach the end of the level
-#endif
--- /dev/null
+#include "sv_invasion.qh"
+
+#include <common/monsters/sv_spawn.qh>
+#include <common/monsters/sv_spawner.qh>
+#include <common/monsters/sv_monsters.qh>
+
+#include <server/teamplay.qh>
+
+IntrusiveList g_invasion_roundends;
+IntrusiveList g_invasion_waves;
+IntrusiveList g_invasion_spawns;
+STATIC_INIT(g_invasion)
+{
+ g_invasion_roundends = IL_NEW();
+ g_invasion_waves = IL_NEW();
+ g_invasion_spawns = IL_NEW();
+}
+
+float autocvar_g_invasion_round_timelimit;
+float autocvar_g_invasion_spawnpoint_spawn_delay;
+float autocvar_g_invasion_warmup;
+int autocvar_g_invasion_monster_count;
+bool autocvar_g_invasion_zombies_only;
+float autocvar_g_invasion_spawn_delay;
+
+bool victent_present;
+.bool inv_endreached;
+
+bool inv_warning_shown; // spammy
+
+void target_invasion_roundend_use(entity this, entity actor, entity trigger)
+{
+ if(!IS_PLAYER(actor)) { return; }
+
+ actor.inv_endreached = true;
+
+ int plnum = 0;
+ int realplnum = 0;
+ // let's not count bots
+ FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
+ ++realplnum;
+ if(it.inv_endreached)
+ ++plnum;
+ });
+ if(plnum < ceil(realplnum * min(1, this.count))) // 70% of players
+ return;
+
+ this.winning = true;
+}
+
+spawnfunc(target_invasion_roundend)
+{
+ if(!g_invasion) { delete(this); return; }
+
+ victent_present = true; // a victory entity is present, we don't need to rely on monster count TODO: merge this with the intrusive list (can check empty)
+
+ if(!this.count) { this.count = 0.7; } // require at least 70% of the players to reach the end before triggering victory
+
+ this.use = target_invasion_roundend_use;
+
+ IL_PUSH(g_invasion_roundends, this);
+}
+
+spawnfunc(invasion_wave)
+{
+ if(!g_invasion) { delete(this); return; }
+
+ IL_PUSH(g_invasion_waves, this);
+}
+
+spawnfunc(invasion_spawnpoint)
+{
+ if(!g_invasion) { delete(this); return; }
+
+ this.classname = "invasion_spawnpoint";
+ IL_PUSH(g_invasion_spawns, this);
+}
+
+void ClearWinners();
+
+// Invasion stage mode winning condition: If the attackers triggered a round end (by fulfilling all objectives)
+// they win.
+int WinningCondition_Invasion()
+{
+ WinningConditionHelper(NULL); // set worldstatus
+
+ int status = WINNING_NO;
+
+ if(autocvar_g_invasion_type == INV_TYPE_STAGE)
+ {
+ SetWinners(inv_endreached, true);
+
+ int found = 0;
+ IL_EACH(g_invasion_roundends, true,
+ {
+ ++found;
+ if(it.winning)
+ {
+ bprint("Invasion: round completed.\n");
+ // winners already set (TODO: teamplay support)
+
+ status = WINNING_YES;
+ break;
+ }
+ });
+
+ if(!found)
+ status = WINNING_YES; // just end it? TODO: should warn mapper!
+ }
+ else if(autocvar_g_invasion_type == INV_TYPE_HUNT)
+ {
+ ClearWinners();
+
+ int found = 0; // NOTE: this ends the round if no monsters are placed
+ IL_EACH(g_monsters, !(it.spawnflags & MONSTERFLAG_RESPAWNED),
+ {
+ ++found;
+ });
+
+ if(found <= 0)
+ {
+ FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+ {
+ it.winning = true;
+ });
+ status = WINNING_YES;
+ }
+ }
+
+ return status;
+}
+
+Monster invasion_PickMonster(int supermonster_count)
+{
+ RandomSelection_Init();
+
+ FOREACH(Monsters, it != MON_Null,
+ {
+ if((it.spawnflags & MON_FLAG_HIDDEN) || (it.spawnflags & MONSTER_TYPE_PASSIVE) || (it.spawnflags & MONSTER_TYPE_FLY) || (it.spawnflags & MONSTER_TYPE_SWIM) ||
+ (it.spawnflags & MONSTER_SIZE_QUAKE) || ((it.spawnflags & MON_FLAG_SUPERMONSTER) && supermonster_count >= 1))
+ continue;
+ if(autocvar_g_invasion_zombies_only && !(it.spawnflags & MONSTER_TYPE_UNDEAD))
+ continue;
+ RandomSelection_AddEnt(it, 1, 1);
+ });
+
+ return RandomSelection_chosen_ent;
+}
+
+entity invasion_PickSpawn()
+{
+ RandomSelection_Init();
+
+ IL_EACH(g_invasion_spawns, true,
+ {
+ RandomSelection_AddEnt(it, 1, ((time < it.spawnshieldtime) ? 0.2 : 1)); // give recently used spawnpoints a very low rating
+ it.spawnshieldtime = time + autocvar_g_invasion_spawnpoint_spawn_delay;
+ });
+
+ return RandomSelection_chosen_ent;
+}
+
+entity invasion_GetWaveEntity(int wavenum)
+{
+ IL_EACH(g_invasion_waves, it.cnt == wavenum,
+ {
+ return it; // found one
+ });
+
+ // if no specific one is found, find the last existing wave ent
+ entity best = NULL;
+ IL_EACH(g_invasion_waves, it.cnt <= wavenum,
+ {
+ if(!best || it.cnt > best.cnt)
+ best = it;
+ });
+
+ return best;
+}
+
+void invasion_SpawnChosenMonster(Monster mon)
+{
+ entity monster;
+ entity spawn_point = invasion_PickSpawn();
+ entity wave_ent = invasion_GetWaveEntity(inv_roundcnt);
+
+ string tospawn = "";
+ if(wave_ent && wave_ent.spawnmob && wave_ent.spawnmob != "")
+ {
+ RandomSelection_Init();
+ FOREACH_WORD(wave_ent.spawnmob, true,
+ {
+ RandomSelection_AddString(it, 1, 1);
+ });
+
+ tospawn = RandomSelection_chosen_string;
+ }
+
+ if(spawn_point == NULL)
+ {
+ if(!inv_warning_shown)
+ {
+ inv_warning_shown = true;
+ LOG_TRACE("Warning: couldn't find any invasion_spawnpoint spawnpoints, attempting to spawn monsters in random locations");
+ }
+ entity e = spawn();
+ setsize(e, mon.m_mins, mon.m_maxs);
+
+ if(MoveToRandomMapLocation(e, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
+ monster = spawnmonster(e, tospawn, mon.monsterid, NULL, NULL, e.origin, false, false, 2);
+ else
+ {
+ delete(e);
+ return;
+ }
+ }
+ else // if spawnmob field falls through (unset), fallback to mon (relying on spawnmonster for that behaviour)
+ monster = spawnmonster(spawn(), ((spawn_point.spawnmob && spawn_point.spawnmob != "") ? spawn_point.spawnmob : tospawn), mon.monsterid, spawn_point, spawn_point, spawn_point.origin, false, false, 2);
+
+ if(!monster)
+ return;
+
+ monster.spawnshieldtime = time;
+
+ if(spawn_point)
+ {
+ if(spawn_point.target_range)
+ monster.target_range = spawn_point.target_range;
+ monster.target2 = spawn_point.target2;
+ }
+
+ if(teamplay)
+ {
+ if(spawn_point && spawn_point.team && inv_monsters_perteam[spawn_point.team] > 0)
+ monster.team = spawn_point.team;
+ else
+ {
+ RandomSelection_Init();
+ if(inv_monsters_perteam[NUM_TEAM_1] > 0) RandomSelection_AddFloat(NUM_TEAM_1, 1, 1);
+ if(inv_monsters_perteam[NUM_TEAM_2] > 0) RandomSelection_AddFloat(NUM_TEAM_2, 1, 1);
+ if(invasion_teams >= 3) if(inv_monsters_perteam[NUM_TEAM_3] > 0) { RandomSelection_AddFloat(NUM_TEAM_3, 1, 1); }
+ if(invasion_teams >= 4) if(inv_monsters_perteam[NUM_TEAM_4] > 0) { RandomSelection_AddFloat(NUM_TEAM_4, 1, 1); }
+
+ monster.team = RandomSelection_chosen_float;
+ }
+
+ monster_setupcolors(monster);
+
+ if(monster.sprite)
+ {
+ WaypointSprite_UpdateTeamRadar(monster.sprite, RADARICON_DANGER, ((monster.team) ? Team_ColorRGB(monster.team) : '1 0 0'));
+
+ monster.sprite.team = 0;
+ monster.sprite.SendFlags |= 1;
+ }
+ }
+
+ if(monster.monster_attack)
+ IL_REMOVE(g_monster_targets, monster);
+ monster.monster_attack = false; // it's the player's job to kill all the monsters
+
+ if(inv_roundcnt >= inv_maxrounds)
+ monster.spawnflags |= MONSTERFLAG_MINIBOSS; // last round spawns minibosses
+}
+
+void invasion_SpawnMonsters(int supermonster_count)
+{
+ Monster chosen_monster = invasion_PickMonster(supermonster_count);
+
+ invasion_SpawnChosenMonster(chosen_monster);
+}
+
+bool Invasion_CheckWinner()
+{
+ if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
+ {
+ IL_EACH(g_monsters, true,
+ {
+ Monster_Remove(it);
+ });
+ IL_CLEAR(g_monsters);
+
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
+ round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
+ return 1;
+ }
+
+ float total_alive_monsters = 0, supermonster_count = 0, red_alive = 0, blue_alive = 0, yellow_alive = 0, pink_alive = 0;
+
+ IL_EACH(g_monsters, GetResourceAmount(it, RESOURCE_HEALTH) > 0,
+ {
+ if((get_monsterinfo(it.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
+ ++supermonster_count;
+ ++total_alive_monsters;
+
+ if(teamplay)
+ switch(it.team)
+ {
+ case NUM_TEAM_1: ++red_alive; break;
+ case NUM_TEAM_2: ++blue_alive; break;
+ case NUM_TEAM_3: ++yellow_alive; break;
+ case NUM_TEAM_4: ++pink_alive; break;
+ }
+ });
+
+ if((total_alive_monsters + inv_numkilled) < inv_maxspawned && inv_maxcurrent < inv_maxspawned)
+ {
+ if(time >= inv_lastcheck)
+ {
+ invasion_SpawnMonsters(supermonster_count);
+ inv_lastcheck = time + autocvar_g_invasion_spawn_delay;
+ }
+
+ return 0;
+ }
+
+ if(inv_numspawned < 1)
+ return 0; // nothing has spawned yet
+
+ if(teamplay)
+ {
+ if(((red_alive > 0) + (blue_alive > 0) + (yellow_alive > 0) + (pink_alive > 0)) > 1)
+ return 0;
+ }
+ else if(inv_numkilled < inv_maxspawned)
+ return 0;
+
+ entity winner = NULL;
+ float winning_score = 0, winner_team = 0;
+
+
+ if(teamplay)
+ {
+ if(red_alive > 0) { winner_team = NUM_TEAM_1; }
+ if(blue_alive > 0)
+ if(winner_team) { winner_team = 0; }
+ else { winner_team = NUM_TEAM_2; }
+ if(yellow_alive > 0)
+ if(winner_team) { winner_team = 0; }
+ else { winner_team = NUM_TEAM_3; }
+ if(pink_alive > 0)
+ if(winner_team) { winner_team = 0; }
+ else { winner_team = NUM_TEAM_4; }
+ }
+ else
+ {
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ float cs = GameRules_scoring_add(it, KILLS, 0);
+ if(cs > winning_score)
+ {
+ winning_score = cs;
+ winner = it;
+ }
+ });
+ }
+
+ IL_EACH(g_monsters, true,
+ {
+ Monster_Remove(it);
+ });
+ IL_CLEAR(g_monsters);
+
+ if(teamplay)
+ {
+ if(winner_team)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
+ }
+ }
+ else if(winner)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, winner.netname);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_PLAYER_WIN, winner.netname);
+ }
+
+ round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
+
+ return 1;
+}
+
+bool Invasion_CheckPlayers()
+{
+ return true;
+}
+
+void Invasion_RoundStart()
+{
+ int numplayers = 0;
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ it.player_blocked = false;
+ ++numplayers;
+ });
+
+ if(inv_roundcnt < inv_maxrounds)
+ inv_roundcnt += 1; // a limiter to stop crazy counts
+
+ inv_monsterskill = inv_roundcnt + max(1, numplayers * 0.3);
+
+ inv_maxcurrent = 0;
+ inv_numspawned = 0;
+ inv_numkilled = 0;
+
+ inv_maxspawned = rint(max(autocvar_g_invasion_monster_count, autocvar_g_invasion_monster_count * (inv_roundcnt * 0.5)));
+
+ if(teamplay)
+ {
+ DistributeEvenly_Init(inv_maxspawned, invasion_teams);
+ inv_monsters_perteam[NUM_TEAM_1] = DistributeEvenly_Get(1);
+ inv_monsters_perteam[NUM_TEAM_2] = DistributeEvenly_Get(1);
+ if(invasion_teams >= 3) inv_monsters_perteam[NUM_TEAM_3] = DistributeEvenly_Get(1);
+ if(invasion_teams >= 4) inv_monsters_perteam[NUM_TEAM_4] = DistributeEvenly_Get(1);
+ }
+}
+
+MUTATOR_HOOKFUNCTION(inv, MonsterDies)
+{
+ entity frag_target = M_ARGV(0, entity);
+ entity frag_attacker = M_ARGV(1, entity);
+
+ if(!(frag_target.spawnflags & MONSTERFLAG_RESPAWNED))
+ {
+ if(autocvar_g_invasion_type == INV_TYPE_ROUND)
+ {
+ inv_numkilled += 1;
+ inv_maxcurrent -= 1;
+ }
+ if(teamplay) { inv_monsters_perteam[frag_target.team] -= 1; }
+
+ if(IS_PLAYER(frag_attacker))
+ if(SAME_TEAM(frag_attacker, frag_target)) // in non-teamplay modes, same team = same player, so this works
+ GameRules_scoring_add(frag_attacker, KILLS, -1);
+ else
+ {
+ GameRules_scoring_add(frag_attacker, KILLS, +1);
+ if(teamplay)
+ TeamScore_AddToTeam(frag_attacker.team, ST_INV_KILLS, +1);
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(inv, MonsterSpawn)
+{
+ entity mon = M_ARGV(0, entity);
+ mon.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
+
+ if(autocvar_g_invasion_type == INV_TYPE_HUNT)
+ return false; // allowed
+
+ if(!(mon.spawnflags & MONSTERFLAG_SPAWNED))
+ return true;
+
+ if(!(mon.spawnflags & MONSTERFLAG_RESPAWNED))
+ {
+ inv_numspawned += 1;
+ inv_maxcurrent += 1;
+ }
+
+ mon.monster_skill = inv_monsterskill;
+
+ if((get_monsterinfo(mon.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_INVASION_SUPERMONSTER, mon.monster_name);
+}
+
+MUTATOR_HOOKFUNCTION(inv, SV_StartFrame)
+{
+ if(autocvar_g_invasion_type != INV_TYPE_ROUND)
+ return; // uses map spawned monsters
+
+ monsters_total = inv_maxspawned; // TODO: make sure numspawned never exceeds maxspawned
+ monsters_killed = inv_numkilled;
+}
+
+MUTATOR_HOOKFUNCTION(inv, PlayerRegen)
+{
+ // no regeneration in invasion, regardless of the game type
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(inv, PlayerSpawn)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(player.bot_attack)
+ IL_REMOVE(g_bot_targets, player);
+ player.bot_attack = false;
+}
+
+MUTATOR_HOOKFUNCTION(inv, Damage_Calculate)
+{
+ entity frag_attacker = M_ARGV(1, entity);
+ entity frag_target = M_ARGV(2, entity);
+ float frag_damage = M_ARGV(4, float);
+ vector frag_force = M_ARGV(6, vector);
+
+ if(IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target) && frag_attacker != frag_target)
+ {
+ frag_damage = 0;
+ frag_force = '0 0 0';
+
+ M_ARGV(4, float) = frag_damage;
+ M_ARGV(6, vector) = frag_force;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(inv, BotShouldAttack)
+{
+ entity targ = M_ARGV(1, entity);
+
+ if(!IS_MONSTER(targ))
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(inv, SetStartItems)
+{
+ if(autocvar_g_invasion_type == INV_TYPE_ROUND)
+ {
+ start_health = 200;
+ start_armorvalue = 200;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(inv, AccuracyTargetValid)
+{
+ entity frag_target = M_ARGV(1, entity);
+
+ if(IS_MONSTER(frag_target))
+ return MUT_ACCADD_INVALID;
+ return MUT_ACCADD_INDIFFERENT;
+}
+
+MUTATOR_HOOKFUNCTION(inv, AllowMobSpawning)
+{
+ // monster spawning disabled during an invasion
+ M_ARGV(1, string) = "You cannot spawn monsters during an invasion!";
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(inv, CheckRules_World)
+{
+ if(autocvar_g_invasion_type == INV_TYPE_ROUND)
+ return false;
+
+ M_ARGV(0, float) = WinningCondition_Invasion();
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(inv, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+ M_ARGV(0, float) = invasion_teams;
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(inv, AllowMobButcher)
+{
+ M_ARGV(0, string) = "This command does not work during an invasion!";
+ return true;
+}
+
+void invasion_ScoreRules(int inv_teams)
+{
+ GameRules_score_enabled(false);
+ GameRules_scoring(inv_teams, 0, 0, {
+ if (inv_teams) {
+ field_team(ST_INV_KILLS, "frags", SFL_SORT_PRIO_PRIMARY);
+ }
+ field(SP_KILLS, "frags", ((inv_teams) ? SFL_SORT_PRIO_SECONDARY : SFL_SORT_PRIO_PRIMARY));
+ });
+}
+
+void invasion_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
+{
+ if(autocvar_g_invasion_type == INV_TYPE_HUNT || autocvar_g_invasion_type == INV_TYPE_STAGE)
+ cvar_set("fraglimit", "0");
+
+ if(autocvar_g_invasion_teams)
+ {
+ invasion_teams = BITS(bound(2, autocvar_g_invasion_teams, 4));
+ }
+ else
+ invasion_teams = 0;
+
+ independent_players = 1; // to disable extra useless scores
+
+ invasion_ScoreRules(invasion_teams);
+
+ independent_players = 0;
+
+ if(autocvar_g_invasion_type == INV_TYPE_ROUND)
+ {
+ round_handler_Spawn(Invasion_CheckPlayers, Invasion_CheckWinner, Invasion_RoundStart);
+ round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
+
+ inv_roundcnt = 0;
+ inv_maxrounds = 15; // 15?
+ }
+}
+
+void invasion_Initialize()
+{
+ InitializeEntity(NULL, invasion_DelayedInit, INITPRIO_GAMETYPE);
+}
--- /dev/null
+#pragma once
+
+#include <common/mutators/base.qh>
+#define autocvar_g_invasion_point_limit cvar("g_invasion_point_limit")
+int autocvar_g_invasion_teams;
+int autocvar_g_invasion_type;
+bool autocvar_g_invasion_team_spawns;
+bool g_invasion;
+void invasion_Initialize();
+
+REGISTER_MUTATOR(inv, false)
+{
+ MUTATOR_STATIC();
+ MUTATOR_ONADD
+ {
+ if (autocvar_g_invasion_teams >= 2) {
+ GameRules_teams(true);
+ GameRules_spawning_teams(autocvar_g_invasion_team_spawns);
+ }
+ GameRules_limit_score(autocvar_g_invasion_point_limit);
+
+ g_invasion = true;
+ cvar_settemp("g_monsters", "1");
+ invasion_Initialize();
+ }
+ return 0;
+}
+
+float inv_numspawned;
+float inv_maxspawned;
+float inv_roundcnt;
+float inv_maxrounds;
+float inv_numkilled;
+float inv_lastcheck;
+float inv_maxcurrent;
+
+float invasion_teams;
+float inv_monsters_perteam[17];
+
+float inv_monsterskill;
+
+const float ST_INV_KILLS = 1;
+
+const int INV_TYPE_ROUND = 0; // round-based waves of enemies
+const int INV_TYPE_HUNT = 1; // clear the map of placed enemies
+const int INV_TYPE_STAGE = 2; // reach the end of the level
// generated file; do not modify
-#include <common/gamemodes/gamemode/keepaway/keepaway.qc>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/keepaway/sv_keepaway.qc>
+#endif
// generated file; do not modify
-#include <common/gamemodes/gamemode/keepaway/keepaway.qh>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/keepaway/sv_keepaway.qh>
+#endif
+++ /dev/null
-#include "keepaway.qh"
-
-// TODO: keepaway
-#ifdef SVQC
-#include <common/effects/all.qh>
-
-.entity ballcarried;
-
-int autocvar_g_keepaway_ballcarrier_effects;
-float autocvar_g_keepaway_ballcarrier_damage;
-float autocvar_g_keepaway_ballcarrier_force;
-float autocvar_g_keepaway_ballcarrier_highspeed;
-float autocvar_g_keepaway_ballcarrier_selfdamage;
-float autocvar_g_keepaway_ballcarrier_selfforce;
-float autocvar_g_keepaway_noncarrier_damage;
-float autocvar_g_keepaway_noncarrier_force;
-float autocvar_g_keepaway_noncarrier_selfdamage;
-float autocvar_g_keepaway_noncarrier_selfforce;
-bool autocvar_g_keepaway_noncarrier_warn;
-int autocvar_g_keepaway_score_bckill;
-int autocvar_g_keepaway_score_killac;
-int autocvar_g_keepaway_score_timepoints;
-float autocvar_g_keepaway_score_timeinterval;
-float autocvar_g_keepawayball_damageforcescale;
-int autocvar_g_keepawayball_effects;
-float autocvar_g_keepawayball_respawntime;
-int autocvar_g_keepawayball_trail_color;
-
-bool ka_ballcarrier_waypointsprite_visible_for_player(entity this, entity player, entity view) // runs on waypoints which are attached to ballcarriers, updates once per frame
-{
- if(view.ballcarried)
- if(IS_SPEC(player))
- return false; // we don't want spectators of the ballcarrier to see the attached waypoint on the top of their screen
-
- // TODO: Make the ballcarrier lack a waypointsprite whenever they have the invisibility powerup
-
- return true;
-}
-
-void ka_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later
-{
- if(autocvar_sv_eventlog)
- GameLogEcho(strcat(":ka:", mode, ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
-}
-
-void ka_TouchEvent(entity this, entity toucher);
-void ka_RespawnBall(entity this) // runs whenever the ball needs to be relocated
-{
- if(game_stopped) return;
- vector oldballorigin = this.origin;
-
- if(!MoveToRandomMapLocation(this, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
- {
- entity spot = SelectSpawnPoint(this, true);
- setorigin(this, spot.origin);
- this.angles = spot.angles;
- }
-
- makevectors(this.angles);
- set_movetype(this, MOVETYPE_BOUNCE);
- this.velocity = '0 0 200';
- this.angles = '0 0 0';
- this.effects = autocvar_g_keepawayball_effects;
- settouch(this, ka_TouchEvent);
- setthink(this, ka_RespawnBall);
- this.nextthink = time + autocvar_g_keepawayball_respawntime;
- navigation_dynamicgoal_set(this);
-
- Send_Effect(EFFECT_ELECTRO_COMBO, oldballorigin, '0 0 0', 1);
- Send_Effect(EFFECT_ELECTRO_COMBO, this.origin, '0 0 0', 1);
-
- WaypointSprite_Spawn(WP_KaBall, 0, 0, this, '0 0 64', NULL, this.team, this, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER);
- WaypointSprite_Ping(this.waypointsprite_attachedforcarrier);
-
- sound(this, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
-}
-
-void ka_TimeScoring(entity this)
-{
- if(this.owner.ballcarried)
- { // add points for holding the ball after a certain amount of time
- if(autocvar_g_keepaway_score_timepoints)
- GameRules_scoring_add(this.owner, SCORE, autocvar_g_keepaway_score_timepoints);
-
- GameRules_scoring_add(this.owner, KEEPAWAY_BCTIME, (autocvar_g_keepaway_score_timeinterval / 1)); // interval is divided by 1 so that time always shows "seconds"
- this.nextthink = time + autocvar_g_keepaway_score_timeinterval;
- }
-}
-
-void ka_TouchEvent(entity this, entity toucher) // runs any time that the ball comes in contact with something
-{
- if(game_stopped) return;
- if(!this) return;
- if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
- { // The ball fell off the map, respawn it since players can't get to it
- ka_RespawnBall(this);
- return;
- }
- if(IS_DEAD(toucher)) { return; }
- if(STAT(FROZEN, toucher)) { return; }
- if (!IS_PLAYER(toucher))
- { // The ball just touched an object, most likely the world
- Send_Effect(EFFECT_BALL_SPARKS, this.origin, '0 0 0', 1);
- sound(this, CH_TRIGGER, SND_KA_TOUCH, VOL_BASE, ATTEN_NORM);
- return;
- }
- else if(this.wait > time) { return; }
-
- // attach the ball to the player
- this.owner = toucher;
- toucher.ballcarried = this;
- GameRules_scoring_vip(toucher, true);
- setattachment(this, toucher, "");
- setorigin(this, '0 0 0');
-
- // make the ball invisible/unable to do anything/set up time scoring
- this.velocity = '0 0 0';
- set_movetype(this, MOVETYPE_NONE);
- this.effects |= EF_NODRAW;
- settouch(this, func_null);
- setthink(this, ka_TimeScoring);
- this.nextthink = time + autocvar_g_keepaway_score_timeinterval;
- this.takedamage = DAMAGE_NO;
- navigation_dynamicgoal_unset(this);
-
- // apply effects to player
- toucher.glow_color = autocvar_g_keepawayball_trail_color;
- toucher.glow_trail = true;
- toucher.effects |= autocvar_g_keepaway_ballcarrier_effects;
-
- // messages and sounds
- ka_EventLog("pickup", toucher);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_KEEPAWAY_PICKUP, toucher.netname);
- Send_Notification(NOTIF_ALL_EXCEPT, toucher, MSG_CENTER, CENTER_KEEPAWAY_PICKUP, toucher.netname);
- Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_KEEPAWAY_PICKUP_SELF);
- sound(this.owner, CH_TRIGGER, SND_KA_PICKEDUP, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
-
- // scoring
- GameRules_scoring_add(toucher, KEEPAWAY_PICKUPS, 1);
-
- // waypoints
- WaypointSprite_AttachCarrier(WP_KaBallCarrier, toucher, RADARICON_FLAGCARRIER);
- toucher.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = ka_ballcarrier_waypointsprite_visible_for_player;
- WaypointSprite_UpdateRule(toucher.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
- WaypointSprite_Ping(toucher.waypointsprite_attachedforcarrier);
- WaypointSprite_Kill(this.waypointsprite_attachedforcarrier);
-}
-
-void ka_DropEvent(entity plyr) // runs any time that a player is supposed to lose the ball
-{
- entity ball;
- ball = plyr.ballcarried;
-
- if(!ball) { return; }
-
- // reset the ball
- setattachment(ball, NULL, "");
- set_movetype(ball, MOVETYPE_BOUNCE);
- ball.wait = time + 1;
- settouch(ball, ka_TouchEvent);
- setthink(ball, ka_RespawnBall);
- ball.nextthink = time + autocvar_g_keepawayball_respawntime;
- ball.takedamage = DAMAGE_YES;
- ball.effects &= ~EF_NODRAW;
- setorigin(ball, plyr.origin + '0 0 10');
- ball.velocity = '0 0 200' + '0 100 0'*crandom() + '100 0 0'*crandom();
- entity e = ball.owner; ball.owner = NULL;
- e.ballcarried = NULL;
- GameRules_scoring_vip(e, false);
- navigation_dynamicgoal_set(ball);
-
- // reset the player effects
- plyr.glow_trail = false;
- plyr.effects &= ~autocvar_g_keepaway_ballcarrier_effects;
-
- // messages and sounds
- ka_EventLog("dropped", plyr);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_KEEPAWAY_DROPPED, plyr.netname);
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEEPAWAY_DROPPED, plyr.netname);
- sound(NULL, CH_TRIGGER, SND_KA_DROPPED, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
-
- // scoring
- // GameRules_scoring_add(plyr, KEEPAWAY_DROPS, 1); Not anymore, this is 100% the same as pickups and is useless.
-
- // waypoints
- WaypointSprite_Spawn(WP_KaBall, 0, 0, ball, '0 0 64', NULL, ball.team, ball, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER);
- WaypointSprite_UpdateRule(ball.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
- WaypointSprite_Ping(ball.waypointsprite_attachedforcarrier);
- WaypointSprite_Kill(plyr.waypointsprite_attachedforcarrier);
-}
-
-/** used to clear the ballcarrier whenever the match switches from warmup to normal */
-void ka_Reset(entity this)
-{
- if((this.owner) && (IS_PLAYER(this.owner)))
- ka_DropEvent(this.owner);
-
- if(time < game_starttime)
- {
- setthink(this, ka_RespawnBall);
- settouch(this, func_null);
- this.nextthink = game_starttime;
- }
- else
- ka_RespawnBall(this);
-}
-
-
-// ================
-// Bot player logic
-// ================
-
-void havocbot_goalrating_ball(entity this, float ratingscale, vector org)
-{
- float t;
- entity ball_owner;
- ball_owner = ka_ball.owner;
-
- if (ball_owner == this)
- return;
-
- // If ball is carried by player then hunt them down.
- if (ball_owner)
- {
- t = (GetResourceAmount(this, RESOURCE_HEALTH) + GetResourceAmount(this, RESOURCE_ARMOR)) / (GetResourceAmount(ball_owner, RESOURCE_HEALTH) + GetResourceAmount(ball_owner, RESOURCE_ARMOR));
- navigation_routerating(this, ball_owner, t * ratingscale, 2000);
- }
- else // Ball has been dropped so collect.
- navigation_routerating(this, ka_ball, ratingscale, 2000);
-}
-
-void havocbot_role_ka_carrier(entity this)
-{
- if (IS_DEAD(this))
- return;
-
- if (navigation_goalrating_timeout(this))
- {
- 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;
- navigation_goalrating_timeout_expire(this, 2);
- }
-}
-
-void havocbot_role_ka_collector(entity this)
-{
- if (IS_DEAD(this))
- return;
-
- if (navigation_goalrating_timeout(this))
- {
- 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;
- navigation_goalrating_timeout_expire(this, 2);
- }
-}
-
-
-// ==============
-// Hook Functions
-// ==============
-
-MUTATOR_HOOKFUNCTION(ka, PlayerDies)
-{
- entity frag_attacker = M_ARGV(1, entity);
- entity frag_target = M_ARGV(2, entity);
-
- if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)))
- {
- if(frag_target.ballcarried) { // add to amount of times killing carrier
- GameRules_scoring_add(frag_attacker, KEEPAWAY_CARRIERKILLS, 1);
- if(autocvar_g_keepaway_score_bckill) // add bckills to the score
- GameRules_scoring_add(frag_attacker, SCORE, autocvar_g_keepaway_score_bckill);
- }
- else if(!frag_attacker.ballcarried)
- if(autocvar_g_keepaway_noncarrier_warn)
- Send_Notification(NOTIF_ONE_ONLY, frag_attacker, MSG_CENTER, CENTER_KEEPAWAY_WARN);
-
- if(frag_attacker.ballcarried) // add to amount of kills while ballcarrier
- GameRules_scoring_add(frag_attacker, SCORE, autocvar_g_keepaway_score_killac);
- }
-
- if(frag_target.ballcarried) { ka_DropEvent(frag_target); } // a player with the ball has died, drop it
-}
-
-MUTATOR_HOOKFUNCTION(ka, GiveFragsForKill)
-{
- M_ARGV(2, float) = 0; // no frags counted in keepaway
- return true; // you deceptive little bugger ;3 This needs to be true in order for this function to even count.
-}
-
-MUTATOR_HOOKFUNCTION(ka, PlayerPreThink)
-{
- entity player = M_ARGV(0, entity);
-
- // clear the item used for the ball in keepaway
- player.items &= ~IT_KEY1;
-
- // if the player has the ball, make sure they have the item for it (Used for HUD primarily)
- if(player.ballcarried)
- player.items |= IT_KEY1;
-}
-
-MUTATOR_HOOKFUNCTION(ka, PlayerUseKey)
-{
- entity player = M_ARGV(0, entity);
-
- if(MUTATOR_RETURNVALUE == 0)
- if(player.ballcarried)
- {
- ka_DropEvent(player);
- return true;
- }
-}
-
-MUTATOR_HOOKFUNCTION(ka, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
-{
- entity frag_attacker = M_ARGV(1, entity);
- entity frag_target = M_ARGV(2, entity);
- float frag_damage = M_ARGV(4, float);
- vector frag_force = M_ARGV(6, vector);
-
- if(frag_attacker.ballcarried) // if the attacker is a ballcarrier
- {
- if(frag_target == frag_attacker) // damage done to yourself
- {
- frag_damage *= autocvar_g_keepaway_ballcarrier_selfdamage;
- frag_force *= autocvar_g_keepaway_ballcarrier_selfforce;
- }
- else // damage done to noncarriers
- {
- frag_damage *= autocvar_g_keepaway_ballcarrier_damage;
- frag_force *= autocvar_g_keepaway_ballcarrier_force;
- }
- }
- else if (!frag_target.ballcarried) // if the target is a noncarrier
- {
- if(frag_target == frag_attacker) // damage done to yourself
- {
- frag_damage *= autocvar_g_keepaway_noncarrier_selfdamage;
- frag_force *= autocvar_g_keepaway_noncarrier_selfforce;
- }
- else // damage done to other noncarriers
- {
- frag_damage *= autocvar_g_keepaway_noncarrier_damage;
- frag_force *= autocvar_g_keepaway_noncarrier_force;
- }
- }
-
- M_ARGV(4, float) = frag_damage;
- M_ARGV(6, vector) = frag_force;
-}
-
-MUTATOR_HOOKFUNCTION(ka, ClientDisconnect)
-{
- entity player = M_ARGV(0, entity);
-
- if(player.ballcarried) { ka_DropEvent(player); } // a player with the ball has left the match, drop it
-}
-
-MUTATOR_HOOKFUNCTION(ka, MakePlayerObserver)
-{
- entity player = M_ARGV(0, entity);
-
- if(player.ballcarried) { ka_DropEvent(player); } // a player with the ball has left the match, drop it
-}
-
-MUTATOR_HOOKFUNCTION(ka, PlayerPowerups)
-{
- entity player = M_ARGV(0, entity);
-
- // In the future this hook is supposed to allow me to do some extra stuff with waypointsprites and invisibility powerup
- // So bare with me until I can fix a certain bug with ka_ballcarrier_waypointsprite_visible_for_player()
-
- player.effects &= ~autocvar_g_keepaway_ballcarrier_effects;
-
- if(player.ballcarried)
- player.effects |= autocvar_g_keepaway_ballcarrier_effects;
-}
-
-
-MUTATOR_HOOKFUNCTION(ka, PlayerPhysics_UpdateStats)
-{
- entity player = M_ARGV(0, entity);
- // these automatically reset, no need to worry
-
- if(player.ballcarried)
- STAT(MOVEVARS_HIGHSPEED, player) *= autocvar_g_keepaway_ballcarrier_highspeed;
-}
-
-MUTATOR_HOOKFUNCTION(ka, BotShouldAttack)
-{
- entity bot = M_ARGV(0, entity);
- entity targ = M_ARGV(1, entity);
-
- // if neither player has ball then don't attack unless the ball is on the ground
- if(!targ.ballcarried && !bot.ballcarried && ka_ball.owner)
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ka, HavocBot_ChooseRole)
-{
- entity bot = M_ARGV(0, entity);
-
- if (bot.ballcarried)
- bot.havocbot_role = havocbot_role_ka_carrier;
- else
- bot.havocbot_role = havocbot_role_ka_collector;
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ka, DropSpecialItems)
-{
- entity frag_target = M_ARGV(0, entity);
-
- if(frag_target.ballcarried)
- ka_DropEvent(frag_target);
-}
-
-.bool pushable;
-
-// ==============
-// Initialization
-// ==============
-
-MODEL(KA_BALL, "models/orbs/orbblue.md3");
-
-void ka_SpawnBall() // loads various values for the ball, runs only once at start of match
-{
- entity e = new(keepawayball);
- setmodel(e, MDL_KA_BALL);
- setsize(e, '-16 -16 -20', '16 16 20'); // 20 20 20 was too big, player is only 16 16 24... gotta cheat with the Z (20) axis so that the particle isn't cut off
- e.damageforcescale = autocvar_g_keepawayball_damageforcescale;
- e.takedamage = DAMAGE_YES;
- e.solid = SOLID_TRIGGER;
- set_movetype(e, MOVETYPE_BOUNCE);
- e.glow_color = autocvar_g_keepawayball_trail_color;
- e.glow_trail = true;
- e.flags = FL_ITEM;
- IL_PUSH(g_items, e);
- e.pushable = true;
- e.reset = ka_Reset;
- settouch(e, ka_TouchEvent);
- e.owner = NULL;
- ka_ball = e;
- navigation_dynamicgoal_init(ka_ball, false);
-
- InitializeEntity(e, ka_RespawnBall, INITPRIO_SETLOCATION); // is this the right priority? Neh, I have no idea.. Well-- it works! So.
-}
-
-void ka_Initialize() // run at the start of a match, initiates game mode
-{
- ka_SpawnBall();
-}
-#endif
+++ /dev/null
-#pragma once
-
-#ifdef SVQC
-#include <common/mutators/base.qh>
-#include <common/scores.qh>
-void ka_Initialize();
-
-REGISTER_MUTATOR(ka, false)
-{
- MUTATOR_STATIC();
- MUTATOR_ONADD
- {
- GameRules_scoring(0, SFL_SORT_PRIO_PRIMARY, 0, {
- field(SP_KEEPAWAY_PICKUPS, "pickups", 0);
- field(SP_KEEPAWAY_CARRIERKILLS, "bckills", 0);
- field(SP_KEEPAWAY_BCTIME, "bctime", SFL_SORT_PRIO_SECONDARY);
- });
-
- ka_Initialize();
- }
- return false;
-}
-
-
-entity ka_ball;
-
-void(entity this) havocbot_role_ka_carrier;
-void(entity this) havocbot_role_ka_collector;
-
-void ka_DropEvent(entity plyr);
-#endif
--- /dev/null
+#include "sv_keepaway.qh"
+
+#include <common/effects/all.qh>
+
+.entity ballcarried;
+
+int autocvar_g_keepaway_ballcarrier_effects;
+float autocvar_g_keepaway_ballcarrier_damage;
+float autocvar_g_keepaway_ballcarrier_force;
+float autocvar_g_keepaway_ballcarrier_highspeed;
+float autocvar_g_keepaway_ballcarrier_selfdamage;
+float autocvar_g_keepaway_ballcarrier_selfforce;
+float autocvar_g_keepaway_noncarrier_damage;
+float autocvar_g_keepaway_noncarrier_force;
+float autocvar_g_keepaway_noncarrier_selfdamage;
+float autocvar_g_keepaway_noncarrier_selfforce;
+bool autocvar_g_keepaway_noncarrier_warn;
+int autocvar_g_keepaway_score_bckill;
+int autocvar_g_keepaway_score_killac;
+int autocvar_g_keepaway_score_timepoints;
+float autocvar_g_keepaway_score_timeinterval;
+float autocvar_g_keepawayball_damageforcescale;
+int autocvar_g_keepawayball_effects;
+float autocvar_g_keepawayball_respawntime;
+int autocvar_g_keepawayball_trail_color;
+
+bool ka_ballcarrier_waypointsprite_visible_for_player(entity this, entity player, entity view) // runs on waypoints which are attached to ballcarriers, updates once per frame
+{
+ if(view.ballcarried)
+ if(IS_SPEC(player))
+ return false; // we don't want spectators of the ballcarrier to see the attached waypoint on the top of their screen
+
+ // TODO: Make the ballcarrier lack a waypointsprite whenever they have the invisibility powerup
+
+ return true;
+}
+
+void ka_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later
+{
+ if(autocvar_sv_eventlog)
+ GameLogEcho(strcat(":ka:", mode, ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
+}
+
+void ka_TouchEvent(entity this, entity toucher);
+void ka_RespawnBall(entity this) // runs whenever the ball needs to be relocated
+{
+ if(game_stopped) return;
+ vector oldballorigin = this.origin;
+
+ if(!MoveToRandomMapLocation(this, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
+ {
+ entity spot = SelectSpawnPoint(this, true);
+ setorigin(this, spot.origin);
+ this.angles = spot.angles;
+ }
+
+ makevectors(this.angles);
+ set_movetype(this, MOVETYPE_BOUNCE);
+ this.velocity = '0 0 200';
+ this.angles = '0 0 0';
+ this.effects = autocvar_g_keepawayball_effects;
+ settouch(this, ka_TouchEvent);
+ setthink(this, ka_RespawnBall);
+ this.nextthink = time + autocvar_g_keepawayball_respawntime;
+ navigation_dynamicgoal_set(this, NULL);
+
+ Send_Effect(EFFECT_ELECTRO_COMBO, oldballorigin, '0 0 0', 1);
+ Send_Effect(EFFECT_ELECTRO_COMBO, this.origin, '0 0 0', 1);
+
+ WaypointSprite_Spawn(WP_KaBall, 0, 0, this, '0 0 64', NULL, this.team, this, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER);
+ WaypointSprite_Ping(this.waypointsprite_attachedforcarrier);
+
+ sound(this, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
+}
+
+void ka_TimeScoring(entity this)
+{
+ if(this.owner.ballcarried)
+ { // add points for holding the ball after a certain amount of time
+ if(autocvar_g_keepaway_score_timepoints)
+ GameRules_scoring_add(this.owner, SCORE, autocvar_g_keepaway_score_timepoints);
+
+ GameRules_scoring_add(this.owner, KEEPAWAY_BCTIME, (autocvar_g_keepaway_score_timeinterval / 1)); // interval is divided by 1 so that time always shows "seconds"
+ this.nextthink = time + autocvar_g_keepaway_score_timeinterval;
+ }
+}
+
+void ka_TouchEvent(entity this, entity toucher) // runs any time that the ball comes in contact with something
+{
+ if (!this || game_stopped)
+ return;
+
+ if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
+ { // The ball fell off the map, respawn it since players can't get to it
+ ka_RespawnBall(this);
+ return;
+ }
+ if(IS_DEAD(toucher)) { return; }
+ if(STAT(FROZEN, toucher)) { return; }
+ if (!IS_PLAYER(toucher))
+ { // The ball just touched an object, most likely the world
+ Send_Effect(EFFECT_BALL_SPARKS, this.origin, '0 0 0', 1);
+ sound(this, CH_TRIGGER, SND_KA_TOUCH, VOL_BASE, ATTEN_NORM);
+ return;
+ }
+ else if(this.wait > time) { return; }
+
+ // attach the ball to the player
+ this.owner = toucher;
+ toucher.ballcarried = this;
+ GameRules_scoring_vip(toucher, true);
+ setattachment(this, toucher, "");
+ setorigin(this, '0 0 0');
+
+ // make the ball invisible/unable to do anything/set up time scoring
+ this.velocity = '0 0 0';
+ set_movetype(this, MOVETYPE_NONE);
+ this.effects |= EF_NODRAW;
+ settouch(this, func_null);
+ setthink(this, ka_TimeScoring);
+ this.nextthink = time + autocvar_g_keepaway_score_timeinterval;
+ this.takedamage = DAMAGE_NO;
+ navigation_dynamicgoal_unset(this);
+
+ // apply effects to player
+ toucher.glow_color = autocvar_g_keepawayball_trail_color;
+ toucher.glow_trail = true;
+ toucher.effects |= autocvar_g_keepaway_ballcarrier_effects;
+
+ // messages and sounds
+ ka_EventLog("pickup", toucher);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_KEEPAWAY_PICKUP, toucher.netname);
+ Send_Notification(NOTIF_ALL_EXCEPT, toucher, MSG_CENTER, CENTER_KEEPAWAY_PICKUP, toucher.netname);
+ Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_KEEPAWAY_PICKUP_SELF);
+ sound(this.owner, CH_TRIGGER, SND_KA_PICKEDUP, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
+
+ // scoring
+ GameRules_scoring_add(toucher, KEEPAWAY_PICKUPS, 1);
+
+ // waypoints
+ WaypointSprite_AttachCarrier(WP_KaBallCarrier, toucher, RADARICON_FLAGCARRIER);
+ toucher.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = ka_ballcarrier_waypointsprite_visible_for_player;
+ WaypointSprite_UpdateRule(toucher.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
+ WaypointSprite_Ping(toucher.waypointsprite_attachedforcarrier);
+ WaypointSprite_Kill(this.waypointsprite_attachedforcarrier);
+}
+
+void ka_PlayerReset(entity plyr)
+{
+ plyr.ballcarried = NULL;
+ GameRules_scoring_vip(plyr, false);
+ WaypointSprite_Kill(plyr.waypointsprite_attachedforcarrier);
+
+ // reset the player effects
+ plyr.glow_trail = false;
+ plyr.effects &= ~autocvar_g_keepaway_ballcarrier_effects;
+}
+
+void ka_DropEvent(entity plyr) // runs any time that a player is supposed to lose the ball
+{
+ entity ball;
+ ball = plyr.ballcarried;
+
+ if(!ball) { return; }
+
+ // reset the ball
+ setattachment(ball, NULL, "");
+ set_movetype(ball, MOVETYPE_BOUNCE);
+ ball.wait = time + 1;
+ settouch(ball, ka_TouchEvent);
+ setthink(ball, ka_RespawnBall);
+ ball.nextthink = time + autocvar_g_keepawayball_respawntime;
+ ball.takedamage = DAMAGE_YES;
+ ball.effects &= ~EF_NODRAW;
+ setorigin(ball, plyr.origin + '0 0 10');
+ ball.velocity = '0 0 200' + '0 100 0'*crandom() + '100 0 0'*crandom();
+ ball.owner = NULL;
+ navigation_dynamicgoal_set(ball, plyr);
+
+ // messages and sounds
+ ka_EventLog("dropped", plyr);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_KEEPAWAY_DROPPED, plyr.netname);
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEEPAWAY_DROPPED, plyr.netname);
+ sound(NULL, CH_TRIGGER, SND_KA_DROPPED, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
+
+ // waypoints
+ WaypointSprite_Spawn(WP_KaBall, 0, 0, ball, '0 0 64', NULL, ball.team, ball, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER);
+ WaypointSprite_UpdateRule(ball.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
+ WaypointSprite_Ping(ball.waypointsprite_attachedforcarrier);
+
+ ka_PlayerReset(plyr);
+}
+
+.bool pushable;
+
+MODEL(KA_BALL, "models/orbs/orbblue.md3");
+
+void ka_RemoveBall()
+{
+ entity plyr = ka_ball.owner;
+ if (plyr) // it was attached
+ ka_PlayerReset(plyr);
+ else
+ WaypointSprite_DetachCarrier(ka_ball);
+ delete(ka_ball);
+ ka_ball = NULL;
+}
+
+void ka_SpawnBall()
+{
+ entity e = new(keepawayball);
+ setmodel(e, MDL_KA_BALL);
+ setsize(e, '-16 -16 -20', '16 16 20'); // 20 20 20 was too big, player is only 16 16 24... gotta cheat with the Z (20) axis so that the particle isn't cut off
+ e.damageforcescale = autocvar_g_keepawayball_damageforcescale;
+ e.takedamage = DAMAGE_YES;
+ e.solid = SOLID_TRIGGER;
+ set_movetype(e, MOVETYPE_BOUNCE);
+ e.glow_color = autocvar_g_keepawayball_trail_color;
+ e.glow_trail = true;
+ e.flags = FL_ITEM;
+ IL_PUSH(g_items, e);
+ e.pushable = true;
+ settouch(e, ka_TouchEvent);
+ e.owner = NULL;
+ ka_ball = e;
+ navigation_dynamicgoal_init(ka_ball, false);
+
+ InitializeEntity(e, ka_RespawnBall, INITPRIO_SETLOCATION); // is this the right priority? Neh, I have no idea.. Well-- it works! So.
+}
+
+void ka_Handler_CheckBall(entity this)
+{
+ if(time < game_starttime)
+ {
+ if (ka_ball)
+ ka_RemoveBall();
+ }
+ else
+ {
+ if (!ka_ball)
+ ka_SpawnBall();
+ }
+
+ this.nextthink = time;
+}
+
+void ka_Initialize() // run at the start of a match, initiates game mode
+{
+ ka_Handler = new(ka_Handler);
+ setthink(ka_Handler, ka_Handler_CheckBall);
+ ka_Handler.nextthink = time;
+}
+
+
+// ================
+// Bot player logic
+// ================
+
+void havocbot_goalrating_ball(entity this, float ratingscale, vector org)
+{
+ entity ball_owner;
+ ball_owner = ka_ball.owner;
+
+ if (ball_owner == this)
+ return;
+
+ if (ball_owner)
+ navigation_routerating(this, ball_owner, ratingscale, 2000);
+ else
+ navigation_routerating(this, ka_ball, ratingscale, 2000);
+}
+
+void havocbot_role_ka_carrier(entity this)
+{
+ if (IS_DEAD(this))
+ return;
+
+ if (navigation_goalrating_timeout(this))
+ {
+ navigation_goalrating_start(this);
+ havocbot_goalrating_items(this, 10000, this.origin, 10000);
+ havocbot_goalrating_enemyplayers(this, 10000, 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;
+ navigation_goalrating_timeout_expire(this, 2);
+ }
+}
+
+void havocbot_role_ka_collector(entity this)
+{
+ if (IS_DEAD(this))
+ return;
+
+ if (navigation_goalrating_timeout(this))
+ {
+ navigation_goalrating_start(this);
+ havocbot_goalrating_items(this, 10000, this.origin, 10000);
+ havocbot_goalrating_enemyplayers(this, 500, this.origin, 10000);
+ havocbot_goalrating_ball(this, 8000, this.origin);
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+
+ if (this.ballcarried)
+ {
+ this.havocbot_role = havocbot_role_ka_carrier;
+ navigation_goalrating_timeout_expire(this, 2);
+ }
+}
+
+
+// ==============
+// Hook Functions
+// ==============
+
+MUTATOR_HOOKFUNCTION(ka, PlayerDies)
+{
+ entity frag_attacker = M_ARGV(1, entity);
+ entity frag_target = M_ARGV(2, entity);
+
+ if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)))
+ {
+ if(frag_target.ballcarried) { // add to amount of times killing carrier
+ GameRules_scoring_add(frag_attacker, KEEPAWAY_CARRIERKILLS, 1);
+ if(autocvar_g_keepaway_score_bckill) // add bckills to the score
+ GameRules_scoring_add(frag_attacker, SCORE, autocvar_g_keepaway_score_bckill);
+ }
+ else if(!frag_attacker.ballcarried)
+ if(autocvar_g_keepaway_noncarrier_warn)
+ Send_Notification(NOTIF_ONE_ONLY, frag_attacker, MSG_CENTER, CENTER_KEEPAWAY_WARN);
+
+ if(frag_attacker.ballcarried) // add to amount of kills while ballcarrier
+ GameRules_scoring_add(frag_attacker, SCORE, autocvar_g_keepaway_score_killac);
+ }
+
+ if(frag_target.ballcarried) { ka_DropEvent(frag_target); } // a player with the ball has died, drop it
+}
+
+MUTATOR_HOOKFUNCTION(ka, GiveFragsForKill)
+{
+ M_ARGV(2, float) = 0; // no frags counted in keepaway
+ return true; // you deceptive little bugger ;3 This needs to be true in order for this function to even count.
+}
+
+MUTATOR_HOOKFUNCTION(ka, PlayerPreThink)
+{
+ entity player = M_ARGV(0, entity);
+
+ // clear the item used for the ball in keepaway
+ player.items &= ~IT_KEY1;
+
+ // if the player has the ball, make sure they have the item for it (Used for HUD primarily)
+ if(player.ballcarried)
+ player.items |= IT_KEY1;
+}
+
+MUTATOR_HOOKFUNCTION(ka, PlayerUseKey)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(MUTATOR_RETURNVALUE == 0)
+ if(player.ballcarried)
+ {
+ ka_DropEvent(player);
+ return true;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ka, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
+{
+ entity frag_attacker = M_ARGV(1, entity);
+ entity frag_target = M_ARGV(2, entity);
+ float frag_damage = M_ARGV(4, float);
+ vector frag_force = M_ARGV(6, vector);
+
+ if(frag_attacker.ballcarried) // if the attacker is a ballcarrier
+ {
+ if(frag_target == frag_attacker) // damage done to yourself
+ {
+ frag_damage *= autocvar_g_keepaway_ballcarrier_selfdamage;
+ frag_force *= autocvar_g_keepaway_ballcarrier_selfforce;
+ }
+ else // damage done to noncarriers
+ {
+ frag_damage *= autocvar_g_keepaway_ballcarrier_damage;
+ frag_force *= autocvar_g_keepaway_ballcarrier_force;
+ }
+ }
+ else if (!frag_target.ballcarried) // if the target is a noncarrier
+ {
+ if(frag_target == frag_attacker) // damage done to yourself
+ {
+ frag_damage *= autocvar_g_keepaway_noncarrier_selfdamage;
+ frag_force *= autocvar_g_keepaway_noncarrier_selfforce;
+ }
+ else // damage done to other noncarriers
+ {
+ frag_damage *= autocvar_g_keepaway_noncarrier_damage;
+ frag_force *= autocvar_g_keepaway_noncarrier_force;
+ }
+ }
+
+ M_ARGV(4, float) = frag_damage;
+ M_ARGV(6, vector) = frag_force;
+}
+
+MUTATOR_HOOKFUNCTION(ka, ClientDisconnect)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(player.ballcarried) { ka_DropEvent(player); } // a player with the ball has left the match, drop it
+}
+
+MUTATOR_HOOKFUNCTION(ka, MakePlayerObserver)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(player.ballcarried) { ka_DropEvent(player); } // a player with the ball has left the match, drop it
+}
+
+MUTATOR_HOOKFUNCTION(ka, PlayerPowerups)
+{
+ entity player = M_ARGV(0, entity);
+
+ // In the future this hook is supposed to allow me to do some extra stuff with waypointsprites and invisibility powerup
+ // So bare with me until I can fix a certain bug with ka_ballcarrier_waypointsprite_visible_for_player()
+
+ player.effects &= ~autocvar_g_keepaway_ballcarrier_effects;
+
+ if(player.ballcarried)
+ player.effects |= autocvar_g_keepaway_ballcarrier_effects;
+}
+
+
+MUTATOR_HOOKFUNCTION(ka, PlayerPhysics_UpdateStats)
+{
+ entity player = M_ARGV(0, entity);
+ // these automatically reset, no need to worry
+
+ if(player.ballcarried)
+ STAT(MOVEVARS_HIGHSPEED, player) *= autocvar_g_keepaway_ballcarrier_highspeed;
+}
+
+MUTATOR_HOOKFUNCTION(ka, BotShouldAttack)
+{
+ entity bot = M_ARGV(0, entity);
+ entity targ = M_ARGV(1, entity);
+
+ // if neither player has ball then don't attack unless the ball is on the ground
+ if(!targ.ballcarried && !bot.ballcarried && ka_ball.owner)
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ka, HavocBot_ChooseRole)
+{
+ entity bot = M_ARGV(0, entity);
+
+ if (bot.ballcarried)
+ bot.havocbot_role = havocbot_role_ka_carrier;
+ else
+ bot.havocbot_role = havocbot_role_ka_collector;
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ka, DropSpecialItems)
+{
+ entity frag_target = M_ARGV(0, entity);
+
+ if(frag_target.ballcarried)
+ ka_DropEvent(frag_target);
+}
--- /dev/null
+#pragma once
+
+#include <common/mutators/base.qh>
+#include <common/scores.qh>
+void ka_Initialize();
+
+REGISTER_MUTATOR(ka, false)
+{
+ MUTATOR_STATIC();
+ MUTATOR_ONADD
+ {
+ GameRules_scoring(0, SFL_SORT_PRIO_PRIMARY, 0, {
+ field(SP_KEEPAWAY_PICKUPS, "pickups", 0);
+ field(SP_KEEPAWAY_CARRIERKILLS, "bckills", 0);
+ field(SP_KEEPAWAY_BCTIME, "bctime", SFL_SORT_PRIO_SECONDARY);
+ });
+
+ ka_Initialize();
+ }
+ return false;
+}
+
+
+entity ka_ball;
+entity ka_Handler;
+
+void(entity this) havocbot_role_ka_carrier;
+void(entity this) havocbot_role_ka_collector;
+
+void ka_DropEvent(entity plyr);
// generated file; do not modify
-#include <common/gamemodes/gamemode/keyhunt/keyhunt.qc>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/keyhunt/sv_keyhunt.qc>
+#endif
// generated file; do not modify
-#include <common/gamemodes/gamemode/keyhunt/keyhunt.qh>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/keyhunt/sv_keyhunt.qh>
+#endif
+++ /dev/null
-#include "keyhunt.qh"
-
-// TODO: sv_keyhunt
-#ifdef SVQC
-float autocvar_g_balance_keyhunt_damageforcescale;
-float autocvar_g_balance_keyhunt_delay_collect;
-float autocvar_g_balance_keyhunt_delay_damage_return;
-float autocvar_g_balance_keyhunt_delay_return;
-float autocvar_g_balance_keyhunt_delay_round;
-float autocvar_g_balance_keyhunt_delay_tracking;
-float autocvar_g_balance_keyhunt_return_when_unreachable;
-float autocvar_g_balance_keyhunt_dropvelocity;
-float autocvar_g_balance_keyhunt_maxdist;
-float autocvar_g_balance_keyhunt_protecttime;
-
-int autocvar_g_balance_keyhunt_score_capture;
-int autocvar_g_balance_keyhunt_score_carrierfrag;
-int autocvar_g_balance_keyhunt_score_collect;
-int autocvar_g_balance_keyhunt_score_destroyed;
-int autocvar_g_balance_keyhunt_score_destroyed_ownfactor;
-int autocvar_g_balance_keyhunt_score_push;
-float autocvar_g_balance_keyhunt_throwvelocity;
-
-//int autocvar_g_keyhunt_teams;
-int autocvar_g_keyhunt_teams_override;
-
-// #define KH_PLAYER_USE_ATTACHMENT
-// #define KH_PLAYER_USE_CARRIEDMODEL
-
-#ifdef KH_PLAYER_USE_ATTACHMENT
-const vector KH_PLAYER_ATTACHMENT_DIST_ROTATED = '0 -4 0';
-const vector KH_PLAYER_ATTACHMENT_DIST = '4 0 0';
-const vector KH_PLAYER_ATTACHMENT = '0 0 0';
-const vector KH_PLAYER_ATTACHMENT_ANGLES = '0 0 0';
-const string KH_PLAYER_ATTACHMENT_BONE = "";
-#else
-const float KH_KEY_ZSHIFT = 22;
-const float KH_KEY_XYDIST = 24;
-const float KH_KEY_XYSPEED = 45;
-#endif
-const float KH_KEY_WP_ZSHIFT = 20;
-
-const vector KH_KEY_MIN = '-10 -10 -46';
-const vector KH_KEY_MAX = '10 10 3';
-const float KH_KEY_BRIGHTNESS = 2;
-
-bool kh_no_radar_circles;
-
-// kh_state
-// bits 0- 4: team of key 1, or 0 for no such key, or 30 for dropped, or 31 for self
-// bits 5- 9: team of key 2, or 0 for no such key, or 30 for dropped, or 31 for self
-// bits 10-14: team of key 3, or 0 for no such key, or 30 for dropped, or 31 for self
-// bits 15-19: team of key 4, or 0 for no such key, or 30 for dropped, or 31 for self
-.float siren_time; // time delay the siren
-//.float stuff_time; // time delay to stuffcmd a cvar
-
-int kh_keystatus[17];
-//kh_keystatus[0] = status of dropped keys, kh_keystatus[1 - 16] = player #
-//replace 17 with cvar("maxplayers") or similar !!!!!!!!!
-//for(i = 0; i < maxplayers; ++i)
-// kh_keystatus[i] = "0";
-
-int kh_Team_ByID(int t)
-{
- if(t == 0) return NUM_TEAM_1;
- if(t == 1) return NUM_TEAM_2;
- if(t == 2) return NUM_TEAM_3;
- if(t == 3) return NUM_TEAM_4;
- return 0;
-}
-
-//entity kh_worldkeylist;
-.entity kh_worldkeynext;
-entity kh_controller;
-//bool kh_tracking_enabled;
-int kh_teams;
-int kh_interferemsg_team;
-float kh_interferemsg_time;
-.entity kh_next, kh_prev; // linked list
-.float kh_droptime;
-.int kh_dropperteam;
-.entity kh_previous_owner;
-.int kh_previous_owner_playerid;
-
-int kh_key_dropped, kh_key_carried;
-
-int kh_Key_AllOwnedByWhichTeam();
-
-const int ST_KH_CAPS = 1;
-void kh_ScoreRules(int teams)
-{
- GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, {
- field_team(ST_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
- field(SP_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
- field(SP_KH_PUSHES, "pushes", 0);
- field(SP_KH_DESTROYS, "destroyed", SFL_LOWER_IS_BETTER);
- field(SP_KH_PICKUPS, "pickups", 0);
- field(SP_KH_KCKILLS, "kckills", 0);
- field(SP_KH_LOSSES, "losses", SFL_LOWER_IS_BETTER);
- });
-}
-
-bool kh_KeyCarrier_waypointsprite_visible_for_player(entity this, entity player, entity view) // runs all the time
-{
- if(!IS_PLAYER(view) || DIFF_TEAM(this, view))
- if(!kh_tracking_enabled)
- return false;
-
- return true;
-}
-
-bool kh_Key_waypointsprite_visible_for_player(entity this, entity player, entity view)
-{
- if(!kh_tracking_enabled)
- return false;
- if(!this.owner)
- return true;
- if(!this.owner.owner)
- return true;
- return false; // draw only when key is not owned
-}
-
-void kh_update_state()
-{
- entity key;
- int f;
- int s = 0;
- FOR_EACH_KH_KEY(key)
- {
- if(key.owner)
- f = key.team;
- else
- f = 30;
- s |= (32 ** key.count) * f;
- }
-
- FOREACH_CLIENT(true, { STAT(KH_KEYS, it) = s; });
-
- FOR_EACH_KH_KEY(key)
- {
- if(key.owner)
- STAT(KH_KEYS, key.owner) |= (32 ** key.count) * 31;
- }
- //print(ftos((nextent(NULL)).kh_state), "\n");
-}
-
-
-
-
-var kh_Think_t kh_Controller_Thinkfunc;
-void kh_Controller_SetThink(float t, kh_Think_t func) // runs occasionaly
-{
- kh_Controller_Thinkfunc = func;
- kh_controller.cnt = ceil(t);
- if(t == 0)
- kh_controller.nextthink = time; // force
-}
-void kh_WaitForPlayers();
-void kh_Controller_Think(entity this) // called a lot
-{
- if(game_stopped)
- return;
- if(this.cnt > 0)
- {
- if(getthink(this) != kh_WaitForPlayers)
- this.cnt -= 1;
- }
- else if(this.cnt == 0)
- {
- this.cnt -= 1;
- kh_Controller_Thinkfunc();
- }
- this.nextthink = time + 1;
-}
-
-// frags f: take from cvar * f
-// frags 0: no frags
-void kh_Scores_Event(entity player, entity key, string what, float frags_player, float frags_owner) // update the score when a key is captured
-{
- string s;
- if(game_stopped)
- return;
-
- if(frags_player)
- UpdateFrags(player, frags_player);
-
- if(key && key.owner && frags_owner)
- UpdateFrags(key.owner, frags_owner);
-
- if(!autocvar_sv_eventlog) //output extra info to the console or text file
- return;
-
- s = strcat(":keyhunt:", what, ":", ftos(player.playerid), ":", ftos(frags_player));
-
- if(key && key.owner)
- s = strcat(s, ":", ftos(key.owner.playerid));
- else
- s = strcat(s, ":0");
-
- s = strcat(s, ":", ftos(frags_owner), ":");
-
- if(key)
- s = strcat(s, key.netname);
-
- GameLogEcho(s);
-}
-
-vector kh_AttachedOrigin(entity e) // runs when a team captures the flag, it can run 2 or 3 times.
-{
- if(e.tag_entity)
- {
- makevectors(e.tag_entity.angles);
- return e.tag_entity.origin + e.origin.x * v_forward - e.origin.y * v_right + e.origin.z * v_up;
- }
- else
- return e.origin;
-}
-
-void kh_Key_Attach(entity key) // runs when a player picks up a key and several times when a key is assigned to a player at the start of a round
-{
-#ifdef KH_PLAYER_USE_ATTACHMENT
- entity first = key.owner.kh_next;
- if(key == first)
- {
- setattachment(key, key.owner, KH_PLAYER_ATTACHMENT_BONE);
- if(key.kh_next)
- {
- setattachment(key.kh_next, key, "");
- setorigin(key, key.kh_next.origin - 0.5 * KH_PLAYER_ATTACHMENT_DIST);
- setorigin(key.kh_next, KH_PLAYER_ATTACHMENT_DIST_ROTATED);
- key.kh_next.angles = '0 0 0';
- }
- else
- setorigin(key, KH_PLAYER_ATTACHMENT);
- key.angles = KH_PLAYER_ATTACHMENT_ANGLES;
- }
- else
- {
- setattachment(key, key.kh_prev, "");
- if(key.kh_next)
- setattachment(key.kh_next, key, "");
- setorigin(key, KH_PLAYER_ATTACHMENT_DIST_ROTATED);
- setorigin(first, first.origin - 0.5 * KH_PLAYER_ATTACHMENT_DIST);
- key.angles = '0 0 0';
- }
-#else
- setattachment(key, key.owner, "");
- setorigin(key, '0 0 1' * KH_KEY_ZSHIFT); // fixing x, y in think
- key.angles_y -= key.owner.angles.y;
-#endif
- key.flags = 0;
- if(IL_CONTAINS(g_items, key))
- IL_REMOVE(g_items, key);
- key.solid = SOLID_NOT;
- set_movetype(key, MOVETYPE_NONE);
- key.team = key.owner.team;
- key.nextthink = time;
- key.damageforcescale = 0;
- key.takedamage = DAMAGE_NO;
- key.modelindex = kh_key_carried;
- navigation_dynamicgoal_unset(key);
-}
-
-void kh_Key_Detach(entity key) // runs every time a key is dropped or lost. Runs several times times when all the keys are captured
-{
-#ifdef KH_PLAYER_USE_ATTACHMENT
- entity first = key.owner.kh_next;
- if(key == first)
- {
- if(key.kh_next)
- {
- setattachment(key.kh_next, key.owner, KH_PLAYER_ATTACHMENT_BONE);
- setorigin(key.kh_next, key.origin + 0.5 * KH_PLAYER_ATTACHMENT_DIST);
- key.kh_next.angles = KH_PLAYER_ATTACHMENT_ANGLES;
- }
- }
- else
- {
- if(key.kh_next)
- setattachment(key.kh_next, key.kh_prev, "");
- setorigin(first, first.origin + 0.5 * KH_PLAYER_ATTACHMENT_DIST);
- }
- // in any case:
- setattachment(key, NULL, "");
- setorigin(key, key.owner.origin + '0 0 1' * (STAT(PL_MIN, key.owner).z - KH_KEY_MIN_z));
- key.angles = key.owner.angles;
-#else
- setorigin(key, key.owner.origin + key.origin.z * '0 0 1');
- setattachment(key, NULL, "");
- key.angles_y += key.owner.angles.y;
-#endif
- key.flags = FL_ITEM;
- if(!IL_CONTAINS(g_items, key))
- IL_PUSH(g_items, key);
- key.solid = SOLID_TRIGGER;
- set_movetype(key, MOVETYPE_TOSS);
- key.pain_finished = time + autocvar_g_balance_keyhunt_delay_return;
- key.damageforcescale = autocvar_g_balance_keyhunt_damageforcescale;
- key.takedamage = DAMAGE_YES;
- // let key.team stay
- key.modelindex = kh_key_dropped;
- navigation_dynamicgoal_set(key);
- key.kh_previous_owner = key.owner;
- key.kh_previous_owner_playerid = key.owner.playerid;
-}
-
-void kh_Key_AssignTo(entity key, entity player) // runs every time a key is picked up or assigned. Runs prior to kh_key_attach
-{
- if(key.owner == player)
- return;
-
- int ownerteam0 = kh_Key_AllOwnedByWhichTeam();
-
- if(key.owner)
- {
- kh_Key_Detach(key);
-
- // remove from linked list
- if(key.kh_next)
- key.kh_next.kh_prev = key.kh_prev;
- key.kh_prev.kh_next = key.kh_next;
- key.kh_next = NULL;
- key.kh_prev = NULL;
-
- if(key.owner.kh_next == NULL)
- {
- // No longer a key carrier
- if(!kh_no_radar_circles)
- WaypointSprite_Ping(key.owner.waypointsprite_attachedforcarrier);
- WaypointSprite_DetachCarrier(key.owner);
- }
- }
-
- key.owner = player;
-
- if(player)
- {
- // insert into linked list
- key.kh_next = player.kh_next;
- key.kh_prev = player;
- player.kh_next = key;
- if(key.kh_next)
- key.kh_next.kh_prev = key;
-
- float i;
- i = kh_keystatus[key.owner.playerid];
- if(key.netname == "^1red key")
- i += 1;
- if(key.netname == "^4blue key")
- i += 2;
- if(key.netname == "^3yellow key")
- i += 4;
- if(key.netname == "^6pink key")
- i += 8;
- kh_keystatus[key.owner.playerid] = i;
-
- kh_Key_Attach(key);
-
- if(key.kh_next == NULL)
- {
- // player is now a key carrier
- entity wp = WaypointSprite_AttachCarrier(WP_Null, player, RADARICON_FLAGCARRIER);
- wp.colormod = colormapPaletteColor(player.team - 1, 0);
- player.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = kh_KeyCarrier_waypointsprite_visible_for_player;
- WaypointSprite_UpdateRule(player.waypointsprite_attachedforcarrier, player.team, SPRITERULE_TEAMPLAY);
- if(player.team == NUM_TEAM_1)
- WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierRed, WP_KeyCarrierFriend, WP_KeyCarrierRed);
- else if(player.team == NUM_TEAM_2)
- WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierBlue, WP_KeyCarrierFriend, WP_KeyCarrierBlue);
- else if(player.team == NUM_TEAM_3)
- WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierYellow, WP_KeyCarrierFriend, WP_KeyCarrierYellow);
- else if(player.team == NUM_TEAM_4)
- WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierPink, WP_KeyCarrierFriend, WP_KeyCarrierPink);
- if(!kh_no_radar_circles)
- WaypointSprite_Ping(player.waypointsprite_attachedforcarrier);
- }
- }
-
- // moved that here, also update if there's no player
- kh_update_state();
-
- key.pusher = NULL;
-
- int ownerteam = kh_Key_AllOwnedByWhichTeam();
- if(ownerteam != ownerteam0)
- {
- entity k;
- if(ownerteam != -1)
- {
- kh_interferemsg_time = time + 0.2;
- kh_interferemsg_team = player.team;
-
- // audit all key carrier sprites, update them to "Run here"
- FOR_EACH_KH_KEY(k)
- {
- if (!k.owner) continue;
- entity first = WP_Null;
- FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model1, { first = it; break; });
- entity third = WP_Null;
- FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, { third = it; break; });
- WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, first, WP_KeyCarrierFinish, third);
- }
- }
- else
- {
- kh_interferemsg_time = 0;
-
- // audit all key carrier sprites, update them to "Key Carrier"
- FOR_EACH_KH_KEY(k)
- {
- if (!k.owner) continue;
- entity first = WP_Null;
- FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model1, { first = it; break; });
- entity third = WP_Null;
- FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, { third = it; break; });
- WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, first, WP_KeyCarrierFriend, third);
- }
- }
- }
-}
-
-void kh_Key_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
-{
- if(this.owner)
- return;
- if(ITEM_DAMAGE_NEEDKILL(deathtype))
- {
- this.pain_finished = bound(time, time + autocvar_g_balance_keyhunt_delay_damage_return, this.pain_finished);
- return;
- }
- if(force == '0 0 0')
- return;
- if(time > this.pushltime)
- if(IS_PLAYER(attacker))
- this.team = attacker.team;
-}
-
-void kh_Key_Collect(entity key, entity player) //a player picks up a dropped key
-{
- sound(player, CH_TRIGGER, SND_KH_COLLECT, VOL_BASE, ATTEN_NORM);
-
- if(key.kh_dropperteam != player.team)
- {
- kh_Scores_Event(player, key, "collect", autocvar_g_balance_keyhunt_score_collect, 0);
- GameRules_scoring_add(player, KH_PICKUPS, 1);
- }
- key.kh_dropperteam = 0;
- int realteam = kh_Team_ByID(key.count);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_PICKUP), player.netname);
-
- kh_Key_AssignTo(key, player); // this also updates .kh_state
-}
-
-void kh_Key_Touch(entity this, entity toucher) // runs many, many times when a key has been dropped and can be picked up
-{
- if(game_stopped)
- return;
-
- if(this.owner) // already carried
- return;
-
- if(ITEM_TOUCH_NEEDKILL())
- {
- this.pain_finished = bound(time, time + autocvar_g_balance_keyhunt_delay_damage_return, this.pain_finished);
- return;
- }
-
- if (!IS_PLAYER(toucher))
- return;
- if(IS_DEAD(toucher))
- return;
- if(toucher == this.enemy)
- if(time < this.kh_droptime + autocvar_g_balance_keyhunt_delay_collect)
- return; // you just dropped it!
- kh_Key_Collect(this, toucher);
-}
-
-void kh_Key_Remove(entity key) // runs after when all the keys have been collected or when a key has been dropped for more than X seconds
-{
- entity o = key.owner;
- kh_Key_AssignTo(key, NULL);
- if(o) // it was attached
- WaypointSprite_Kill(key.waypointsprite_attachedforcarrier);
- else // it was dropped
- WaypointSprite_DetachCarrier(key);
-
- // remove key from key list
- if (kh_worldkeylist == key)
- kh_worldkeylist = kh_worldkeylist.kh_worldkeynext;
- else
- {
- o = kh_worldkeylist;
- while (o)
- {
- if (o.kh_worldkeynext == key)
- {
- o.kh_worldkeynext = o.kh_worldkeynext.kh_worldkeynext;
- break;
- }
- o = o.kh_worldkeynext;
- }
- }
-
- delete(key);
-
- kh_update_state();
-}
-
-void kh_FinishRound() // runs when a team captures the keys
-{
- // prepare next round
- kh_interferemsg_time = 0;
- entity key;
-
- kh_no_radar_circles = true;
- FOR_EACH_KH_KEY(key)
- kh_Key_Remove(key);
- kh_no_radar_circles = false;
-
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEYHUNT_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round);
- kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round, kh_StartRound);
-}
-
-void nades_GiveBonus(entity player, float score);
-
-void kh_WinnerTeam(int winner_team) // runs when a team wins
-{
- // all key carriers get some points
- entity key;
- float score = (NumTeams(kh_teams) - 1) * autocvar_g_balance_keyhunt_score_capture;
- DistributeEvenly_Init(score, NumTeams(kh_teams));
- // twice the score for 3 team games, three times the score for 4 team games!
- // note: for a win by destroying the key, this should NOT be applied
- FOR_EACH_KH_KEY(key)
- {
- float f = DistributeEvenly_Get(1);
- kh_Scores_Event(key.owner, key, "capture", f, 0);
- GameRules_scoring_add_team(key.owner, KH_CAPS, 1);
- nades_GiveBonus(key.owner, autocvar_g_nades_bonus_score_high);
- }
-
- bool first = true;
- string keyowner = "";
- FOR_EACH_KH_KEY(key)
- if(key.owner.kh_next == key)
- {
- if(!first)
- keyowner = strcat(keyowner, ", ");
- keyowner = key.owner.netname;
- first = false;
- }
-
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_KEYHUNT_CAPTURE), keyowner);
-
- first = true;
- vector firstorigin = '0 0 0', lastorigin = '0 0 0', midpoint = '0 0 0';
- FOR_EACH_KH_KEY(key)
- {
- vector thisorigin = kh_AttachedOrigin(key);
- //dprint("Key origin: ", vtos(thisorigin), "\n");
- midpoint += thisorigin;
-
- if(!first)
- te_lightning2(NULL, lastorigin, thisorigin);
- lastorigin = thisorigin;
- if(first)
- firstorigin = thisorigin;
- first = false;
- }
- if(NumTeams(kh_teams) > 2)
- {
- te_lightning2(NULL, lastorigin, firstorigin);
- }
- midpoint = midpoint * (1 / NumTeams(kh_teams));
- te_customflash(midpoint, 1000, 1, Team_ColorRGB(winner_team) * 0.5 + '0.5 0.5 0.5'); // make the color >=0.5 in each component
-
- play2all(SND(KH_CAPTURE));
- kh_FinishRound();
-}
-
-void kh_LoserTeam(int loser_team, entity lostkey) // runs when a player pushes a flag carrier off the map
-{
- float f;
- entity attacker = NULL;
- if(lostkey.pusher)
- if(lostkey.pusher.team != loser_team)
- if(IS_PLAYER(lostkey.pusher))
- attacker = lostkey.pusher;
-
- if(attacker)
- {
- if(lostkey.kh_previous_owner)
- kh_Scores_Event(lostkey.kh_previous_owner, NULL, "pushed", 0, -autocvar_g_balance_keyhunt_score_push);
- // don't actually GIVE him the -nn points, just log
- kh_Scores_Event(attacker, NULL, "push", autocvar_g_balance_keyhunt_score_push, 0);
- GameRules_scoring_add(attacker, KH_PUSHES, 1);
- //centerprint(attacker, "Your push is the best!"); // does this really need to exist?
- }
- else
- {
- int players = 0;
- float of = autocvar_g_balance_keyhunt_score_destroyed_ownfactor;
-
- FOREACH_CLIENT(IS_PLAYER(it) && it.team != loser_team, { ++players; });
-
- entity key;
- int keys = 0;
- FOR_EACH_KH_KEY(key)
- if(key.owner && key.team != loser_team)
- ++keys;
-
- if(lostkey.kh_previous_owner)
- kh_Scores_Event(lostkey.kh_previous_owner, NULL, "destroyed", 0, -autocvar_g_balance_keyhunt_score_destroyed);
- // don't actually GIVE him the -nn points, just log
-
- if(lostkey.kh_previous_owner.playerid == lostkey.kh_previous_owner_playerid)
- GameRules_scoring_add(lostkey.kh_previous_owner, KH_DESTROYS, 1);
-
- DistributeEvenly_Init(autocvar_g_balance_keyhunt_score_destroyed, keys * of + players);
-
- FOR_EACH_KH_KEY(key)
- if(key.owner && key.team != loser_team)
- {
- f = DistributeEvenly_Get(of);
- kh_Scores_Event(key.owner, NULL, "destroyed_holdingkey", f, 0);
- }
-
- int fragsleft = DistributeEvenly_Get(players);
-
- // Now distribute these among all other teams...
- int j = NumTeams(kh_teams) - 1;
- for(int i = 0; i < NumTeams(kh_teams); ++i)
- {
- int thisteam = kh_Team_ByID(i);
- if(thisteam == loser_team) // bad boy, no cookie - this WILL happen
- continue;
-
- players = 0;
- FOREACH_CLIENT(IS_PLAYER(it) && it.team == thisteam, { ++players; });
-
- DistributeEvenly_Init(fragsleft, j);
- fragsleft = DistributeEvenly_Get(j - 1);
- DistributeEvenly_Init(DistributeEvenly_Get(1), players);
-
- FOREACH_CLIENT(IS_PLAYER(it) && it.team == thisteam, {
- f = DistributeEvenly_Get(1);
- kh_Scores_Event(it, NULL, "destroyed", f, 0);
- });
-
- --j;
- }
- }
-
- int realteam = kh_Team_ByID(lostkey.count);
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(loser_team, CENTER_ROUND_TEAM_LOSS));
- if(attacker)
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_PUSHED), attacker.netname, lostkey.kh_previous_owner.netname);
- else
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_DESTROYED), lostkey.kh_previous_owner.netname);
-
- play2all(SND(KH_DESTROY));
- te_tarexplosion(lostkey.origin);
-
- kh_FinishRound();
-}
-
-void kh_Key_Think(entity this) // runs all the time
-{
- if(game_stopped)
- return;
-
- if(this.owner)
- {
-#ifndef KH_PLAYER_USE_ATTACHMENT
- makevectors('0 1 0' * (this.cnt + (time % 360) * KH_KEY_XYSPEED));
- setorigin(this, v_forward * KH_KEY_XYDIST + '0 0 1' * this.origin.z);
-#endif
- }
-
- // if in nodrop or time over, end the round
- if(!this.owner)
- if(time > this.pain_finished)
- kh_LoserTeam(this.team, this);
-
- if(this.owner)
- if(kh_Key_AllOwnedByWhichTeam() != -1)
- {
- if(this.siren_time < time)
- {
- sound(this.owner, CH_TRIGGER, SND_KH_ALARM, VOL_BASE, ATTEN_NORM); // play a simple alarm
- this.siren_time = time + 2.5; // repeat every 2.5 seconds
- }
-
- entity key;
- vector p = this.owner.origin;
- FOR_EACH_KH_KEY(key)
- if(vdist(key.owner.origin - p, >, autocvar_g_balance_keyhunt_maxdist))
- goto not_winning;
- kh_WinnerTeam(this.team);
-LABEL(not_winning)
- }
-
- if(kh_interferemsg_time && time > kh_interferemsg_time)
- {
- kh_interferemsg_time = 0;
- FOREACH_CLIENT(IS_PLAYER(it), {
- if(it.team == kh_interferemsg_team)
- if(it.kh_next)
- Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_KEYHUNT_MEET);
- else
- Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_KEYHUNT_HELP);
- else
- Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_TEAM_NUM(kh_interferemsg_team, CENTER_KEYHUNT_INTERFERE));
- });
- }
-
- this.nextthink = time + 0.05;
-}
-
-void key_reset(entity this)
-{
- kh_Key_AssignTo(this, NULL);
- kh_Key_Remove(this);
-}
-
-const string STR_ITEM_KH_KEY = "item_kh_key";
-void kh_Key_Spawn(entity initial_owner, float _angle, float i) // runs every time a new flag is created, ie after all the keys have been collected
-{
- entity key = spawn();
- key.count = i;
- key.classname = STR_ITEM_KH_KEY;
- settouch(key, kh_Key_Touch);
- setthink(key, kh_Key_Think);
- key.nextthink = time;
- key.items = IT_KEY1 | IT_KEY2;
- key.cnt = _angle;
- key.angles = '0 360 0' * random();
- key.event_damage = kh_Key_Damage;
- key.takedamage = DAMAGE_YES;
- key.damagedbytriggers = autocvar_g_balance_keyhunt_return_when_unreachable;
- key.damagedbycontents = autocvar_g_balance_keyhunt_return_when_unreachable;
- key.modelindex = kh_key_dropped;
- key.model = "key";
- key.kh_dropperteam = 0;
- key.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
- setsize(key, KH_KEY_MIN, KH_KEY_MAX);
- key.colormod = Team_ColorRGB(initial_owner.team) * KH_KEY_BRIGHTNESS;
- key.reset = key_reset;
- navigation_dynamicgoal_init(key, false);
-
- switch(initial_owner.team)
- {
- case NUM_TEAM_1:
- key.netname = "^1red key";
- break;
- case NUM_TEAM_2:
- key.netname = "^4blue key";
- break;
- case NUM_TEAM_3:
- key.netname = "^3yellow key";
- break;
- case NUM_TEAM_4:
- key.netname = "^6pink key";
- break;
- default:
- key.netname = "NETGIER key";
- break;
- }
-
- // link into key list
- key.kh_worldkeynext = kh_worldkeylist;
- kh_worldkeylist = key;
-
- Send_Notification(NOTIF_ONE, initial_owner, MSG_CENTER, APP_TEAM_NUM(initial_owner.team, CENTER_KEYHUNT_START));
-
- WaypointSprite_Spawn(WP_KeyDropped, 0, 0, key, '0 0 1' * KH_KEY_WP_ZSHIFT, NULL, key.team, key, waypointsprite_attachedforcarrier, false, RADARICON_FLAG);
- key.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = kh_Key_waypointsprite_visible_for_player;
-
- kh_Key_AssignTo(key, initial_owner);
-}
-
-// -1 when no team completely owns all keys yet
-int kh_Key_AllOwnedByWhichTeam() // constantly called. check to see if all the keys are owned by the same team
-{
- entity key;
- int teem = -1;
- int keys = NumTeams(kh_teams);
- FOR_EACH_KH_KEY(key)
- {
- if(!key.owner)
- return -1;
- if(teem == -1)
- teem = key.team;
- else if(teem != key.team)
- return -1;
- --keys;
- }
- if(keys != 0)
- return -1;
- return teem;
-}
-
-void kh_Key_DropOne(entity key)
-{
- // prevent collecting this one for some time
- entity player = key.owner;
-
- key.kh_droptime = time;
- key.enemy = player;
-
- kh_Scores_Event(player, key, "dropkey", 0, 0);
- GameRules_scoring_add(player, KH_LOSSES, 1);
- int realteam = kh_Team_ByID(key.count);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_DROP), player.netname);
-
- kh_Key_AssignTo(key, NULL);
- makevectors(player.v_angle);
- key.velocity = W_CalculateProjectileVelocity(player, player.velocity, autocvar_g_balance_keyhunt_throwvelocity * v_forward, false);
- key.pusher = NULL;
- key.pushltime = time + autocvar_g_balance_keyhunt_protecttime;
- key.kh_dropperteam = key.team;
-
- sound(player, CH_TRIGGER, SND_KH_DROP, VOL_BASE, ATTEN_NORM);
-}
-
-void kh_Key_DropAll(entity player, float suicide) // runs whenever a player dies
-{
- if(player.kh_next)
- {
- entity mypusher = NULL;
- if(player.pusher)
- if(time < player.pushltime)
- mypusher = player.pusher;
-
- entity key;
- while((key = player.kh_next))
- {
- kh_Scores_Event(player, key, "losekey", 0, 0);
- GameRules_scoring_add(player, KH_LOSSES, 1);
- int realteam = kh_Team_ByID(key.count);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_LOST), player.netname);
- kh_Key_AssignTo(key, NULL);
- makevectors('-1 0 0' * (45 + 45 * random()) + '0 360 0' * random());
- key.velocity = W_CalculateProjectileVelocity(player, player.velocity, autocvar_g_balance_keyhunt_dropvelocity * v_forward, false);
- key.pusher = mypusher;
- key.pushltime = time + autocvar_g_balance_keyhunt_protecttime;
- if(suicide)
- key.kh_dropperteam = player.team;
- }
- sound(player, CH_TRIGGER, SND_KH_DROP, VOL_BASE, ATTEN_NORM);
- }
-}
-
-int kh_GetMissingTeams()
-{
- int missing_teams = 0;
- for(int i = 0; i < NumTeams(kh_teams); ++i)
- {
- int teem = kh_Team_ByID(i);
- int players = 0;
- FOREACH_CLIENT(IS_PLAYER(it), {
- if(!IS_DEAD(it) && !PHYS_INPUT_BUTTON_CHAT(it) && it.team == teem)
- ++players;
- });
- if (!players)
- missing_teams |= (2 ** i);
- }
- return missing_teams;
-}
-
-void kh_WaitForPlayers() // delay start of the round until enough players are present
-{
- static int prev_missing_teams_mask;
- if(time < game_starttime)
- {
- if (prev_missing_teams_mask > 0)
- Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
- prev_missing_teams_mask = -1;
- kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers);
- return;
- }
-
- int missing_teams_mask = kh_GetMissingTeams();
- if(!missing_teams_mask)
- {
- if(prev_missing_teams_mask > 0)
- Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
- prev_missing_teams_mask = -1;
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEYHUNT_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round);
- kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round, kh_StartRound);
- }
- else
- {
- if(player_count == 0)
- {
- if(prev_missing_teams_mask > 0)
- Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
- prev_missing_teams_mask = -1;
- }
- else
- {
- if(prev_missing_teams_mask != missing_teams_mask)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
- prev_missing_teams_mask = missing_teams_mask;
- }
- }
- kh_Controller_SetThink(1, kh_WaitForPlayers);
- }
-}
-
-void kh_EnableTrackingDevice() // runs after each round
-{
- Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT);
- Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT_OTHER);
-
- kh_tracking_enabled = true;
-}
-
-void kh_StartRound() // runs at the start of each round
-{
- if(time < game_starttime)
- {
- kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers);
- return;
- }
-
- if(kh_GetMissingTeams())
- {
- kh_Controller_SetThink(1, kh_WaitForPlayers);
- return;
- }
-
- Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT);
- Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT_OTHER);
-
- for(int i = 0; i < NumTeams(kh_teams); ++i)
- {
- int teem = kh_Team_ByID(i);
- int players = 0;
- entity my_player = NULL;
- FOREACH_CLIENT(IS_PLAYER(it), {
- if(!IS_DEAD(it) && !PHYS_INPUT_BUTTON_CHAT(it) && it.team == teem)
- {
- ++players;
- if(random() * players <= 1)
- my_player = it;
- }
- });
- kh_Key_Spawn(my_player, 360 * i / NumTeams(kh_teams), i);
- }
-
- kh_tracking_enabled = false;
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEYHUNT_SCAN, autocvar_g_balance_keyhunt_delay_tracking);
- kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_tracking, kh_EnableTrackingDevice);
-}
-
-float kh_HandleFrags(entity attacker, entity targ, float f) // adds to the player score
-{
- if(attacker == targ)
- return f;
-
- if(targ.kh_next)
- {
- if(attacker.team == targ.team)
- {
- int nk = 0;
- for(entity k = targ.kh_next; k != NULL; k = k.kh_next)
- ++nk;
- kh_Scores_Event(attacker, targ.kh_next, "carrierfrag", -nk * autocvar_g_balance_keyhunt_score_collect, 0);
- }
- else
- {
- kh_Scores_Event(attacker, targ.kh_next, "carrierfrag", autocvar_g_balance_keyhunt_score_carrierfrag-1, 0);
- GameRules_scoring_add(attacker, KH_KCKILLS, 1);
- // the frag gets added later
- }
- }
-
- return f;
-}
-
-void kh_Initialize() // sets up th KH environment
-{
- // setup variables
- kh_teams = autocvar_g_keyhunt_teams_override;
- if(kh_teams < 2)
- kh_teams = cvar("g_keyhunt_teams"); // read the cvar directly as it gets written earlier in the same frame
- kh_teams = BITS(bound(2, kh_teams, 4));
-
- // make a KH entity for controlling the game
- kh_controller = spawn();
- setthink(kh_controller, kh_Controller_Think);
- kh_Controller_SetThink(0, kh_WaitForPlayers);
-
- setmodel(kh_controller, MDL_KH_KEY);
- kh_key_dropped = kh_controller.modelindex;
- /*
- dprint(vtos(kh_controller.mins));
- dprint(vtos(kh_controller.maxs));
- dprint("\n");
- */
-#ifdef KH_PLAYER_USE_CARRIEDMODEL
- setmodel(kh_controller, MDL_KH_KEY_CARRIED);
- kh_key_carried = kh_controller.modelindex;
-#else
- kh_key_carried = kh_key_dropped;
-#endif
-
- kh_controller.model = "";
- kh_controller.modelindex = 0;
-
- kh_ScoreRules(kh_teams);
-}
-
-void kh_finalize()
-{
- // to be called before intermission
- kh_FinishRound();
- delete(kh_controller);
- kh_controller = NULL;
-}
-
-// legacy bot role
-
-void(entity this) havocbot_role_kh_carrier;
-void(entity this) havocbot_role_kh_defense;
-void(entity this) havocbot_role_kh_offense;
-void(entity this) havocbot_role_kh_freelancer;
-
-
-void havocbot_goalrating_kh(entity this, float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy)
-{
- entity head;
- for (head = kh_worldkeylist; head; head = head.kh_worldkeynext)
- {
- if(head.owner == this)
- continue;
- if(!kh_tracking_enabled)
- {
- // if it's carried by our team we know about it
- // otherwise we have to see it to know about it
- if(!head.owner || head.team != this.team)
- {
- traceline(this.origin + this.view_ofs, head.origin, MOVE_NOMONSTERS, this);
- if (trace_fraction < 1 && trace_ent != head)
- continue; // skip what I can't see
- }
- }
- if(!head.owner)
- navigation_routerating(this, head, ratingscale_dropped * 10000, 100000);
- else if(head.team == this.team)
- navigation_routerating(this, head.owner, ratingscale_team * 10000, 100000);
- else
- navigation_routerating(this, head.owner, ratingscale_enemy * 10000, 100000);
- }
-
- havocbot_goalrating_items(this, 1, this.origin, 10000);
-}
-
-void havocbot_role_kh_carrier(entity this)
-{
- if(IS_DEAD(this))
- return;
-
- if (!(this.kh_next))
- {
- LOG_TRACE("changing role to freelancer");
- this.havocbot_role = havocbot_role_kh_freelancer;
- this.havocbot_role_timeout = 0;
- return;
- }
-
- if (navigation_goalrating_timeout(this))
- {
- navigation_goalrating_start(this);
-
- if(kh_Key_AllOwnedByWhichTeam() == this.team)
- havocbot_goalrating_kh(this, 10, 0.1, 0.1); // bring home
- else
- havocbot_goalrating_kh(this, 4, 4, 1); // play defensively
-
- navigation_goalrating_end(this);
-
- navigation_goalrating_timeout_set(this);
- }
-}
-
-void havocbot_role_kh_defense(entity this)
-{
- if(IS_DEAD(this))
- return;
-
- if (this.kh_next)
- {
- LOG_TRACE("changing role to carrier");
- this.havocbot_role = havocbot_role_kh_carrier;
- this.havocbot_role_timeout = 0;
- return;
- }
-
- if (!this.havocbot_role_timeout)
- this.havocbot_role_timeout = time + random() * 10 + 20;
- if (time > this.havocbot_role_timeout)
- {
- LOG_TRACE("changing role to freelancer");
- this.havocbot_role = havocbot_role_kh_freelancer;
- this.havocbot_role_timeout = 0;
- return;
- }
-
- if (navigation_goalrating_timeout(this))
- {
- float key_owner_team;
- navigation_goalrating_start(this);
-
- key_owner_team = kh_Key_AllOwnedByWhichTeam();
- if(key_owner_team == this.team)
- havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend key carriers
- else if(key_owner_team == -1)
- havocbot_goalrating_kh(this, 4, 1, 0.1); // play defensively
- else
- havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK ANYWAY
-
- navigation_goalrating_end(this);
-
- navigation_goalrating_timeout_set(this);
- }
-}
-
-void havocbot_role_kh_offense(entity this)
-{
- if(IS_DEAD(this))
- return;
-
- if (this.kh_next)
- {
- LOG_TRACE("changing role to carrier");
- this.havocbot_role = havocbot_role_kh_carrier;
- this.havocbot_role_timeout = 0;
- return;
- }
-
- if (!this.havocbot_role_timeout)
- this.havocbot_role_timeout = time + random() * 10 + 20;
- if (time > this.havocbot_role_timeout)
- {
- LOG_TRACE("changing role to freelancer");
- this.havocbot_role = havocbot_role_kh_freelancer;
- this.havocbot_role_timeout = 0;
- return;
- }
-
- if (navigation_goalrating_timeout(this))
- {
- float key_owner_team;
-
- navigation_goalrating_start(this);
-
- key_owner_team = kh_Key_AllOwnedByWhichTeam();
- if(key_owner_team == this.team)
- havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend anyway
- else if(key_owner_team == -1)
- havocbot_goalrating_kh(this, 0.1, 1, 4); // play offensively
- else
- havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK! EMERGENCY!
-
- navigation_goalrating_end(this);
-
- navigation_goalrating_timeout_set(this);
- }
-}
-
-void havocbot_role_kh_freelancer(entity this)
-{
- if(IS_DEAD(this))
- return;
-
- if (this.kh_next)
- {
- LOG_TRACE("changing role to carrier");
- this.havocbot_role = havocbot_role_kh_carrier;
- this.havocbot_role_timeout = 0;
- return;
- }
-
- if (!this.havocbot_role_timeout)
- this.havocbot_role_timeout = time + random() * 10 + 10;
- if (time > this.havocbot_role_timeout)
- {
- if (random() < 0.5)
- {
- LOG_TRACE("changing role to offense");
- this.havocbot_role = havocbot_role_kh_offense;
- }
- else
- {
- LOG_TRACE("changing role to defense");
- this.havocbot_role = havocbot_role_kh_defense;
- }
- this.havocbot_role_timeout = 0;
- return;
- }
-
- if (navigation_goalrating_timeout(this))
- {
- navigation_goalrating_start(this);
-
- int key_owner_team = kh_Key_AllOwnedByWhichTeam();
- if(key_owner_team == this.team)
- havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend anyway
- else if(key_owner_team == -1)
- havocbot_goalrating_kh(this, 1, 10, 4); // prefer dropped keys
- else
- havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK ANYWAY
-
- navigation_goalrating_end(this);
-
- navigation_goalrating_timeout_set(this);
- }
-}
-
-
-// register this as a mutator
-
-MUTATOR_HOOKFUNCTION(kh, ClientDisconnect)
-{
- entity player = M_ARGV(0, entity);
-
- kh_Key_DropAll(player, true);
-}
-
-MUTATOR_HOOKFUNCTION(kh, MakePlayerObserver)
-{
- entity player = M_ARGV(0, entity);
-
- kh_Key_DropAll(player, true);
-}
-
-MUTATOR_HOOKFUNCTION(kh, PlayerDies)
-{
- entity frag_attacker = M_ARGV(1, entity);
- entity frag_target = M_ARGV(2, entity);
-
- if(frag_target == frag_attacker)
- kh_Key_DropAll(frag_target, true);
- else if(IS_PLAYER(frag_attacker))
- kh_Key_DropAll(frag_target, false);
- else
- kh_Key_DropAll(frag_target, true);
-}
-
-MUTATOR_HOOKFUNCTION(kh, GiveFragsForKill, CBC_ORDER_FIRST)
-{
- entity frag_attacker = M_ARGV(0, entity);
- entity frag_target = M_ARGV(1, entity);
- float frag_score = M_ARGV(2, float);
- M_ARGV(2, float) = kh_HandleFrags(frag_attacker, frag_target, frag_score);
-}
-
-MUTATOR_HOOKFUNCTION(kh, MatchEnd)
-{
- kh_finalize();
-}
-
-MUTATOR_HOOKFUNCTION(kh, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
-{
- M_ARGV(0, float) = kh_teams;
-}
-
-MUTATOR_HOOKFUNCTION(kh, SpectateCopy)
-{
- entity spectatee = M_ARGV(0, entity);
- entity client = M_ARGV(1, entity);
-
- STAT(KH_KEYS, client) = STAT(KH_KEYS, spectatee);
-}
-
-MUTATOR_HOOKFUNCTION(kh, PlayerUseKey)
-{
- entity player = M_ARGV(0, entity);
-
- if(MUTATOR_RETURNVALUE == 0)
- {
- entity k = player.kh_next;
- if(k)
- {
- kh_Key_DropOne(k);
- return true;
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(kh, HavocBot_ChooseRole)
-{
- entity bot = M_ARGV(0, entity);
-
- if(IS_DEAD(bot))
- return true;
-
- float r = random() * 3;
- if (r < 1)
- bot.havocbot_role = havocbot_role_kh_offense;
- else if (r < 2)
- bot.havocbot_role = havocbot_role_kh_defense;
- else
- bot.havocbot_role = havocbot_role_kh_freelancer;
-
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(kh, DropSpecialItems)
-{
- entity frag_target = M_ARGV(0, entity);
-
- kh_Key_DropAll(frag_target, false);
-}
-
-MUTATOR_HOOKFUNCTION(kh, reset_map_global)
-{
- kh_WaitForPlayers(); // takes care of killing the "missing teams" message
-}
-#endif
+++ /dev/null
-#pragma once
-
-#ifdef SVQC
-#include <common/mutators/base.qh>
-#define autocvar_g_keyhunt_point_limit cvar("g_keyhunt_point_limit")
-int autocvar_g_keyhunt_point_leadlimit;
-bool autocvar_g_keyhunt_team_spawns;
-void kh_Initialize();
-
-REGISTER_MUTATOR(kh, false)
-{
- MUTATOR_STATIC();
- MUTATOR_ONADD
- {
- GameRules_teams(true);
- GameRules_spawning_teams(autocvar_g_keyhunt_team_spawns);
- GameRules_limit_score(autocvar_g_keyhunt_point_limit);
- GameRules_limit_lead(autocvar_g_keyhunt_point_leadlimit);
-
- kh_Initialize();
- }
- return 0;
-}
-
-#define FOR_EACH_KH_KEY(v) for(v = kh_worldkeylist; v; v = v.kh_worldkeynext )
-
-// ALL OF THESE should be removed in the future, as other code should not have to care
-
-// used by bots:
-bool kh_tracking_enabled;
-.entity kh_next;
-
-USING(kh_Think_t, void());
-void kh_StartRound();
-void kh_Controller_SetThink(float t, kh_Think_t func);
-#endif
--- /dev/null
+#include "sv_keyhunt.qh"
+
+float autocvar_g_balance_keyhunt_damageforcescale;
+float autocvar_g_balance_keyhunt_delay_collect;
+float autocvar_g_balance_keyhunt_delay_damage_return;
+float autocvar_g_balance_keyhunt_delay_return;
+float autocvar_g_balance_keyhunt_delay_round;
+float autocvar_g_balance_keyhunt_delay_tracking;
+float autocvar_g_balance_keyhunt_return_when_unreachable;
+float autocvar_g_balance_keyhunt_dropvelocity;
+float autocvar_g_balance_keyhunt_maxdist;
+float autocvar_g_balance_keyhunt_protecttime;
+
+int autocvar_g_balance_keyhunt_score_capture;
+int autocvar_g_balance_keyhunt_score_carrierfrag;
+int autocvar_g_balance_keyhunt_score_collect;
+int autocvar_g_balance_keyhunt_score_destroyed;
+int autocvar_g_balance_keyhunt_score_destroyed_ownfactor;
+int autocvar_g_balance_keyhunt_score_push;
+float autocvar_g_balance_keyhunt_throwvelocity;
+
+//int autocvar_g_keyhunt_teams;
+int autocvar_g_keyhunt_teams_override;
+
+// #define KH_PLAYER_USE_ATTACHMENT
+// #define KH_PLAYER_USE_CARRIEDMODEL
+
+#ifdef KH_PLAYER_USE_ATTACHMENT
+const vector KH_PLAYER_ATTACHMENT_DIST_ROTATED = '0 -4 0';
+const vector KH_PLAYER_ATTACHMENT_DIST = '4 0 0';
+const vector KH_PLAYER_ATTACHMENT = '0 0 0';
+const vector KH_PLAYER_ATTACHMENT_ANGLES = '0 0 0';
+const string KH_PLAYER_ATTACHMENT_BONE = "";
+#else
+const float KH_KEY_ZSHIFT = 22;
+const float KH_KEY_XYDIST = 24;
+const float KH_KEY_XYSPEED = 45;
+#endif
+const float KH_KEY_WP_ZSHIFT = 20;
+
+const vector KH_KEY_MIN = '-10 -10 -46';
+const vector KH_KEY_MAX = '10 10 3';
+const float KH_KEY_BRIGHTNESS = 2;
+
+bool kh_no_radar_circles;
+
+// kh_state
+// bits 0- 4: team of key 1, or 0 for no such key, or 30 for dropped, or 31 for self
+// bits 5- 9: team of key 2, or 0 for no such key, or 30 for dropped, or 31 for self
+// bits 10-14: team of key 3, or 0 for no such key, or 30 for dropped, or 31 for self
+// bits 15-19: team of key 4, or 0 for no such key, or 30 for dropped, or 31 for self
+.float siren_time; // time delay the siren
+//.float stuff_time; // time delay to stuffcmd a cvar
+
+int kh_Team_ByID(int t)
+{
+ if(t == 0) return NUM_TEAM_1;
+ if(t == 1) return NUM_TEAM_2;
+ if(t == 2) return NUM_TEAM_3;
+ if(t == 3) return NUM_TEAM_4;
+ return 0;
+}
+
+//entity kh_worldkeylist;
+.entity kh_worldkeynext;
+entity kh_controller;
+//bool kh_tracking_enabled;
+int kh_teams;
+int kh_interferemsg_team;
+float kh_interferemsg_time;
+.entity kh_next, kh_prev; // linked list
+.float kh_droptime;
+.int kh_dropperteam;
+.entity kh_previous_owner;
+.int kh_previous_owner_playerid;
+
+int kh_key_dropped, kh_key_carried;
+
+int kh_Key_AllOwnedByWhichTeam();
+
+const int ST_KH_CAPS = 1;
+void kh_ScoreRules(int teams)
+{
+ GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, {
+ field_team(ST_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
+ field(SP_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
+ field(SP_KH_PUSHES, "pushes", 0);
+ field(SP_KH_DESTROYS, "destroyed", SFL_LOWER_IS_BETTER);
+ field(SP_KH_PICKUPS, "pickups", 0);
+ field(SP_KH_KCKILLS, "kckills", 0);
+ field(SP_KH_LOSSES, "losses", SFL_LOWER_IS_BETTER);
+ });
+}
+
+bool kh_KeyCarrier_waypointsprite_visible_for_player(entity this, entity player, entity view) // runs all the time
+{
+ if(!IS_PLAYER(view) || DIFF_TEAM(this, view))
+ if(!kh_tracking_enabled)
+ return false;
+
+ return true;
+}
+
+bool kh_Key_waypointsprite_visible_for_player(entity this, entity player, entity view)
+{
+ if(!kh_tracking_enabled)
+ return false;
+ if(!this.owner)
+ return true;
+ if(!this.owner.owner)
+ return true;
+ return false; // draw only when key is not owned
+}
+
+void kh_update_state()
+{
+ entity key;
+ int f;
+ int s = 0;
+ FOR_EACH_KH_KEY(key)
+ {
+ if(key.owner)
+ f = key.team;
+ else
+ f = 30;
+ s |= (32 ** key.count) * f;
+ }
+
+ FOREACH_CLIENT(true, { STAT(KH_KEYS, it) = s; });
+
+ FOR_EACH_KH_KEY(key)
+ {
+ if(key.owner)
+ STAT(KH_KEYS, key.owner) |= (32 ** key.count) * 31;
+ }
+ //print(ftos((nextent(NULL)).kh_state), "\n");
+}
+
+
+
+
+var kh_Think_t kh_Controller_Thinkfunc;
+void kh_Controller_SetThink(float t, kh_Think_t func) // runs occasionaly
+{
+ kh_Controller_Thinkfunc = func;
+ kh_controller.cnt = ceil(t);
+ if(t == 0)
+ kh_controller.nextthink = time; // force
+}
+void kh_WaitForPlayers();
+void kh_Controller_Think(entity this) // called a lot
+{
+ if(game_stopped)
+ return;
+ if(this.cnt > 0)
+ {
+ if(getthink(this) != kh_WaitForPlayers)
+ this.cnt -= 1;
+ }
+ else if(this.cnt == 0)
+ {
+ this.cnt -= 1;
+ kh_Controller_Thinkfunc();
+ }
+ this.nextthink = time + 1;
+}
+
+// frags f: take from cvar * f
+// frags 0: no frags
+void kh_Scores_Event(entity player, entity key, string what, float frags_player, float frags_owner) // update the score when a key is captured
+{
+ string s;
+ if(game_stopped)
+ return;
+
+ if(frags_player)
+ UpdateFrags(player, frags_player);
+
+ if(key && key.owner && frags_owner)
+ UpdateFrags(key.owner, frags_owner);
+
+ if(!autocvar_sv_eventlog) //output extra info to the console or text file
+ return;
+
+ s = strcat(":keyhunt:", what, ":", ftos(player.playerid), ":", ftos(frags_player));
+
+ if(key && key.owner)
+ s = strcat(s, ":", ftos(key.owner.playerid));
+ else
+ s = strcat(s, ":0");
+
+ s = strcat(s, ":", ftos(frags_owner), ":");
+
+ if(key)
+ s = strcat(s, key.netname);
+
+ GameLogEcho(s);
+}
+
+vector kh_AttachedOrigin(entity e) // runs when a team captures the flag, it can run 2 or 3 times.
+{
+ if(e.tag_entity)
+ {
+ makevectors(e.tag_entity.angles);
+ return e.tag_entity.origin + e.origin.x * v_forward - e.origin.y * v_right + e.origin.z * v_up;
+ }
+ else
+ return e.origin;
+}
+
+void kh_Key_Attach(entity key) // runs when a player picks up a key and several times when a key is assigned to a player at the start of a round
+{
+#ifdef KH_PLAYER_USE_ATTACHMENT
+ entity first = key.owner.kh_next;
+ if(key == first)
+ {
+ setattachment(key, key.owner, KH_PLAYER_ATTACHMENT_BONE);
+ if(key.kh_next)
+ {
+ setattachment(key.kh_next, key, "");
+ setorigin(key, key.kh_next.origin - 0.5 * KH_PLAYER_ATTACHMENT_DIST);
+ setorigin(key.kh_next, KH_PLAYER_ATTACHMENT_DIST_ROTATED);
+ key.kh_next.angles = '0 0 0';
+ }
+ else
+ setorigin(key, KH_PLAYER_ATTACHMENT);
+ key.angles = KH_PLAYER_ATTACHMENT_ANGLES;
+ }
+ else
+ {
+ setattachment(key, key.kh_prev, "");
+ if(key.kh_next)
+ setattachment(key.kh_next, key, "");
+ setorigin(key, KH_PLAYER_ATTACHMENT_DIST_ROTATED);
+ setorigin(first, first.origin - 0.5 * KH_PLAYER_ATTACHMENT_DIST);
+ key.angles = '0 0 0';
+ }
+#else
+ setattachment(key, key.owner, "");
+ setorigin(key, '0 0 1' * KH_KEY_ZSHIFT); // fixing x, y in think
+ key.angles_y -= key.owner.angles.y;
+#endif
+ key.flags = 0;
+ if(IL_CONTAINS(g_items, key))
+ IL_REMOVE(g_items, key);
+ key.solid = SOLID_NOT;
+ set_movetype(key, MOVETYPE_NONE);
+ key.team = key.owner.team;
+ key.nextthink = time;
+ key.damageforcescale = 0;
+ key.takedamage = DAMAGE_NO;
+ key.modelindex = kh_key_carried;
+ navigation_dynamicgoal_unset(key);
+}
+
+void kh_Key_Detach(entity key) // runs every time a key is dropped or lost. Runs several times times when all the keys are captured
+{
+#ifdef KH_PLAYER_USE_ATTACHMENT
+ entity first = key.owner.kh_next;
+ if(key == first)
+ {
+ if(key.kh_next)
+ {
+ setattachment(key.kh_next, key.owner, KH_PLAYER_ATTACHMENT_BONE);
+ setorigin(key.kh_next, key.origin + 0.5 * KH_PLAYER_ATTACHMENT_DIST);
+ key.kh_next.angles = KH_PLAYER_ATTACHMENT_ANGLES;
+ }
+ }
+ else
+ {
+ if(key.kh_next)
+ setattachment(key.kh_next, key.kh_prev, "");
+ setorigin(first, first.origin + 0.5 * KH_PLAYER_ATTACHMENT_DIST);
+ }
+ // in any case:
+ setattachment(key, NULL, "");
+ setorigin(key, key.owner.origin + '0 0 1' * (STAT(PL_MIN, key.owner).z - KH_KEY_MIN_z));
+ key.angles = key.owner.angles;
+#else
+ setorigin(key, key.owner.origin + key.origin.z * '0 0 1');
+ setattachment(key, NULL, "");
+ key.angles_y += key.owner.angles.y;
+#endif
+ key.flags = FL_ITEM;
+ if(!IL_CONTAINS(g_items, key))
+ IL_PUSH(g_items, key);
+ key.solid = SOLID_TRIGGER;
+ set_movetype(key, MOVETYPE_TOSS);
+ key.pain_finished = time + autocvar_g_balance_keyhunt_delay_return;
+ key.damageforcescale = autocvar_g_balance_keyhunt_damageforcescale;
+ key.takedamage = DAMAGE_YES;
+ // let key.team stay
+ key.modelindex = kh_key_dropped;
+ navigation_dynamicgoal_set(key, key.owner);
+ key.kh_previous_owner = key.owner;
+ key.kh_previous_owner_playerid = key.owner.playerid;
+}
+
+void kh_Key_AssignTo(entity key, entity player) // runs every time a key is picked up or assigned. Runs prior to kh_key_attach
+{
+ if(key.owner == player)
+ return;
+
+ int ownerteam0 = kh_Key_AllOwnedByWhichTeam();
+
+ if(key.owner)
+ {
+ kh_Key_Detach(key);
+
+ // remove from linked list
+ if(key.kh_next)
+ key.kh_next.kh_prev = key.kh_prev;
+ key.kh_prev.kh_next = key.kh_next;
+ key.kh_next = NULL;
+ key.kh_prev = NULL;
+
+ if(key.owner.kh_next == NULL)
+ {
+ // No longer a key carrier
+ if(!kh_no_radar_circles)
+ WaypointSprite_Ping(key.owner.waypointsprite_attachedforcarrier);
+ WaypointSprite_DetachCarrier(key.owner);
+ }
+ }
+
+ key.owner = player;
+
+ if(player)
+ {
+ // insert into linked list
+ key.kh_next = player.kh_next;
+ key.kh_prev = player;
+ player.kh_next = key;
+ if(key.kh_next)
+ key.kh_next.kh_prev = key;
+
+ kh_Key_Attach(key);
+
+ if(key.kh_next == NULL)
+ {
+ // player is now a key carrier
+ entity wp = WaypointSprite_AttachCarrier(WP_Null, player, RADARICON_FLAGCARRIER);
+ wp.colormod = colormapPaletteColor(player.team - 1, 0);
+ player.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = kh_KeyCarrier_waypointsprite_visible_for_player;
+ WaypointSprite_UpdateRule(player.waypointsprite_attachedforcarrier, player.team, SPRITERULE_TEAMPLAY);
+ if(player.team == NUM_TEAM_1)
+ WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierRed, WP_KeyCarrierFriend, WP_KeyCarrierRed);
+ else if(player.team == NUM_TEAM_2)
+ WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierBlue, WP_KeyCarrierFriend, WP_KeyCarrierBlue);
+ else if(player.team == NUM_TEAM_3)
+ WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierYellow, WP_KeyCarrierFriend, WP_KeyCarrierYellow);
+ else if(player.team == NUM_TEAM_4)
+ WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierPink, WP_KeyCarrierFriend, WP_KeyCarrierPink);
+ if(!kh_no_radar_circles)
+ WaypointSprite_Ping(player.waypointsprite_attachedforcarrier);
+ }
+ }
+
+ // moved that here, also update if there's no player
+ kh_update_state();
+
+ key.pusher = NULL;
+
+ int ownerteam = kh_Key_AllOwnedByWhichTeam();
+ if(ownerteam != ownerteam0)
+ {
+ entity k;
+ if(ownerteam != -1)
+ {
+ kh_interferemsg_time = time + 0.2;
+ kh_interferemsg_team = player.team;
+
+ // audit all key carrier sprites, update them to "Run here"
+ FOR_EACH_KH_KEY(k)
+ {
+ if (!k.owner) continue;
+ entity first = WP_Null;
+ FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model1, { first = it; break; });
+ entity third = WP_Null;
+ FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, { third = it; break; });
+ WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, first, WP_KeyCarrierFinish, third);
+ }
+ }
+ else
+ {
+ kh_interferemsg_time = 0;
+
+ // audit all key carrier sprites, update them to "Key Carrier"
+ FOR_EACH_KH_KEY(k)
+ {
+ if (!k.owner) continue;
+ entity first = WP_Null;
+ FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model1, { first = it; break; });
+ entity third = WP_Null;
+ FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, { third = it; break; });
+ WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, first, WP_KeyCarrierFriend, third);
+ }
+ }
+ }
+}
+
+void kh_Key_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
+{
+ if(this.owner)
+ return;
+ if(ITEM_DAMAGE_NEEDKILL(deathtype))
+ {
+ this.pain_finished = bound(time, time + autocvar_g_balance_keyhunt_delay_damage_return, this.pain_finished);
+ return;
+ }
+ if(force == '0 0 0')
+ return;
+ if(time > this.pushltime)
+ if(IS_PLAYER(attacker))
+ this.team = attacker.team;
+}
+
+void kh_Key_Collect(entity key, entity player) //a player picks up a dropped key
+{
+ sound(player, CH_TRIGGER, SND_KH_COLLECT, VOL_BASE, ATTEN_NORM);
+
+ if(key.kh_dropperteam != player.team)
+ {
+ kh_Scores_Event(player, key, "collect", autocvar_g_balance_keyhunt_score_collect, 0);
+ GameRules_scoring_add(player, KH_PICKUPS, 1);
+ }
+ key.kh_dropperteam = 0;
+ int realteam = kh_Team_ByID(key.count);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_PICKUP), player.netname);
+
+ kh_Key_AssignTo(key, player); // this also updates .kh_state
+}
+
+void kh_Key_Touch(entity this, entity toucher) // runs many, many times when a key has been dropped and can be picked up
+{
+ if(game_stopped)
+ return;
+
+ if(this.owner) // already carried
+ return;
+
+ if(ITEM_TOUCH_NEEDKILL())
+ {
+ this.pain_finished = bound(time, time + autocvar_g_balance_keyhunt_delay_damage_return, this.pain_finished);
+ return;
+ }
+
+ if (!IS_PLAYER(toucher))
+ return;
+ if(IS_DEAD(toucher))
+ return;
+ if(toucher == this.enemy)
+ if(time < this.kh_droptime + autocvar_g_balance_keyhunt_delay_collect)
+ return; // you just dropped it!
+ kh_Key_Collect(this, toucher);
+}
+
+void kh_Key_Remove(entity key) // runs after when all the keys have been collected or when a key has been dropped for more than X seconds
+{
+ entity o = key.owner;
+ kh_Key_AssignTo(key, NULL);
+ if(o) // it was attached
+ WaypointSprite_Kill(key.waypointsprite_attachedforcarrier);
+ else // it was dropped
+ WaypointSprite_DetachCarrier(key);
+
+ // remove key from key list
+ if (kh_worldkeylist == key)
+ kh_worldkeylist = kh_worldkeylist.kh_worldkeynext;
+ else
+ {
+ o = kh_worldkeylist;
+ while (o)
+ {
+ if (o.kh_worldkeynext == key)
+ {
+ o.kh_worldkeynext = o.kh_worldkeynext.kh_worldkeynext;
+ break;
+ }
+ o = o.kh_worldkeynext;
+ }
+ }
+
+ delete(key);
+
+ kh_update_state();
+}
+
+void kh_FinishRound() // runs when a team captures the keys
+{
+ // prepare next round
+ kh_interferemsg_time = 0;
+ entity key;
+
+ kh_no_radar_circles = true;
+ FOR_EACH_KH_KEY(key)
+ kh_Key_Remove(key);
+ kh_no_radar_circles = false;
+
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEYHUNT_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round);
+ kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round, kh_StartRound);
+}
+
+void nades_GiveBonus(entity player, float score);
+
+void kh_WinnerTeam(int winner_team) // runs when a team wins
+{
+ // all key carriers get some points
+ entity key;
+ float score = (NumTeams(kh_teams) - 1) * autocvar_g_balance_keyhunt_score_capture;
+ DistributeEvenly_Init(score, NumTeams(kh_teams));
+ // twice the score for 3 team games, three times the score for 4 team games!
+ // note: for a win by destroying the key, this should NOT be applied
+ FOR_EACH_KH_KEY(key)
+ {
+ float f = DistributeEvenly_Get(1);
+ kh_Scores_Event(key.owner, key, "capture", f, 0);
+ GameRules_scoring_add_team(key.owner, KH_CAPS, 1);
+ nades_GiveBonus(key.owner, autocvar_g_nades_bonus_score_high);
+ }
+
+ bool first = true;
+ string keyowner = "";
+ FOR_EACH_KH_KEY(key)
+ if(key.owner.kh_next == key)
+ {
+ if(!first)
+ keyowner = strcat(keyowner, ", ");
+ keyowner = key.owner.netname;
+ first = false;
+ }
+
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_KEYHUNT_CAPTURE), keyowner);
+
+ first = true;
+ vector firstorigin = '0 0 0', lastorigin = '0 0 0', midpoint = '0 0 0';
+ FOR_EACH_KH_KEY(key)
+ {
+ vector thisorigin = kh_AttachedOrigin(key);
+ //dprint("Key origin: ", vtos(thisorigin), "\n");
+ midpoint += thisorigin;
+
+ if(!first)
+ te_lightning2(NULL, lastorigin, thisorigin);
+ lastorigin = thisorigin;
+ if(first)
+ firstorigin = thisorigin;
+ first = false;
+ }
+ if(NumTeams(kh_teams) > 2)
+ {
+ te_lightning2(NULL, lastorigin, firstorigin);
+ }
+ midpoint = midpoint * (1 / NumTeams(kh_teams));
+ te_customflash(midpoint, 1000, 1, Team_ColorRGB(winner_team) * 0.5 + '0.5 0.5 0.5'); // make the color >=0.5 in each component
+
+ play2all(SND(KH_CAPTURE));
+ kh_FinishRound();
+}
+
+void kh_LoserTeam(int loser_team, entity lostkey) // runs when a player pushes a flag carrier off the map
+{
+ float f;
+ entity attacker = NULL;
+ if(lostkey.pusher)
+ if(lostkey.pusher.team != loser_team)
+ if(IS_PLAYER(lostkey.pusher))
+ attacker = lostkey.pusher;
+
+ if(attacker)
+ {
+ if(lostkey.kh_previous_owner)
+ kh_Scores_Event(lostkey.kh_previous_owner, NULL, "pushed", 0, -autocvar_g_balance_keyhunt_score_push);
+ // don't actually GIVE him the -nn points, just log
+ kh_Scores_Event(attacker, NULL, "push", autocvar_g_balance_keyhunt_score_push, 0);
+ GameRules_scoring_add(attacker, KH_PUSHES, 1);
+ //centerprint(attacker, "Your push is the best!"); // does this really need to exist?
+ }
+ else
+ {
+ int players = 0;
+ float of = autocvar_g_balance_keyhunt_score_destroyed_ownfactor;
+
+ FOREACH_CLIENT(IS_PLAYER(it) && it.team != loser_team, { ++players; });
+
+ entity key;
+ int keys = 0;
+ FOR_EACH_KH_KEY(key)
+ if(key.owner && key.team != loser_team)
+ ++keys;
+
+ if(lostkey.kh_previous_owner)
+ kh_Scores_Event(lostkey.kh_previous_owner, NULL, "destroyed", 0, -autocvar_g_balance_keyhunt_score_destroyed);
+ // don't actually GIVE him the -nn points, just log
+
+ if(lostkey.kh_previous_owner.playerid == lostkey.kh_previous_owner_playerid)
+ GameRules_scoring_add(lostkey.kh_previous_owner, KH_DESTROYS, 1);
+
+ DistributeEvenly_Init(autocvar_g_balance_keyhunt_score_destroyed, keys * of + players);
+
+ FOR_EACH_KH_KEY(key)
+ if(key.owner && key.team != loser_team)
+ {
+ f = DistributeEvenly_Get(of);
+ kh_Scores_Event(key.owner, NULL, "destroyed_holdingkey", f, 0);
+ }
+
+ int fragsleft = DistributeEvenly_Get(players);
+
+ // Now distribute these among all other teams...
+ int j = NumTeams(kh_teams) - 1;
+ for(int i = 0; i < NumTeams(kh_teams); ++i)
+ {
+ int thisteam = kh_Team_ByID(i);
+ if(thisteam == loser_team) // bad boy, no cookie - this WILL happen
+ continue;
+
+ players = 0;
+ FOREACH_CLIENT(IS_PLAYER(it) && it.team == thisteam, { ++players; });
+
+ DistributeEvenly_Init(fragsleft, j);
+ fragsleft = DistributeEvenly_Get(j - 1);
+ DistributeEvenly_Init(DistributeEvenly_Get(1), players);
+
+ FOREACH_CLIENT(IS_PLAYER(it) && it.team == thisteam, {
+ f = DistributeEvenly_Get(1);
+ kh_Scores_Event(it, NULL, "destroyed", f, 0);
+ });
+
+ --j;
+ }
+ }
+
+ int realteam = kh_Team_ByID(lostkey.count);
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(loser_team, CENTER_ROUND_TEAM_LOSS));
+ if(attacker)
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_PUSHED), attacker.netname, lostkey.kh_previous_owner.netname);
+ else
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_DESTROYED), lostkey.kh_previous_owner.netname);
+
+ play2all(SND(KH_DESTROY));
+ te_tarexplosion(lostkey.origin);
+
+ kh_FinishRound();
+}
+
+void kh_Key_Think(entity this) // runs all the time
+{
+ if(game_stopped)
+ return;
+
+ if(this.owner)
+ {
+#ifndef KH_PLAYER_USE_ATTACHMENT
+ makevectors('0 1 0' * (this.cnt + (time % 360) * KH_KEY_XYSPEED));
+ setorigin(this, v_forward * KH_KEY_XYDIST + '0 0 1' * this.origin.z);
+#endif
+ }
+
+ // if in nodrop or time over, end the round
+ if(!this.owner)
+ if(time > this.pain_finished)
+ kh_LoserTeam(this.team, this);
+
+ if(this.owner)
+ if(kh_Key_AllOwnedByWhichTeam() != -1)
+ {
+ if(this.siren_time < time)
+ {
+ sound(this.owner, CH_TRIGGER, SND_KH_ALARM, VOL_BASE, ATTEN_NORM); // play a simple alarm
+ this.siren_time = time + 2.5; // repeat every 2.5 seconds
+ }
+
+ entity key;
+ vector p = this.owner.origin;
+ FOR_EACH_KH_KEY(key)
+ if(vdist(key.owner.origin - p, >, autocvar_g_balance_keyhunt_maxdist))
+ goto not_winning;
+ kh_WinnerTeam(this.team);
+LABEL(not_winning)
+ }
+
+ if(kh_interferemsg_time && time > kh_interferemsg_time)
+ {
+ kh_interferemsg_time = 0;
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ if(it.team == kh_interferemsg_team)
+ if(it.kh_next)
+ Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_KEYHUNT_MEET);
+ else
+ Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_KEYHUNT_HELP);
+ else
+ Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_TEAM_NUM(kh_interferemsg_team, CENTER_KEYHUNT_INTERFERE));
+ });
+ }
+
+ this.nextthink = time + 0.05;
+}
+
+void key_reset(entity this)
+{
+ kh_Key_AssignTo(this, NULL);
+ kh_Key_Remove(this);
+}
+
+const string STR_ITEM_KH_KEY = "item_kh_key";
+void kh_Key_Spawn(entity initial_owner, float _angle, float i) // runs every time a new flag is created, ie after all the keys have been collected
+{
+ entity key = spawn();
+ key.count = i;
+ key.classname = STR_ITEM_KH_KEY;
+ settouch(key, kh_Key_Touch);
+ setthink(key, kh_Key_Think);
+ key.nextthink = time;
+ key.items = IT_KEY1 | IT_KEY2;
+ key.cnt = _angle;
+ key.angles = '0 360 0' * random();
+ key.event_damage = kh_Key_Damage;
+ key.takedamage = DAMAGE_YES;
+ key.damagedbytriggers = autocvar_g_balance_keyhunt_return_when_unreachable;
+ key.damagedbycontents = autocvar_g_balance_keyhunt_return_when_unreachable;
+ key.modelindex = kh_key_dropped;
+ key.model = "key";
+ key.kh_dropperteam = 0;
+ key.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
+ setsize(key, KH_KEY_MIN, KH_KEY_MAX);
+ key.colormod = Team_ColorRGB(initial_owner.team) * KH_KEY_BRIGHTNESS;
+ key.reset = key_reset;
+ navigation_dynamicgoal_init(key, false);
+
+ switch(initial_owner.team)
+ {
+ case NUM_TEAM_1:
+ key.netname = "^1red key";
+ break;
+ case NUM_TEAM_2:
+ key.netname = "^4blue key";
+ break;
+ case NUM_TEAM_3:
+ key.netname = "^3yellow key";
+ break;
+ case NUM_TEAM_4:
+ key.netname = "^6pink key";
+ break;
+ default:
+ key.netname = "NETGIER key";
+ break;
+ }
+
+ // link into key list
+ key.kh_worldkeynext = kh_worldkeylist;
+ kh_worldkeylist = key;
+
+ Send_Notification(NOTIF_ONE, initial_owner, MSG_CENTER, APP_TEAM_NUM(initial_owner.team, CENTER_KEYHUNT_START));
+
+ WaypointSprite_Spawn(WP_KeyDropped, 0, 0, key, '0 0 1' * KH_KEY_WP_ZSHIFT, NULL, key.team, key, waypointsprite_attachedforcarrier, false, RADARICON_FLAG);
+ key.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = kh_Key_waypointsprite_visible_for_player;
+
+ kh_Key_AssignTo(key, initial_owner);
+}
+
+// -1 when no team completely owns all keys yet
+int kh_Key_AllOwnedByWhichTeam() // constantly called. check to see if all the keys are owned by the same team
+{
+ entity key;
+ int teem = -1;
+ int keys = NumTeams(kh_teams);
+ FOR_EACH_KH_KEY(key)
+ {
+ if(!key.owner)
+ return -1;
+ if(teem == -1)
+ teem = key.team;
+ else if(teem != key.team)
+ return -1;
+ --keys;
+ }
+ if(keys != 0)
+ return -1;
+ return teem;
+}
+
+void kh_Key_DropOne(entity key)
+{
+ // prevent collecting this one for some time
+ entity player = key.owner;
+
+ key.kh_droptime = time;
+ key.enemy = player;
+
+ kh_Scores_Event(player, key, "dropkey", 0, 0);
+ GameRules_scoring_add(player, KH_LOSSES, 1);
+ int realteam = kh_Team_ByID(key.count);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_DROP), player.netname);
+
+ kh_Key_AssignTo(key, NULL);
+ makevectors(player.v_angle);
+ key.velocity = W_CalculateProjectileVelocity(player, player.velocity, autocvar_g_balance_keyhunt_throwvelocity * v_forward, false);
+ key.pusher = NULL;
+ key.pushltime = time + autocvar_g_balance_keyhunt_protecttime;
+ key.kh_dropperteam = key.team;
+
+ sound(player, CH_TRIGGER, SND_KH_DROP, VOL_BASE, ATTEN_NORM);
+}
+
+void kh_Key_DropAll(entity player, float suicide) // runs whenever a player dies
+{
+ if(player.kh_next)
+ {
+ entity mypusher = NULL;
+ if(player.pusher)
+ if(time < player.pushltime)
+ mypusher = player.pusher;
+
+ entity key;
+ while((key = player.kh_next))
+ {
+ kh_Scores_Event(player, key, "losekey", 0, 0);
+ GameRules_scoring_add(player, KH_LOSSES, 1);
+ int realteam = kh_Team_ByID(key.count);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_LOST), player.netname);
+ kh_Key_AssignTo(key, NULL);
+ makevectors('-1 0 0' * (45 + 45 * random()) + '0 360 0' * random());
+ key.velocity = W_CalculateProjectileVelocity(player, player.velocity, autocvar_g_balance_keyhunt_dropvelocity * v_forward, false);
+ key.pusher = mypusher;
+ key.pushltime = time + autocvar_g_balance_keyhunt_protecttime;
+ if(suicide)
+ key.kh_dropperteam = player.team;
+ }
+ sound(player, CH_TRIGGER, SND_KH_DROP, VOL_BASE, ATTEN_NORM);
+ }
+}
+
+int kh_GetMissingTeams()
+{
+ int missing_teams = 0;
+ for(int i = 0; i < NumTeams(kh_teams); ++i)
+ {
+ int teem = kh_Team_ByID(i);
+ int players = 0;
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ if(!IS_DEAD(it) && !PHYS_INPUT_BUTTON_CHAT(it) && it.team == teem)
+ ++players;
+ });
+ if (!players)
+ missing_teams |= (2 ** i);
+ }
+ return missing_teams;
+}
+
+void kh_WaitForPlayers() // delay start of the round until enough players are present
+{
+ static int prev_missing_teams_mask;
+ if(time < game_starttime)
+ {
+ if (prev_missing_teams_mask > 0)
+ Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+ prev_missing_teams_mask = -1;
+ kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers);
+ return;
+ }
+
+ int missing_teams_mask = kh_GetMissingTeams();
+ if(!missing_teams_mask)
+ {
+ if(prev_missing_teams_mask > 0)
+ Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+ prev_missing_teams_mask = -1;
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEYHUNT_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round);
+ kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round, kh_StartRound);
+ }
+ else
+ {
+ if(player_count == 0)
+ {
+ if(prev_missing_teams_mask > 0)
+ Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+ prev_missing_teams_mask = -1;
+ }
+ else
+ {
+ if(prev_missing_teams_mask != missing_teams_mask)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
+ prev_missing_teams_mask = missing_teams_mask;
+ }
+ }
+ kh_Controller_SetThink(1, kh_WaitForPlayers);
+ }
+}
+
+void kh_EnableTrackingDevice() // runs after each round
+{
+ Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT);
+ Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT_OTHER);
+
+ kh_tracking_enabled = true;
+}
+
+void kh_StartRound() // runs at the start of each round
+{
+ if(time < game_starttime)
+ {
+ kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers);
+ return;
+ }
+
+ if(kh_GetMissingTeams())
+ {
+ kh_Controller_SetThink(1, kh_WaitForPlayers);
+ return;
+ }
+
+ Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT);
+ Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT_OTHER);
+
+ for(int i = 0; i < NumTeams(kh_teams); ++i)
+ {
+ int teem = kh_Team_ByID(i);
+ int players = 0;
+ entity my_player = NULL;
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ if(!IS_DEAD(it) && !PHYS_INPUT_BUTTON_CHAT(it) && it.team == teem)
+ {
+ ++players;
+ if(random() * players <= 1)
+ my_player = it;
+ }
+ });
+ kh_Key_Spawn(my_player, 360 * i / NumTeams(kh_teams), i);
+ }
+
+ kh_tracking_enabled = false;
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEYHUNT_SCAN, autocvar_g_balance_keyhunt_delay_tracking);
+ kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_tracking, kh_EnableTrackingDevice);
+}
+
+float kh_HandleFrags(entity attacker, entity targ, float f) // adds to the player score
+{
+ if(attacker == targ)
+ return f;
+
+ if(targ.kh_next)
+ {
+ if(attacker.team == targ.team)
+ {
+ int nk = 0;
+ for(entity k = targ.kh_next; k != NULL; k = k.kh_next)
+ ++nk;
+ kh_Scores_Event(attacker, targ.kh_next, "carrierfrag", -nk * autocvar_g_balance_keyhunt_score_collect, 0);
+ }
+ else
+ {
+ kh_Scores_Event(attacker, targ.kh_next, "carrierfrag", autocvar_g_balance_keyhunt_score_carrierfrag-1, 0);
+ GameRules_scoring_add(attacker, KH_KCKILLS, 1);
+ // the frag gets added later
+ }
+ }
+
+ return f;
+}
+
+void kh_Initialize() // sets up th KH environment
+{
+ // setup variables
+ kh_teams = autocvar_g_keyhunt_teams_override;
+ if(kh_teams < 2)
+ kh_teams = cvar("g_keyhunt_teams"); // read the cvar directly as it gets written earlier in the same frame
+ kh_teams = BITS(bound(2, kh_teams, 4));
+
+ // make a KH entity for controlling the game
+ kh_controller = spawn();
+ setthink(kh_controller, kh_Controller_Think);
+ kh_Controller_SetThink(0, kh_WaitForPlayers);
+
+ setmodel(kh_controller, MDL_KH_KEY);
+ kh_key_dropped = kh_controller.modelindex;
+ /*
+ dprint(vtos(kh_controller.mins));
+ dprint(vtos(kh_controller.maxs));
+ dprint("\n");
+ */
+#ifdef KH_PLAYER_USE_CARRIEDMODEL
+ setmodel(kh_controller, MDL_KH_KEY_CARRIED);
+ kh_key_carried = kh_controller.modelindex;
+#else
+ kh_key_carried = kh_key_dropped;
+#endif
+
+ kh_controller.model = "";
+ kh_controller.modelindex = 0;
+
+ kh_ScoreRules(kh_teams);
+}
+
+void kh_finalize()
+{
+ // to be called before intermission
+ kh_FinishRound();
+ delete(kh_controller);
+ kh_controller = NULL;
+}
+
+// legacy bot role
+
+void(entity this) havocbot_role_kh_carrier;
+void(entity this) havocbot_role_kh_defense;
+void(entity this) havocbot_role_kh_offense;
+void(entity this) havocbot_role_kh_freelancer;
+
+
+void havocbot_goalrating_kh(entity this, float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy)
+{
+ entity head;
+ for (head = kh_worldkeylist; head; head = head.kh_worldkeynext)
+ {
+ if(head.owner == this)
+ continue;
+ if(!kh_tracking_enabled)
+ {
+ // if it's carried by our team we know about it
+ // otherwise we have to see it to know about it
+ if(!head.owner || head.team != this.team)
+ {
+ traceline(this.origin + this.view_ofs, head.origin, MOVE_NOMONSTERS, this);
+ if (trace_fraction < 1 && trace_ent != head)
+ continue; // skip what I can't see
+ }
+ }
+ if(!head.owner)
+ navigation_routerating(this, head, ratingscale_dropped * 10000, 100000);
+ else if(head.team == this.team)
+ navigation_routerating(this, head.owner, ratingscale_team * 10000, 100000);
+ else
+ navigation_routerating(this, head.owner, ratingscale_enemy * 10000, 100000);
+ }
+
+ havocbot_goalrating_items(this, 1, this.origin, 10000);
+}
+
+void havocbot_role_kh_carrier(entity this)
+{
+ if(IS_DEAD(this))
+ return;
+
+ if (!(this.kh_next))
+ {
+ LOG_TRACE("changing role to freelancer");
+ this.havocbot_role = havocbot_role_kh_freelancer;
+ this.havocbot_role_timeout = 0;
+ return;
+ }
+
+ if (navigation_goalrating_timeout(this))
+ {
+ navigation_goalrating_start(this);
+
+ if(kh_Key_AllOwnedByWhichTeam() == this.team)
+ havocbot_goalrating_kh(this, 10, 0.1, 0.05); // bring home
+ else
+ havocbot_goalrating_kh(this, 4, 4, 0.5); // play defensively
+
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+void havocbot_role_kh_defense(entity this)
+{
+ if(IS_DEAD(this))
+ return;
+
+ if (this.kh_next)
+ {
+ LOG_TRACE("changing role to carrier");
+ this.havocbot_role = havocbot_role_kh_carrier;
+ this.havocbot_role_timeout = 0;
+ return;
+ }
+
+ if (!this.havocbot_role_timeout)
+ this.havocbot_role_timeout = time + random() * 10 + 20;
+ if (time > this.havocbot_role_timeout)
+ {
+ LOG_TRACE("changing role to freelancer");
+ this.havocbot_role = havocbot_role_kh_freelancer;
+ this.havocbot_role_timeout = 0;
+ return;
+ }
+
+ if (navigation_goalrating_timeout(this))
+ {
+ float key_owner_team;
+ navigation_goalrating_start(this);
+
+ key_owner_team = kh_Key_AllOwnedByWhichTeam();
+ if(key_owner_team == this.team)
+ havocbot_goalrating_kh(this, 10, 0.1, 0.05); // defend key carriers
+ else if(key_owner_team == -1)
+ havocbot_goalrating_kh(this, 4, 1, 0.05); // play defensively
+ else
+ havocbot_goalrating_kh(this, 0.1, 0.1, 5); // ATTACK ANYWAY
+
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+void havocbot_role_kh_offense(entity this)
+{
+ if(IS_DEAD(this))
+ return;
+
+ if (this.kh_next)
+ {
+ LOG_TRACE("changing role to carrier");
+ this.havocbot_role = havocbot_role_kh_carrier;
+ this.havocbot_role_timeout = 0;
+ return;
+ }
+
+ if (!this.havocbot_role_timeout)
+ this.havocbot_role_timeout = time + random() * 10 + 20;
+ if (time > this.havocbot_role_timeout)
+ {
+ LOG_TRACE("changing role to freelancer");
+ this.havocbot_role = havocbot_role_kh_freelancer;
+ this.havocbot_role_timeout = 0;
+ return;
+ }
+
+ if (navigation_goalrating_timeout(this))
+ {
+ float key_owner_team;
+
+ navigation_goalrating_start(this);
+
+ key_owner_team = kh_Key_AllOwnedByWhichTeam();
+ if(key_owner_team == this.team)
+ havocbot_goalrating_kh(this, 10, 0.1, 0.05); // defend anyway
+ else if(key_owner_team == -1)
+ havocbot_goalrating_kh(this, 0.1, 1, 2); // play offensively
+ else
+ havocbot_goalrating_kh(this, 0.1, 0.1, 5); // ATTACK! EMERGENCY!
+
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+void havocbot_role_kh_freelancer(entity this)
+{
+ if(IS_DEAD(this))
+ return;
+
+ if (this.kh_next)
+ {
+ LOG_TRACE("changing role to carrier");
+ this.havocbot_role = havocbot_role_kh_carrier;
+ this.havocbot_role_timeout = 0;
+ return;
+ }
+
+ if (!this.havocbot_role_timeout)
+ this.havocbot_role_timeout = time + random() * 10 + 10;
+ if (time > this.havocbot_role_timeout)
+ {
+ if (random() < 0.5)
+ {
+ LOG_TRACE("changing role to offense");
+ this.havocbot_role = havocbot_role_kh_offense;
+ }
+ else
+ {
+ LOG_TRACE("changing role to defense");
+ this.havocbot_role = havocbot_role_kh_defense;
+ }
+ this.havocbot_role_timeout = 0;
+ return;
+ }
+
+ if (navigation_goalrating_timeout(this))
+ {
+ navigation_goalrating_start(this);
+
+ int key_owner_team = kh_Key_AllOwnedByWhichTeam();
+ if(key_owner_team == this.team)
+ havocbot_goalrating_kh(this, 10, 0.1, 0.05); // defend anyway
+ else if(key_owner_team == -1)
+ havocbot_goalrating_kh(this, 1, 10, 2); // prefer dropped keys
+ else
+ havocbot_goalrating_kh(this, 0.1, 0.1, 5); // ATTACK ANYWAY
+
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+
+// register this as a mutator
+
+MUTATOR_HOOKFUNCTION(kh, ClientDisconnect)
+{
+ entity player = M_ARGV(0, entity);
+
+ kh_Key_DropAll(player, true);
+}
+
+MUTATOR_HOOKFUNCTION(kh, MakePlayerObserver)
+{
+ entity player = M_ARGV(0, entity);
+
+ kh_Key_DropAll(player, true);
+}
+
+MUTATOR_HOOKFUNCTION(kh, PlayerDies)
+{
+ entity frag_attacker = M_ARGV(1, entity);
+ entity frag_target = M_ARGV(2, entity);
+
+ if(frag_target == frag_attacker)
+ kh_Key_DropAll(frag_target, true);
+ else if(IS_PLAYER(frag_attacker))
+ kh_Key_DropAll(frag_target, false);
+ else
+ kh_Key_DropAll(frag_target, true);
+}
+
+MUTATOR_HOOKFUNCTION(kh, GiveFragsForKill, CBC_ORDER_FIRST)
+{
+ entity frag_attacker = M_ARGV(0, entity);
+ entity frag_target = M_ARGV(1, entity);
+ float frag_score = M_ARGV(2, float);
+ M_ARGV(2, float) = kh_HandleFrags(frag_attacker, frag_target, frag_score);
+}
+
+MUTATOR_HOOKFUNCTION(kh, MatchEnd)
+{
+ kh_finalize();
+}
+
+MUTATOR_HOOKFUNCTION(kh, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+ M_ARGV(0, float) = kh_teams;
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(kh, SpectateCopy)
+{
+ entity spectatee = M_ARGV(0, entity);
+ entity client = M_ARGV(1, entity);
+
+ STAT(KH_KEYS, client) = STAT(KH_KEYS, spectatee);
+}
+
+MUTATOR_HOOKFUNCTION(kh, PlayerUseKey)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(MUTATOR_RETURNVALUE == 0)
+ {
+ entity k = player.kh_next;
+ if(k)
+ {
+ kh_Key_DropOne(k);
+ return true;
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(kh, HavocBot_ChooseRole)
+{
+ entity bot = M_ARGV(0, entity);
+
+ if(IS_DEAD(bot))
+ return true;
+
+ float r = random() * 3;
+ if (r < 1)
+ bot.havocbot_role = havocbot_role_kh_offense;
+ else if (r < 2)
+ bot.havocbot_role = havocbot_role_kh_defense;
+ else
+ bot.havocbot_role = havocbot_role_kh_freelancer;
+
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(kh, DropSpecialItems)
+{
+ entity frag_target = M_ARGV(0, entity);
+
+ kh_Key_DropAll(frag_target, false);
+}
+
+MUTATOR_HOOKFUNCTION(kh, reset_map_global)
+{
+ kh_WaitForPlayers(); // takes care of killing the "missing teams" message
+}
--- /dev/null
+#pragma once
+
+#include <common/mutators/base.qh>
+#define autocvar_g_keyhunt_point_limit cvar("g_keyhunt_point_limit")
+int autocvar_g_keyhunt_point_leadlimit;
+bool autocvar_g_keyhunt_team_spawns;
+void kh_Initialize();
+
+REGISTER_MUTATOR(kh, false)
+{
+ MUTATOR_STATIC();
+ MUTATOR_ONADD
+ {
+ GameRules_teams(true);
+ GameRules_spawning_teams(autocvar_g_keyhunt_team_spawns);
+ GameRules_limit_score(autocvar_g_keyhunt_point_limit);
+ GameRules_limit_lead(autocvar_g_keyhunt_point_leadlimit);
+
+ kh_Initialize();
+ }
+ return 0;
+}
+
+#define FOR_EACH_KH_KEY(v) for(v = kh_worldkeylist; v; v = v.kh_worldkeynext )
+
+// ALL OF THESE should be removed in the future, as other code should not have to care
+
+// used by bots:
+bool kh_tracking_enabled;
+.entity kh_next;
+
+USING(kh_Think_t, void());
+void kh_StartRound();
+void kh_Controller_SetThink(float t, kh_Think_t func);
// generated file; do not modify
-#include <common/gamemodes/gamemode/lms/lms.qc>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/lms/sv_lms.qc>
+#endif
// generated file; do not modify
-#include <common/gamemodes/gamemode/lms/lms.qh>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/lms/sv_lms.qh>
+#endif
+++ /dev/null
-#include "lms.qh"
-
-#ifdef SVQC
-#include <common/mutators/mutator/instagib/items.qh>
-#include <server/campaign.qh>
-#include <server/command/_mod.qh>
-
-int autocvar_g_lms_extra_lives;
-bool autocvar_g_lms_join_anytime;
-int autocvar_g_lms_last_join;
-bool autocvar_g_lms_regenerate;
-
-// main functions
-float LMS_NewPlayerLives()
-{
- float fl;
- fl = autocvar_fraglimit;
- if(fl == 0)
- fl = 999;
-
- // first player has left the game for dying too much? Nobody else can get in.
- if(lms_lowest_lives < 1)
- return 0;
-
- if(!autocvar_g_lms_join_anytime)
- if(lms_lowest_lives < fl - autocvar_g_lms_last_join)
- return 0;
-
- return bound(1, lms_lowest_lives, fl);
-}
-
-void ClearWinners();
-
-// LMS winning condition: game terminates if and only if there's at most one
-// one player who's living lives. Top two scores being equal cancels the time
-// limit.
-int WinningCondition_LMS()
-{
- entity first_player = NULL;
- int total_players = 0;
- FOREACH_CLIENT(IS_PLAYER(it), {
- if (!total_players)
- first_player = it;
- ++total_players;
- });
-
- if (total_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
- {
- // exactly one player?
-
- ClearWinners();
- SetWinners(winning, 0); // NOTE: exactly one player is still "player", so this works out
-
- if (LMS_NewPlayerLives())
- {
- // game still running (that is, nobody got removed from the game by a frag yet)? then continue
- return WINNING_NO;
- }
- else
- {
- // a winner!
- // and assign him his first place
- GameRules_scoring_add(first_player, LMS_RANK, 1);
- if(warmup_stage)
- return WINNING_NO;
- else
- return WINNING_YES;
- }
- }
- }
- else
- {
- // nobody is playing at all...
- if (LMS_NewPlayerLives())
- {
- // wait for players...
- }
- else
- {
- // SNAFU (maybe a draw game?)
- ClearWinners();
- LOG_TRACE("No players, ending game.");
- return WINNING_YES;
- }
- }
-
- // When we get here, we have at least two players who are actually LIVING,
- // now check if the top two players have equal score.
- WinningConditionHelper(NULL);
-
- ClearWinners();
- if(WinningConditionHelper_winner)
- WinningConditionHelper_winner.winning = true;
- if(WinningConditionHelper_topscore == WinningConditionHelper_secondscore)
- return WINNING_NEVER;
-
- // Top two have different scores? Way to go for our beloved TIMELIMIT!
- return WINNING_NO;
-}
-
-// mutator hooks
-MUTATOR_HOOKFUNCTION(lms, reset_map_global)
-{
- lms_lowest_lives = 999;
-}
-
-MUTATOR_HOOKFUNCTION(lms, reset_map_players)
-{
- FOREACH_CLIENT(true, {
- TRANSMUTE(Player, it);
- it.frags = FRAGS_PLAYER;
- GameRules_scoring_add(it, LMS_LIVES, LMS_NewPlayerLives());
- PutClientInServer(it);
- });
-}
-
-MUTATOR_HOOKFUNCTION(lms, PutClientInServer)
-{
- entity player = M_ARGV(0, entity);
-
- if(player.frags == FRAGS_SPECTATOR)
- TRANSMUTE(Observer, player);
- else
- {
- float tl = GameRules_scoring_add(player, LMS_LIVES, 0);
- if(tl < lms_lowest_lives)
- lms_lowest_lives = tl;
- if(tl <= 0)
- TRANSMUTE(Observer, player);
- if(warmup_stage)
- GameRules_scoring_add(player, LMS_RANK, -GameRules_scoring_add(player, LMS_RANK, 0));
- }
-}
-
-MUTATOR_HOOKFUNCTION(lms, ForbidSpawn)
-{
- entity player = M_ARGV(0, entity);
-
- if(warmup_stage)
- return false;
- if(player.frags == FRAGS_SPECTATOR)
- return true;
- if(GameRules_scoring_add(player, LMS_LIVES, 0) <= 0)
- {
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_LMS_NOLIVES);
- return true;
- }
- return false;
-}
-
-MUTATOR_HOOKFUNCTION(lms, PlayerDies)
-{
- entity frag_target = M_ARGV(2, entity);
-
- frag_target.respawn_flags |= RESPAWN_FORCE;
-}
-
-void lms_RemovePlayer(entity player)
-{
- static int quitters = 0;
- float player_rank = GameRules_scoring_add(player, LMS_RANK, 0);
- if (!player_rank)
- {
- int pl_cnt = 0;
- FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; });
- if (player.lms_spectate_warning != 2)
- {
- if(IS_BOT_CLIENT(player))
- bot_clear(player);
- player.frags = FRAGS_LMS_LOSER;
- GameRules_scoring_add(player, LMS_RANK, pl_cnt + 1);
- }
- else
- {
- lms_lowest_lives = 999;
- FOREACH_CLIENT(true, {
- if (it.frags == FRAGS_LMS_LOSER)
- {
- float it_rank = GameRules_scoring_add(it, LMS_RANK, 0);
- if (it_rank > player_rank && it_rank <= 256)
- GameRules_scoring_add(it, LMS_RANK, -1);
- lms_lowest_lives = 0;
- }
- else if (it.frags != FRAGS_SPECTATOR)
- {
- float tl = GameRules_scoring_add(it, LMS_LIVES, 0);
- if(tl < lms_lowest_lives)
- lms_lowest_lives = tl;
- }
- });
- GameRules_scoring_add(player, LMS_RANK, 665 - quitters); // different from 666
- if(!warmup_stage)
- {
- GameRules_scoring_add(player, LMS_LIVES, -GameRules_scoring_add(player, LMS_LIVES, 0));
- ++quitters;
- }
- player.frags = FRAGS_LMS_LOSER;
- TRANSMUTE(Observer, player);
- }
- if (pl_cnt == 2 && !warmup_stage) // a player is forfeiting leaving only one player
- lms_lowest_lives = 0; // end the game now!
- }
-
- if(CS(player).killcount != FRAGS_SPECTATOR)
- if(GameRules_scoring_add(player, LMS_RANK, 0) > 0 && player.lms_spectate_warning != 2)
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_NOLIVES, player.netname);
- else
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_FORFEIT, player.netname);
-}
-
-MUTATOR_HOOKFUNCTION(lms, ClientDisconnect)
-{
- entity player = M_ARGV(0, entity);
-
- lms_RemovePlayer(player);
-}
-
-MUTATOR_HOOKFUNCTION(lms, MakePlayerObserver)
-{
- entity player = M_ARGV(0, entity);
-
- if (!IS_PLAYER(player))
- return true;
-
- lms_RemovePlayer(player);
- return true; // prevent team reset
-}
-
-MUTATOR_HOOKFUNCTION(lms, ClientConnect)
-{
- entity player = M_ARGV(0, entity);
-
- if(GameRules_scoring_add(player, LMS_LIVES, LMS_NewPlayerLives()) <= 0)
- {
- GameRules_scoring_add(player, LMS_RANK, 666); // mark as forced spectator for the hud code
- player.frags = FRAGS_SPECTATOR;
- }
-}
-
-MUTATOR_HOOKFUNCTION(lms, AutoJoinOnConnection)
-{
- if(autocvar_g_campaign)
- return false;
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, PlayerPreThink)
-{
- entity player = M_ARGV(0, entity);
-
- if(player.deadflag == DEAD_DYING)
- player.deadflag = DEAD_RESPAWNING;
-}
-
-MUTATOR_HOOKFUNCTION(lms, PlayerRegen)
-{
- if(autocvar_g_lms_regenerate)
- return false;
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, ForbidThrowCurrentWeapon)
-{
- // forbode!
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, GiveFragsForKill)
-{
- entity frag_target = M_ARGV(1, entity);
-
- if (!warmup_stage)
- {
- // remove a life
- int tl = GameRules_scoring_add(frag_target, LMS_LIVES, -1);
- if(tl < lms_lowest_lives)
- lms_lowest_lives = tl;
- if(tl <= 0)
- {
- int pl_cnt = 0;
- FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; });
- if(IS_BOT_CLIENT(frag_target))
- bot_clear(frag_target);
- frag_target.frags = FRAGS_LMS_LOSER;
- GameRules_scoring_add(frag_target, LMS_RANK, pl_cnt);
- }
- }
- M_ARGV(2, float) = 0; // frag score
-
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, SetStartItems)
-{
- start_items &= ~IT_UNLIMITED_AMMO;
- start_health = warmup_start_health = cvar("g_lms_start_health");
- start_armorvalue = warmup_start_armorvalue = cvar("g_lms_start_armor");
- start_ammo_shells = warmup_start_ammo_shells = cvar("g_lms_start_ammo_shells");
- start_ammo_nails = warmup_start_ammo_nails = cvar("g_lms_start_ammo_nails");
- start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
- start_ammo_cells = warmup_start_ammo_cells = cvar("g_lms_start_ammo_cells");
- start_ammo_plasma = warmup_start_ammo_plasma = cvar("g_lms_start_ammo_plasma");
- start_ammo_fuel = warmup_start_ammo_fuel = cvar("g_lms_start_ammo_fuel");
-}
-
-MUTATOR_HOOKFUNCTION(lms, ForbidPlayerScore_Clear)
-{
- // don't clear player score
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, FilterItemDefinition)
-{
- entity definition = M_ARGV(0, entity);
-
- if (autocvar_g_lms_extra_lives && definition == ITEM_ExtraLife)
- {
- return false;
- }
- return true;
-}
-
-void lms_extralife(entity this)
-{
- StartItem(this, ITEM_ExtraLife);
-}
-
-MUTATOR_HOOKFUNCTION(lms, OnEntityPreSpawn)
-{
- if (!autocvar_g_powerups) return false;
- if (!autocvar_g_lms_extra_lives) return false;
-
- entity ent = M_ARGV(0, entity);
-
- // Can't use .itemdef here
- if (ent.classname != "item_health_mega") return false;
-
- entity e = spawn();
- setthink(e, lms_extralife);
-
- e.nextthink = time + 0.1;
- e.spawnflags = ent.spawnflags;
- e.noalign = ent.noalign;
- setorigin(e, ent.origin);
-
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, ItemTouch)
-{
- entity item = M_ARGV(0, entity);
- entity toucher = M_ARGV(1, entity);
-
- if(item.itemdef == ITEM_ExtraLife)
- {
- Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_EXTRALIVES);
- GameRules_scoring_add(toucher, LMS_LIVES, autocvar_g_lms_extra_lives);
- return MUT_ITEMTOUCH_PICKUP;
- }
-
- return MUT_ITEMTOUCH_CONTINUE;
-}
-
-MUTATOR_HOOKFUNCTION(lms, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
-{
- FOREACH_CLIENT(IS_REAL_CLIENT(it), {
- ++M_ARGV(0, int); // activerealplayers
- ++M_ARGV(1, int); // realplayers
- });
-
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, ClientCommand_Spectate)
-{
- entity player = M_ARGV(0, entity);
-
- if(warmup_stage || player.lms_spectate_warning)
- {
- // for the forfeit message...
- player.lms_spectate_warning = 2;
- }
- else
- {
- if(player.frags != FRAGS_SPECTATOR && player.frags != FRAGS_LMS_LOSER)
- {
- player.lms_spectate_warning = 1;
- sprint(player, "WARNING: you won't be able to enter the game again after spectating in LMS. Use the same command again to spectate anyway.\n");
- }
- return MUT_SPECCMD_RETURN;
- }
- return MUT_SPECCMD_CONTINUE;
-}
-
-MUTATOR_HOOKFUNCTION(lms, CheckRules_World)
-{
- M_ARGV(0, float) = WinningCondition_LMS();
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, WantWeapon)
-{
- M_ARGV(2, bool) = true; // all weapons
-}
-
-MUTATOR_HOOKFUNCTION(lms, GetPlayerStatus)
-{
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, AddPlayerScore)
-{
- if(game_stopped)
- if(M_ARGV(0, entity) == SP_LMS_RANK) // score field
- return true; // allow writing to this field in intermission as it is needed for newly joining players
-}
-
-void lms_Initialize()
-{
- lms_lowest_lives = 9999;
-}
-#endif
+++ /dev/null
-#pragma once
-
-#ifdef SVQC
-#include <common/mutators/base.qh>
-#include <common/scores.qh>
-.float lms_spectate_warning;
-#define autocvar_g_lms_lives_override cvar("g_lms_lives_override")
-void lms_Initialize();
-
-REGISTER_MUTATOR(lms, false)
-{
- MUTATOR_STATIC();
- MUTATOR_ONADD
- {
- GameRules_limit_score(((!autocvar_g_lms_lives_override) ? -1 : autocvar_g_lms_lives_override));
- GameRules_limit_lead(0);
- GameRules_score_enabled(false);
- GameRules_scoring(0, 0, 0, {
- field(SP_LMS_LIVES, "lives", SFL_SORT_PRIO_SECONDARY);
- field(SP_LMS_RANK, "rank", SFL_LOWER_IS_BETTER | SFL_RANK | SFL_SORT_PRIO_PRIMARY | SFL_ALLOW_HIDE);
- });
-
- lms_Initialize();
- }
- return 0;
-}
-
-// lives related defs
-float lms_lowest_lives;
-float LMS_NewPlayerLives();
-#endif
--- /dev/null
+#include "sv_lms.qh"
+
+#include <common/mutators/mutator/instagib/items.qh>
+#include <server/campaign.qh>
+#include <server/command/_mod.qh>
+
+int autocvar_g_lms_extra_lives;
+bool autocvar_g_lms_join_anytime;
+int autocvar_g_lms_last_join;
+bool autocvar_g_lms_regenerate;
+
+// main functions
+float LMS_NewPlayerLives()
+{
+ float fl;
+ fl = autocvar_fraglimit;
+ if(fl == 0)
+ fl = 999;
+
+ // first player has left the game for dying too much? Nobody else can get in.
+ if(lms_lowest_lives < 1)
+ return 0;
+
+ if(!autocvar_g_lms_join_anytime)
+ if(lms_lowest_lives < fl - autocvar_g_lms_last_join)
+ return 0;
+
+ return bound(1, lms_lowest_lives, fl);
+}
+
+void ClearWinners();
+
+// LMS winning condition: game terminates if and only if there's at most one
+// one player who's living lives. Top two scores being equal cancels the time
+// limit.
+int WinningCondition_LMS()
+{
+ entity first_player = NULL;
+ int totalplayers = 0;
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ if (!totalplayers)
+ first_player = it;
+ ++totalplayers;
+ });
+
+ if (totalplayers)
+ {
+ if (totalplayers > 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
+ {
+ // exactly one player?
+
+ ClearWinners();
+ SetWinners(winning, 0); // NOTE: exactly one player is still "player", so this works out
+
+ if (LMS_NewPlayerLives())
+ {
+ // game still running (that is, nobody got removed from the game by a frag yet)? then continue
+ return WINNING_NO;
+ }
+ else
+ {
+ // a winner!
+ // and assign him his first place
+ GameRules_scoring_add(first_player, LMS_RANK, 1);
+ if(warmup_stage)
+ return WINNING_NO;
+ else
+ return WINNING_YES;
+ }
+ }
+ }
+ else
+ {
+ // nobody is playing at all...
+ if (LMS_NewPlayerLives())
+ {
+ // wait for players...
+ }
+ else
+ {
+ // SNAFU (maybe a draw game?)
+ ClearWinners();
+ LOG_TRACE("No players, ending game.");
+ return WINNING_YES;
+ }
+ }
+
+ // When we get here, we have at least two players who are actually LIVING,
+ // now check if the top two players have equal score.
+ WinningConditionHelper(NULL);
+
+ ClearWinners();
+ if(WinningConditionHelper_winner)
+ WinningConditionHelper_winner.winning = true;
+ if(WinningConditionHelper_topscore == WinningConditionHelper_secondscore)
+ return WINNING_NEVER;
+
+ // Top two have different scores? Way to go for our beloved TIMELIMIT!
+ return WINNING_NO;
+}
+
+// mutator hooks
+MUTATOR_HOOKFUNCTION(lms, reset_map_global)
+{
+ lms_lowest_lives = 999;
+}
+
+MUTATOR_HOOKFUNCTION(lms, reset_map_players)
+{
+ FOREACH_CLIENT(true, {
+ TRANSMUTE(Player, it);
+ it.frags = FRAGS_PLAYER;
+ GameRules_scoring_add(it, LMS_LIVES, LMS_NewPlayerLives());
+ PutClientInServer(it);
+ });
+}
+
+MUTATOR_HOOKFUNCTION(lms, PutClientInServer)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(player.frags == FRAGS_SPECTATOR)
+ TRANSMUTE(Observer, player);
+ else
+ {
+ float tl = GameRules_scoring_add(player, LMS_LIVES, 0);
+ if(tl < lms_lowest_lives)
+ lms_lowest_lives = tl;
+ if(tl <= 0)
+ TRANSMUTE(Observer, player);
+ if(warmup_stage)
+ GameRules_scoring_add(player, LMS_RANK, -GameRules_scoring_add(player, LMS_RANK, 0));
+ }
+}
+
+MUTATOR_HOOKFUNCTION(lms, ForbidSpawn)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(warmup_stage)
+ return false;
+ if(player.frags == FRAGS_SPECTATOR)
+ return true;
+ if(GameRules_scoring_add(player, LMS_LIVES, 0) <= 0)
+ {
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_LMS_NOLIVES);
+ return true;
+ }
+ return false;
+}
+
+MUTATOR_HOOKFUNCTION(lms, PlayerDies)
+{
+ entity frag_target = M_ARGV(2, entity);
+
+ frag_target.respawn_flags |= RESPAWN_FORCE;
+}
+
+void lms_RemovePlayer(entity player)
+{
+ static int quitters = 0;
+ float player_rank = GameRules_scoring_add(player, LMS_RANK, 0);
+ if (!player_rank)
+ {
+ int pl_cnt = 0;
+ FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; });
+ if (player.lms_spectate_warning != 2)
+ {
+ if(IS_BOT_CLIENT(player))
+ bot_clear(player);
+ player.frags = FRAGS_LMS_LOSER;
+ GameRules_scoring_add(player, LMS_RANK, pl_cnt + 1);
+ }
+ else
+ {
+ lms_lowest_lives = 999;
+ FOREACH_CLIENT(true, {
+ if (it.frags == FRAGS_LMS_LOSER)
+ {
+ float it_rank = GameRules_scoring_add(it, LMS_RANK, 0);
+ if (it_rank > player_rank && it_rank <= 256)
+ GameRules_scoring_add(it, LMS_RANK, -1);
+ lms_lowest_lives = 0;
+ }
+ else if (it.frags != FRAGS_SPECTATOR)
+ {
+ float tl = GameRules_scoring_add(it, LMS_LIVES, 0);
+ if(tl < lms_lowest_lives)
+ lms_lowest_lives = tl;
+ }
+ });
+ GameRules_scoring_add(player, LMS_RANK, 665 - quitters); // different from 666
+ if(!warmup_stage)
+ {
+ GameRules_scoring_add(player, LMS_LIVES, -GameRules_scoring_add(player, LMS_LIVES, 0));
+ ++quitters;
+ }
+ player.frags = FRAGS_LMS_LOSER;
+ TRANSMUTE(Observer, player);
+ }
+ if (pl_cnt == 2 && !warmup_stage) // a player is forfeiting leaving only one player
+ lms_lowest_lives = 0; // end the game now!
+ }
+
+ if(CS(player).killcount != FRAGS_SPECTATOR)
+ if(GameRules_scoring_add(player, LMS_RANK, 0) > 0 && player.lms_spectate_warning != 2)
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_NOLIVES, player.netname);
+ else
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_FORFEIT, player.netname);
+}
+
+MUTATOR_HOOKFUNCTION(lms, ClientDisconnect)
+{
+ entity player = M_ARGV(0, entity);
+
+ lms_RemovePlayer(player);
+}
+
+MUTATOR_HOOKFUNCTION(lms, MakePlayerObserver)
+{
+ entity player = M_ARGV(0, entity);
+
+ if (!IS_PLAYER(player))
+ return true;
+
+ lms_RemovePlayer(player);
+ return true; // prevent team reset
+}
+
+MUTATOR_HOOKFUNCTION(lms, ClientConnect)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(GameRules_scoring_add(player, LMS_LIVES, LMS_NewPlayerLives()) <= 0)
+ {
+ GameRules_scoring_add(player, LMS_RANK, 666); // mark as forced spectator for the hud code
+ player.frags = FRAGS_SPECTATOR;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(lms, AutoJoinOnConnection)
+{
+ if(autocvar_g_campaign)
+ return false;
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, PlayerPreThink)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(player.deadflag == DEAD_DYING)
+ player.deadflag = DEAD_RESPAWNING;
+}
+
+MUTATOR_HOOKFUNCTION(lms, PlayerRegen)
+{
+ if(autocvar_g_lms_regenerate)
+ return false;
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, ForbidThrowCurrentWeapon)
+{
+ // forbode!
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, GiveFragsForKill)
+{
+ entity frag_target = M_ARGV(1, entity);
+
+ if (!warmup_stage)
+ {
+ // remove a life
+ int tl = GameRules_scoring_add(frag_target, LMS_LIVES, -1);
+ if(tl < lms_lowest_lives)
+ lms_lowest_lives = tl;
+ if(tl <= 0)
+ {
+ int pl_cnt = 0;
+ FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; });
+ if(IS_BOT_CLIENT(frag_target))
+ bot_clear(frag_target);
+ frag_target.frags = FRAGS_LMS_LOSER;
+ GameRules_scoring_add(frag_target, LMS_RANK, pl_cnt);
+ }
+ }
+ M_ARGV(2, float) = 0; // frag score
+
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, SetStartItems)
+{
+ start_items &= ~IT_UNLIMITED_AMMO;
+ start_health = warmup_start_health = cvar("g_lms_start_health");
+ start_armorvalue = warmup_start_armorvalue = cvar("g_lms_start_armor");
+ start_ammo_shells = warmup_start_ammo_shells = cvar("g_lms_start_ammo_shells");
+ start_ammo_nails = warmup_start_ammo_nails = cvar("g_lms_start_ammo_nails");
+ start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
+ start_ammo_cells = warmup_start_ammo_cells = cvar("g_lms_start_ammo_cells");
+ start_ammo_plasma = warmup_start_ammo_plasma = cvar("g_lms_start_ammo_plasma");
+ start_ammo_fuel = warmup_start_ammo_fuel = cvar("g_lms_start_ammo_fuel");
+}
+
+MUTATOR_HOOKFUNCTION(lms, ForbidPlayerScore_Clear)
+{
+ // don't clear player score
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, FilterItemDefinition)
+{
+ entity definition = M_ARGV(0, entity);
+
+ if (autocvar_g_lms_extra_lives && definition == ITEM_ExtraLife)
+ {
+ return false;
+ }
+ return true;
+}
+
+void lms_extralife(entity this)
+{
+ StartItem(this, ITEM_ExtraLife);
+}
+
+MUTATOR_HOOKFUNCTION(lms, OnEntityPreSpawn)
+{
+ if (!autocvar_g_powerups) return false;
+ if (!autocvar_g_lms_extra_lives) return false;
+
+ entity ent = M_ARGV(0, entity);
+
+ // Can't use .itemdef here
+ if (ent.classname != "item_health_mega") return false;
+
+ entity e = spawn();
+ setthink(e, lms_extralife);
+
+ e.nextthink = time + 0.1;
+ e.spawnflags = ent.spawnflags;
+ e.noalign = ent.noalign;
+ setorigin(e, ent.origin);
+
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, ItemTouch)
+{
+ entity item = M_ARGV(0, entity);
+ entity toucher = M_ARGV(1, entity);
+
+ if(item.itemdef == ITEM_ExtraLife)
+ {
+ Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_EXTRALIVES);
+ GameRules_scoring_add(toucher, LMS_LIVES, autocvar_g_lms_extra_lives);
+ return MUT_ITEMTOUCH_PICKUP;
+ }
+
+ return MUT_ITEMTOUCH_CONTINUE;
+}
+
+MUTATOR_HOOKFUNCTION(lms, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
+{
+ FOREACH_CLIENT(IS_REAL_CLIENT(it), {
+ ++M_ARGV(0, int); // activerealplayers
+ ++M_ARGV(1, int); // realplayers
+ });
+
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, ClientCommand_Spectate)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(warmup_stage || player.lms_spectate_warning)
+ {
+ // for the forfeit message...
+ player.lms_spectate_warning = 2;
+ }
+ else
+ {
+ if(player.frags != FRAGS_SPECTATOR && player.frags != FRAGS_LMS_LOSER)
+ {
+ player.lms_spectate_warning = 1;
+ sprint(player, "WARNING: you won't be able to enter the game again after spectating in LMS. Use the same command again to spectate anyway.\n");
+ }
+ return MUT_SPECCMD_RETURN;
+ }
+ return MUT_SPECCMD_CONTINUE;
+}
+
+MUTATOR_HOOKFUNCTION(lms, CheckRules_World)
+{
+ M_ARGV(0, float) = WinningCondition_LMS();
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, WantWeapon)
+{
+ M_ARGV(2, bool) = true; // all weapons
+}
+
+MUTATOR_HOOKFUNCTION(lms, GetPlayerStatus)
+{
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, AddPlayerScore)
+{
+ if(game_stopped)
+ if(M_ARGV(0, entity) == SP_LMS_RANK) // score field
+ return true; // allow writing to this field in intermission as it is needed for newly joining players
+}
+
+void lms_Initialize()
+{
+ lms_lowest_lives = 9999;
+}
--- /dev/null
+#pragma once
+
+#include <common/mutators/base.qh>
+#include <common/scores.qh>
+.float lms_spectate_warning;
+#define autocvar_g_lms_lives_override cvar("g_lms_lives_override")
+void lms_Initialize();
+
+REGISTER_MUTATOR(lms, false)
+{
+ MUTATOR_STATIC();
+ MUTATOR_ONADD
+ {
+ GameRules_limit_score(((!autocvar_g_lms_lives_override) ? -1 : autocvar_g_lms_lives_override));
+ GameRules_limit_lead(0);
+ GameRules_score_enabled(false);
+ GameRules_scoring(0, 0, 0, {
+ field(SP_LMS_LIVES, "lives", SFL_SORT_PRIO_SECONDARY);
+ field(SP_LMS_RANK, "rank", SFL_LOWER_IS_BETTER | SFL_RANK | SFL_SORT_PRIO_PRIMARY | SFL_ALLOW_HIDE);
+ });
+
+ lms_Initialize();
+ }
+ return 0;
+}
+
+// lives related defs
+float lms_lowest_lives;
+float LMS_NewPlayerLives();
MUTATOR_HOOKFUNCTION(cl_nb, WantEventchase)
{
- if(autocvar_cl_eventchase_nexball && gametype == MAPINFO_TYPE_NEXBALL && !(WepSet_GetFromStat() & WEPSET(NEXBALL)))
+ if(autocvar_cl_eventchase_nexball && ISGAMETYPE(NEXBALL) && !(WepSet_GetFromStat() & WEPSET(NEXBALL)))
return true;
return false;
}
EXACTTRIGGER_INIT;
- if(this.team != GOAL_OUT && Team_TeamToNumber(this.team) != -1)
+ if(this.team != GOAL_OUT && Team_IsValidTeam(this.team))
{
entity wp = WaypointSprite_SpawnFixed(WP_NbGoal, (this.absmin + this.absmax) * 0.5, this, sprite, RADARICON_NONE);
wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 0.5 0');
return MUT_ITEMTOUCH_CONTINUE;
}
-MUTATOR_HOOKFUNCTION(nb, CheckAllowedTeams)
+MUTATOR_HOOKFUNCTION(nb, TeamBalance_CheckAllowedTeams)
{
M_ARGV(1, string) = "nexball_team";
return true;
#include "sv_weapon.qh"
-void W_Nexball_Attack(entity actor, .entity weaponentity, float t);
-void W_Nexball_Attack2(entity actor, .entity weaponentity);
+void W_Nexball_Attack(Weapon thiswep, entity actor, .entity weaponentity, float t);
+void W_Nexball_Attack2(Weapon thiswep, entity actor, .entity weaponentity);
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))
}
else
{
- W_Nexball_Attack(actor, weaponentity, -1);
+ W_Nexball_Attack(thiswep, actor, weaponentity, -1);
weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, autocvar_g_balance_nexball_primary_animtime, w_ready);
}
if(fire & 2)
if(weapon_prepareattack(thiswep, actor, weaponentity, true, autocvar_g_balance_nexball_secondary_refire))
{
- W_Nexball_Attack2(actor, weaponentity);
+ W_Nexball_Attack2(thiswep, actor, weaponentity);
weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, autocvar_g_balance_nexball_secondary_animtime, w_ready);
}
if(!(fire & 1) && STAT(NB_METERSTART, actor) && actor.ballcarried)
{
- W_Nexball_Attack(actor, weaponentity, time - STAT(NB_METERSTART, actor));
+ W_Nexball_Attack(thiswep, actor, weaponentity, time - STAT(NB_METERSTART, actor));
// DropBall or stealing will set metertime back to 0
weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, autocvar_g_balance_nexball_primary_animtime, w_ready);
}
delete(this);
}
-void W_Nexball_Attack(entity actor, .entity weaponentity, float t)
+void W_Nexball_Attack(Weapon thiswep, entity actor, .entity weaponentity, float t)
{
entity ball;
float mul, mi, ma;
//TODO: use the speed_up cvar too ??
}
-void W_Nexball_Attack2(entity actor, .entity weaponentity)
+void W_Nexball_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
{
if(actor.ballcarried.enemy)
{
float autocvar_g_onslaught_spawn_choose;
float autocvar_g_onslaught_click_radius;
-void FixSize(entity e);
entity cam;
// =======================
// Junk Pile
// ==========
-void setmodel_fixsize(entity e, Model m)
-{
- setmodel(e, m);
- FixSize(e);
-}
-
void onslaught_updatelinks()
{
entity l;
this.owner.waslinked = this.owner.islinked;
if(this.owner.model != "models/onslaught/controlpoint_pad.md3")
- setmodel_fixsize(this.owner, MDL_ONS_CP_PAD1);
+ setmodel(this.owner, MDL_ONS_CP_PAD1);
//setsize(this, '-32 -32 0', '32 32 8');
delete(this);
this.SendFlags |= CPSF_SETUP;
}
if(this.owner.model != MDL_ONS_CP_PAD2.model_str())
- setmodel_fixsize(this.owner, MDL_ONS_CP_PAD2);
+ setmodel(this.owner, MDL_ONS_CP_PAD2);
if(random() < 0.9 - GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health)
Send_Effect(EFFECT_RAGE, this.origin + 10 * randomvec(), '0 0 -1', 1);
setthink(this, ons_ControlPoint_Think);
this.ons_toucher = NULL;
this.nextthink = time + ONS_CP_THINKRATE;
- setmodel_fixsize(this, MDL_ONS_CP_PAD1);
+ setmodel(this, MDL_ONS_CP_PAD1);
WaypointSprite_UpdateMaxHealth(this.sprite, 0);
WaypointSprite_UpdateRule(this.sprite,this.team,SPRITERULE_TEAMPLAY);
if(cp.message == "") { cp.message = "a"; }
// appearence
- setmodel_fixsize(cp, MDL_ONS_CP_PAD1);
+ setmodel(cp, MDL_ONS_CP_PAD1);
// control point placement
if((cp.spawnflags & 1) || cp.noalign) // don't drop to floor, just stay at fixed location
void Onslaught_count_generators()
{
entity e;
- total_generators = redowned = blueowned = yellowowned = pinkowned = 0;
+ total_generators = 0;
+ for (int i = 1; i <= NUM_TEAMS; ++i)
+ {
+ Team_SetNumberOfControlPoints(Team_GetTeamFromIndex(i), 0);
+ }
for(e = ons_worldgeneratorlist; e; e = e.ons_worldgeneratornext)
{
++total_generators;
- redowned += (e.team == NUM_TEAM_1 && GetResourceAmount(e, RESOURCE_HEALTH) > 0);
- blueowned += (e.team == NUM_TEAM_2 && GetResourceAmount(e, RESOURCE_HEALTH) > 0);
- yellowowned += (e.team == NUM_TEAM_3 && GetResourceAmount(e, RESOURCE_HEALTH) > 0);
- pinkowned += (e.team == NUM_TEAM_4 && GetResourceAmount(e, RESOURCE_HEALTH) > 0);
+ if (GetResourceAmount(e, RESOURCE_HEALTH) < 1)
+ {
+ continue;
+ }
+ entity team_ = Entity_GetTeam(e);
+ int num_control_points = Team_GetNumberOfControlPoints(team_);
+ ++num_control_points;
+ Team_SetNumberOfControlPoints(team_, num_control_points);
}
}
int Onslaught_GetWinnerTeam()
{
int winner_team = 0;
- if(redowned > 0)
- winner_team = NUM_TEAM_1;
- if(blueowned > 0)
+ if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(1)) >= 1)
{
- if(winner_team) return 0;
- winner_team = NUM_TEAM_2;
+ winner_team = NUM_TEAM_1;
}
- if(yellowowned > 0)
+ for (int i = 2; i <= NUM_TEAMS; ++i)
{
- if(winner_team) return 0;
- winner_team = NUM_TEAM_3;
+ if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(i)) >= 1)
+ {
+ if (winner_team != 0)
+ {
+ return 0;
+ }
+ winner_team = Team_IndexToTeam(i);
+ }
}
- if(pinkowned > 0)
+ if (winner_team)
{
- if(winner_team) return 0;
- winner_team = NUM_TEAM_4;
- }
- if(winner_team)
return winner_team;
+ }
return -1; // no generators left?
}
void nades_Clear(entity e);
-#define ONS_OWNED_GENERATORS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
-#define ONS_OWNED_GENERATORS_OK() (ONS_OWNED_GENERATORS() > 1)
bool Onslaught_CheckWinner()
{
if ((autocvar_timelimit && time > game_starttime + autocvar_timelimit * 60) || (round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0))
Onslaught_count_generators();
- if(ONS_OWNED_GENERATORS_OK())
+ if (Team_GetNumberOfTeamsWithControlPoints() > 1)
+ {
return 0;
+ }
int winner_team = Onslaught_GetWinnerTeam();
// NOTE: LEGACY CODE, needs to be re-written!
-void havocbot_goalrating_ons_offenseitems(entity this, float ratingscale, vector org, float sradius)
-{
- bool needarmor = false, needweapons = false;
-
- // Needs armor/health?
- if(GetResourceAmount(this, RESOURCE_HEALTH) < 100)
- needarmor = true;
-
- // Needs weapons?
- int c = 0;
- FOREACH(Weapons, it != WEP_Null, {
- if(STAT(WEAPONS, this) & (it.m_wepset))
- if(++c >= 4)
- break;
- });
-
- if(c<4)
- needweapons = true;
-
- if(!needweapons && !needarmor)
- return;
-
- LOG_DEBUG(this.netname, " needs weapons ", ftos(needweapons));
- LOG_DEBUG(this.netname, " needs armor ", ftos(needarmor));
-
- // See what is around
- IL_EACH(g_items, it.bot_pickup,
- {
- // gather health and armor only
- if (it.solid)
- if ( ((GetResourceAmount(it, RESOURCE_HEALTH) || GetResourceAmount(it, RESOURCE_ARMOR)) && needarmor) || (STAT(WEAPONS, it) && needweapons ) )
- if (vdist(it.origin - org, <, sradius))
- {
- int t = it.bot_pickupevalfunc(this, it);
- if (t > 0)
- navigation_routerating(this, it, t * ratingscale, 500);
- }
- });
-}
-
void havocbot_role_ons_setrole(entity this, int role)
{
- LOG_DEBUG(this.netname," switched to ");
switch(role)
{
case HAVOCBOT_ONS_ROLE_DEFENSE:
- LOG_DEBUG("defense");
+ LOG_DEBUG(this.netname, " switched to defense");
this.havocbot_role = havocbot_role_ons_defense;
- this.havocbot_role_flags = HAVOCBOT_ONS_ROLE_DEFENSE;
this.havocbot_role_timeout = 0;
break;
case HAVOCBOT_ONS_ROLE_ASSISTANT:
- LOG_DEBUG("assistant");
+ LOG_DEBUG(this.netname, " switched to assistant");
this.havocbot_role = havocbot_role_ons_assistant;
- this.havocbot_role_flags = HAVOCBOT_ONS_ROLE_ASSISTANT;
this.havocbot_role_timeout = 0;
break;
case HAVOCBOT_ONS_ROLE_OFFENSE:
- LOG_DEBUG("offense");
+ LOG_DEBUG(this.netname, " switched to offense");
this.havocbot_role = havocbot_role_ons_offense;
- this.havocbot_role_flags = HAVOCBOT_ONS_ROLE_OFFENSE;
this.havocbot_role_timeout = 0;
break;
}
- LOG_DEBUG("");
}
void havocbot_goalrating_ons_controlpoints_attack(entity this, float ratingscale)
// Count team mates interested in this control point
// (easier and cleaner than keeping counters per cp and teams)
- FOREACH_CLIENT(IS_PLAYER(it), {
+ FOREACH_CLIENT(it != this && IS_PLAYER(it), {
if(SAME_TEAM(it, this))
- if(it.havocbot_role_flags & HAVOCBOT_ONS_ROLE_OFFENSE)
+ if(it.havocbot_role == havocbot_role_ons_offense)
if(it.havocbot_ons_target == cp2)
++c;
});
}
// We'll consider only the best case
- bestvalue = 99999999999;
+ bestvalue = FLOAT_MAX;
cp = NULL;
for(cp1 = ons_worldcplist; cp1; cp1 = cp1.ons_worldcpnext)
{
// Rate waypoints near it
found = false;
best = NULL;
- bestvalue = 99999999999;
- for(radius=0; radius<1000 && !found; radius+=500)
+ bestvalue = FLOAT_MAX;
+ for (radius = 500; radius <= 1000 && !found; radius += 500)
{
- for(wp=findradius(cp.origin,radius); wp; wp=wp.chain)
+ IL_EACH(g_waypoints, vdist(cp.origin - it.origin, <, radius),
{
- if(!(wp.wpflags & WAYPOINTFLAG_GENERATED))
- if(wp.classname=="waypoint")
- if(checkpvs(wp.origin,cp))
+ if (!(it.wpflags & WAYPOINTFLAG_GENERATED) && checkpvs(it.origin, cp))
{
found = true;
- if(wp.cnt<bestvalue)
+ if (it.cnt < bestvalue)
{
- best = wp;
- bestvalue = wp.cnt;
+ best = it;
+ bestvalue = it.cnt;
}
}
- }
+ });
}
if(best)
{
// Should be touched
LOG_DEBUG(this.netname, " found a touchable controlpoint at ", vtos(cp.origin));
- found = false;
-
- // Look for auto generated waypoint
- if (!bot_waypoints_for_items)
- for (wp = findradius(cp.origin,100); wp; wp = wp.chain)
- {
- if(wp.classname=="waypoint")
- {
- navigation_routerating(this, wp, ratingscale, 10000);
- found = true;
- }
- }
-
- // Nothing found, rate the controlpoint itself
- if (!found)
- navigation_routerating(this, cp, ratingscale, 10000);
+ navigation_routerating(this, cp, ratingscale * 2, 10000);
}
}
{
entity g, wp, bestwp;
bool found;
- int best;
+ int bestvalue;
for(g = ons_worldgeneratorlist; g; g = g.ons_worldgeneratornext)
{
// Rate waypoints near it
found = false;
bestwp = NULL;
- best = 99999999999;
+ bestvalue = FLOAT_MAX;
- for(wp=findradius(g.origin,400); wp; wp=wp.chain)
+ IL_EACH(g_waypoints, vdist(g.origin - it.origin, <, 400),
{
- if(wp.classname=="waypoint")
- if(checkpvs(wp.origin,g))
+ if (checkpvs(it.origin, g))
{
found = true;
- if(wp.cnt<best)
+ if (it.cnt < bestvalue)
{
- bestwp = wp;
- best = wp.cnt;
+ bestwp = it;
+ bestvalue = it.cnt;
}
}
- }
+ });
if(bestwp)
{
{
navigation_goalrating_start(this);
havocbot_goalrating_enemyplayers(this, 20000, this.origin, 650);
- if(!havocbot_goalrating_ons_generator_attack(this, 20000))
- havocbot_goalrating_ons_controlpoints_attack(this, 20000);
- havocbot_goalrating_ons_offenseitems(this, 10000, this.origin, 10000);
+ if(!havocbot_goalrating_ons_generator_attack(this, 10000))
+ havocbot_goalrating_ons_controlpoints_attack(this, 10000);
+ havocbot_goalrating_items(this, 25000, this.origin, 10000);
navigation_goalrating_end(this);
navigation_goalrating_timeout_set(this);
return true;
}
-MUTATOR_HOOKFUNCTION(ons, CheckAllowedTeams)
+MUTATOR_HOOKFUNCTION(ons, TeamBalance_CheckAllowedTeams)
{
// onslaught is special
for(entity tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext)
{
- switch(tmp_entity.team)
+ if (Team_IsValidTeam(tmp_entity.team))
{
- case NUM_TEAM_1: c1 = 0; break;
- case NUM_TEAM_2: c2 = 0; break;
- case NUM_TEAM_3: c3 = 0; break;
- case NUM_TEAM_4: c4 = 0; break;
+ M_ARGV(0, float) |= Team_TeamToBit(tmp_entity.team);
}
}
// scoreboard setup
void ons_ScoreRules()
{
- CheckAllowedTeams(NULL);
- int teams = 0;
- if(c1 >= 0) teams |= BIT(0);
- if(c2 >= 0) teams |= BIT(1);
- if(c3 >= 0) teams |= BIT(2);
- if(c4 >= 0) teams |= BIT(3);
+ entity balance = TeamBalance_CheckAllowedTeams(NULL);
+ int teams = TeamBalance_GetAllowedTeams(balance);
+ TeamBalance_Destroy(balance);
GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
field_team(ST_ONS_CAPS, "destroyed", SFL_SORT_PRIO_PRIMARY);
field(SP_ONS_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
.entity havocbot_ons_target;
-.int havocbot_role_flags;
.float havocbot_attack_time;
void havocbot_role_ons_defense(entity this);
// generated file; do not modify
-#include <common/gamemodes/gamemode/race/race.qc>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/race/sv_race.qc>
+#endif
// generated file; do not modify
-#include <common/gamemodes/gamemode/race/race.qh>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/race/sv_race.qh>
+#endif
+++ /dev/null
-#include "race.qh"
-
-// TODO: sv_race
-#ifdef SVQC
-#include <server/race.qh>
-
-#define autocvar_g_race_laps_limit cvar("g_race_laps_limit")
-float autocvar_g_race_qualifying_timelimit;
-float autocvar_g_race_qualifying_timelimit_override;
-int autocvar_g_race_teams;
-
-// legacy bot roles
-.float race_checkpoint;
-void havocbot_role_race(entity this)
-{
- if(IS_DEAD(this))
- return;
-
- if (navigation_goalrating_timeout(this))
- {
- 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 == 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);
- }
-}
-
-void race_ScoreRules()
-{
- GameRules_score_enabled(false);
- GameRules_scoring(race_teams, 0, 0, {
- if (race_teams) {
- field_team(ST_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
- field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
- field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
- field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
- } else if (g_race_qualifying) {
- field(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
- } else {
- field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
- field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
- field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
- }
- });
-}
-
-void race_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later
-{
- if(autocvar_sv_eventlog)
- GameLogEcho(strcat(":race:", mode, ":", ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
-}
-
-float WinningCondition_Race(float fraglimit)
-{
- float wc;
- float n, c;
-
- n = 0;
- c = 0;
- FOREACH_CLIENT(IS_PLAYER(it), {
- ++n;
- if(CS(it).race_completed)
- ++c;
- });
- if(n && (n == c))
- return WINNING_YES;
- wc = WinningCondition_Scores(fraglimit, 0);
-
- // ALWAYS initiate overtime, unless EVERYONE has finished the race!
- if(wc == WINNING_YES || wc == WINNING_STARTSUDDENDEATHOVERTIME)
- // do NOT support equality when the laps are all raced!
- return WINNING_STARTSUDDENDEATHOVERTIME;
- else
- return WINNING_NEVER;
-}
-
-float WinningCondition_QualifyingThenRace(float limit)
-{
- float wc;
- wc = WinningCondition_Scores(limit, 0);
-
- // NEVER initiate overtime
- if(wc == WINNING_YES || wc == WINNING_STARTSUDDENDEATHOVERTIME)
- {
- return WINNING_YES;
- }
-
- return wc;
-}
-
-MUTATOR_HOOKFUNCTION(rc, ClientKill)
-{
- if(g_race_qualifying)
- M_ARGV(1, float) = 0; // killtime
-}
-
-MUTATOR_HOOKFUNCTION(rc, AbortSpeedrun)
-{
- entity player = M_ARGV(0, entity);
-
- if(autocvar_g_allow_checkpoints)
- race_PreparePlayer(player); // nice try
-}
-
-MUTATOR_HOOKFUNCTION(rc, PlayerPhysics)
-{
- entity player = M_ARGV(0, entity);
- float dt = M_ARGV(1, float);
-
- player.race_movetime_frac += dt;
- float f = floor(player.race_movetime_frac);
- player.race_movetime_frac -= f;
- player.race_movetime_count += f;
- player.race_movetime = player.race_movetime_frac + player.race_movetime_count;
-
-#ifdef SVQC
- if(IS_PLAYER(player))
- {
- if (player.race_penalty)
- if (time > player.race_penalty)
- player.race_penalty = 0;
- if(player.race_penalty)
- {
- player.velocity = '0 0 0';
- set_movetype(player, MOVETYPE_NONE);
- player.disableclientprediction = 2;
- }
- }
-#endif
-
- // force kbd movement for fairness
- float wishspeed;
- vector wishvel;
-
- // if record times matter
- // ensure nothing EVIL is being done (i.e. div0_evade)
- // this hinders joystick users though
- // but it still gives SOME analog control
- wishvel.x = fabs(CS(player).movement.x);
- wishvel.y = fabs(CS(player).movement.y);
- if(wishvel.x != 0 && wishvel.y != 0 && wishvel.x != wishvel.y)
- {
- wishvel.z = 0;
- wishspeed = vlen(wishvel);
- if(wishvel.x >= 2 * wishvel.y)
- {
- // pure X motion
- if(CS(player).movement.x > 0)
- CS(player).movement_x = wishspeed;
- else
- CS(player).movement_x = -wishspeed;
- CS(player).movement_y = 0;
- }
- else if(wishvel.y >= 2 * wishvel.x)
- {
- // pure Y motion
- CS(player).movement_x = 0;
- if(CS(player).movement.y > 0)
- CS(player).movement_y = wishspeed;
- else
- CS(player).movement_y = -wishspeed;
- }
- else
- {
- // diagonal
- if(CS(player).movement.x > 0)
- CS(player).movement_x = M_SQRT1_2 * wishspeed;
- else
- CS(player).movement_x = -M_SQRT1_2 * wishspeed;
- if(CS(player).movement.y > 0)
- CS(player).movement_y = M_SQRT1_2 * wishspeed;
- else
- CS(player).movement_y = -M_SQRT1_2 * wishspeed;
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(rc, reset_map_global)
-{
- float s;
-
- Score_NicePrint(NULL);
-
- race_ClearRecords();
- PlayerScore_Sort(race_place, 0, 1, 0);
-
- FOREACH_CLIENT(true, {
- if(it.race_place)
- {
- s = GameRules_scoring_add(it, RACE_FASTEST, 0);
- if(!s)
- it.race_place = 0;
- }
- race_EventLog(ftos(it.race_place), it);
- });
-
- if(g_race_qualifying == 2)
- {
- g_race_qualifying = 0;
- independent_players = 0;
- cvar_set("fraglimit", ftos(race_fraglimit));
- cvar_set("leadlimit", ftos(race_leadlimit));
- cvar_set("timelimit", ftos(race_timelimit));
- race_ScoreRules();
- }
-}
-
-MUTATOR_HOOKFUNCTION(rc, ClientConnect)
-{
- entity player = M_ARGV(0, entity);
-
- race_PreparePlayer(player);
- player.race_checkpoint = -1;
-
- string rr = RACE_RECORD;
-
- if(IS_REAL_CLIENT(player))
- {
- msg_entity = player;
- race_send_recordtime(MSG_ONE);
- race_send_speedaward(MSG_ONE);
-
- speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed")));
- speedaward_alltimebest_holder = uid2name(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp")));
- race_send_speedaward_alltimebest(MSG_ONE);
-
- float i;
- int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
- race_send_rankings_cnt(MSG_ONE);
- for (i = 1; i <= m; ++i)
- {
- race_SendRankings(i, 0, 0, MSG_ONE);
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(rc, MakePlayerObserver)
-{
- entity player = M_ARGV(0, entity);
-
- if(g_race_qualifying)
- if(GameRules_scoring_add(player, RACE_FASTEST, 0))
- player.frags = FRAGS_LMS_LOSER;
- else
- player.frags = FRAGS_SPECTATOR;
-
- race_PreparePlayer(player);
- player.race_checkpoint = -1;
-}
-
-MUTATOR_HOOKFUNCTION(rc, PlayerSpawn)
-{
- entity player = M_ARGV(0, entity);
- entity spawn_spot = M_ARGV(1, entity);
-
- if(spawn_spot.target == "")
- // Emergency: this wasn't a real spawnpoint. Can this ever happen?
- race_PreparePlayer(player);
-
- // if we need to respawn, do it right
- player.race_respawn_checkpoint = player.race_checkpoint;
- player.race_respawn_spotref = spawn_spot;
-
- player.race_place = 0;
-}
-
-MUTATOR_HOOKFUNCTION(rc, PutClientInServer)
-{
- entity player = M_ARGV(0, entity);
-
- if(IS_PLAYER(player))
- if(!game_stopped)
- {
- if(CS(player).killcount == FRAGS_SPECTATOR /* initial spawn */ || g_race_qualifying) // spawn
- race_PreparePlayer(player);
- else // respawn
- race_RetractPlayer(player);
-
- race_AbandonRaceCheck(player);
- }
-}
-
-MUTATOR_HOOKFUNCTION(rc, PlayerDies)
-{
- entity frag_target = M_ARGV(2, entity);
-
- frag_target.respawn_flags |= RESPAWN_FORCE;
- race_AbandonRaceCheck(frag_target);
-}
-
-MUTATOR_HOOKFUNCTION(rc, HavocBot_ChooseRole)
-{
- entity bot = M_ARGV(0, entity);
-
- bot.havocbot_role = havocbot_role_race;
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(rc, GetPressedKeys)
-{
- entity player = M_ARGV(0, entity);
-
- if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
- {
- if (!player.stored_netname)
- player.stored_netname = strzone(uid2name(player.crypto_idfp));
- if(player.stored_netname != player.netname)
- {
- db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
- strcpy(player.stored_netname, player.netname);
- }
- }
-
- if (!IS_OBSERVER(player))
- {
- if(vdist(player.velocity - player.velocity_z * '0 0 1', >, speedaward_speed))
- {
- speedaward_speed = vlen(player.velocity - player.velocity_z * '0 0 1');
- speedaward_holder = player.netname;
- speedaward_uid = player.crypto_idfp;
- speedaward_lastupdate = time;
- }
- if (speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1)
- {
- string rr = RACE_RECORD;
- race_send_speedaward(MSG_ALL);
- speedaward_lastsent = speedaward_speed;
- if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "")
- {
- speedaward_alltimebest = speedaward_speed;
- speedaward_alltimebest_holder = speedaward_holder;
- speedaward_alltimebest_uid = speedaward_uid;
- db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest));
- db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid);
- race_send_speedaward_alltimebest(MSG_ALL);
- }
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(rc, ForbidPlayerScore_Clear)
-{
- if(g_race_qualifying)
- return true; // in qualifying, you don't lose score by observing
-}
-
-MUTATOR_HOOKFUNCTION(rc, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
-{
- M_ARGV(0, float) = race_teams;
-}
-
-MUTATOR_HOOKFUNCTION(rc, Scores_CountFragsRemaining)
-{
- // announce remaining frags if not in qualifying mode
- if(!g_race_qualifying)
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(rc, GetRecords)
-{
- int record_page = M_ARGV(0, int);
- string ret_string = M_ARGV(1, string);
-
- for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
- {
- if(MapInfo_Get_ByID(i))
- {
- float r = race_readTime(MapInfo_Map_bspname, 1);
-
- if(!r)
- continue;
-
- string h = race_readName(MapInfo_Map_bspname, 1);
- ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r)), " ", h, "\n");
- }
- }
-
- M_ARGV(1, string) = ret_string;
-}
-
-MUTATOR_HOOKFUNCTION(rc, HideTeamNagger)
-{
- return true; // doesn't work so well
-}
-
-MUTATOR_HOOKFUNCTION(rc, FixClientCvars)
-{
- entity player = M_ARGV(0, entity);
-
- stuffcmd(player, "cl_cmd settemp cl_movecliptokeyboard 2\n");
-}
-
-MUTATOR_HOOKFUNCTION(rc, CheckRules_World)
-{
- float checkrules_timelimit = M_ARGV(1, float);
- float checkrules_fraglimit = M_ARGV(2, float);
-
- if(checkrules_timelimit >= 0)
- {
- if(!g_race_qualifying)
- {
- M_ARGV(0, float) = WinningCondition_Race(checkrules_fraglimit);
- return true;
- }
- else if(g_race_qualifying == 2)
- {
- M_ARGV(0, float) = WinningCondition_QualifyingThenRace(checkrules_fraglimit);
- return true;
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(rc, ReadLevelCvars)
-{
- if(g_race_qualifying == 2)
- warmup_stage = 0;
-}
-
-void race_Initialize()
-{
- race_ScoreRules();
- if(g_race_qualifying == 2)
- warmup_stage = 0;
-}
-
-void rc_SetLimits()
-{
- int fraglimit_override, leadlimit_override;
- float timelimit_override, qualifying_override;
-
- if(autocvar_g_race_teams)
- {
- GameRules_teams(true);
- race_teams = BITS(bound(2, autocvar_g_race_teams, 4));
- }
- else
- race_teams = 0;
-
- qualifying_override = autocvar_g_race_qualifying_timelimit_override;
- fraglimit_override = autocvar_g_race_laps_limit;
- leadlimit_override = 0; // currently not supported by race
- timelimit_override = autocvar_timelimit_override;
-
- float want_qualifying = ((qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit) > 0;
-
- if(autocvar_g_campaign)
- {
- g_race_qualifying = 1;
- independent_players = 1;
- }
- else if(want_qualifying)
- {
- g_race_qualifying = 2;
- independent_players = 1;
- race_fraglimit = (fraglimit_override >= 0) ? fraglimit_override : autocvar_fraglimit;
- race_leadlimit = (leadlimit_override >= 0) ? leadlimit_override : autocvar_leadlimit;
- race_timelimit = (timelimit_override >= 0) ? timelimit_override : autocvar_timelimit;
- qualifying_override = (qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit;
- fraglimit_override = 0;
- leadlimit_override = 0;
- timelimit_override = qualifying_override;
- }
- else
- g_race_qualifying = 0;
- GameRules_limit_score(fraglimit_override);
- GameRules_limit_lead(leadlimit_override);
- GameRules_limit_time(timelimit_override);
- GameRules_limit_time_qualifying(qualifying_override);
-}
-#endif
+++ /dev/null
-#pragma once
-
-#ifdef SVQC
-#include <common/mutators/base.qh>
-void rc_SetLimits();
-void race_Initialize();
-
-REGISTER_MUTATOR(rc, false)
-{
- MUTATOR_STATIC();
- MUTATOR_ONADD
- {
- rc_SetLimits();
-
- race_Initialize();
- }
- return 0;
-}
-#endif
--- /dev/null
+#include "sv_race.qh"
+
+#include <server/race.qh>
+
+#define autocvar_g_race_laps_limit cvar("g_race_laps_limit")
+float autocvar_g_race_qualifying_timelimit;
+float autocvar_g_race_qualifying_timelimit_override;
+int autocvar_g_race_teams;
+
+// legacy bot roles
+.float race_checkpoint;
+void havocbot_role_race(entity this)
+{
+ if(IS_DEAD(this))
+ return;
+
+ if (navigation_goalrating_timeout(this))
+ {
+ 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 == 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);
+ }
+}
+
+void race_ScoreRules()
+{
+ GameRules_score_enabled(false);
+ GameRules_scoring(race_teams, 0, 0, {
+ if (race_teams) {
+ field_team(ST_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
+ field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
+ field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
+ field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
+ } else if (g_race_qualifying) {
+ field(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
+ } else {
+ field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
+ field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
+ field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
+ }
+ });
+}
+
+void race_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later
+{
+ if(autocvar_sv_eventlog)
+ GameLogEcho(strcat(":race:", mode, ":", ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
+}
+
+float WinningCondition_Race(float fraglimit)
+{
+ float wc;
+ float n, c;
+
+ n = 0;
+ c = 0;
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ ++n;
+ if(CS(it).race_completed)
+ ++c;
+ });
+ if(n && (n == c))
+ return WINNING_YES;
+ wc = WinningCondition_Scores(fraglimit, 0);
+
+ // ALWAYS initiate overtime, unless EVERYONE has finished the race!
+ if(wc == WINNING_YES || wc == WINNING_STARTSUDDENDEATHOVERTIME)
+ // do NOT support equality when the laps are all raced!
+ return WINNING_STARTSUDDENDEATHOVERTIME;
+ else
+ return WINNING_NEVER;
+}
+
+float WinningCondition_QualifyingThenRace(float limit)
+{
+ float wc;
+ wc = WinningCondition_Scores(limit, 0);
+
+ // NEVER initiate overtime
+ if(wc == WINNING_YES || wc == WINNING_STARTSUDDENDEATHOVERTIME)
+ {
+ return WINNING_YES;
+ }
+
+ return wc;
+}
+
+MUTATOR_HOOKFUNCTION(rc, ClientKill)
+{
+ if(g_race_qualifying)
+ M_ARGV(1, float) = 0; // killtime
+}
+
+MUTATOR_HOOKFUNCTION(rc, AbortSpeedrun)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(autocvar_g_allow_checkpoints)
+ race_PreparePlayer(player); // nice try
+}
+
+MUTATOR_HOOKFUNCTION(rc, PlayerPhysics)
+{
+ entity player = M_ARGV(0, entity);
+ float dt = M_ARGV(1, float);
+
+ player.race_movetime_frac += dt;
+ float f = floor(player.race_movetime_frac);
+ player.race_movetime_frac -= f;
+ player.race_movetime_count += f;
+ player.race_movetime = player.race_movetime_frac + player.race_movetime_count;
+
+#ifdef SVQC
+ if(IS_PLAYER(player))
+ {
+ if (player.race_penalty)
+ if (time > player.race_penalty)
+ player.race_penalty = 0;
+ if(player.race_penalty)
+ {
+ player.velocity = '0 0 0';
+ set_movetype(player, MOVETYPE_NONE);
+ player.disableclientprediction = 2;
+ }
+ }
+#endif
+
+ // force kbd movement for fairness
+ float wishspeed;
+ vector wishvel;
+
+ // if record times matter
+ // ensure nothing EVIL is being done (i.e. div0_evade)
+ // this hinders joystick users though
+ // but it still gives SOME analog control
+ wishvel.x = fabs(CS(player).movement.x);
+ wishvel.y = fabs(CS(player).movement.y);
+ if(wishvel.x != 0 && wishvel.y != 0 && wishvel.x != wishvel.y)
+ {
+ wishvel.z = 0;
+ wishspeed = vlen(wishvel);
+ if(wishvel.x >= 2 * wishvel.y)
+ {
+ // pure X motion
+ if(CS(player).movement.x > 0)
+ CS(player).movement_x = wishspeed;
+ else
+ CS(player).movement_x = -wishspeed;
+ CS(player).movement_y = 0;
+ }
+ else if(wishvel.y >= 2 * wishvel.x)
+ {
+ // pure Y motion
+ CS(player).movement_x = 0;
+ if(CS(player).movement.y > 0)
+ CS(player).movement_y = wishspeed;
+ else
+ CS(player).movement_y = -wishspeed;
+ }
+ else
+ {
+ // diagonal
+ if(CS(player).movement.x > 0)
+ CS(player).movement_x = M_SQRT1_2 * wishspeed;
+ else
+ CS(player).movement_x = -M_SQRT1_2 * wishspeed;
+ if(CS(player).movement.y > 0)
+ CS(player).movement_y = M_SQRT1_2 * wishspeed;
+ else
+ CS(player).movement_y = -M_SQRT1_2 * wishspeed;
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(rc, reset_map_global)
+{
+ float s;
+
+ Score_NicePrint(NULL);
+
+ race_ClearRecords();
+ PlayerScore_Sort(race_place, 0, 1, 0);
+
+ FOREACH_CLIENT(true, {
+ if(it.race_place)
+ {
+ s = GameRules_scoring_add(it, RACE_FASTEST, 0);
+ if(!s)
+ it.race_place = 0;
+ }
+ race_EventLog(ftos(it.race_place), it);
+ });
+
+ if(g_race_qualifying == 2)
+ {
+ g_race_qualifying = 0;
+ independent_players = 0;
+ cvar_set("fraglimit", ftos(race_fraglimit));
+ cvar_set("leadlimit", ftos(race_leadlimit));
+ cvar_set("timelimit", ftos(race_timelimit));
+ race_ScoreRules();
+ }
+}
+
+MUTATOR_HOOKFUNCTION(rc, ClientConnect)
+{
+ entity player = M_ARGV(0, entity);
+
+ race_PreparePlayer(player);
+ player.race_checkpoint = -1;
+
+ string rr = RACE_RECORD;
+
+ if(IS_REAL_CLIENT(player))
+ {
+ msg_entity = player;
+ race_send_recordtime(MSG_ONE);
+ race_send_speedaward(MSG_ONE);
+
+ speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed")));
+ speedaward_alltimebest_holder = uid2name(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp")));
+ race_send_speedaward_alltimebest(MSG_ONE);
+
+ float i;
+ int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
+ race_send_rankings_cnt(MSG_ONE);
+ for (i = 1; i <= m; ++i)
+ {
+ race_SendRankings(i, 0, 0, MSG_ONE);
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(rc, MakePlayerObserver)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(g_race_qualifying)
+ if(GameRules_scoring_add(player, RACE_FASTEST, 0))
+ player.frags = FRAGS_LMS_LOSER;
+ else
+ player.frags = FRAGS_SPECTATOR;
+
+ race_PreparePlayer(player);
+ player.race_checkpoint = -1;
+}
+
+MUTATOR_HOOKFUNCTION(rc, PlayerSpawn)
+{
+ entity player = M_ARGV(0, entity);
+ entity spawn_spot = M_ARGV(1, entity);
+
+ if(spawn_spot.target == "")
+ // Emergency: this wasn't a real spawnpoint. Can this ever happen?
+ race_PreparePlayer(player);
+
+ // if we need to respawn, do it right
+ player.race_respawn_checkpoint = player.race_checkpoint;
+ player.race_respawn_spotref = spawn_spot;
+
+ player.race_place = 0;
+}
+
+MUTATOR_HOOKFUNCTION(rc, PutClientInServer)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(IS_PLAYER(player))
+ if(!game_stopped)
+ {
+ if(CS(player).killcount == FRAGS_SPECTATOR /* initial spawn */ || g_race_qualifying) // spawn
+ race_PreparePlayer(player);
+ else // respawn
+ race_RetractPlayer(player);
+
+ race_AbandonRaceCheck(player);
+ }
+}
+
+MUTATOR_HOOKFUNCTION(rc, PlayerDies)
+{
+ entity frag_target = M_ARGV(2, entity);
+
+ frag_target.respawn_flags |= RESPAWN_FORCE;
+ race_AbandonRaceCheck(frag_target);
+}
+
+MUTATOR_HOOKFUNCTION(rc, HavocBot_ChooseRole)
+{
+ entity bot = M_ARGV(0, entity);
+
+ bot.havocbot_role = havocbot_role_race;
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(rc, GetPressedKeys)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
+ {
+ if (!player.stored_netname)
+ player.stored_netname = strzone(uid2name(player.crypto_idfp));
+ if(player.stored_netname != player.netname)
+ {
+ db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
+ strcpy(player.stored_netname, player.netname);
+ }
+ }
+
+ if (!IS_OBSERVER(player))
+ {
+ if(vdist(player.velocity - player.velocity_z * '0 0 1', >, speedaward_speed))
+ {
+ speedaward_speed = vlen(player.velocity - player.velocity_z * '0 0 1');
+ speedaward_holder = player.netname;
+ speedaward_uid = player.crypto_idfp;
+ speedaward_lastupdate = time;
+ }
+ if (speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1)
+ {
+ string rr = RACE_RECORD;
+ race_send_speedaward(MSG_ALL);
+ speedaward_lastsent = speedaward_speed;
+ if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "")
+ {
+ speedaward_alltimebest = speedaward_speed;
+ speedaward_alltimebest_holder = speedaward_holder;
+ speedaward_alltimebest_uid = speedaward_uid;
+ db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest));
+ db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid);
+ race_send_speedaward_alltimebest(MSG_ALL);
+ }
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(rc, ForbidPlayerScore_Clear)
+{
+ if(g_race_qualifying)
+ return true; // in qualifying, you don't lose score by observing
+}
+
+MUTATOR_HOOKFUNCTION(rc, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+ M_ARGV(0, float) = race_teams;
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(rc, Scores_CountFragsRemaining)
+{
+ // announce remaining frags if not in qualifying mode
+ if(!g_race_qualifying)
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(rc, GetRecords)
+{
+ int record_page = M_ARGV(0, int);
+ string ret_string = M_ARGV(1, string);
+
+ for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
+ {
+ if(MapInfo_Get_ByID(i))
+ {
+ float r = race_readTime(MapInfo_Map_bspname, 1);
+
+ if(!r)
+ continue;
+
+ string h = race_readName(MapInfo_Map_bspname, 1);
+ ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r)), " ", h, "\n");
+ }
+ }
+
+ M_ARGV(1, string) = ret_string;
+}
+
+MUTATOR_HOOKFUNCTION(rc, HideTeamNagger)
+{
+ return true; // doesn't work so well
+}
+
+MUTATOR_HOOKFUNCTION(rc, FixClientCvars)
+{
+ entity player = M_ARGV(0, entity);
+
+ stuffcmd(player, "cl_cmd settemp cl_movecliptokeyboard 2\n");
+}
+
+MUTATOR_HOOKFUNCTION(rc, CheckRules_World)
+{
+ float checkrules_timelimit = M_ARGV(1, float);
+ float checkrules_fraglimit = M_ARGV(2, float);
+
+ if(checkrules_timelimit >= 0)
+ {
+ if(!g_race_qualifying)
+ {
+ M_ARGV(0, float) = WinningCondition_Race(checkrules_fraglimit);
+ return true;
+ }
+ else if(g_race_qualifying == 2)
+ {
+ M_ARGV(0, float) = WinningCondition_QualifyingThenRace(checkrules_fraglimit);
+ return true;
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(rc, ReadLevelCvars)
+{
+ if(g_race_qualifying == 2)
+ warmup_stage = 0;
+}
+
+void race_Initialize()
+{
+ race_ScoreRules();
+ if(g_race_qualifying == 2)
+ warmup_stage = 0;
+}
+
+void rc_SetLimits()
+{
+ int fraglimit_override, leadlimit_override;
+ float timelimit_override, qualifying_override;
+
+ if(autocvar_g_race_teams)
+ {
+ GameRules_teams(true);
+ race_teams = BITS(bound(2, autocvar_g_race_teams, 4));
+ }
+ else
+ race_teams = 0;
+
+ qualifying_override = autocvar_g_race_qualifying_timelimit_override;
+ fraglimit_override = autocvar_g_race_laps_limit;
+ leadlimit_override = 0; // currently not supported by race
+ timelimit_override = autocvar_timelimit_override;
+
+ float want_qualifying = ((qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit) > 0;
+
+ if(autocvar_g_campaign)
+ {
+ g_race_qualifying = 1;
+ independent_players = 1;
+ }
+ else if(want_qualifying)
+ {
+ g_race_qualifying = 2;
+ independent_players = 1;
+ race_fraglimit = (fraglimit_override >= 0) ? fraglimit_override : autocvar_fraglimit;
+ race_leadlimit = (leadlimit_override >= 0) ? leadlimit_override : autocvar_leadlimit;
+ race_timelimit = (timelimit_override >= 0) ? timelimit_override : autocvar_timelimit;
+ qualifying_override = (qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit;
+ fraglimit_override = 0;
+ leadlimit_override = 0;
+ timelimit_override = qualifying_override;
+ }
+ else
+ g_race_qualifying = 0;
+ GameRules_limit_score(fraglimit_override);
+ GameRules_limit_lead(leadlimit_override);
+ GameRules_limit_time(timelimit_override);
+ GameRules_limit_time_qualifying(qualifying_override);
+}
--- /dev/null
+#pragma once
+
+#include <common/mutators/base.qh>
+void rc_SetLimits();
+void race_Initialize();
+
+REGISTER_MUTATOR(rc, false)
+{
+ MUTATOR_STATIC();
+ MUTATOR_ONADD
+ {
+ rc_SetLimits();
+
+ race_Initialize();
+ }
+ return 0;
+}
// generated file; do not modify
-#include <common/gamemodes/gamemode/tdm/tdm.qc>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/tdm/sv_tdm.qc>
+#endif
// generated file; do not modify
-#include <common/gamemodes/gamemode/tdm/tdm.qh>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/tdm/sv_tdm.qh>
+#endif
--- /dev/null
+#include "sv_tdm.qh"
+
+// TODO? rename to teamdeathmatch
+int autocvar_g_tdm_teams;
+int autocvar_g_tdm_teams_override;
+
+/*QUAKED spawnfunc_tdm_team (0 .5 .8) (-16 -16 -24) (16 16 32)
+Team declaration for TDM gameplay, this allows you to decide what team names and control point models are used in your map.
+Note: If you use spawnfunc_tdm_team entities you must define at least 2! However, unlike domination, you don't need to make a blank one too.
+Keys:
+"netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
+"cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
+spawnfunc(tdm_team)
+{
+ if(!g_tdm || !this.cnt) { delete(this); return; }
+
+ this.classname = "tdm_team";
+ this.team = this.cnt + 1;
+}
+
+// code from here on is just to support maps that don't have team entities
+void tdm_SpawnTeam (string teamname, int teamcolor)
+{
+ entity this = new_pure(tdm_team);
+ this.netname = teamname;
+ this.cnt = teamcolor - 1;
+ this.team = teamcolor;
+ this.spawnfunc_checked = true;
+ //spawnfunc_tdm_team(this);
+}
+
+void tdm_DelayedInit(entity this)
+{
+ // if no teams are found, spawn defaults
+ if(find(NULL, classname, "tdm_team") == NULL)
+ {
+ LOG_TRACE("No \"tdm_team\" entities found on this map, creating them anyway.");
+
+ int numteams = autocvar_g_tdm_teams_override;
+ if(numteams < 2) { numteams = autocvar_g_tdm_teams; }
+
+ int teams = BITS(bound(2, numteams, 4));
+ if(teams & BIT(0))
+ tdm_SpawnTeam("Red", NUM_TEAM_1);
+ if(teams & BIT(1))
+ tdm_SpawnTeam("Blue", NUM_TEAM_2);
+ if(teams & BIT(2))
+ tdm_SpawnTeam("Yellow", NUM_TEAM_3);
+ if(teams & BIT(3))
+ tdm_SpawnTeam("Pink", NUM_TEAM_4);
+ }
+}
+
+MUTATOR_HOOKFUNCTION(tdm, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+ M_ARGV(1, string) = "tdm_team";
+}
+
+MUTATOR_HOOKFUNCTION(tdm, Scores_CountFragsRemaining)
+{
+ // announce remaining frags
+ return true;
+}
--- /dev/null
+#pragma once
+
+#include <common/mutators/base.qh>
+int autocvar_g_tdm_point_limit;
+int autocvar_g_tdm_point_leadlimit;
+bool autocvar_g_tdm_team_spawns;
+void tdm_DelayedInit(entity this);
+
+REGISTER_MUTATOR(tdm, false)
+{
+ MUTATOR_STATIC();
+ MUTATOR_ONADD
+ {
+ GameRules_teams(true);
+ GameRules_spawning_teams(autocvar_g_tdm_team_spawns);
+ GameRules_limit_score(autocvar_g_tdm_point_limit);
+ GameRules_limit_lead(autocvar_g_tdm_point_leadlimit);
+
+ InitializeEntity(NULL, tdm_DelayedInit, INITPRIO_GAMETYPE);
+ }
+ return 0;
+}
+++ /dev/null
-#include "tdm.qh"
-
-// TODO: sv_tdm
-// TODO? rename to teamdeathmatch
-#ifdef SVQC
-int autocvar_g_tdm_teams;
-int autocvar_g_tdm_teams_override;
-
-/*QUAKED spawnfunc_tdm_team (0 .5 .8) (-16 -16 -24) (16 16 32)
-Team declaration for TDM gameplay, this allows you to decide what team names and control point models are used in your map.
-Note: If you use spawnfunc_tdm_team entities you must define at least 2! However, unlike domination, you don't need to make a blank one too.
-Keys:
-"netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
-"cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
-spawnfunc(tdm_team)
-{
- if(!g_tdm || !this.cnt) { delete(this); return; }
-
- this.classname = "tdm_team";
- this.team = this.cnt + 1;
-}
-
-// code from here on is just to support maps that don't have team entities
-void tdm_SpawnTeam (string teamname, int teamcolor)
-{
- entity this = new_pure(tdm_team);
- this.netname = teamname;
- this.cnt = teamcolor - 1;
- this.team = teamcolor;
- this.spawnfunc_checked = true;
- //spawnfunc_tdm_team(this);
-}
-
-void tdm_DelayedInit(entity this)
-{
- // if no teams are found, spawn defaults
- if(find(NULL, classname, "tdm_team") == NULL)
- {
- LOG_TRACE("No \"tdm_team\" entities found on this map, creating them anyway.");
-
- int numteams = autocvar_g_tdm_teams_override;
- if(numteams < 2) { numteams = autocvar_g_tdm_teams; }
-
- int teams = BITS(bound(2, numteams, 4));
- if(teams & BIT(0))
- tdm_SpawnTeam("Red", NUM_TEAM_1);
- if(teams & BIT(1))
- tdm_SpawnTeam("Blue", NUM_TEAM_2);
- if(teams & BIT(2))
- tdm_SpawnTeam("Yellow", NUM_TEAM_3);
- if(teams & BIT(3))
- tdm_SpawnTeam("Pink", NUM_TEAM_4);
- }
-}
-
-MUTATOR_HOOKFUNCTION(tdm, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
-{
- M_ARGV(1, string) = "tdm_team";
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(tdm, Scores_CountFragsRemaining)
-{
- // announce remaining frags
- return true;
-}
-#endif
+++ /dev/null
-#pragma once
-
-#ifdef SVQC
-#include <common/mutators/base.qh>
-int autocvar_g_tdm_point_limit;
-int autocvar_g_tdm_point_leadlimit;
-bool autocvar_g_tdm_team_spawns;
-void tdm_DelayedInit(entity this);
-
-REGISTER_MUTATOR(tdm, false)
-{
- MUTATOR_STATIC();
- MUTATOR_ONADD
- {
- GameRules_teams(true);
- GameRules_spawning_teams(autocvar_g_tdm_team_spawns);
- GameRules_limit_score(autocvar_g_tdm_point_limit);
- GameRules_limit_lead(autocvar_g_tdm_point_leadlimit);
-
- InitializeEntity(NULL, tdm_DelayedInit, INITPRIO_GAMETYPE);
- }
- return 0;
-}
-#endif
#pragma once
// TODO: find a better location for these?
-float total_players;
-float redalive, bluealive, yellowalive, pinkalive;
+int total_players;
// todo: accept the number of teams as a parameter
void GameRules_teams(bool value);
REGISTER_IMPULSE(waypoint_clear, 48)
LEGACY_IMPULSE(g_waypointsprite_clear, 48, "waypoint_clear")
-REGISTER_IMPULSE(navwaypoint_spawn, 103)
-LEGACY_IMPULSE(g_waypointeditor_spawn, 103, "navwaypoint_spawn")
-
-REGISTER_IMPULSE(navwaypoint_remove, 104)
-LEGACY_IMPULSE(g_waypointeditor_remove, 104, "navwaypoint_remove")
-
-REGISTER_IMPULSE(navwaypoint_relink, 105)
-LEGACY_IMPULSE(g_waypointeditor_relinkall, 105, "navwaypoint_relink")
-
-REGISTER_IMPULSE(navwaypoint_save, 106)
-LEGACY_IMPULSE(g_waypointeditor_saveall, 106, "navwaypoint_save")
-
-REGISTER_IMPULSE(navwaypoint_unreachable, 107)
-LEGACY_IMPULSE(g_waypointeditor_unreachable, 107, "navwaypoint_unreachable")
#define CHIMPULSE(id, n) _CHIMPULSE(CHIMPULSE_##id, n)
#define _CHIMPULSE(id, n) \
TC(Inventory, this);
WriteHeader(MSG_ENTITY, ENT_CLIENT_INVENTORY);
entity e = this.owner;
- if (IS_SPEC(e)) e = e.enemy;
+ if (IS_SPEC(e)) e = PS(e.enemy); // TODO: how can this *ever* be the case?
TC(Player, e);
Inventory data = e.inventory;
Inventory_Write(data);
{
Inventory inv = NEW(Inventory), bak = NEW(Inventory);
inv.inventory = bak;
- inv.drawonlytoclient = e;
+ inv.drawonlytoclient = IS_CLIENT(e) ? e : e.m_client;
Net_LinkEntity((inv.owner = e).inventory = inv, false, 0, Inventory_Send);
}
void Inventory_delete(entity e) { delete(e.inventory.inventory); delete(e.inventory); }
.float strength_finished = _STAT(STRENGTH_FINISHED);
.float invincible_finished = _STAT(INVINCIBLE_FINISHED);
+#define spawnfunc_body(item) \
+ if (!Item_IsDefinitionAllowed(item)) \
+ { \
+ startitem_failed = true; \
+ delete(this); \
+ return; \
+ } \
+ StartItem(this, item)
+
#define SPAWNFUNC_ITEM(name, item) \
- spawnfunc(name) \
+ spawnfunc(name) \
+ { \
+ spawnfunc_body(item); \
+ }
+
+#define SPAWNFUNC_ITEM_COND(name, cond, item1, item2) \
+ spawnfunc(name) \
{ \
- if (!Item_IsDefinitionAllowed(item)) \
- { \
- startitem_failed = true; \
- delete(this); \
- return; \
- } \
- StartItem(this, item); \
+ entity item = (cond) ? item1 : item2; \
+ spawnfunc_body(item); \
}
#else
{
ITEM_FLAG_NORMAL = BIT(0), ///< Item is usable during normal gameplay.
ITEM_FLAG_MUTATORBLOCKED = BIT(1),
- ITEM_FLAG_RESOURCE = BIT(2) ///< Item is is a resource, not a held item.
+ ITEM_FLAG_RESOURCE = BIT(2) ///< Item is is a resource, not a held item.
};
#define ITEM_HANDLE(signal, ...) __Item_Send_##signal(__VA_ARGS__)
this.m_model = MDL_Bullets_ITEM;
#endif
this.netname = "bullets";
- this.m_name = "bullets";
+ this.m_name = _("bullets");
this.m_icon = "ammo_bullets";
#ifdef SVQC
this.m_botvalue = 1500;
this.m_model = MDL_Cells_ITEM;
#endif
this.netname = "cells";
- this.m_name = "cells";
+ this.m_name = _("cells");
this.m_icon = "ammo_cells";
#ifdef SVQC
this.m_botvalue = 1500;
this.m_model = MDL_Plasma_ITEM;
#endif
this.netname = "plasma";
- this.m_name = "plasma";
+ this.m_name = _("plasma");
this.m_icon = "ammo_plasma";
#ifdef SVQC
this.m_botvalue = 1500;
this.m_model = MDL_Rockets_ITEM;
#endif
this.netname = "rockets";
- this.m_name = "rockets";
+ this.m_name = _("rockets");
this.m_icon = "ammo_rockets";
#ifdef SVQC
this.m_botvalue = 1500;
this.m_model = MDL_Shells_ITEM;
#endif
this.netname = "shells";
- this.m_name = "shells";
+ this.m_name = _("shells");
this.m_icon = "ammo_shells";
#ifdef SVQC
this.m_botvalue = 1000;
this.m_sound = SND_ArmorSmall;
#endif
this.netname = "armor_small";
- this.m_name = "5 Armor";
+ this.m_name = _("Small armor");
this.m_icon = "armor";
#ifdef SVQC
this.m_itemid = IT_ARMOR_SHARD;
this.m_sound = SND_ArmorMedium;
#endif
this.netname = "armor_medium";
- this.m_name = "25 Armor";
+ this.m_name = _("Medium armor");
this.m_icon = "armor";
#ifdef SVQC
this.m_itemid = IT_ARMOR;
this.m_sound = SND_ArmorBig;
#endif
this.netname = "armor_big";
- this.m_name = "50 Armor";
+ this.m_name = _("Big armor");
this.m_icon = "armor";
this.m_color = '0 1 0';
this.m_waypoint = _("Big armor");
this.m_sound = SND_ArmorMega;
#endif
this.netname = "armor_mega";
- this.m_name = "100 Armor";
+ this.m_name = _("Mega armor");
this.m_icon = "item_large_armor";
this.m_color = '0 1 0';
this.m_waypoint = _("Mega armor");
this.m_sound = SND_HealthSmall;
#endif
this.netname = "health_small";
- this.m_name = "5 Health";
+ this.m_name = _("Small health");
this.m_icon = "health";
#ifdef SVQC
this.m_itemid = IT_5HP;
this.m_sound = SND_HealthMedium;
#endif
this.netname = "health_medium";
- this.m_name = "25 Health";
+ this.m_name = _("Medium health");
this.m_icon = "health";
#ifdef SVQC
this.m_itemid = IT_25HP;
this.m_sound = SND_HealthBig;
#endif
this.netname = "health_big";
- this.m_name = "50 Health";
+ this.m_name = _("Big health");
this.m_icon = "health";
this.m_color = '1 0 0';
this.m_waypoint = _("Big health");
this.m_sound = SND_HealthMega;
#endif
this.netname = "health_mega";
- this.m_name = "100 Health";
+ this.m_name = _("Mega health");
this.m_icon = "item_mega_health";
this.m_color = '1 0 0';
this.m_waypoint = _("Mega health");
this.m_itemid = IT_JETPACK;
#endif
this.netname = "jetpack";
- this.m_name = "Jetpack";
+ this.m_name = _("Jetpack");
this.m_icon = "jetpack";
this.m_color = '0.5 0.5 0.5';
this.m_waypoint = _("Jetpack");
this.m_model = MDL_JetpackFuel_ITEM;
#endif
this.netname = "fuel";
- this.m_name = "Fuel";
+ this.m_name = _("fuel");
this.m_icon = "ammo_fuel";
#ifdef SVQC
this.m_botvalue = 2000;
this.m_model = MDL_JetpackRegen_ITEM;
#endif
this.netname = "fuel_regen";
- this.m_name = "Fuel regenerator";
+ this.m_name = _("Fuel regenerator");
this.m_icon = "fuelregen";
this.m_color = '1 0.5 0';
this.m_waypoint = _("Fuel regen");
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);
+ //LOG_DEBUGF("entity %i picked up %s", player, this.m_name);
+ entity store = IS_PLAYER(player) ? PS(player) : player;
+ store.inventory.inv_items[this.m_id]++;
+ Inventory_update(store);
}
return b;
}
#ifdef SVQC
ATTRIB(Powerup, m_mins, vector, '-16 -16 0');
ATTRIB(Powerup, m_maxs, vector, '16 16 80');
- ATTRIB(Powerup, m_botvalue, int, 20000);
+ ATTRIB(Powerup, m_botvalue, int, 11000);
ATTRIB(Powerup, m_itemflags, int, FL_POWERUP);
ATTRIB(Powerup, m_respawntime, float(), GET(g_pickup_respawntime_powerup));
ATTRIB(Powerup, m_respawntimejitter, float(), GET(g_pickup_respawntimejitter_powerup));
this.m_respawnsound = SND_STRENGTH_RESPAWN;
#endif
this.netname = "strength";
- this.m_name = "Strength Powerup";
+ this.m_name = _("Strength");
this.m_icon = "strength";
this.m_color = '0 0 1';
this.m_waypoint = _("Strength");
this.m_respawnsound = SND_SHIELD_RESPAWN;
#endif
this.netname = "invincible";
- this.m_name = "Shield";
+ this.m_name = _("Shield");
this.m_icon = "shield";
this.m_color = '1 0 1';
this.m_waypoint = _("Shield");
string _MapInfo_GetDefault(Gametype t)
{
- switch(t)
- {
- case MAPINFO_TYPE_DEATHMATCH: return "30 20 0";
- case MAPINFO_TYPE_TEAM_DEATHMATCH: return "50 20 2 0";
- case MAPINFO_TYPE_DOMINATION: return "200 20 0";
- case MAPINFO_TYPE_CTF: return "300 20 10 0";
- case MAPINFO_TYPE_LMS: return "9 20 0";
- case MAPINFO_TYPE_CA: return "10 20 0";
- case MAPINFO_TYPE_KEYHUNT: return "1000 20 3 0";
- case MAPINFO_TYPE_ASSAULT: return "20 0";
- case MAPINFO_TYPE_RACE: return "20 5 7 15 0";
- case MAPINFO_TYPE_ONSLAUGHT: return "20 0";
- case MAPINFO_TYPE_NEXBALL: return "5 20 0";
- case MAPINFO_TYPE_CTS: return "20 0 0";
- case MAPINFO_TYPE_FREEZETAG: return "10 20 0";
- // NOTE: DO NOT ADD ANY MORE GAME TYPES HERE
- // THIS IS JUST LEGACY SUPPORT FOR NEXUIZ MAPS
- // ONLY ADD NEW STUFF TO _MapInfo_GetDefaultEx
- // THIS FUNCTION WILL EVENTUALLY BE REMOVED
- default: return "";
- }
+ return t.m_legacydefaults;
}
void _MapInfo_Map_ApplyGametype(string s, Gametype pWantedType, Gametype pThisType, int load_default)
if(load_default)
_MapInfo_Map_ApplyGametype(_MapInfo_GetDefault(pThisType), pWantedType, pThisType, false);
- if(pWantedType == MAPINFO_TYPE_ASSAULT || pWantedType == MAPINFO_TYPE_ONSLAUGHT || pWantedType == MAPINFO_TYPE_RACE || pWantedType == MAPINFO_TYPE_CTS) // these modes don't use fraglimit
+ if(!pWantedType.frags) // these modes don't use fraglimit
{
cvar_set("fraglimit", "0");
}
// rc = timelimit timelimit_qualification laps laps_teamplay
if(pWantedType == MAPINFO_TYPE_RACE)
{
+ cvar_set("fraglimit", "0"); // special case!
+
sa = car(s); if(sa == "") sa = cvar_string("timelimit");
cvar_set("g_race_qualifying_timelimit", sa);
s = cdr(s);
s = cdr(s);
}
- if(pWantedType == MAPINFO_TYPE_ASSAULT || pWantedType == MAPINFO_TYPE_ONSLAUGHT || pWantedType == MAPINFO_TYPE_CTS) // these modes don't use fraglimit
+ if(!pWantedType.frags) // these modes don't use fraglimit
{
cvar_set("leadlimit", "0");
}
if(fexists(strcat("scripts/", pFilename, ".arena")))
fputs(fh, "settemp_for_type all sv_q3acompat_machineshotgunswap 1\n");
+ if(fexists(strcat("scripts/", pFilename, ".defi")))
+ fputs(fh, "settemp_for_type all sv_vq3compat 1\n");
+
fputs(fh, "// optional: fog density red green blue alpha mindist maxdist\n");
fputs(fh, "// optional: settemp_for_type (all|gametypename) cvarname value\n");
fputs(fh, "// optional: clientsettemp_for_type (all|gametypename) cvarname value\n");
{
int r = MapInfo_Get_ByName_NoFallbacks(pFilename, pAllowGenerate, pGametypeToSet);
- if(cvar("g_tdm_on_dm_maps"))
- {
- // if this is set, all DM maps support TDM too
- if (!(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_TEAM_DEATHMATCH.m_flags))
- if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH.m_flags)
- _MapInfo_Map_ApplyGametypeEx ("", pGametypeToSet, MAPINFO_TYPE_TEAM_DEATHMATCH);
- }
+ FOREACH(Gametypes, it.m_isForcedSupported(it), _MapInfo_Map_ApplyGametypeEx("", pGametypeToSet, it));
if(pGametypeToSet)
{
ATTRIB(Gametype, message, string);
/** does this gametype support teamplay? */
ATTRIB(Gametype, team, bool, false);
+ /** does this gametype use a point limit? */
+ ATTRIB(Gametype, frags, bool, true);
/** game type defaults */
ATTRIB(Gametype, model2, string);
/** game type description */
ATTRIB(Gametype, m_modicons_reset, void());
#endif
+ /** DO NOT USE, this is compatibility for legacy maps! */
+ ATTRIB(Gametype, m_legacydefaults, string, "");
+
ATTRIB(Gametype, m_mutators, string);
METHOD(Gametype, m_parse_mapinfo, bool(string k, string v))
{
{
return false;
}
+ METHOD(Gametype, m_isForcedSupported, bool(Gametype this))
+ {
+ return false;
+ }
+ METHOD(Gametype, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+ {
+ TC(Gametype, this);
+ returns(menu, _("Frag limit:"), 5, 100, 5, "fraglimit_override", string_null, _("The amount of frags needed before the match will end"));
+ }
METHOD(Gametype, describe, string(Gametype this))
{
returns(this.message, strcat("gametype_", this.mdl));
}
- METHOD(Gametype, gametype_init, void(Gametype this, string hname, string sname, string g_name, bool gteamplay, string mutators, string defaults, string gdescription))
+ METHOD(Gametype, gametype_init, void(Gametype this, string hname, string sname, string g_name, bool gteamplay, bool gusepoints, string mutators, string defaults, string gdescription))
{
this.netname = g_name;
this.mdl = sname;
this.m_mutators = cons(sname, mutators);
this.model2 = defaults;
this.gametype_description = gdescription;
+ this.frags = gusepoints;
// same as `1 << m_id`
MAPINFO_TYPE_ALL |= this.items = this.m_flags = (MAPINFO_TYPE_ALL + 1);
CLASS(Deathmatch, Gametype)
INIT(Deathmatch)
{
- this.gametype_init(this, _("Deathmatch"),"dm","g_dm",false,"","timelimit=15 pointlimit=30 leadlimit=0",_("Score as many frags as you can"));
+ this.gametype_init(this, _("Deathmatch"),"dm","g_dm",false,true,"","timelimit=15 pointlimit=30 leadlimit=0",_("Score as many frags as you can"));
}
METHOD(Deathmatch, m_isAlwaysSupported, bool(Gametype this, int spawnpoints, float diameter))
{
return true;
}
+ ATTRIB(Deathmatch, m_legacydefaults, string, "30 20 0");
ENDCLASS(Deathmatch)
REGISTER_GAMETYPE(DEATHMATCH, NEW(Deathmatch));
CLASS(LastManStanding, Gametype)
INIT(LastManStanding)
{
- this.gametype_init(this, _("Last Man Standing"),"lms","g_lms",false,"","timelimit=20 lives=9 leadlimit=0",_("Survive and kill until the enemies have no lives left"));
+ this.gametype_init(this, _("Last Man Standing"),"lms","g_lms",false,true,"","timelimit=20 lives=9 leadlimit=0",_("Survive and kill until the enemies have no lives left"));
}
METHOD(LastManStanding, m_isAlwaysSupported, bool(Gametype this, int spawnpoints, float diameter))
{
return true;
}
+ METHOD(LastManStanding, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+ {
+ TC(Gametype, this);
+ returns(menu, _("Lives:"), 3, 50, 1, "g_lms_lives_override", string_null, string_null);
+ }
+ ATTRIB(LastManStanding, m_legacydefaults, string, "9 20 0");
ENDCLASS(LastManStanding)
REGISTER_GAMETYPE(LMS, NEW(LastManStanding));
CLASS(Race, Gametype)
INIT(Race)
{
- this.gametype_init(this, _("Race"),"rc","g_race",false,"","timelimit=20 qualifying_timelimit=5 laplimit=7 teamlaplimit=15 leadlimit=0",_("Race against other players to the finish line"));
+ this.gametype_init(this, _("Race"),"rc","g_race",false,true,"","timelimit=20 qualifying_timelimit=5 laplimit=7 teamlaplimit=15 leadlimit=0",_("Race against other players to the finish line"));
}
METHOD(Race, m_parse_mapinfo, bool(string k, string v))
{
{
return true;
}
+ METHOD(Race, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+ {
+ TC(Gametype, this);
+ returns(menu, _("Laps:"), 1, 25, 1, "g_race_laps_limit", string_null, string_null);
+ }
#ifdef CSQC
ATTRIB(Race, m_modicons, void(vector pos, vector mySize), HUD_Mod_Race);
#endif
+ ATTRIB(Race, m_legacydefaults, string, "20 5 7 15 0");
ENDCLASS(Race)
REGISTER_GAMETYPE(RACE, NEW(Race));
#define g_race IS_GAMETYPE(RACE)
CLASS(RaceCTS, Gametype)
INIT(RaceCTS)
{
- this.gametype_init(this, _("Race CTS"),"cts","g_cts",false,"cloaked","timelimit=20",_("Race for fastest time."));
+ this.gametype_init(this, _("Race CTS"),"cts","g_cts",false,false,"cloaked","timelimit=20",_("Race for fastest time."));
}
METHOD(RaceCTS, m_generate_mapinfo, void(Gametype this, string v))
{
// for map databases
// cvar_set("fraglimit", sa);
}
+ METHOD(RaceCTS, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+ {
+ TC(Gametype, this);
+ returns(menu, _("Point limit:"), 50, 500, 10, string_null, string_null, string_null);
+ }
#ifdef CSQC
ATTRIB(RaceCTS, m_modicons, void(vector pos, vector mySize), HUD_Mod_Race);
#endif
+ ATTRIB(RaceCTS, m_legacydefaults, string, "20 0 0");
ENDCLASS(RaceCTS)
REGISTER_GAMETYPE(CTS, NEW(RaceCTS));
#define g_cts IS_GAMETYPE(CTS)
CLASS(TeamDeathmatch, Gametype)
INIT(TeamDeathmatch)
{
- this.gametype_init(this, _("Team Deathmatch"),"tdm","g_tdm",true,"","timelimit=15 pointlimit=50 teams=2 leadlimit=0",_("Help your team score the most frags against the enemy team"));
+ this.gametype_init(this, _("Team Deathmatch"),"tdm","g_tdm",true,true,"","timelimit=15 pointlimit=50 teams=2 leadlimit=0",_("Help your team score the most frags against the enemy team"));
}
METHOD(TeamDeathmatch, m_parse_mapinfo, bool(string k, string v))
{
return true;
return false;
}
+ METHOD(TeamDeathmatch, m_isForcedSupported, bool(Gametype this))
+ {
+ if(cvar("g_tdm_on_dm_maps"))
+ {
+ // if this is set, all DM maps support TDM too
+ if(!(MapInfo_Map_supportedGametypes & this.m_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH.m_flags))
+ return true; // TODO: references another gametype (alternatively, we could check which gamemodes are always enabled and append this if any are supported)
+ }
+ return false;
+ }
METHOD(TeamDeathmatch, m_setTeams, void(string sa))
{
cvar_set("g_tdm_teams", sa);
}
+ METHOD(TeamDeathmatch, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+ {
+ TC(Gametype, this);
+ returns(menu, _("Point limit:"), 5, 100, 5, "g_tdm_point_limit", "g_tdm_teams_override", _("The amount of points needed before the match will end"));
+ }
+ ATTRIB(TeamDeathmatch, m_legacydefaults, string, "50 20 2 0");
ENDCLASS(TeamDeathmatch)
REGISTER_GAMETYPE(TEAM_DEATHMATCH, NEW(TeamDeathmatch));
#define g_tdm IS_GAMETYPE(TEAM_DEATHMATCH)
CLASS(CaptureTheFlag, Gametype)
INIT(CaptureTheFlag)
{
- this.gametype_init(this, _("Capture the Flag"),"ctf","g_ctf",true,"","timelimit=20 caplimit=10 leadlimit=6",_("Find and bring the enemy flag to your base to capture it, defend your base from the other team"));
+ this.gametype_init(this, _("Capture the Flag"),"ctf","g_ctf",true,true,"","timelimit=20 caplimit=10 leadlimit=6",_("Find and bring the enemy flag to your base to capture it, defend your base from the other team"));
}
METHOD(CaptureTheFlag, m_generate_mapinfo, void(Gametype this, string v))
{
{
cvar_set("fraglimit", sa);
}
+ METHOD(CaptureTheFlag, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+ {
+ TC(Gametype, this);
+ returns(menu, _("Capture limit:"), 1, 20, 1, "capturelimit_override", string_null, _("The amount of captures needed before the match will end"));
+ }
#ifdef CSQC
ATTRIB(CaptureTheFlag, m_modicons, void(vector pos, vector mySize), HUD_Mod_CTF);
ATTRIB(CaptureTheFlag, m_modicons_reset, void(), HUD_Mod_CTF_Reset);
#endif
+ ATTRIB(CaptureTheFlag, m_legacydefaults, string, "300 20 10 0");
ENDCLASS(CaptureTheFlag)
REGISTER_GAMETYPE(CTF, NEW(CaptureTheFlag));
#define g_ctf IS_GAMETYPE(CTF)
CLASS(ClanArena, Gametype)
INIT(ClanArena)
{
- this.gametype_init(this, _("Clan Arena"),"ca","g_ca",true,"","timelimit=20 pointlimit=10 teams=2 leadlimit=0",_("Kill all enemy teammates to win the round"));
+ this.gametype_init(this, _("Clan Arena"),"ca","g_ca",true,true,"","timelimit=20 pointlimit=10 teams=2 leadlimit=0",_("Kill all enemy teammates to win the round"));
}
METHOD(ClanArena, m_parse_mapinfo, bool(string k, string v))
{
{
cvar_set("g_ca_teams", sa);
}
+ METHOD(ClanArena, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+ {
+ TC(Gametype, this);
+ returns(menu, _("Frag limit:"), 5, 100, 5, "fraglimit_override", "g_ca_teams_override", _("The amount of frags needed before the match will end"));
+ }
#ifdef CSQC
ATTRIB(ClanArena, m_modicons, void(vector pos, vector mySize), HUD_Mod_CA);
#endif
+ ATTRIB(ClanArena, m_legacydefaults, string, "10 20 0");
ENDCLASS(ClanArena)
REGISTER_GAMETYPE(CA, NEW(ClanArena));
#define g_ca IS_GAMETYPE(CA)
CLASS(Domination, Gametype)
INIT(Domination)
{
- this.gametype_init(this, _("Domination"),"dom","g_domination",true,"","timelimit=20 pointlimit=200 teams=2 leadlimit=0",_("Capture and defend all the control points to win"));
+ this.gametype_init(this, _("Domination"),"dom","g_domination",true,true,"","timelimit=20 pointlimit=200 teams=2 leadlimit=0",_("Capture and defend all the control points to win"));
}
METHOD(Domination, m_parse_mapinfo, bool(string k, string v))
{
if(v == "dom_controlpoint")
MapInfo_Map_supportedGametypes |= this.m_flags;
}
+ METHOD(Domination, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+ {
+ TC(Gametype, this);
+ returns(menu, _("Point limit:"), 50, 500, 10, "g_domination_point_limit", "g_domination_teams_override", _("The amount of points needed before the match will end"));
+ }
#ifdef CSQC
ATTRIB(Domination, m_modicons, void(vector pos, vector mySize), HUD_Mod_Dom);
#endif
+ ATTRIB(Domination, m_legacydefaults, string, "200 20 0");
ENDCLASS(Domination)
REGISTER_GAMETYPE(DOMINATION, NEW(Domination));
CLASS(KeyHunt, Gametype)
INIT(KeyHunt)
{
- this.gametype_init(this, _("Key Hunt"),"kh","g_keyhunt",true,"","timelimit=20 pointlimit=1000 teams=3 leadlimit=0",_("Gather all the keys to win the round"));
+ this.gametype_init(this, _("Key Hunt"),"kh","g_keyhunt",true,true,"","timelimit=20 pointlimit=1000 teams=3 leadlimit=0",_("Gather all the keys to win the round"));
}
METHOD(KeyHunt, m_parse_mapinfo, bool(string k, string v))
{
{
cvar_set("g_keyhunt_teams", sa);
}
+ METHOD(KeyHunt, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+ {
+ TC(Gametype, this);
+ returns(menu, _("Point limit:"), 200, 1500, 50, "g_keyhunt_point_limit", "g_keyhunt_teams_override", _("The amount of points needed before the match will end"));
+ }
#ifdef CSQC
ATTRIB(KeyHunt, m_modicons, void(vector pos, vector mySize), HUD_Mod_KH);
#endif
+ ATTRIB(KeyHunt, m_legacydefaults, string, "1000 20 3 0");
ENDCLASS(KeyHunt)
REGISTER_GAMETYPE(KEYHUNT, NEW(KeyHunt));
CLASS(Assault, Gametype)
INIT(Assault)
{
- this.gametype_init(this, _("Assault"),"as","g_assault",true,"","timelimit=20",_("Destroy obstacles to find and destroy the enemy power core before time runs out"));
+ this.gametype_init(this, _("Assault"),"as","g_assault",true,false,"","timelimit=20",_("Destroy obstacles to find and destroy the enemy power core before time runs out"));
}
METHOD(Assault, m_generate_mapinfo, void(Gametype this, string v))
{
{
return true;
}
+ METHOD(Assault, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+ {
+ TC(Gametype, this);
+ returns(menu, _("Point limit:"), 50, 500, 10, string_null, string_null, string_null);
+ }
+ ATTRIB(Assault, m_legacydefaults, string, "20 0");
ENDCLASS(Assault)
REGISTER_GAMETYPE(ASSAULT, NEW(Assault));
#define g_assault IS_GAMETYPE(ASSAULT)
CLASS(Onslaught, Gametype)
INIT(Onslaught)
{
- this.gametype_init(this, _("Onslaught"),"ons","g_onslaught",true,"","pointlimit=1 timelimit=20",_("Capture control points to reach and destroy the enemy generator"));
+ this.gametype_init(this, _("Onslaught"),"ons","g_onslaught",true,false,"","pointlimit=1 timelimit=20",_("Capture control points to reach and destroy the enemy generator"));
}
METHOD(Onslaught, m_generate_mapinfo, void(Gametype this, string v))
{
if(v == "onslaught_generator")
MapInfo_Map_supportedGametypes |= this.m_flags;
}
+ METHOD(Onslaught, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+ {
+ TC(Gametype, this);
+ returns(menu, _("Point limit:"), 50, 500, 10, string_null, string_null, string_null);
+ }
+ ATTRIB(Onslaught, m_legacydefaults, string, "20 0");
ENDCLASS(Onslaught)
REGISTER_GAMETYPE(ONSLAUGHT, NEW(Onslaught));
CLASS(NexBall, Gametype)
INIT(NexBall)
{
- this.gametype_init(this, _("Nexball"),"nb","g_nexball",true,"","timelimit=20 pointlimit=5 leadlimit=0",_("Shoot and kick the ball into the enemies goal, keep your goal clean"));
+ this.gametype_init(this, _("Nexball"),"nb","g_nexball",true,true,"","timelimit=20 pointlimit=5 leadlimit=0",_("Shoot and kick the ball into the enemies goal, keep your goal clean"));
}
METHOD(NexBall, m_generate_mapinfo, void(Gametype this, string v))
{
{
return true;
}
+ METHOD(NexBall, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+ {
+ TC(Gametype, this);
+ returns(menu, _("Goals:"), 1, 50, 1, "g_nexball_goallimit", string_null, _("The amount of goals needed before the match will end"));
+ }
#ifdef CSQC
ATTRIB(NexBall, m_modicons, void(vector pos, vector mySize), HUD_Mod_NexBall);
#endif
+ ATTRIB(NexBall, m_legacydefaults, string, "5 20 0");
ENDCLASS(NexBall)
REGISTER_GAMETYPE(NEXBALL, NEW(NexBall));
#define g_nexball IS_GAMETYPE(NEXBALL)
CLASS(FreezeTag, Gametype)
INIT(FreezeTag)
{
- this.gametype_init(this, _("Freeze Tag"),"ft","g_freezetag",true,"","timelimit=20 pointlimit=10 teams=2 leadlimit=0",_("Kill enemies to freeze them, stand next to frozen teammates to revive them; freeze all enemies to win"));
+ this.gametype_init(this, _("Freeze Tag"),"ft","g_freezetag",true,true,"","timelimit=20 pointlimit=10 teams=2 leadlimit=0",_("Kill enemies to freeze them, stand next to frozen teammates to revive them; freeze all enemies to win"));
}
METHOD(FreezeTag, m_parse_mapinfo, bool(string k, string v))
{
{
cvar_set("g_freezetag_teams", sa);
}
+ METHOD(FreezeTag, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+ {
+ TC(Gametype, this);
+ returns(menu, _("Frag limit:"), 5, 100, 5, "fraglimit_override", "g_freezetag_teams_override", _("The amount of frags needed before the match will end"));
+ }
#ifdef CSQC
ATTRIB(FreezeTag, m_modicons, void(vector pos, vector mySize), HUD_Mod_CA);
#endif
+ ATTRIB(FreezeTag, m_legacydefaults, string, "10 20 0");
ENDCLASS(FreezeTag)
REGISTER_GAMETYPE(FREEZETAG, NEW(FreezeTag));
#define g_freezetag IS_GAMETYPE(FREEZETAG)
CLASS(Keepaway, Gametype)
INIT(Keepaway)
{
- this.gametype_init(this, _("Keepaway"),"ka","g_keepaway",false,"","timelimit=20 pointlimit=30",_("Hold the ball to get points for kills"));
+ this.gametype_init(this, _("Keepaway"),"ka","g_keepaway",false,true,"","timelimit=20 pointlimit=30",_("Hold the ball to get points for kills"));
}
METHOD(Keepaway, m_isAlwaysSupported, bool(Gametype this, int spawnpoints, float diameter))
{
CLASS(Invasion, Gametype)
INIT(Invasion)
{
- this.gametype_init(this, _("Invasion"),"inv","g_invasion",false,"","pointlimit=50 teams=0 type=0",_("Survive against waves of monsters"));
+ this.gametype_init(this, _("Invasion"),"inv","g_invasion",false,true,"","pointlimit=50 teams=0 type=0",_("Survive against waves of monsters"));
}
METHOD(Invasion, m_parse_mapinfo, bool(string k, string v))
{
if(v == "invasion_spawnpoint")
MapInfo_Map_supportedGametypes |= this.m_flags;
}
+ METHOD(Invasion, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+ {
+ TC(Gametype, this);
+ returns(menu, _("Point limit:"), 50, 500, 10, string_null, string_null, string_null);
+ }
ENDCLASS(Invasion)
REGISTER_GAMETYPE(INVASION, NEW(Invasion));
+CLASS(Duel, Gametype)
+ INIT(Duel)
+ {
+ this.gametype_init(this, _("Duel"),"duel","g_duel",false,true,"","timelimit=10 pointlimit=0 leadlimit=0",_("Fight in a one versus one arena battle to decide the winner"));
+ }
+ METHOD(Duel, m_isAlwaysSupported, bool(Gametype this, int spawnpoints, float diameter))
+ {
+ return (diameter < 16384);
+ }
+ METHOD(Duel, m_isForcedSupported, bool(Gametype this))
+ {
+ // force all DM maps to work in duel?!
+ // TODO: we should really check the size of maps, some DM maps do not work for duel!
+ if(!(MapInfo_Map_supportedGametypes & this.m_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH.m_flags))
+ return true;
+ return false;
+ }
+ENDCLASS(Duel)
+REGISTER_GAMETYPE(DUEL, NEW(Duel));
+#define g_duel IS_GAMETYPE(DUEL)
+
const int MAPINFO_FEATURE_WEAPONS = 1; // not defined for instagib-only maps
const int MAPINFO_FEATURE_VEHICLES = 2;
const int MAPINFO_FEATURE_TURRETS = 4;
void MapInfo_Shutdown(); // call this in the shutdown handler
#define MAPINFO_SETTEMP_ACL_USER cvar_string("g_mapinfo_settemp_acl")
-#define MAPINFO_SETTEMP_ACL_SYSTEM "-g_mapinfo_* -rcon_* -_* -g_ban* +*"
+#define MAPINFO_SETTEMP_ACL_SYSTEM "-g_mapinfo_* -rcon_* -_* -g_ban* -r_water +*"
this.active = ACTIVE_ACTIVE;
+ this.draggable = drag_undraggable;
+
// damage when blocked
setblocked(this, generic_plat_blocked);
if(this.dmg && (this.message == ""))
void button_wait(entity this);
void button_return(entity this);
+// in case button is deactivated by a relay_deactivate while it pressed down
+// set both fields to -1 in button_return!!
+.float wait_remaining;
+.float activation_time;
+
+void button_setactive(entity this, int astate)
+{
+ int oldstate = this.active;
+ if (astate == ACTIVE_TOGGLE)
+ {
+ if (this.active == ACTIVE_ACTIVE)
+ this.active = ACTIVE_NOT;
+ else
+ this.active = ACTIVE_ACTIVE;
+ }
+ else
+ this.active = astate;
+
+ if (this.active == ACTIVE_ACTIVE && oldstate == ACTIVE_NOT)
+ {
+ // button was deactivated while it was pressed
+ if (this.wait_remaining >= 0)
+ {
+ this.nextthink = this.wait_remaining + this.ltime;
+ setthink(this, button_return);
+ }
+ }
+ else if (this.active == ACTIVE_NOT && oldstate == ACTIVE_ACTIVE)
+ {
+ // check if button is in pressed state
+ if (this.activation_time >= 0)
+ {
+ this.wait_remaining = this.wait - (time - this.activation_time);
+ }
+ }
+}
+
void button_wait(entity this)
{
this.state = STATE_TOP;
void button_return(entity this)
{
+ if (this.active != ACTIVE_ACTIVE)
+ {
+ return;
+ }
this.state = STATE_DOWN;
SUB_CalcMove (this, this.pos1, TSPEED_LINEAR, this.speed, button_done);
this.frame = 0; // use normal textures
if (GetResourceAmount(this, RESOURCE_HEALTH))
this.takedamage = DAMAGE_YES; // can be shot again
+ this.wait_remaining = -1;
+ this.activation_time = -1;
}
if (this.state == STATE_UP || this.state == STATE_TOP)
return;
+ this.activation_time = time;
+
if (this.noise != "")
_sound (this, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM);
this.frame = 0; // use normal textures
this.state = STATE_BOTTOM;
this.velocity = '0 0 0';
+ this.wait_remaining = -1;
+ this.activation_time = -1;
+ this.active = ACTIVE_ACTIVE;
setthink(this, func_null);
this.nextthink = 0;
if (GetResourceAmount(this, RESOURCE_HEALTH))
void button_touch(entity this, entity toucher)
{
+ if (this.active != ACTIVE_ACTIVE)
+ return;
if (!toucher)
return;
if (!toucher.iscreature)
void button_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
{
+ if (this.active != ACTIVE_ACTIVE)
+ return;
if(this.spawnflags & NOSPLASH)
if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
return;
if(this.noise != "")
precache_sound(this.noise);
- this.active = ACTIVE_ACTIVE;
+ this.draggable = drag_undraggable;
+
+ this.setactive = button_setactive;
this.pos1 = this.origin;
this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip);
this.reset = generic_netlinked_reset;
this.reset(this);
- FixSize(this);
-
Net_LinkEntity(this, 0, false, conveyor_send);
this.SendFlags |= SF_TRIGGER_INIT;
{
SetResourceAmountExplicit(this.owner, RESOURCE_HEALTH, this.owner.max_health);
this.owner.takedamage = DAMAGE_NO; // will be reset upon return
- door_use(this.owner, NULL, NULL);
+ door_use(this.owner, attacker, NULL);
}
}
void door_link()
{
- // set size now, as everything is loaded
- //FixSize(this);
//Net_LinkEntity(this, false, 0, door_send);
}
#endif
setblocked(this, door_blocked);
this.use = door_use;
- this.pos1 = this.origin;
- this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip);
-
if(this.spawnflags & DOOR_NONSOLID)
this.solid = SOLID_NOT;
door_init_shared(this);
+ this.pos1 = this.origin;
+ this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip);
+
if (!this.speed)
{
this.speed = 100;
this.angles = '0 0 0';
this.classname = "plat";
+ this.draggable = drag_undraggable;
if (!InitMovingBrushTrigger(this))
return;
this.effects |= EF_LOWPRECISION;
#include <common/mapobjects/misc/corner.qc>
#include <common/mapobjects/misc/dynlight.qc>
#include <common/mapobjects/misc/follow.qc>
+#include <common/mapobjects/misc/keys.qc>
#include <common/mapobjects/misc/laser.qc>
#include <common/mapobjects/misc/teleport_dest.qc>
#include <common/mapobjects/misc/corner.qh>
#include <common/mapobjects/misc/dynlight.qh>
#include <common/mapobjects/misc/follow.qh>
+#include <common/mapobjects/misc/keys.qh>
#include <common/mapobjects/misc/laser.qh>
#include <common/mapobjects/misc/teleport_dest.qh>
--- /dev/null
+#include "keys.qh"
+
+#ifdef CSQC
+bool item_keys_usekey(entity l, entity p)
+{
+ int valid = (l.itemkeys & p.itemkeys); // TODO: itemkeys isn't networked or anything!
+ l.itemkeys &= ~valid; // only some of the needed keys were given
+ return valid != 0;
+}
+#endif
+
+#ifdef SVQC
+/*
+TODO:
+- add an unlock sound (here to trigger_keylock and to func_door)
+- display available keys on the HUD
+- make more tests
+- think about adding NOT_EASY/NOT_NORMAL/NOT_HARD for Q1 compatibility
+- should keys have a trigger?
+*/
+
+bool item_keys_usekey(entity l, entity p)
+{
+ int valid = l.itemkeys & p.itemkeys;
+
+ if (!valid) {
+ // player has none of the needed keys
+ return false;
+ } else if (l.itemkeys == valid) {
+ // ALL needed keys were given
+ l.itemkeys = 0;
+ return true;
+ } else {
+ // only some of the needed keys were given
+ l.itemkeys &= ~valid;
+ return true;
+ }
+}
+
+string item_keys_keylist(float keylist) {
+ // no keys
+ if (!keylist)
+ return "";
+
+ // one key
+ if ((keylist & (keylist-1)) == 0)
+ return strcat("the ", item_keys_names[lowestbit(keylist)]);
+
+ string n = "";
+ int base = 0;
+ while (keylist) {
+ int l = lowestbit(keylist);
+ if (n)
+ n = strcat(n, ", the ", item_keys_names[base + l]);
+ else
+ n = strcat("the ", item_keys_names[base + l]);
+
+ keylist = bitshift(keylist, -(l + 1));
+ base+= l + 1;
+ }
+
+ return n;
+}
+
+
+/*
+================================
+item_key
+================================
+*/
+
+/**
+ * Key touch handler.
+ */
+void item_key_touch(entity this, entity toucher)
+{
+ if (!IS_PLAYER(toucher))
+ return;
+
+ // player already picked up this key
+ if (PS(toucher).itemkeys & this.itemkeys)
+ return;
+
+ PS(toucher).itemkeys |= this.itemkeys;
+ play2(toucher, this.noise);
+
+ centerprint(toucher, this.message);
+
+ string oldmsg = this.message;
+ this.message = "";
+ SUB_UseTargets(this, toucher, toucher); // TODO: should we be using toucher for the trigger here?
+ this.message = oldmsg;
+}
+
+/**
+ * Spawn a key with given model, key code and color.
+ */
+void spawn_item_key(entity this)
+{
+ precache_model(this.model);
+
+ if (this.spawnflags & 1) // FLOATING
+ this.noalign = 1;
+
+ if (this.noalign)
+ set_movetype(this, MOVETYPE_NONE);
+ else
+ set_movetype(this, MOVETYPE_TOSS);
+
+ precache_sound(this.noise);
+
+ this.mdl = this.model;
+ this.effects = EF_LOWPRECISION;
+ _setmodel(this, this.model);
+ //setsize(this, '-16 -16 -24', '16 16 32');
+ setorigin(this, this.origin + '0 0 32');
+ setsize(this, '-16 -16 -56', '16 16 0');
+ this.modelflags |= MF_ROTATE;
+ this.solid = SOLID_TRIGGER;
+
+ if (!this.noalign)
+ {
+ // first nudge it off the floor a little bit to avoid math errors
+ setorigin(this, this.origin + '0 0 1');
+ // note droptofloor returns false if stuck/or would fall too far
+ droptofloor(this);
+ }
+
+ settouch(this, item_key_touch);
+}
+
+
+/*QUAKED item_key (0 .5 .8) (-16 -16 -24) (16 16 32) FLOATING
+A key entity.
+The itemkeys should contain one of the following key IDs:
+1 - GOLD key -
+2 - SILVER key
+4 - BRONZE key
+8 - RED keycard
+16 - BLUE keycard
+32 - GREEN keycard
+Custom keys:
+... - last key is 1<<23
+Keys with bigger Id than 32 don't have a default netname and model, if you use one of them, you MUST provide those.
+-----------KEYS------------
+colormod: color of the key (default: '.9 .9 .9').
+itemkeys: a key Id.
+message: message to print when player picks up this key.
+model: custom key model to use.
+netname: the display name of the key.
+noise: custom sound to play when player picks up the key.
+-------- SPAWNFLAGS --------
+FLOATING: the item will float in air, instead of aligning to the floor by falling
+---------NOTES----------
+This is the only correct way to put keys on the map!
+
+itemkeys MUST always have exactly one bit set.
+*/
+spawnfunc(item_key)
+{
+ string _netname;
+ vector _colormod;
+
+ // reject this entity if more than one key was set!
+ if (this.itemkeys>0 && (this.itemkeys & (this.itemkeys-1)) != 0) {
+ objerror(this, "item_key.itemkeys must contain only 1 bit set specifying the key it represents!");
+ delete(this);
+ return;
+ }
+
+ // find default netname and colormod
+ switch(this.itemkeys) {
+ case BIT(0):
+ _netname = "GOLD key";
+ _colormod = '1 .9 0';
+ break;
+
+ case BIT(1):
+ _netname = "SILVER key";
+ _colormod = '.9 .9 .9';
+ break;
+
+ case BIT(2):
+ _netname = "BRONZE key";
+ _colormod = '.6 .25 0';
+ break;
+
+ case BIT(3):
+ _netname = "RED keycard";
+ _colormod = '.9 0 0';
+ break;
+
+ case BIT(4):
+ _netname = "BLUE keycard";
+ _colormod = '0 0 .9';
+ break;
+
+ case BIT(5):
+ _netname = "GREEN keycard";
+ _colormod = '0 .9 0';
+ break;
+
+ default:
+ _netname = "FLUFFY PINK keycard";
+ _colormod = '1 1 1';
+
+ if (this.netname == "") {
+ objerror(this, "item_key doesn't have a default name for this key and a custom one was not specified!");
+ delete(this);
+ return;
+ }
+ break;
+
+ }
+
+ // find default model
+ string _model = string_null;
+ if (this.itemkeys <= ITEM_KEY_BIT(2)) {
+ _model = "models/keys/key.md3";
+ } else if (this.itemkeys >= ITEM_KEY_BIT(3) && this.itemkeys <= ITEM_KEY_BIT(5)) {
+ _model = "models/keys/key.md3"; // FIXME: replace it by a keycard model!
+ } else if (this.model == "") {
+ objerror(this, "item_key doesn't have a default model for this key and a custom one was not specified!");
+ delete(this);
+ return;
+ }
+
+ // set defailt netname
+ if (this.netname == "")
+ this.netname = _netname;
+
+ // set default colormod
+ if (!this.colormod)
+ this.colormod = _colormod;
+
+ // set default model
+ if (this.model == "")
+ this.model = _model;
+
+ // set default pickup message
+ if (this.message == "")
+ this.message = strzone(strcat("You've picked up the ", this.netname, "!"));
+
+ if (this.noise == "")
+ this.noise = strzone(SND(ITEMPICKUP));
+
+ // save the name for later
+ item_keys_names[lowestbit(this.itemkeys)] = this.netname;
+
+ // put the key on the map
+ spawn_item_key(this);
+}
+
+/*QUAKED item_key1 (0 .5 .8) (-16 -16 -24) (16 16 32) FLOATING
+SILVER key.
+-----------KEYS------------
+colormod: color of the key (default: '.9 .9 .9').
+message: message to print when player picks up this key.
+model: custom model to use.
+noise: custom sound to play when player picks up the key.
+-------- SPAWNFLAGS --------
+FLOATING: the item will float in air, instead of aligning to the floor by falling
+---------NOTES----------
+Don't use this entity on new maps! Use item_key instead.
+*/
+spawnfunc(item_key1)
+{
+ this.classname = "item_key";
+ this.itemkeys = ITEM_KEY_BIT(1);
+ spawnfunc_item_key(this);
+}
+
+/*QUAKED item_key2 (0 .5 .8) (-16 -16 -24) (16 16 32) FLOATING
+GOLD key.
+-----------KEYS------------
+colormod: color of the key (default: '1 .9 0').
+message: message to print when player picks up this key.
+model: custom model to use.
+noise: custom sound to play when player picks up the key.
+-------- SPAWNFLAGS --------
+FLOATING: the item will float in air, instead of aligning to the floor by falling
+---------NOTES----------
+Don't use this entity on new maps! Use item_key instead.
+*/
+spawnfunc(item_key2)
+{
+ this.classname = "item_key";
+ this.itemkeys = ITEM_KEY_BIT(0);
+ spawnfunc_item_key(this);
+}
+
+#endif
--- /dev/null
+#pragma once
+
+/**
+ * Returns the bit ID of a key
+ */
+#define ITEM_KEY_BIT(n) ( bitshift(1, n) )
+
+#define ITEM_KEY_MAX 24
+
+/**
+ * list of key names.
+ */
+#ifdef SVQC
+string item_keys_names[ITEM_KEY_MAX];
+
+/**
+ * Use keys from p on l.
+ * Returns true if any new keys were given, false otherwise.
+ */
+float item_keys_usekey(entity l, entity p);
+
+/**
+ * Returns a string with a comma separated list of key names, as specified in keylist.
+ */
+string item_keys_keylist(float keylist);
+#endif
void g_clientmodel_use(entity this, entity actor, entity trigger)
{
+ // Flag to set func_clientwall state
+ // 1 == deactivate, 2 == activate, 0 == do nothing
+ if(this.classname == "func_clientwall" || this.classname == "func_clientillusionary")
+ this.antiwall_flag = trigger.antiwall_flag;
+
if (this.antiwall_flag == 1)
{
this.inactive = 1;
WriteVector(MSG_ENTITY, this.movedir);
WriteByte(MSG_ENTITY, floor(this.lip * 255));
}
- WriteByte(MSG_ENTITY, this.fade_start);
- WriteByte(MSG_ENTITY, this.fade_end);
- WriteByte(MSG_ENTITY, this.alpha_max);
- WriteByte(MSG_ENTITY, this.alpha_min);
+ WriteShort(MSG_ENTITY, bound(0, this.fade_start, 65535));
+ WriteShort(MSG_ENTITY, bound(0, this.fade_end, 65535));
+ WriteByte(MSG_ENTITY, floor(this.alpha_max * 256));
+ WriteByte(MSG_ENTITY, floor(this.alpha_min * 256));
WriteByte(MSG_ENTITY, this.inactive);
WriteShort(MSG_ENTITY, this.fade_vertical_offset);
}
void Ent_Wall_PreDraw(entity this)
{
+ float alph = this.alpha;
if (this.inactive)
{
- this.alpha = 0;
+ alph = 0;
}
else
{
vector org = getpropertyvec(VF_ORIGIN);
if(!checkpvs(org, this))
- this.alpha = 0;
+ alph = 0;
else if(this.fade_start || this.fade_end) {
vector offset = '0 0 0';
offset_z = this.fade_vertical_offset;
- float player_dist = vlen(org - this.origin - 0.5 * (this.mins + this.maxs) + offset);
+ vector player_dist_math = org - this.origin - 0.5 * (this.mins + this.maxs) + offset;
if (this.fade_end == this.fade_start)
{
- if (player_dist >= this.fade_start)
- this.alpha = 0;
+ if (vdist(player_dist_math, >=, this.fade_start))
+ alph = 0;
else
- this.alpha = 1;
+ alph = 1;
}
else
{
- this.alpha = (this.alpha_min + this.alpha_max * bound(0,
+ float player_dist = vlen(player_dist_math);
+ alph = (this.alpha_min + this.alpha_max * bound(0,
(this.fade_end - player_dist)
/ (this.fade_end - this.fade_start), 1)) / 100.0;
}
}
else
{
- this.alpha = 1;
+ alph = 1;
}
}
- if(this.alpha <= 0)
- this.drawmask = 0;
- else
- this.drawmask = MASK_NORMAL;
+ this.alpha = alph;
+ this.drawmask = (alph <= 0) ? 0 : MASK_NORMAL;
}
void Ent_Wall_Draw(entity this)
this.movedir = ReadVector();
this.lip = ReadByte() / 255.0;
}
- this.fade_start = ReadByte();
- this.fade_end = ReadByte();
- this.alpha_max = ReadByte();
- this.alpha_min = ReadByte();
+ this.fade_start = ReadShort();
+ this.fade_end = ReadShort();
+ this.alpha_max = ReadByte() / 255.0;
+ this.alpha_min = ReadByte() / 255.0;
this.inactive = ReadByte();
this.fade_vertical_offset = ReadShort();
BGMScript_InitEntity(this);
classfield(Wall) .vector saved;
// Needed for interactive clientwalls
-.float inactive; // Clientwall disappears when inactive
+.bool inactive; // Clientwall disappears when inactive
.float alpha_max, alpha_min;
// If fade_start > fade_end, fadeout will be inverted
// fade_vertical_offset is a vertival offset for player position
Fade 'ent' out when time >= 'when'
==================
*/
+.float fade_rate;
void SUB_SetFade(entity ent, float when, float fading_time);
.vector finaldest, finalangle; //plat.qc stuff
void counter_use(entity this, entity actor, entity trigger)
{
- this.count -= 1;
- if (this.count < 0)
+ entity store = this;
+ if(this.spawnflags & COUNTER_PER_PLAYER)
+ {
+ if(!IS_PLAYER(actor))
+ return;
+ store = actor;
+ }
+
+ store.counter_cnt += 1;
+ if (store.counter_cnt > this.count)
return;
bool doactivate = (this.spawnflags & COUNTER_FIRE_AT_COUNT);
- if (this.count == 0)
+ if (store.counter_cnt == this.count)
{
if(IS_PLAYER(actor) && !(this.spawnflags & SPAWNFLAG_NOMESSAGE))
Send_Notification(NOTIF_ONE, actor, MSG_CENTER, CENTER_SEQUENCE_COMPLETED);
{
if(IS_PLAYER(actor) && !(this.spawnflags & SPAWNFLAG_NOMESSAGE))
{
- if(this.count >= 4)
+ if((this.count - store.counter_cnt) >= 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);
+ Send_Notification(NOTIF_ONE, actor, MSG_CENTER, CENTER_SEQUENCE_COUNTER_FEWMORE, this.count - store.counter_cnt);
}
}
{
setthink(this, func_null);
this.nextthink = 0;
- this.count = this.cnt;
+ this.counter_cnt = 0;
}
/*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage COUNTER_FIRE_AT_COUNT
{
if (!this.count)
this.count = 2;
- this.cnt = this.count;
+ this.counter_cnt = 0;
this.use = counter_use;
this.reset = counter_reset;
}
#pragma once
+#ifdef SVQC
+spawnfunc(trigger_counter);
+
+.float counter_cnt;
+#endif
const int COUNTER_FIRE_AT_COUNT = BIT(2);
+const int COUNTER_PER_PLAYER = BIT(3);
if (toucher.triggerhurttime < time)
{
EXACTTRIGGER_TOUCH(this, toucher);
- toucher.triggerhurttime = time + 1;
+ toucher.triggerhurttime = time + ((autocvar_sv_vq3compat && !(this.spawnflags & HURT_SLOW)) ? 0.1 : 1);
entity own;
own = this.enemy;
/*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
Any object touching this will be hurt
set dmg to damage amount
-default dmg = 1000
+default dmg = 10000
*/
.entity trigger_hurt_next;
entity trigger_hurt_last;
this.use = trigger_hurt_use;
this.enemy = world; // I hate you all
if (!this.dmg)
- this.dmg = 1000;
+ this.dmg = ((autocvar_sv_vq3compat) ? 5 : 10000);
if (this.message == "")
this.message = "was in the wrong place";
if (this.message2 == "")
#pragma once
+
+const int HURT_SLOW = BIT(4);
if (!isPushable(targ))
return false;
+ vector org = targ.origin;
+#ifdef SVQC
+ if(autocvar_sv_vq3compat)
+#elif defined(CSQC)
+ if(STAT(VQ3COMPAT))
+#endif
+ {
+ org.z += targ.mins_z;
+ org.z += 1; // off by 1!
+ }
+
if(this.enemy)
{
- targ.velocity = trigger_push_calculatevelocity(targ.origin, this.enemy, this.height, targ);
+ targ.velocity = trigger_push_calculatevelocity(org, this.enemy, this.height, targ);
}
else if(this.target && this.target != "")
{
else
RandomSelection_AddEnt(e, 1, 1);
}
- targ.velocity = trigger_push_calculatevelocity(targ.origin, RandomSelection_chosen_ent, this.height, targ);
+ targ.velocity = trigger_push_calculatevelocity(org, RandomSelection_chosen_ent, this.height, targ);
}
else
{
STATIC_INIT(g_jumppads) { g_jumppads = IL_NEW(); }
.float pushltime;
-.float istypefrag;
+.bool istypefrag;
.float height;
const int NUM_JUMPPADSUSED = 3;
// check silver key
if(this.itemkeys)
- key_used = item_keys_usekey(this, toucher);
+ {
+#ifdef SVQC
+ entity store = PS(toucher);
+#elif defined(CSQC)
+ entity store = toucher;
+#endif
+ key_used = item_keys_usekey(this, store);
+ }
if(this.itemkeys)
{
#pragma once
-
-#ifdef CSQC
-bool item_keys_usekey(entity l, entity p)
-{
- int valid = (l.itemkeys & p.itemkeys); // TODO: itemkeys isn't networked or anything!
- l.itemkeys &= ~valid; // only some of the needed keys were given
- return valid != 0;
-}
-#endif
#include "triggers.qh"
-#ifdef SVQC
- #include <server/item_key.qh>
-#endif
void SUB_DontUseTargets(entity this, entity actor, entity trigger) { }
delete(this);
}
-void FixSize(entity e)
-{
- e.mins_x = rint(e.mins_x);
- e.mins_y = rint(e.mins_y);
- e.mins_z = rint(e.mins_z);
-
- e.maxs_x = rint(e.maxs_x);
- e.maxs_y = rint(e.maxs_y);
- e.maxs_z = rint(e.maxs_z);
-}
-
#ifdef SVQC
void generic_setactive(entity this, int act)
{
// Compatibility with old maps
void generic_netlinked_legacy_use(entity this, entity actor, entity trigger)
{
- LOG_WARNF("Entity %s was (de)activated by a trigger, please update map to use relays", this.targetname);
+ //LOG_WARNF("Entity %s was (de)activated by a trigger, please update map to use relays", this.targetname);
this.setactive(this, ACTIVE_TOGGLE);
}
}
if (s != "")
{
- // Flag to set func_clientwall state
- // 1 == deactivate, 2 == activate, 0 == do nothing
- int aw_flag = this.antiwall_flag;
for(entity t = NULL; (t = find(t, targetname, s)); )
{
- if(t.use && (t.sub_target_used != time || !preventReuse))
+ if(t != this && t.use && (t.sub_target_used != time || !preventReuse))
{
if(this.target_random)
{
}
else
{
- if (t.classname == "func_clientwall" || t.classname == "func_clientillusionary")
- t.antiwall_flag = aw_flag;
-
t.use(t, actor, this);
if(preventReuse)
t.sub_target_used = time;
.vector dest;
-void FixSize(entity e);
-
#ifdef CSQC
void trigger_common_read(entity this, bool withtarget);
void trigger_remove_generic(entity this);
HUD_MinigameMenu_entries = NULL;
HUD_MinigameMenu_last_entry = NULL;
HUD_MinigameMenu_activeitem = NULL;
- if(autocvar_hud_cursormode)
- if ( !autocvar__hud_configure )
- setcursormode(0);
}
}
HUD_MinigameMenu_last_entry );
HUD_MinigameMenu_CurrentButton();
HUD_MinigameMenu_activeitem = NULL;
- if(autocvar_hud_cursormode)
- setcursormode(1);
}
}
if( !HUD_MinigameMenu_IsOpened() || autocvar__hud_configure || mv_active )
return;
- if (!autocvar_hud_cursormode)
- update_mousepos();
-
if ( HUD_MinigameMenu_IsOpened() && HUD_mouse_over(HUD_PANEL(MINIGAMEMENU)) )
HUD_MinigameMenu_MouseInput();
-
- draw_cursor_normal(mousepos, '1 1 1', panel_fg_alpha);
}
{
sent.message = bd_turn_to_string(sent.minigame_flags);
//if ( sent.minigame_flags & minigame_self.team )
- minigame_prompt();
+ //minigame_prompt();
}
}
else if(sent.classname == "minigame_board_piece")
if ( sf & MINIG_SF_UPDATE )
{
sent.message = ps_turn_to_string(sent.minigame_flags);
- if ( sent.minigame_flags & minigame_self.team )
- minigame_prompt();
+ //if ( sent.minigame_flags & minigame_self.team )
+ //minigame_prompt();
}
}
set_movetype(player, MOVETYPE_WALK);
else
set_movetype(player, MOVETYPE_FLY_WORLDONLY);
- player.team_forced = 0;
+ Player_SetForcedTeamIndex(player, TEAM_FORCE_DEFAULT);
}
void minigame_rmplayer(entity minigame_session, entity player)
PutObserverInServer(player);
}
if ( autocvar_sv_minigames_observer == 2 )
- player.team_forced = -1;
+ Player_SetForcedTeamIndex(player, TEAM_FORCE_SPECTATOR);
minigame_resend(minigame_session);
}
bool M_Spider_Attack(int attack_type, entity actor, entity targ, .entity weaponentity)
{
+ Weapon wep = WEP_SPIDER_ATTACK;
switch(attack_type)
{
- Weapon wep = WEP_SPIDER_ATTACK;
case MONSTER_ATTACK_MELEE:
{
wep.wr_think(wep, actor, weaponentity, 2);
}
.entity draggedby;
-.entity target2;
void Monster_Move(entity this, float runspeed, float walkspeed, float stpspeed)
{
setorigin(this, this.pos1);
this.angles = this.pos2;
- Unfreeze(this); // remove any icy remains
+ Unfreeze(this, false); // remove any icy remains
SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
this.velocity = '0 0 0';
this.monster_lifetime = time + 5;
if(STAT(FROZEN, this))
- {
- Unfreeze(this); // remove any icy remains
- SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0); // reset by Unfreeze (TODO)
- }
+ Unfreeze(this, false); // remove any icy remains
monster_dropitem(this, attacker);
void Monster_Frozen_Think(entity this)
{
- if(STAT(FROZEN, this) == 2)
+ if (STAT(FROZEN, this) == FROZEN_TEMP_REVIVING)
{
STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) + this.ticrate * this.revive_speed, 1);
SetResourceAmountExplicit(this, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, this) * this.max_health));
WaypointSprite_UpdateHealth(this.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
if(STAT(REVIVE_PROGRESS, this) >= 1)
- Unfreeze(this);
+ Unfreeze(this, false);
}
- else if(STAT(FROZEN, this) == 3)
+ else if (STAT(FROZEN, this) == FROZEN_TEMP_DYING)
{
STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) - this.ticrate * this.revive_speed, 1);
SetResourceAmountExplicit(this, RESOURCE_HEALTH, max(0, autocvar_g_nades_ice_health + (this.max_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this)));
if(GetResourceAmount(this, RESOURCE_HEALTH) < 1)
{
- Unfreeze(this);
- SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
+ Unfreeze(this, false);
if(this.event_damage)
this.event_damage(this, this, this.frozen_by, 1, DEATH_NADE_ICE_FREEZE.m_id, DMG_NOWEP, this.origin, '0 0 0');
}
-
else if ( STAT(REVIVE_PROGRESS, this) <= 0 )
- Unfreeze(this);
+ Unfreeze(this, false);
}
// otherwise, no revival!
}
REGISTER_BUFF(AMMO) {
- this.m_prettyName = _("Ammo");
- this.m_name = "ammo";
+ this.m_name = _("Ammo");
+ this.netname = "ammo";
this.m_skin = 3;
this.m_color = '0.76 1 0.1';
}
BUFF_SPAWNFUNC_Q3TA_COMPAT(ammoregen, BUFF_AMMO)
REGISTER_BUFF(RESISTANCE) {
- this.m_prettyName = _("Resistance");
- this.m_name = "resistance";
+ this.m_name = _("Resistance");
+ this.netname = "resistance";
this.m_skin = 0;
this.m_color = '0.36 1 0.07';
}
BUFF_SPAWNFUNC_Q3TA_COMPAT(resistance, BUFF_RESISTANCE)
REGISTER_BUFF(SPEED) {
- this.m_prettyName = _("Speed");
- this.m_name = "speed";
+ this.m_name = _("Speed");
+ this.netname = "speed";
this.m_skin = 9;
this.m_color = '0.1 1 0.84';
}
BUFF_SPAWNFUNC_Q3TA_COMPAT(scout, BUFF_SPEED)
REGISTER_BUFF(MEDIC) {
- this.m_prettyName = _("Medic");
- this.m_name = "medic";
+ this.m_name = _("Medic");
+ this.netname = "medic";
this.m_skin = 1;
this.m_color = '1 0.12 0';
}
BUFF_SPAWNFUNC_Q3TA_COMPAT(revival, BUFF_MEDIC)
REGISTER_BUFF(BASH) {
- this.m_prettyName = _("Bash");
- this.m_name = "bash";
+ this.m_name = _("Bash");
+ this.netname = "bash";
this.m_skin = 5;
this.m_color = '1 0.39 0';
}
BUFF_SPAWNFUNCS(bash, BUFF_BASH)
REGISTER_BUFF(VAMPIRE) {
- this.m_prettyName = _("Vampire");
- this.m_name = "vampire";
+ this.m_name = _("Vampire");
+ this.netname = "vampire";
this.m_skin = 2;
this.m_color = '1 0 0.24';
}
BUFF_SPAWNFUNCS(vampire, BUFF_VAMPIRE)
REGISTER_BUFF(DISABILITY) {
- this.m_prettyName = _("Disability");
- this.m_name = "disability";
+ this.m_name = _("Disability");
+ this.netname = "disability";
this.m_skin = 7;
this.m_color = '0.94 0.3 1';
}
BUFF_SPAWNFUNCS(disability, BUFF_DISABILITY)
REGISTER_BUFF(VENGEANCE) {
- this.m_prettyName = _("Vengeance");
- this.m_name = "vengeance";
+ this.m_name = _("Vengeance");
+ this.netname = "vengeance";
this.m_skin = 15;
this.m_color = '1 0.23 0.61';
}
BUFF_SPAWNFUNCS(vengeance, BUFF_VENGEANCE)
REGISTER_BUFF(JUMP) {
- this.m_prettyName = _("Jump");
- this.m_name = "jump";
+ this.m_name = _("Jump");
+ this.netname = "jump";
this.m_skin = 10;
this.m_color = '0.24 0.78 1';
}
BUFF_SPAWNFUNC_Q3TA_COMPAT(jumper, BUFF_JUMP)
REGISTER_BUFF(INVISIBLE) {
- this.m_prettyName = _("Invisible");
- this.m_name = "invisible";
+ this.m_name = _("Invisible");
+ this.netname = "invisible";
this.m_skin = 12;
this.m_color = '0.5 0.5 1';
}
BUFF_SPAWNFUNC_Q3TA_COMPAT(invis, BUFF_INVISIBLE)
REGISTER_BUFF(INFERNO) {
- this.m_prettyName = _("Inferno");
- this.m_name = "inferno";
+ this.m_name = _("Inferno");
+ this.netname = "inferno";
this.m_skin = 16;
this.m_color = '1 0.62 0';
}
BUFF_SPAWNFUNCS(inferno, BUFF_INFERNO)
REGISTER_BUFF(SWAPPER) {
- this.m_prettyName = _("Swapper");
- this.m_name = "swapper";
+ this.m_name = _("Swapper");
+ this.netname = "swapper";
this.m_skin = 17;
this.m_color = '0.63 0.36 1';
}
BUFF_SPAWNFUNCS(swapper, BUFF_SWAPPER)
REGISTER_BUFF(MAGNET) {
- this.m_prettyName = _("Magnet");
- this.m_name = "magnet";
+ this.m_name = _("Magnet");
+ this.netname = "magnet";
this.m_skin = 18;
this.m_color = '1 0.95 0.18';
}
BUFF_SPAWNFUNCS(magnet, BUFF_MAGNET)
REGISTER_BUFF(LUCK) {
- this.m_prettyName = _("Luck");
- this.m_name = "luck";
+ this.m_name = _("Luck");
+ this.netname = "luck";
this.m_skin = 19;
this.m_color = '1 0.23 0.44';
}
BUFF_SPAWNFUNCS(luck, BUFF_LUCK)
REGISTER_BUFF(FLIGHT) {
- this.m_prettyName = _("Flight");
- this.m_name = "flight";
+ this.m_name = _("Flight");
+ this.netname = "flight";
this.m_skin = 11;
this.m_color = '0.23 0.44 1';
}
string BUFF_NAME(int i)
{
Buff b = Buffs_from(i);
- return strcat(rgb_to_hexcolor(b.m_color), b.m_prettyName);
+ return strcat(rgb_to_hexcolor(b.m_color), b.m_name);
}
entity buff_FirstFromFlags(int _buffs)
CLASS(Buff, Pickup)
/** bit index */
ATTRIB(Buff, m_itemid, int, 0);
- ATTRIB(Buff, m_name, string, "buff");
+ ATTRIB(Buff, netname, string, "buff");
ATTRIB(Buff, m_color, vector, '1 1 1');
- ATTRIB(Buff, m_prettyName, string, "Buff");
+ ATTRIB(Buff, m_name, string, "Buff");
ATTRIB(Buff, m_skin, int, 0);
ATTRIB(Buff, m_sprite, string, "");
METHOD(Buff, display, void(entity this, void(string name, string icon) returns)) {
- returns(this.m_prettyName, sprintf("/gfx/hud/%s/buff_%s", cvar_string("menu_skin"), this.m_name));
+ returns(this.m_name, sprintf("/gfx/hud/%s/buff_%s", cvar_string("menu_skin"), this.netname));
}
#ifdef SVQC
METHOD(Buff, m_time, float(Buff this))
STATIC_INIT(REGISTER_BUFFS) {
FOREACH(Buffs, true, {
- it.netname = it.m_name; \
it.m_itemid = BIT(it.m_id - 1); \
- it.m_sprite = strzone(strcat("buff-", it.m_name)); \
+ it.m_sprite = strzone(strcat("buff-", it.netname)); \
});
}
{
int allBuffs = STAT(BUFFS);
FOREACH(Buffs, it.m_itemid & allBuffs, {
- addPowerupItem(it.m_prettyName, strcat("buff_", it.m_name), it.m_color, bound(0, STAT(BUFF_TIME) - time, 99), 60);
+ addPowerupItem(it.m_name, strcat("buff_", it.netname), it.m_color, bound(0, STAT(BUFF_TIME) - time, 99), 60);
});
}
MUTATOR_HOOKFUNCTION(cl_buffs, WP_Format)
{
Buff b = Buffs_from(this.wp_extra);
M_ARGV(2, vector) = b.m_color;
- M_ARGV(3, string) = b.m_prettyName;
- M_ARGV(4, string) = strcat("buff_", b.m_name);
+ M_ARGV(3, string) = b.m_name;
+ M_ARGV(4, string) = strcat("buff_", b.netname);
return true;
}
}
return false;
if (buff == BUFF_VAMPIRE && cvar("g_vampire"))
return false;
- return cvar(strcat("g_buffs_", buff.m_name));
+ return cvar(strcat("g_buffs_", buff.netname));
}
.int buff_seencount;
{
FOREACH_CLIENT(IS_PLAYER(it) && it != this && vdist(it.origin - this.origin, <=, autocvar_g_buffs_medic_heal_range),
{
- if (!SAME_TEAM(it, this))
+ if (DIFF_TEAM(it, this))
{
continue;
}
#define SV_DAMAGETEXT_ALL() (autocvar_sv_damagetext >= 3)
MUTATOR_HOOKFUNCTION(damagetext, PlayerDamaged) {
if (SV_DAMAGETEXT_DISABLED()) return;
- const entity attacker = M_ARGV(0, entity);
- const entity hit = M_ARGV(1, entity); if (hit == attacker) return;
- const float health = M_ARGV(2, float);
- const float armor = M_ARGV(3, float);
- const int deathtype = M_ARGV(5, int);
- const float potential_damage = M_ARGV(6, float);
+ entity attacker = M_ARGV(0, entity);
+ entity hit = M_ARGV(1, entity); if (hit == attacker) return;
+ float health = M_ARGV(2, float);
+ float armor = M_ARGV(3, float);
+ int deathtype = M_ARGV(5, int);
+ float potential_damage = M_ARGV(6, float);
if(DEATH_WEAPONOF(deathtype) == WEP_VAPORIZER) return;
FOREACH_CLIENT(IS_REAL_CLIENT(it), {
if (
void DynamicHandicap_UpdateHandicap()
{
float total_score = 0;
- float total_players = 0;
+ float totalplayers = 0;
FOREACH_CLIENT(IS_PLAYER(it),
{
total_score += PlayerScore_Get(it, SP_SCORE);
- ++total_players;
+ ++totalplayers;
});
- float mean_score = total_score / total_players;
+ float mean_score = total_score / totalplayers;
FOREACH_CLIENT(true,
{
float score = PlayerScore_Get(it, SP_SCORE);
this.m_sound = SND_VaporizerCells;
#endif
this.netname = "vaporizer_cells";
- this.m_name = "Vaporizer Ammo";
+ this.m_name = _("Vaporizer ammo");
this.m_icon = "ammo_supercells";
#ifdef SVQC
this.m_botvalue = 2000;
this.m_sound = SND_ExtraLife;
#endif
this.netname = "extralife";
- this.m_name = "Extra life";
+ this.m_name = _("Extra life");
this.m_icon = "item_mega_health";
this.m_color = '1 0 0';
this.m_waypoint = _("Extra life");
this.m_respawnsound = SND_STRENGTH_RESPAWN;
#endif
this.netname = "invisibility";
- this.m_name = "Invisibility";
+ this.m_name = _("Invisibility");
this.m_icon = "strength";
this.m_color = '0 0 1';
this.m_waypoint = _("Invisibility");
this.m_respawnsound = SND_SHIELD_RESPAWN;
#endif
this.netname = "speed";
- this.m_name = "Speed";
+ this.m_name = _("Speed");
this.m_icon = "shield";
this.m_color = '1 0 1';
this.m_waypoint = _("Speed");
{
frost_target.frozen_by = freezefield.realowner;
Send_Effect(EFFECT_ELECTRO_IMPACT, frost_target.origin, '0 0 0', 1);
- Freeze(frost_target, 1 / freezetime, 3, false);
+ Freeze(frost_target, 1 / freezetime, FROZEN_TEMP_DYING, false);
Drop_Special_Items(frost_target);
}
if (autocvar_g_nades_bonus)
if (IS_REAL_CLIENT(player))
if (IS_PLAYER(player) && STAT(NADE_BONUS, player) < autocvar_g_nades_bonus_max)
- if (STAT(FROZEN, player) == 0)
+ if (!STAT(FROZEN, player))
if (!IS_DEAD(player))
{
if ( STAT(NADE_BONUS_SCORE, player) < 1 )
}
}
+#ifdef IS_REVIVING
+ #undef IS_REVIVING
+#endif
+
+// returns true if player is reviving it
+#define IS_REVIVING(player, it, revive_extra_size) \
+ (it != player && !STAT(FROZEN, it) && !IS_DEAD(it) && SAME_TEAM(it, player) \
+ && boxesoverlap(player.absmin - revive_extra_size, player.absmax + revive_extra_size, it.absmin, it.absmax))
+
MUTATOR_HOOKFUNCTION(nades, PlayerPreThink)
{
entity player = M_ARGV(0, entity);
}
int n = 0;
- entity o = NULL;
+
+ IntrusiveList reviving_players = NULL;
+
if(player.freezetag_frozen_timeout > 0 && time >= player.freezetag_frozen_timeout)
n = -1;
- else if(STAT(FROZEN, player) == 3)
+ else if (STAT(FROZEN, player) == FROZEN_TEMP_DYING)
{
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) && 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;
- it.reviving = true;
- ++n;
- }
+ FOREACH_CLIENT(IS_PLAYER(it) && IS_REVIVING(player, it, revive_extra_size), {
+ if (!reviving_players)
+ reviving_players = IL_NEW();
+ IL_PUSH(reviving_players, it);
+ ++n;
});
}
- if(n > 0 && STAT(FROZEN, player) == 3) // OK, there is at least one teammate reviving us
+ if (n > 0 && STAT(FROZEN, player) == FROZEN_TEMP_DYING) // OK, there is at least one teammate reviving us
{
STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
SetResourceAmount(player, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, player) * start_health));
if(STAT(REVIVE_PROGRESS, player) >= 1)
{
- Unfreeze(player);
+ Unfreeze(player, false);
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname);
- Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, player.netname);
+ entity first = IL_FIRST(reviving_players);
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_REVIVED, first.netname);
+ Send_Notification(NOTIF_ONE, first, MSG_CENTER, CENTER_FREEZETAG_REVIVE, player.netname);
}
- FOREACH_CLIENT(IS_PLAYER(it) && it.reviving, {
+ IL_EACH(reviving_players, true, {
STAT(REVIVE_PROGRESS, it) = STAT(REVIVE_PROGRESS, player);
- it.reviving = false;
});
}
+ if (reviving_players)
+ IL_DELETE(reviving_players);
}
MUTATOR_HOOKFUNCTION(nades, PlayerPhysics_UpdateStats)
if(autocvar_g_freezetag_revive_nade && STAT(FROZEN, frag_target) && frag_attacker == frag_target && frag_deathtype == DEATH_NADE.m_id)
if(time - frag_inflictor.toss_time <= 0.1)
{
- Unfreeze(frag_target);
+ Unfreeze(frag_target, false);
SetResourceAmount(frag_target, RESOURCE_HEALTH, autocvar_g_freezetag_revive_nade_health);
Send_Effect(EFFECT_ICEORGLASS, frag_target.origin, '0 0 0', 3);
M_ARGV(4, float) = 0;
}
float okhmg_spread = bound(WEP_CVAR_PRI(okhmg, spread_min), WEP_CVAR_PRI(okhmg, spread_min) + (WEP_CVAR_PRI(okhmg, spread_add) * actor.(weaponentity).misc_bulletcounter), WEP_CVAR_PRI(okhmg, spread_max));
- fireBullet(actor, weaponentity, w_shotorg, w_shotdir, okhmg_spread, WEP_CVAR_PRI(okhmg, solidpenetration), WEP_CVAR_PRI(okhmg, damage), WEP_CVAR_PRI(okhmg, force), WEP_OVERKILL_HMG.m_id, 0);
+ fireBullet(actor, weaponentity, w_shotorg, w_shotdir, okhmg_spread, WEP_CVAR_PRI(okhmg, solidpenetration), WEP_CVAR_PRI(okhmg, damage), WEP_CVAR_PRI(okhmg, force), WEP_OVERKILL_HMG.m_id, EFFECT_RIFLE);
actor.(weaponentity).misc_bulletcounter = actor.(weaponentity).misc_bulletcounter + 1;
SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, actor, weaponentity);
}
- int slot = weaponslot(weaponentity);
- ATTACK_FINISHED(actor, slot) = time + WEP_CVAR_PRI(okhmg, refire) * W_WeaponRateFactor(actor);
+ ATTACK_FINISHED(actor, weaponentity) = time + WEP_CVAR_PRI(okhmg, refire) * W_WeaponRateFactor(actor);
weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(okhmg, refire), W_OverkillHeavyMachineGun_Attack_Auto);
}
}
okmachinegun_spread = bound(WEP_CVAR_PRI(okmachinegun, spread_min), WEP_CVAR_PRI(okmachinegun, spread_min) + (WEP_CVAR_PRI(okmachinegun, spread_add) * actor.(weaponentity).misc_bulletcounter), WEP_CVAR_PRI(okmachinegun, spread_max));
- fireBullet(actor, weaponentity, w_shotorg, w_shotdir, okmachinegun_spread, WEP_CVAR_PRI(okmachinegun, solidpenetration), WEP_CVAR_PRI(okmachinegun, damage), WEP_CVAR_PRI(okmachinegun, force), WEP_OVERKILL_MACHINEGUN.m_id, 0);
+ fireBullet(actor, weaponentity, w_shotorg, w_shotdir, okmachinegun_spread, WEP_CVAR_PRI(okmachinegun, solidpenetration), WEP_CVAR_PRI(okmachinegun, damage), WEP_CVAR_PRI(okmachinegun, force), WEP_OVERKILL_MACHINEGUN.m_id, EFFECT_RIFLE);
actor.(weaponentity).misc_bulletcounter = actor.(weaponentity).misc_bulletcounter + 1;
SpawnCasing(((random() * 50 + 50) * v_right) - (v_forward * (random() * 25 + 25)) - ((random() * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, actor, weaponentity);
}
- int slot = weaponslot(weaponentity);
- ATTACK_FINISHED(actor, slot) = time + WEP_CVAR_PRI(okmachinegun, refire) * W_WeaponRateFactor(actor);
+ ATTACK_FINISHED(actor, weaponentity) = time + WEP_CVAR_PRI(okmachinegun, refire) * W_WeaponRateFactor(actor);
weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(okmachinegun, refire), W_OverkillMachineGun_Attack_Auto);
}
}
#endif
-
/* spawnfunc */ ATTRIB(OverkillMachineGun, m_canonical_spawnfunc, string, "weapon_okmachinegun");
/* ammotype */ ATTRIB(OverkillMachineGun, ammo_type, int, RESOURCE_BULLETS);
/* impulse */ ATTRIB(OverkillMachineGun, impulse, int, 3);
-/* flags */ ATTRIB(OverkillMachineGun, spawnflags, int, WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_PENETRATEWALLS | WEP_FLAG_MUTATORBLOCKED);
+/* flags */ ATTRIB(OverkillMachineGun, spawnflags, int, WEP_FLAG_HIDDEN | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_PENETRATEWALLS | WEP_FLAG_MUTATORBLOCKED);
/* rating */ ATTRIB(OverkillMachineGun, bot_pickupbasevalue, float, 7000);
/* color */ ATTRIB(OverkillMachineGun, wpcolor, vector, '1 1 0');
/* modelname */ ATTRIB(OverkillMachineGun, mdl, string, "ok_mg");
mydmg *= charge;
myforce *= charge;
- W_SetupShot(actor, weaponentity, true, 5, SND_NEXFIRE, CH_WEAPON_A, mydmg, WEP_OVERKILL_NEX.m_id);
+ W_SetupShot(actor, weaponentity, true, 5, SND_NEXFIRE, CH_WEAPON_A, mydmg, thiswep.m_id);
if(charge > WEP_CVAR(oknex, charge_animlimit) && WEP_CVAR(oknex, charge_animlimit)) // if the OverkillNex is overcharged, we play an extra sound
{
sound(actor, CH_WEAPON_B, SND_NEXCHARGE, VOL_BASE * (charge - 0.5 * WEP_CVAR(oknex, charge_animlimit)) / (1 - 0.5 * WEP_CVAR(oknex, charge_animlimit)), ATTN_NORM);
yoda = 0;
damage_goodhits = 0;
- FireRailgunBullet(actor, weaponentity, w_shotorg, w_shotorg + w_shotdir * max_shot_distance, mydmg, myforce, mymindist, mymaxdist, myhalflife, myforcehalflife, WEP_OVERKILL_NEX.m_id);
+ FireRailgunBullet(actor, weaponentity, w_shotorg, w_shotorg + w_shotdir * max_shot_distance, mydmg, myforce, mymindist, mymaxdist, myhalflife, myforcehalflife, thiswep.m_id);
if(yoda && flying)
Send_Notification(NOTIF_ONE, actor, MSG_ANNCE, ANNCE_ACHIEVEMENT_YODA);
METHOD(OverkillNex, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
{
float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(oknex, ammo);
- ammo_amount += (autocvar_g_balance_oknex_reload_ammo && actor.(weaponentity).(weapon_load[WEP_OVERKILL_NEX.m_id]) >= WEP_CVAR_PRI(oknex, ammo));
+ ammo_amount += (autocvar_g_balance_oknex_reload_ammo && actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(oknex, ammo));
return ammo_amount;
}
{
// don't allow charging if we don't have enough ammo
float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(oknex, ammo);
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_OVERKILL_NEX.m_id]) >= WEP_CVAR_SEC(oknex, ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(oknex, ammo);
return ammo_amount;
}
else
/* spawnfunc */ ATTRIB(OverkillNex, m_canonical_spawnfunc, string, "weapon_oknex");
/* ammotype */ ATTRIB(OverkillNex, ammo_type, int, RESOURCE_CELLS);
/* impulse */ ATTRIB(OverkillNex, impulse, int, 7);
-/* flags */ ATTRIB(OverkillNex, spawnflags, int, WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_MUTATORBLOCKED);
+/* flags */ ATTRIB(OverkillNex, spawnflags, int, WEP_FLAG_HIDDEN | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_MUTATORBLOCKED);
/* rating */ ATTRIB(OverkillNex, bot_pickupbasevalue, float, 8000);
/* color */ ATTRIB(OverkillNex, wpcolor, vector, '0.5 1 1');
/* modelname */ ATTRIB(OverkillNex, mdl, string, "ok_sniper");
{
// if chainsaw hit something, it removed fired damage (so that direct hit is 100%)
// now that we also damaged something by explosion we'd go over 100% so let's add the fired damage back
- accuracy_add(this.realowner, DEATH_WEAPONOF(this.projectiledeathtype).m_id, WEP_CVAR(okrpc, damage), 0);
+ accuracy_add(this.realowner, DEATH_WEAPONOF(this.projectiledeathtype), WEP_CVAR(okrpc, damage), 0);
}
delete(this);
// We remove it here so that a direct hit that passes through and doesn't damage anything by the explosion later is still 100%.
float fired_damage = WEP_CVAR_PRI(okrpc, damage2) - WEP_CVAR_PRI(okrpc, damage);
float hit_damage = WEP_CVAR_PRI(okrpc, damage2);
- accuracy_add(this.realowner, DEATH_WEAPONOF(this.projectiledeathtype).m_id, fired_damage, hit_damage);
+ accuracy_add(this.realowner, DEATH_WEAPONOF(this.projectiledeathtype), fired_damage, hit_damage);
}
this.m_chainsaw_damage += WEP_CVAR_PRI(okrpc, damage2);
}
this.nextthink = time;
}
-void W_OverkillRocketPropelledChainsaw_Attack (Weapon thiswep, entity actor, .entity weaponentity)
+void W_OverkillRocketPropelledChainsaw_Attack(Weapon thiswep, entity actor, .entity weaponentity)
{
entity missile = spawn(); //WarpZone_RefSys_SpawnSameRefSys(actor);
entity flash = spawn ();
W_DecreaseAmmo(thiswep, actor, WEP_CVAR_PRI(okrpc, ammo), weaponentity);
- W_SetupShot_ProjectileSize(actor, weaponentity, '-3 -3 -3', '3 3 3', false, 5, SND_ROCKET_FIRE, CH_WEAPON_A, WEP_CVAR_PRI(okrpc, damage), WEP_OVERKILL_RPC.m_id);
+ W_SetupShot_ProjectileSize(actor, weaponentity, '-3 -3 -3', '3 3 3', false, 5, SND_ROCKET_FIRE, CH_WEAPON_A, WEP_CVAR_PRI(okrpc, damage), thiswep.m_id);
Send_Effect(EFFECT_ROCKET_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
PROJECTILE_MAKETRIGGER(missile);
IL_PUSH(g_damagedbycontents, missile);
set_movetype(missile, MOVETYPE_FLY);
- missile.projectiledeathtype = WEP_OVERKILL_RPC.m_id;
+ missile.projectiledeathtype = thiswep.m_id;
missile.weaponentity_fld = weaponentity;
setsize (missile, '-3 -3 -3', '3 3 3'); // give it some size so it can be shot
METHOD(OverkillRocketPropelledChainsaw, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
{
float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(okrpc, ammo);
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_OVERKILL_RPC.m_id]) >= WEP_CVAR_PRI(okrpc, ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(okrpc, ammo);
return ammo_amount;
}
METHOD(OverkillRocketPropelledChainsaw, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
{
float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(okrpc, ammo);
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_OVERKILL_RPC.m_id]) >= WEP_CVAR_SEC(okrpc, ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(okrpc, ammo);
return ammo_amount;
}
}
if (fire & 1) // Primary attack
{
- if (!weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(okshotgun, animtime)))
+ if (!weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(okshotgun, refire)))
{
return;
}
WEP_CVAR_PRI(okshotgun, bullets),
WEP_CVAR_PRI(okshotgun, spread),
WEP_CVAR_PRI(okshotgun, solidpenetration),
- WEP_CVAR_PRI(okshotgun, force));
+ WEP_CVAR_PRI(okshotgun, force),
+ EFFECT_RIFLE_WEAK);
weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(okshotgun, animtime), w_ready);
return;
}
/* spawnfunc */ ATTRIB(OverkillShotgun, m_canonical_spawnfunc, string, "weapon_okshotgun");
/* ammotype */ ATTRIB(OverkillShotgun, ammo_type, int, RESOURCE_SHELLS);
/* impulse */ ATTRIB(OverkillShotgun, impulse, int, 2);
-/* flags */ ATTRIB(OverkillShotgun, spawnflags, int, WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_MUTATORBLOCKED);
+/* flags */ ATTRIB(OverkillShotgun, spawnflags, int, WEP_FLAG_HIDDEN | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_MUTATORBLOCKED);
/* rating */ ATTRIB(OverkillShotgun, bot_pickupbasevalue, float, 6000);
/* color */ ATTRIB(OverkillShotgun, wpcolor, vector, '0.5 0.25 0');
/* modelname */ ATTRIB(OverkillShotgun, mdl, string, "ok_shotgun");
entity sandbox_ObjectPort_Load(entity this, string s, float database)
{
// load object properties, and spawn a new object with them
- float n, i;
+ int n, i;
entity e = NULL, parent = NULL;
+ string arg = string_null;
// separate objects between the ; symbols
n = tokenizebyseparator(s, "; ");
// now separate and apply the properties of each object
for(i = 0; i < n; ++i)
{
- float argv_num;
+ #define SANDBOX_GETARG arg = argv(++argv_num);
+ int argv_num = -1; // starts at -1 so I don't need postincrement
+
string tagname = string_null;
- argv_num = 0;
tokenize_console(port_string[i]);
e = sandbox_ObjectSpawn(this, database);
if(i)
{
// properties stored only for child objects
- if(argv(argv_num) != "") tagname = argv(argv_num); else tagname = string_null; ++argv_num;
+ SANDBOX_GETARG; tagname = (arg != "") ? arg : string_null;
}
else
{
// properties stored only for parent objects
if(database)
{
- setorigin(e, stov(argv(argv_num))); ++argv_num;
- e.angles = stov(argv(argv_num)); ++argv_num;
+ SANDBOX_GETARG; setorigin(e, stov(arg));
+ SANDBOX_GETARG; e.angles = stov(arg);
}
parent = e; // mark parent objects as such
}
// properties stored for all objects
- _setmodel(e, argv(argv_num)); ++argv_num;
- e.skin = stof(argv(argv_num)); ++argv_num;
- e.alpha = stof(argv(argv_num)); ++argv_num;
- e.colormod = stov(argv(argv_num)); ++argv_num;
- e.glowmod = stov(argv(argv_num)); ++argv_num;
- e.frame = stof(argv(argv_num)); ++argv_num;
- sandbox_ObjectEdit_Scale(e, stof(argv(argv_num))); ++argv_num;
- e.solid = e.old_solid = stof(argv(argv_num)); ++argv_num;
- e.old_movetype = stof(argv(argv_num)); ++argv_num;
+ SANDBOX_GETARG; _setmodel(e, arg);
+ SANDBOX_GETARG; e.skin = stof(arg);
+ SANDBOX_GETARG; e.alpha = stof(arg);
+ SANDBOX_GETARG; e.colormod = stov(arg);
+ SANDBOX_GETARG; e.glowmod = stov(arg);
+ SANDBOX_GETARG; e.frame = stof(arg);
+ SANDBOX_GETARG; sandbox_ObjectEdit_Scale(e, stof(arg));
+ SANDBOX_GETARG; e.solid = e.old_solid = stof(arg);
+ SANDBOX_GETARG; e.old_movetype = stof(arg);
set_movetype(e, e.old_movetype);
- e.damageforcescale = stof(argv(argv_num)); ++argv_num;
- strfree(e.material); if(argv(argv_num) != "") e.material = strzone(argv(argv_num)); else e.material = string_null; ++argv_num;
+ SANDBOX_GETARG; e.damageforcescale = stof(arg);
+ strfree(e.material);
+ SANDBOX_GETARG; e.material = (arg != "") ? strzone(arg) : string_null;
if(database)
{
// properties stored only for the database
- strfree(e.crypto_idfp); if(argv(argv_num) != "") e.crypto_idfp = strzone(argv(argv_num)); else e.crypto_idfp = string_null; ++argv_num;
- strcpy(e.netname, argv(argv_num)); ++argv_num;
- strcpy(e.message, argv(argv_num)); ++argv_num;
- strcpy(e.message2, argv(argv_num)); ++argv_num;
+ strfree(e.crypto_idfp);
+ SANDBOX_GETARG; e.crypto_idfp = (arg != "") ? strzone(arg) : string_null;
+ SANDBOX_GETARG; strcpy(e.netname, arg);
+ SANDBOX_GETARG; strcpy(e.message, arg);
+ SANDBOX_GETARG; strcpy(e.message2, arg);
}
// attach last
if (autocvar_g_spawn_near_teammate_ignore_spawnpoint_max && tested >= autocvar_g_spawn_near_teammate_ignore_spawnpoint_max) break;
if (PHYS_INPUT_BUTTON_CHAT(it)) continue;
- if (!SAME_TEAM(player, it)) continue;
+ if (DIFF_TEAM(player, it)) continue;
if (autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health && GetResourceAmount(it, RESOURCE_HEALTH) < autocvar_g_balance_health_regenstable) continue;
if (IS_DEAD(it)) continue;
if (time < it.msnt_timer) continue;
thehook.owner.damage_dealt += autocvar_g_vampirehook_damage;
Damage(dmgent, thehook, thehook.owner, autocvar_g_vampirehook_damage, WEP_HOOK.m_id, DMG_NOWEP, thehook.origin, '0 0 0');
entity targ = ((SAME_TEAM(thehook.owner, thehook.aiment)) ? thehook.aiment : thehook.owner);
- Heal(targ, thehook.owner, autocvar_g_vampirehook_health_steal, g_pickup_healthsmall_max);
+ // TODO: we can't do this due to an issue with globals and the mutator arguments
+ //Heal(targ, thehook.owner, autocvar_g_vampirehook_health_steal, g_pickup_healthsmall_max);
+ SetResourceAmountExplicit(targ, RESOURCE_HEALTH, min(GetResourceAmount(targ, RESOURCE_HEALTH) + autocvar_g_vampirehook_health_steal, g_pickup_healthsmall_max));
if(dmgent == thehook.owner)
TakeResource(dmgent, RESOURCE_HEALTH, autocvar_g_vampirehook_damage); // FIXME: friendly fire?!
REGISTER_WAYPOINT(RaceStart, _("Start"), "", '1 0.5 0', 1);
REGISTER_WAYPOINT(RaceStartFinish, _("Start"), "", '1 0.5 0', 1);
-REGISTER_WAYPOINT(AssaultDefend, _("Defend"), "", '1 0.5 0', 1);
-REGISTER_WAYPOINT(AssaultDestroy, _("Destroy"), "", '1 0.5 0', 1);
+REGISTER_WAYPOINT(AssaultDefend, _("Defend"), "as_defend", '1 0.5 0', 1);
+REGISTER_WAYPOINT(AssaultDestroy, _("Destroy"), "as_destroy", '1 0.5 0', 1);
REGISTER_WAYPOINT(AssaultPush, _("Push"), "", '1 0.5 0', 1);
REGISTER_WAYPOINT(FlagCarrier, _("Flag carrier"), "", '0.8 0.8 0', 1);
LOG_INFOF("WARNING: sprite of name %s has no color, using pink so you notice it", spriteimage);
}
- if (time - floor(time) > 0.5)
+ float health_val = GetResourceAmount(this, RESOURCE_HEALTH);
+ float blink_time = (health_val >= 0) ? (health_val * 10) : time;
+ if (blink_time - floor(blink_time) > 0.5)
{
if (this.helpme && time < this.helpme)
a *= SPRITE_HELPME_BLINK;
- else if (this.lifetime > 0) // fading out waypoints don't blink
+ else if (!this.lifetime) // fading out waypoints don't blink
a *= spritelookupblinkvalue(this, spriteimage);
}
ang += M_PI;
float f1 = d.x / vid_conwidth;
- float f2 = d.y / vid_conheight;
+ float f2 = d.y / vid_conheight;
if (f1 == 0) { f1 = 0.000001; }
if (f2 == 0) { f2 = 0.000001; }
entity WaypointSprite_DeployFixed(
entity spr,
- float limited_range,
+ bool limited_range,
entity player,
vector ofs,
entity icon // initial icon
entity WaypointSprite_Attach(
entity spr,
entity player,
- float limited_range,
+ bool limited_range,
entity icon // initial icon
)
{
float autocvar_g_waypointsprite_timealphaexponent;
bool autocvar_g_waypointsprite_turrets = true;
float autocvar_g_waypointsprite_turrets_maxdist = 5000;
+bool autocvar_g_waypointsprite_turrets_text = false;
bool autocvar_g_waypointsprite_uppercase;
bool autocvar_g_waypointsprite_text;
float autocvar_g_waypointsprite_iconsize = 32;
.entity waypointsprite_deployed_fixed;
entity WaypointSprite_DeployFixed(
entity spr,
- float limited_range,
+ bool limited_range,
entity player,
vector ofs,
entity icon // initial icon
entity WaypointSprite_Attach(
entity spr,
entity player,
- float limited_range,
+ bool limited_range,
entity icon // initial icon
);
MSG_INFO_NOTIF(ITEM_BUFF_GOT, N_CONSOLE, 0, 1, "item_buffname", "", "", _("^BGYou got the %s^BG buff!"), "")
MSG_INFO_NOTIF(ITEM_WEAPON_DONTHAVE, N_DISABLE, 0, 1, "item_wepname", "", "", _("^BGYou do not have the ^F1%s"), "")
- MSG_INFO_NOTIF(ITEM_WEAPON_DROP, N_DISABLE, 1, 1, "item_wepname item_wepammo", "", "", _("^BGYou dropped the ^F1%s^BG%s"), "")
+ MSG_INFO_NOTIF(ITEM_WEAPON_DROP, N_DISABLE, 0, 2, "item_wepname item_wepammo", "", "", _("^BGYou dropped the ^F1%s^BG%s"), "")
MSG_INFO_NOTIF(ITEM_WEAPON_GOT, N_DISABLE, 0, 1, "item_wepname", "", "", _("^BGYou got the ^F1%s"), "")
MSG_INFO_NOTIF(ITEM_WEAPON_NOAMMO, N_DISABLE, 0, 1, "item_wepname", "", "", _("^BGYou don't have enough ammo for the ^F1%s"), "")
MSG_INFO_NOTIF(ITEM_WEAPON_PRIMORSEC, N_DISABLE, 0, 3, "item_wepname f2primsec f3primsec", "", "", _("^F1%s %s^BG is unable to fire, but its ^F1%s^BG can"), "")
MSG_INFO_NOTIF(CONNECTING, N_CONSOLE, 1, 0, "s1", "", "", _("^BG%s^BG is connecting..."), "")
MSG_INFO_NOTIF(JOIN_CONNECT, N_CHATCON, 1, 0, "s1", "", "", _("^BG%s^F3 connected"), "")
- MULTITEAM_INFO(JOIN_CONNECT_TEAM, 4, N_CHATCON, 1, 0, "s1", "", "", _("^BG%s^F3 connected and joined the ^TC^TT team"), "", NAME)
MSG_INFO_NOTIF(JOIN_PLAY, N_CONSOLE, 1, 0, "s1", "", "", _("^BG%s^F3 is now playing"), "")
MULTITEAM_INFO(JOIN_PLAY_TEAM, 4, N_CHATCON, 1, 0, "s1", "", "", _("^BG%s^F3 is now playing on the ^TC^TT team"), "", NAME)
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(QUIT_SPECTATE, N_CHATCON, 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"), "")
MSG_INFO_NOTIF(RACE_FAIL_RANKED, N_CONSOLE, 1, 3, "s1 race_col f1ord race_col f3race_time race_diff", "s1 f3race_time", "race_newfail", _("^BG%s^BG couldn't break their %s%s^BG place record of %s%s %s"), "")
MSG_INFO_NOTIF(TEAMCHANGE_LARGERTEAM, N_CONSOLE, 0, 0, "", "", "", _("^BGYou cannot change to a larger team"), "")
MSG_INFO_NOTIF(TEAMCHANGE_NOTALLOWED, N_CONSOLE, 0, 0, "", "", "", _("^BGYou are not allowed to change teams"), "")
- MSG_INFO_NOTIF(VERSION_BETA, N_CHATCON, 2, 0, "s1 s2", "", "", _("^F4NOTE: ^BGThe server is running ^F1Xonotic %s (beta)^BG, you have ^F2Xonotic %s"), "")
+ MSG_INFO_NOTIF(VERSION_BETA, N_CONSOLE, 2, 0, "s1 s2", "", "", _("^F4NOTE: ^BGThe server is running ^F1Xonotic %s (beta)^BG, you have ^F2Xonotic %s"), "")
MSG_INFO_NOTIF(VERSION_OLD, N_CHATCON, 2, 0, "s1 s2", "", "", _("^F4NOTE: ^BGThe server is running ^F1Xonotic %s^BG, you have ^F2Xonotic %s"), "")
MSG_INFO_NOTIF(VERSION_OUTDATED, N_CHATCON, 2, 0, "s1 s2", "", "", _("^F4NOTE: ^F1Xonotic %s^BG is out, and you still have ^F2Xonotic %s^BG - get the update from ^F3http://www.xonotic.org/^BG!"), "")
MSG_CENTER_NOTIF(ITEM_FUELREGEN_GOT, N_ENABLE, 0, 0, "", CPID_ITEM, "item_centime 0", _("^BGYou got the ^F1Fuel regenerator"), "")
MSG_CENTER_NOTIF(ITEM_JETPACK_GOT, N_ENABLE, 0, 0, "", CPID_ITEM, "item_centime 0", _("^BGYou got the ^F1Jetpack"), "")
MSG_CENTER_NOTIF(ITEM_WEAPON_DONTHAVE, N_ENABLE, 0, 1, "item_wepname", CPID_ITEM, "item_centime 0", _("^BGYou do not have the ^F1%s"), "")
- MSG_CENTER_NOTIF(ITEM_WEAPON_DROP, N_ENABLE, 1, 1, "item_wepname item_wepammo", CPID_ITEM, "item_centime 0", _("^BGYou dropped the ^F1%s^BG%s"), "")
+ MSG_CENTER_NOTIF(ITEM_WEAPON_DROP, N_ENABLE, 0, 2, "item_wepname item_wepammo", CPID_ITEM, "item_centime 0", _("^BGYou dropped the ^F1%s^BG%s"), "")
MSG_CENTER_NOTIF(ITEM_WEAPON_GOT, N_ENABLE, 0, 1, "item_wepname", CPID_ITEM, "item_centime 0", _("^BGYou got the ^F1%s"), "")
MSG_CENTER_NOTIF(ITEM_WEAPON_NOAMMO, N_ENABLE, 0, 1, "item_wepname", CPID_ITEM, "item_centime 0", _("^BGYou don't have enough ammo for the ^F1%s"), "")
MSG_CENTER_NOTIF(ITEM_WEAPON_PRIMORSEC, N_ENABLE, 0, 3, "item_wepname f2primsec f3primsec", CPID_ITEM, "item_centime 0", _("^F1%s %s^BG is unable to fire, but its ^F1%s^BG can"), "")
#define A_ALWAYS 2
// MSG_CHOICE_NOTIFICATIONS
- MULTITEAM_CHOICE(CTF_CAPTURE_BROKEN, 4, N__NORMAL, A_ALWAYS, MSG_INFO, INFO_CTF_CAPTURE, INFO_CTF_CAPTURE_BROKEN)
- MULTITEAM_CHOICE(CTF_CAPTURE_TIME, 4, N__NORMAL, A_ALWAYS, MSG_INFO, INFO_CTF_CAPTURE, INFO_CTF_CAPTURE_TIME)
- MULTITEAM_CHOICE(CTF_CAPTURE_UNBROKEN, 4, N__NORMAL, A_ALWAYS, MSG_INFO, INFO_CTF_CAPTURE, INFO_CTF_CAPTURE_UNBROKEN)
+ MULTITEAM_CHOICE(CTF_CAPTURE_BROKEN, 4, N_VERBOSE, A_ALWAYS, MSG_INFO, INFO_CTF_CAPTURE, INFO_CTF_CAPTURE_BROKEN)
+ MULTITEAM_CHOICE(CTF_CAPTURE_TIME, 4, N_VERBOSE, A_ALWAYS, MSG_INFO, INFO_CTF_CAPTURE, INFO_CTF_CAPTURE_TIME)
+ MULTITEAM_CHOICE(CTF_CAPTURE_UNBROKEN, 4, N_VERBOSE, A_ALWAYS, MSG_INFO, INFO_CTF_CAPTURE, INFO_CTF_CAPTURE_UNBROKEN)
MULTITEAM_CHOICE(CTF_PICKUP_TEAM, 4, N__NORMAL, A_ALWAYS, MSG_CENTER, CENTER_CTF_PICKUP_TEAM, CENTER_CTF_PICKUP_TEAM_VERBOSE)
MSG_CHOICE_NOTIF(CTF_PICKUP_TEAM_NEUTRAL, N__NORMAL, A_ALWAYS, MSG_CENTER, CENTER_CTF_PICKUP_TEAM_NEUTRAL, CENTER_CTF_PICKUP_TEAM_VERBOSE_NEUTRAL)
MSG_CHOICE_NOTIF(CTF_PICKUP_ENEMY, N__NORMAL, A_ALWAYS, MSG_CENTER, CENTER_CTF_PICKUP_ENEMY, CENTER_CTF_PICKUP_ENEMY_VERBOSE)
#include <common/teams.qh>
#include <common/util.qh>
#include <common/sounds/sound.qh>
+#include <common/weapons/all.qh>
#ifdef CSQC
#include <client/autocvars.qh>
spree_end: placed at the end of murder messages to show ending of sprees
spree_lost: placed at the end of suicide messages to show losing of sprees
item_wepname: return full name of a weapon from weaponid
- item_wepammo: ammo display for weapon from string
+ item_wepammo: ammo display for weapon from f1 and f2
item_centime: amount of time to display weapon message in centerprint
item_buffname: return full name of a buff from buffid
death_team: show the full name of the team a player is switching from
ARG_CASE(ARG_CS_SV, "item_wepname", Weapons_from(f1).m_name) \
ARG_CASE(ARG_CS_SV, "item_buffname", BUFF_NAME(f1)) \
ARG_CASE(ARG_CS_SV, "f3buffname", BUFF_NAME(f3)) \
- ARG_CASE(ARG_CS_SV, "item_wepammo", (s1 != "" ? sprintf(_(" with %s"), s1) : "")) \
+ ARG_CASE(ARG_CS_SV, "item_wepammo", (f2 > 0 ? notif_arg_item_wepammo(f1, f2) : "")) \
ARG_CASE(ARG_DC, "item_centime", ftos(autocvar_notification_item_centerprinttime)) \
ARG_CASE(ARG_SV, "death_team", Team_ColoredFullName(f1)) \
ARG_CASE(ARG_CS, "death_team", Team_ColoredFullName(f1 - 1)) \
return "";
}
+string notif_arg_item_wepammo(float f1, float f2)
+{
+ string ammoitems = "";
+ Weapon wep = Weapons_from(f1);
+ switch (wep.ammo_type)
+ {
+ case RESOURCE_SHELLS: ammoitems = ITEM_Shells.m_name; break;
+ case RESOURCE_BULLETS: ammoitems = ITEM_Bullets.m_name; break;
+ case RESOURCE_ROCKETS: ammoitems = ITEM_Rockets.m_name; break;
+ case RESOURCE_CELLS: ammoitems = ITEM_Cells.m_name; break;
+ case RESOURCE_PLASMA: ammoitems = ITEM_Plasma.m_name; break;
+ case RESOURCE_FUEL: ammoitems = ITEM_JetpackFuel.m_name; break;
+ default: return ""; // doesn't use ammo
+ }
+ return sprintf(_(" with %d %s"), f2, ammoitems);
+}
+
// ====================================
// Initialization/Create Declarations
if(GAMEPLAYFIX_EASIERWATERJUMP(this) && (this.flags & FL_WATERJUMP) && !(blocked & 8))
this.velocity = primal_velocity;
+ if(PHYS_WALLCLIP(this) && this.pm_time && !(this.flags & FL_WATERJUMP) && !(blocked & 8))
+ this.velocity = primal_velocity;
+
if(applygravity)
{
if(!GAMEPLAYFIX_NOGRAVITYONGROUND || !IS_ONGROUND(this))
bool _Movetype_TestEntityPosition(vector ofs) // SV_TestEntityPosition
{
entity this = _Movetype_TestEntityPosition_ent;
-// vector org = this.origin + ofs;
+ vector org = this.origin + ofs;
int cont = this.dphitcontentsmask;
this.dphitcontentsmask = DPCONTENTS_SOLID;
- tracebox(this.origin, this.mins, this.maxs, this.origin, ((this.move_movetype == MOVETYPE_FLY_WORLDONLY) ? MOVE_WORLDONLY : MOVE_NOMONSTERS), this);
+ tracebox(org, this.mins, this.maxs, org, ((this.move_movetype == MOVETYPE_FLY_WORLDONLY) ? MOVE_WORLDONLY : MOVE_NOMONSTERS), this);
this.dphitcontentsmask = cont;
if(trace_startsolid)
return false;
}
-bool _Movetype_UnstickEntity(entity this) // SV_UnstickEntity
+int _Movetype_UnstickEntity(entity this) // SV_UnstickEntity
{
_Movetype_TestEntityPosition_ent = this;
if (!_Movetype_TestEntityPosition(' 0 0 0')) {
- return true;
+ return UNSTICK_FINE;
}
#define X(v) if (_Movetype_TestEntityPosition(v))
X('-1 0 0') X(' 1 0 0')
{
LOG_DEBUGF("Can't unstick an entity (edict: %d, classname: %s, origin: %s)",
etof(this), this.classname, vtos(this.origin));
- return false;
+ return UNSTICK_STUCK;
}
}
LOG_DEBUGF("Sucessfully unstuck an entity (edict: %d, classname: %s, origin: %s)",
etof(this), this.classname, vtos(this.origin));
_Movetype_LinkEdict(this, true);
- return true;
+ return UNSTICK_FIXED;
+}
+
+void _Movetype_CheckStuck(entity this) // SV_CheckStuck
+{
+ int unstick = _Movetype_UnstickEntity(this); // sets test position entity
+ switch(unstick)
+ {
+ case UNSTICK_FINE:
+ this.oldorigin = this.origin;
+ break;
+ case UNSTICK_FIXED:
+ break; // already sorted
+ case UNSTICK_STUCK:
+ vector offset = this.oldorigin - this.origin;
+ if(!_Movetype_TestEntityPosition(offset))
+ _Movetype_LinkEdict(this, false);
+ // couldn't unstick, should we warn about this?
+ break;
+ }
}
vector _Movetype_ClipVelocity(vector vel, vector norm, float f) // SV_ClipVelocity
#define PHYS_JUMPSTEP(s) STAT(MOVEVARS_JUMPSTEP)
#define PHYS_WALLFRICTION(s) STAT(MOVEVARS_WALLFRICTION)
+#define PHYS_WALLCLIP(s) STAT(MOVEVARS_WALLCLIP)
+
#ifdef CSQC
.float bouncestop;
.float bouncefactor;
void set_movetype(entity this, int mt);
+.float pm_time;
+
.float move_movetype;
.float move_time;
//.vector move_origin;
.float move_suspendedinair;
.float move_didgravity;
+// unsticking
+const int UNSTICK_FINE = 0;
+const int UNSTICK_FIXED = 1;
+const int UNSTICK_STUCK = 2;
+
void _Movetype_WallFriction(entity this, vector stepnormal);
int _Movetype_FlyMove(entity this, float dt, bool applygravity, vector stepnormal, float stepheight);
void _Movetype_CheckVelocity(entity this);
void _Movetype_CheckWaterTransition(entity ent);
+void _Movetype_CheckStuck(entity this);
float _Movetype_CheckWater(entity ent);
void _Movetype_LinkEdict_TouchAreaGrid(entity this);
void _Movetype_LinkEdict(entity this, float touch_triggers);
void _Movetype_LinkEdict(entity this, float touch_triggers);
void _Movetype_LinkEdict_TouchAreaGrid(entity this);
-float _Movetype_UnstickEntity(entity this);
+int _Movetype_UnstickEntity(entity this);
const int MAX_CLIP_PLANES = 5;
const int MOVETYPE_ANGLECLIP = 2;
#endif
+const int MOVETYPE_QCPLAYER = 150; // QC-driven player physics, no think functions!
+
const int FL_ONSLICK = BIT(20);
const int MOVETYPE_FAKEPUSH = 13;
return;
if (GAMEPLAYFIX_UNSTICKPLAYERS(this))
- _Movetype_UnstickEntity(this);
+ _Movetype_CheckStuck(this);
bool applygravity = (!_Movetype_CheckWater(this) && this.move_movetype == MOVETYPE_WALK && !(this.flags & FL_WATERJUMP));
vector start_origin = this.origin;
vector start_velocity = this.velocity;
+ if(PHYS_WALLCLIP(this) && this.pm_time)
+ {
+ if(dt >= this.pm_time || (this.flags & FL_WATERJUMP))
+ this.pm_time = 0;
+ else
+ this.pm_time -= dt;
+ }
+
int clip = _Movetype_FlyMove(this, dt, applygravity, stepnormal, GAMEPLAYFIX_STEPMULTIPLETIMES(this) ? PHYS_STEPHEIGHT(this) : 0);
if (GAMEPLAYFIX_DOWNTRACEONGROUND(this) && !(clip & 1))
// if the move did not hit the ground at any point, we're not on ground
if (!(clip & 1))
UNSET_ONGROUND(this);
+ else if(PHYS_WALLCLIP(this) && !this.groundentity && (PHYS_WALLCLIP(this) == 2 || start_velocity.z < -200)) // don't do landing time if we were just going down a slope
+ this.pm_time = 0.25;
_Movetype_CheckVelocity(this);
_Movetype_LinkEdict(this, true);
}
// move down
- vector downmove = '0 0 1' * (-PHYS_STEPHEIGHT(this) + start_velocity.z * dt);
+ vector downmove = '0 0 0';
+ downmove.z = -PHYS_STEPHEIGHT(this) + start_velocity.z * dt;
_Movetype_PushEntity(this, downmove, true);
if(wasfreed(this))
return;
{
// this has been disabled so that you can't jump when you are stepping
// up while already jumping (also known as the Quake2 double jump bug)
+ // LordHavoc: disabled this check so you can walk on monsters/players
+ //if (PRVM_serveredictfloat(ent, solid) == SOLID_BSP)
+ if(GAMEPLAYFIX_STEPDOWN(this) == 2)
+ {
+ SET_ONGROUND(this);
+ this.groundentity = trace_ent;
+ }
}
else
{
// client side physics
bool Physics_Valid(string thecvar)
{
- return autocvar_g_physics_clientselect && thecvar != "" && thecvar && thecvar != "default" && strhasword(autocvar_g_physics_clientselect_options, thecvar);
+ return thecvar != "" && thecvar && thecvar != "default" && strhasword(autocvar_g_physics_clientselect_options, thecvar);
}
float Physics_ClientOption(entity this, string option, float defaultval)
{
+ if(!autocvar_g_physics_clientselect)
+ return defaultval;
+
if(IS_REAL_CLIENT(this) && Physics_Valid(CS(this).cvar_cl_physics))
{
string s = strcat("g_physics_", CS(this).cvar_cl_physics, "_", option);
if(cvar_type(s) & CVAR_TYPEFLAG_EXISTS)
return cvar(s);
}
- if(autocvar_g_physics_clientselect && autocvar_g_physics_clientselect_default && autocvar_g_physics_clientselect_default != "")
+ if(autocvar_g_physics_clientselect_default && autocvar_g_physics_clientselect_default != "" && autocvar_g_physics_clientselect_default != "default")
{
+ // NOTE: not using Physics_Valid here, so the default can be forced to something normally unavailable
string s = strcat("g_physics_", autocvar_g_physics_clientselect_default, "_", option);
if(cvar_type(s) & CVAR_TYPEFLAG_EXISTS)
return cvar(s);
STAT(MOVEVARS_AIRSPEEDLIMIT_NONQW, this) = Physics_ClientOption(this, "airspeedlimit_nonqw", autocvar_sv_airspeedlimit_nonqw) * maxspd_mod;
STAT(MOVEVARS_MAXSPEED, this) = Physics_ClientOption(this, "maxspeed", autocvar_sv_maxspeed) * maxspd_mod; // also slow walking
- STAT(PL_MIN, this) = autocvar_sv_player_mins;
- STAT(PL_MAX, this) = autocvar_sv_player_maxs;
- STAT(PL_VIEW_OFS, this) = autocvar_sv_player_viewoffset;
- STAT(PL_CROUCH_MIN, this) = autocvar_sv_player_crouch_mins;
- STAT(PL_CROUCH_MAX, this) = autocvar_sv_player_crouch_maxs;
- STAT(PL_CROUCH_VIEW_OFS, this) = autocvar_sv_player_crouch_viewoffset;
+ bool vq3compat = autocvar_sv_vq3compat && autocvar_sv_vq3compat_changehitbox; // NOTE: these hitboxes are off by 1 due to engine differences
+ STAT(PL_MIN, this) = (vq3compat) ? '-15 -15 -24' : autocvar_sv_player_mins;
+ STAT(PL_MAX, this) = (vq3compat) ? '15 15 32' : autocvar_sv_player_maxs;
+ STAT(PL_VIEW_OFS, this) = (vq3compat) ? '0 0 26' : autocvar_sv_player_viewoffset;
+ STAT(PL_CROUCH_MIN, this) = (vq3compat) ? '-15 -15 -24' : autocvar_sv_player_crouch_mins;
+ STAT(PL_CROUCH_MAX, this) = (vq3compat) ? '15 15 16' : autocvar_sv_player_crouch_maxs;
+ STAT(PL_CROUCH_VIEW_OFS, this) = (vq3compat) ? '0 0 12' : autocvar_sv_player_crouch_viewoffset;
// old stats
// fix some new settings
}
}
bool do_crouch = PHYS_INPUT_BUTTON_CROUCH(this);
+ if(this.viewloc && !(this.viewloc.spawnflags & VIEWLOC_FREEMOVE) && PHYS_CS(this).movement.x < 0)
+ do_crouch = true;
if (have_hook) {
do_crouch = false;
//} else if (this.waterlevel >= WATERLEVEL_SWIMMING) {
return false;
if(this.waterlevel >= WATERLEVEL_SWIMMING)
return false;
- traceline(this.origin, this.origin - '0 0 48', MOVE_NORMAL, this);
+ tracebox(this.origin, this.mins, this.maxs, this.origin - '0 0 24', MOVE_NORMAL, this);
+ //traceline(this.origin, this.origin - '0 0 48', MOVE_NORMAL, this);
if(trace_fraction < 1)
return false;
return true;
.float swamp_slowdown;
.float lastflags;
.float lastground;
-.float wasFlying;
+.bool wasFlying;
.int buttons_old;
.vector movement_old;
#define PHYS_INPUT_BUTTON_ZOOMSCRIPT(s) PHYS_INPUT_BUTTON_BUTTON9(s)
#define PHYS_INPUT_BUTTON_JETPACK(s) PHYS_INPUT_BUTTON_BUTTON10(s)
#define PHYS_INPUT_BUTTON_DODGE(s) PHYS_INPUT_BUTTON_BUTTON11(s)
+#define PHYS_INPUT_BUTTON_MINIGAME(s) PHYS_INPUT_BUTTON_BUTTON14(s)
#ifdef CSQC
STATIC_INIT(PHYS_INPUT_BUTTON)
}
}
-void PlayerStats_GameReport_AddTeam(float t)
+void PlayerStats_GameReport_AddTeam(int t)
{
if(PS_GR_OUT_DB < 0) { return; }
strfree(p.playerstats_id);
}
-void PlayerStats_GameReport(float finished)
+void PlayerStats_GameReport(bool finished)
{
if(PS_GR_OUT_DB < 0) { return; }
}
// this... is a hack, a temporary one until we get a proper duel gametype
+// TODO: remove duel hack after servers have migrated to the proper duel gametype!
string PlayerStats_GetGametype()
{
if(IS_GAMETYPE(DEATHMATCH) && autocvar_g_maxplayers == 2)
const string PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM = "achievement-firstvictim";
// delay map switch until this is set
-float PlayerStats_GameReport_DelayMapVote;
+bool PlayerStats_GameReport_DelayMapVote;
// call at initialization
void PlayerStats_GameReport_Init();
.entity realowner;
bool sound_allowed(int to, entity e)
{
+ if(!e) return true; // save on a few checks
for ( ; ; )
{
if (e.classname == "body") e = e.enemy;
{
this._ps = NEW(PlayerState, this);
- Inventory_new(this);
+ Inventory_new(PS(this));
}
void PlayerState_detach(entity this)
if (ps.m_client != this) return; // don't own state, spectator
ps.ps_push(ps, this);
+ Inventory_delete(ps);
FOREACH_CLIENT(PS(it) == ps, { PS(it) = NULL; });
delete(ps);
-
- Inventory_delete(this);
}
void GetCvars(entity this, entity store, int);
entcs_attach(this);
anticheat_init(this);
W_HitPlotOpen(this);
-
- bot_clientconnect(this);
}
void bot_clientdisconnect(entity this);
#endif
REGISTER_STAT(SLICK_APPLYGRAVITY, bool, autocvar_sv_slick_applygravity)
+#ifdef SVQC
+bool autocvar_sv_vq3compat;
+#endif
+REGISTER_STAT(VQ3COMPAT, bool, autocvar_sv_vq3compat)
+
#ifdef SVQC
#include "physics/movetypes/movetypes.qh"
float warmup_limit;
REGISTER_STAT(MOVEVARS_AIRACCEL_QW, float)
REGISTER_STAT(MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION, float)
REGISTER_STAT(MOVEVARS_SPECIALCOMMAND, bool)
+#ifdef SVQC
+int autocvar_sv_wallclip;
+#endif
+REGISTER_STAT(MOVEVARS_WALLCLIP, int, autocvar_sv_wallclip)
#ifdef CSQC
return true;
}
-void Item_Show (entity e, float mode)
+void Item_Show (entity e, int mode)
{
e.effects &= ~(EF_ADDITIVE | EF_STARDUST | EF_FULLBRIGHT | EF_NODEPTHTEST);
e.ItemStatus &= ~ITS_STAYWEP;
return normal_respawntime;
}
- CheckAllowedTeams(NULL);
- GetTeamCounts(NULL);
+ entity balance = TeamBalance_CheckAllowedTeams(NULL);
+ TeamBalance_GetTeamCounts(balance, NULL);
int players = 0;
- if (c1 != -1) players += c1;
- if (c2 != -1) players += c2;
- if (c3 != -1) players += c3;
- if (c4 != -1) players += c4;
-
+ for (int i = 1; i <= NUM_TEAMS; ++i)
+ {
+ if (TeamBalance_IsTeamAllowed(balance, i))
+ {
+ players += TeamBalance_GetNumberOfPlayers(balance, i);
+ }
+ }
+ TeamBalance_Destroy(balance);
+
if (players >= 2) {
return normal_respawntime * (r / (players + o) + l);
} else {
return 0;
// crude hack to enforce switching weapons
- if(g_cts && item.itemdef.instanceOfWeaponPickup)
+ if(g_cts && item.itemdef.instanceOfWeaponPickup && !CS(player).cvar_cl_cts_noautoswitch)
{
for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
{
|| (def.instanceOfHealth && def != ITEM_HealthSmall)
|| (def.instanceOfArmor && def != ITEM_ArmorSmall)
|| (itemid & (IT_KEY1 | IT_KEY2))
- ) this.target = "###item###"; // for finding the nearest item using find()
+ ) this.target = "###item###"; // for finding the nearest item using findnearest
Item_ItemsTime_SetTime(this, 0);
}
.float item_respawncounter;
-void Item_Show (entity e, float mode);
+void Item_Show (entity e, int mode);
void Item_Respawn (entity this);
float ammo_pickupevalfunc(entity player, entity item);
float healtharmor_pickupevalfunc(entity player, entity item);
-.float is_item;
+.bool is_item;
.entity itemdef;
void _StartItem(entity this, entity def, float defaultrespawntime, float defaultrespawntimejitter);
#pragma once
+const int NUM_TEAMS = 4; ///< Number of teams in the game.
+
#ifdef TEAMNUMBERS_THAT_ARENT_STUPID
const int NUM_TEAM_1 = 1; // red
const int NUM_TEAM_2 = 2; // blue
const string STATIC_NAME_TEAM_4 = "Pink";
#ifdef CSQC
-float teamplay;
-float myteam;
+bool teamplay;
+int myteam;
#endif
-string Team_ColorCode(float teamid)
+string Team_ColorCode(int teamid)
{
switch(teamid)
{
return "^7";
}
-vector Team_ColorRGB(float teamid)
+vector Team_ColorRGB(int teamid)
{
switch(teamid)
{
return '0 0 0';
}
-string Team_ColorName(float teamid)
+string Team_ColorName(int teamid)
{
switch(teamid)
{
}
// used for replacement in filenames or such where the name CANNOT be allowed to be translated
-string Static_Team_ColorName(float teamid)
+string Static_Team_ColorName(int teamid)
{
switch(teamid)
{
return -1;
}
-/// \brief Returns whether team is valid.
-/// \param[in] team_ Team to check.
+/// \brief Returns whether team value is valid.
+/// \param[in] team_num Team to check.
/// \return True if team is valid, false otherwise.
-bool Team_IsValidTeam(int team_)
+bool Team_IsValidTeam(int team_num)
{
- switch (team_)
+ switch (team_num)
{
case NUM_TEAM_1:
case NUM_TEAM_2:
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)
+/// \brief Returns whether the team index is valid.
+/// \param[in] index Team index to check.
+/// \return True if team index is valid, false otherwise.
+bool Team_IsValidIndex(int index)
{
- switch (number)
+ switch (index)
{
case 1:
case 2:
return false;
}
-float Team_NumberToTeam(float number)
+/// \brief Converts team index into team value.
+/// \param[in] index Team index to convert.
+/// \return Team value.
+int Team_IndexToTeam(int index)
{
- switch(number)
+ switch (index)
{
case 1: return NUM_TEAM_1;
case 2: return NUM_TEAM_2;
case 3: return NUM_TEAM_3;
case 4: return NUM_TEAM_4;
}
-
return -1;
}
-float Team_TeamToNumber(float teamid)
+/// \brief Converts team value into team index.
+/// \param[in] team_num Team value to convert.
+/// \return Team index.
+int Team_TeamToIndex(int team_num)
{
- switch(teamid)
+ switch (team_num)
{
case NUM_TEAM_1: return 1;
case NUM_TEAM_2: return 2;
case NUM_TEAM_3: return 3;
case NUM_TEAM_4: return 4;
}
-
return -1;
}
+/// \brief Converts team value into bit value that is used in team bitmasks.
+/// \param[in] team_num Team value to convert.
+/// \return Team bit.
+int Team_TeamToBit(int team_num)
+{
+ if (!Team_IsValidTeam(team_num))
+ {
+ return 0;
+ }
+ return BIT(Team_TeamToIndex(team_num) - 1);
+}
+
+/// \brief Converts team index into bit value that is used in team bitmasks.
+/// \param[in] index Team index to convert.
+/// \return Team bit.
+int Team_IndexToBit(int index)
+{
+ return BIT(index - 1);
+}
+
// legacy aliases for shitty code
-#define TeamByColor(teamid) (Team_TeamToNumber(teamid) - 1)
-#define ColorByTeam(number) Team_NumberToTeam(number + 1)
+#define TeamByColor(teamid) (Team_TeamToIndex(teamid) - 1)
+#define ColorByTeam(number) Team_IndexToTeam(number + 1)
// useful aliases
#define Team_ColorName_Lower(teamid) strtolower(Team_ColorName(teamid))
#define Team_FullName(teamid) strcat(Team_ColorName(teamid), " ", NAME_TEAM, "^7")
#define Team_ColoredFullName(teamid) strcat(Team_ColorCode(teamid), Team_ColorName(teamid), " ", NAME_TEAM, "^7")
-#define Team_NumberToFullName(number) Team_FullName(Team_NumberToTeam(number))
-#define Team_NumberToColoredFullName(number) Team_ColoredFullName(Team_NumberToTeam(number))
+#define Team_IndexToFullName(index) Team_FullName(Team_IndexToTeam(index))
+#define Team_IndexToColoredFullName(index) Team_ColoredFullName(Team_IndexToTeam(index))
// replace these flags in a string with the strings provided
#define TCR(input,type,team) strreplace("^TC", COL_TEAM_##team, strreplace("^TT", strtoupper(type##_TEAM_##team), input))
}
o = drawspritearrow(o, M_PI, rgb, a, SPRITE_ARROW_SCALE * t);
- o = drawsprite_TextOrIcon(true, o, M_PI, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
+ if(autocvar_g_waypointsprite_turrets_text)
+ {
+ o = drawsprite_TextOrIcon(true, o, M_PI, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
+ }
drawhealthbar(
o,
0,
+ TFL_TARGETSELECT_LOS
+ TFL_TARGETSELECT_PLAYERS
+ TFL_TARGETSELECT_MISSILES
+ + TFL_TARGETSELECT_VEHICLES
- TFL_TARGETSELECT_TRIGGERTARGET
+ TFL_TARGETSELECT_ANGLELIMITS
+ TFL_TARGETSELECT_RANGELIMITS
if(!checkpvs(e_target.origin, e_turret))
return -1;
- if(e_target.alpha <= 0.3)
+ if(e_target.alpha != 0 && e_target.alpha <= 0.3)
return -1;
if(MUTATOR_CALLHOOK(TurretValidateTarget, e_turret, e_target, validate_flags))
return -5;
// Cant touch this
+ if (GetResourceAmount(e_target, RESOURCE_HEALTH) <= 0)
+ return -6;
+ else if (STAT(FROZEN, e_target))
+ return -6;
+
+ // vehicle
if(IS_VEHICLE(e_target))
{
- if (e_target.vehicle_health <= 0)
- return -6;
+ if ((validate_flags & TFL_TARGETSELECT_VEHICLES) && !e_target.owner)
+ return -7;
}
- else if (GetResourceAmount(e_target, RESOURCE_HEALTH) <= 0)
- return -6;
- else if(STAT(FROZEN, e_target) > 0)
- return -6;
// player
if (IS_CLIENT(e_target))
// target selection flags
.int target_select_flags;
.int target_validate_flags;
-const int TFL_TARGETSELECT_NO = 2; // don't automatically find targets
-const int TFL_TARGETSELECT_LOS = 4; // require line of sight to find targets
-const int TFL_TARGETSELECT_PLAYERS = 8; // target players
-const int TFL_TARGETSELECT_MISSILES = 16; // target projectiles
-const int TFL_TARGETSELECT_TRIGGERTARGET = 32; // respond to turret_trigger_target events
-const int TFL_TARGETSELECT_ANGLELIMITS = 64; // apply extra angular limits to target selection
-const int TFL_TARGETSELECT_RANGELIMITS = 128; // limit target selection range
-const int TFL_TARGETSELECT_TEAMCHECK = 256; // don't attack teammates
-const int TFL_TARGETSELECT_NOBUILTIN = 512; // only attack targets when triggered
-const int TFL_TARGETSELECT_OWNTEAM = 1024; // only attack teammates
-const int TFL_TARGETSELECT_NOTURRETS = 2048; // don't attack other turrets
-const int TFL_TARGETSELECT_FOV = 4096; // extra limits to attack range
-const int TFL_TARGETSELECT_MISSILESONLY = 8192; // only attack missiles
+const int TFL_TARGETSELECT_NO = BIT(1); // don't automatically find targets
+const int TFL_TARGETSELECT_LOS = BIT(2); // require line of sight to find targets
+const int TFL_TARGETSELECT_PLAYERS = BIT(3); // target players
+const int TFL_TARGETSELECT_MISSILES = BIT(4); // target projectiles
+const int TFL_TARGETSELECT_TRIGGERTARGET = BIT(5); // respond to turret_trigger_target events
+const int TFL_TARGETSELECT_ANGLELIMITS = BIT(6); // apply extra angular limits to target selection
+const int TFL_TARGETSELECT_RANGELIMITS = BIT(7); // limit target selection range
+const int TFL_TARGETSELECT_TEAMCHECK = BIT(8); // don't attack teammates
+const int TFL_TARGETSELECT_NOBUILTIN = BIT(9); // only attack targets when triggered
+const int TFL_TARGETSELECT_OWNTEAM = BIT(10); // only attack teammates
+const int TFL_TARGETSELECT_NOTURRETS = BIT(11); // don't attack other turrets
+const int TFL_TARGETSELECT_FOV = BIT(12); // extra limits to attack range
+const int TFL_TARGETSELECT_MISSILESONLY = BIT(13); // only attack missiles
+const int TFL_TARGETSELECT_VEHICLES = BIT(14); // target manned vehicles
// aim flags
.int aim_flags;
{
it.ammo_flags = TFL_AMMO_ROCKETS | TFL_AMMO_RECHARGE;
it.aim_flags = TFL_AIM_SIMPLE;
- it.target_select_flags = TFL_TARGETSELECT_LOS | TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_TRIGGERTARGET | TFL_TARGETSELECT_RANGELIMITS | TFL_TARGETSELECT_TEAMCHECK;
+ it.target_select_flags = TFL_TARGETSELECT_LOS | TFL_TARGETSELECT_VEHICLES | TFL_TARGETSELECT_TRIGGERTARGET | TFL_TARGETSELECT_RANGELIMITS | TFL_TARGETSELECT_TEAMCHECK;
it.firecheck_flags = TFL_FIRECHECK_DEAD | TFL_FIRECHECK_TEAMCHECK | TFL_FIRECHECK_REFIRE | TFL_FIRECHECK_AFF;
it.shoot_flags = TFL_SHOOT_CLEARTARGET;
- it.target_validate_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_TEAMCHECK;
+ it.target_validate_flags = TFL_TARGETSELECT_VEHICLES | TFL_TARGETSELECT_TEAMCHECK;
it.turret_addtarget = turret_hk_addtarget;
}
actor.tur_head = actor;
weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, 0, w_ready);
}
- fireBullet (actor, weaponentity, actor.tur_shotorg, actor.tur_shotdir_updated, actor.shot_spread, 0, actor.shot_dmg, actor.shot_force, DEATH_TURRET_MACHINEGUN.m_id, 0);
+ fireBullet(actor, weaponentity, actor.tur_shotorg, actor.tur_shotdir_updated, actor.shot_spread, 0, actor.shot_dmg, actor.shot_force, DEATH_TURRET_MACHINEGUN.m_id, EFFECT_BULLET);
W_MachineGun_MuzzleFlash(actor, weaponentity);
setattachment(actor.(weaponentity).muzzle_flash, actor.tur_head, "tag_fire");
}
weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(electro, animtime), w_ready);
}
sound (actor, CH_WEAPON_A, SND_UZI_FIRE, VOL_BASE, ATTEN_NORM);
- fireBullet (actor, weaponentity, actor.tur_shotorg, actor.tur_shotdir_updated, actor.shot_spread, 0, actor.shot_dmg, actor.shot_force, DEATH_TURRET_WALK_GUN.m_id, 0);
+ fireBullet(actor, weaponentity, actor.tur_shotorg, actor.tur_shotdir_updated, actor.shot_spread, 0, actor.shot_dmg, actor.shot_force, DEATH_TURRET_WALK_GUN.m_id, EFFECT_BULLET);
Send_Effect(EFFECT_BLASTER_MUZZLEFLASH, actor.tur_shotorg, actor.tur_shotdir_updated * 1000, 1);
}
}
if(s == t)
{
r = d;
+ break; // if we found a killing case, apply it! other settings may be allowed in the future, but this one is caught
}
}
return r;
vector vehicle_aimturret(entity _vehic, vector _target, entity _turrret, string _tagname,
float _pichlimit_min, float _pichlimit_max,
- float _rotlimit_min, float _rotlimit_max, float _aimspeed)
+ float _rotlimit_min, float _rotlimit_max, float _aimspeed, float dt)
{
vector vtmp, vtag;
float ftmp;
vtmp = vectoangles(normalize(_target - vtag));
vtmp = AnglesTransform_ToAngles(AnglesTransform_LeftDivide(AnglesTransform_FromAngles(_vehic.angles), AnglesTransform_FromAngles(vtmp))) - _turrret.angles;
vtmp = AnglesTransform_Normalize(vtmp, true);
- ftmp = _aimspeed * frametime;
+ ftmp = _aimspeed * dt;
vtmp_y = bound(-ftmp, vtmp_y, ftmp);
vtmp_x = bound(-ftmp, vtmp_x, ftmp);
_turrret.angles_y = bound(_rotlimit_min, _turrret.angles_y + vtmp_y, _rotlimit_max);
if(timer + rpause < time)
{
if(_healthscale)
- regen = regen * (this.vehicle_health / this.max_health);
+ regen = regen * (GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health);
this.(regen_field) = min(this.(regen_field) + regen * delta_time, field_max);
}
}
+void vehicles_regen_resource(entity this, float timer, .float regen_field, float field_max, float rpause, float regen, float delta_time, float _healthscale, int resource)
+{
+ float resource_amount = GetResourceAmount(this, resource);
+
+ if(resource_amount < field_max)
+ if(timer + rpause < time)
+ {
+ if(_healthscale)
+ regen = regen * (resource_amount / this.max_health);
+
+ SetResourceAmount(this, resource, min(resource_amount + regen * delta_time, field_max));
+
+ if(this.owner)
+ this.owner.(regen_field) = (GetResourceAmount(this, resource) / field_max) * 100;
+ }
+}
+
void shieldhit_think(entity this)
{
this.alpha -= 0.1;
void vehicles_painframe(entity this)
{
- int myhealth = ((this.owner) ? this.owner.vehicle_health : ((this.vehicle_health / this.max_health) * 100));
+ int myhealth = ((this.owner) ? this.owner.vehicle_health : ((GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health) * 100));
if(myhealth <= 50)
if(this.pain_frame < time)
if(this.vehicle_shield < 0)
{
- this.vehicle_health -= fabs(this.vehicle_shield);
+ TakeResource(this, RESOURCE_HEALTH, fabs(this.vehicle_shield));
this.vehicle_shieldent.colormod = '2 0 0';
this.vehicle_shield = 0;
this.vehicle_shieldent.alpha = 0.75;
}
else
{
- this.vehicle_health -= damage;
+ TakeResource(this, RESOURCE_HEALTH, damage);
if(sound_allowed(MSG_BROADCAST, attacker))
spamsound (this, CH_PAIN, SND_ONS_HIT2, VOL_BASE, ATTEN_NORM); // FIXME: PLACEHOLDER
else
this.velocity += force;
- if(this.vehicle_health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
{
if(this.owner)
if(this.vehicle_flags & VHF_DEATHEJECT)
bool vehicles_heal(entity targ, entity inflictor, float amount, float limit)
{
float true_limit = ((limit != RESOURCE_LIMIT_NONE) ? limit : targ.max_health);
- //if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0 || GetResourceAmount(targ, RESOURCE_HEALTH) >= true_limit)
- if(targ.vehicle_health <= 0 || targ.vehicle_health >= true_limit)
+ if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0 || GetResourceAmount(targ, RESOURCE_HEALTH) >= true_limit)
return false;
- targ.vehicle_health = min(targ.vehicle_health + amount, true_limit);
- //GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, true_limit);
- //if(targ.owner)
- //targ.owner.vehicle_health = (targ.vehicle_health / targ.max_health) * 100;
+ GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, true_limit);
+ if(targ.owner)
+ targ.owner.vehicle_health = (GetResourceAmount(targ, RESOURCE_HEALTH) / targ.max_health) * 100;
return true;
}
void vehicles_enter(entity pl, entity veh)
{
- // Remove this when bots know how to use vehicles
+ // Remove this when bots know how to use vehicles
if((IS_BOT_CLIENT(pl) && !autocvar_g_vehicles_allow_bots))
return;
.entity gunner1;
.entity gunner2;
-.float vehicle_health = _STAT(VEHICLESTAT_HEALTH); /// If ent is player this is 0..100 indicating precentage of health left on vehicle. If ent is vehicle, this is the real health value.
+.float vehicle_health = _STAT(VEHICLESTAT_HEALTH); /// If ent is player this is 0..100 indicating precentage of health left on vehicle. Vehicle's value is the health resource
.float vehicle_energy = _STAT(VEHICLESTAT_ENERGY); /// If ent is player this is 0..100 indicating precentage of energy left on vehicle. If ent is vehicle, this is the real energy value.
.float vehicle_shield = _STAT(VEHICLESTAT_SHIELD); /// If ent is player this is 0..100 indicating precentage of shield left on vehicle. If ent is vehicle, this is the real shield value.
#define VEHICLE_UPDATE_PLAYER(ply,vehi,fld,vhname) \
ply.vehicle_##fld = (vehi.vehicle_##fld / autocvar_g_vehicle_##vhname##_##fld) * 100
+#define VEHICLE_UPDATE_PLAYER_RESOURCE(ply,vehi,fld,vhname,res) \
+ ply.vehicle_##fld = (GetResourceAmount(vehi, res) / autocvar_g_vehicle_##vhname##_##fld) * 100
+
.float vehicle_enter_delay; // prevent players jumping to and from vehicles instantly
void vehicles_exit(entity vehic, int eject);
float autocvar_g_vehicle_bumblebee_cannon_ammo_regen = 100;
float autocvar_g_vehicle_bumblebee_cannon_ammo_regen_pause = 1;
-float autocvar_g_vehicle_bumblebee_cannon_lock = 0;
+float autocvar_g_vehicle_bumblebee_cannon_lock = 1;
-float autocvar_g_vehicle_bumblebee_cannon_turnspeed = 160;
+float autocvar_g_vehicle_bumblebee_cannon_turnspeed = 260;
float autocvar_g_vehicle_bumblebee_cannon_pitchlimit_down = 60;
float autocvar_g_vehicle_bumblebee_cannon_pitchlimit_up = 60;
float autocvar_g_vehicle_bumblebee_cannon_turnlimit_in = 20;
if(autocvar_g_vehicle_bumblebee_cannon_lock)
{
- if(gun.lock_time < time)
+ if(gun.lock_time < time || IS_DEAD(gun.enemy) || STAT(FROZEN, gun.enemy))
gun.enemy = NULL;
if(trace_ent)
- if(trace_ent.move_movetype)
- if(trace_ent.takedamage)
- if(!IS_DEAD(trace_ent) && !STAT(FROZEN, trace_ent))
- {
- if(DIFF_TEAM(trace_ent, this))
- {
- gun.enemy = trace_ent;
- gun.lock_time = time + 5;
- }
- }
+ if(trace_ent.move_movetype)
+ if(trace_ent.takedamage)
+ if(!IS_DEAD(trace_ent) && !STAT(FROZEN, trace_ent))
+ {
+ if(teamplay)
+ {
+ if(DIFF_TEAM(trace_ent, this))
+ {
+ gun.enemy = trace_ent;
+ gun.lock_time = time + 2.5;
+ }
+ }
+ else
+ {
+ gun.enemy = trace_ent;
+ gun.lock_time = time + 0.5;
+ }
+ }
}
if(gun.enemy)
UpdateAuxiliaryXhair(this, ad, '1 0 1', 1);
vehicle_aimturret(vehic, trace_endpos, gun, "fire",
autocvar_g_vehicle_bumblebee_cannon_pitchlimit_down * -1, autocvar_g_vehicle_bumblebee_cannon_pitchlimit_up,
- _out * -1, _in, autocvar_g_vehicle_bumblebee_cannon_turnspeed);
+ _out * -1, _in, autocvar_g_vehicle_bumblebee_cannon_turnspeed, dt);
}
else
vehicle_aimturret(vehic, _ct, gun, "fire",
autocvar_g_vehicle_bumblebee_cannon_pitchlimit_down * -1, autocvar_g_vehicle_bumblebee_cannon_pitchlimit_up,
- _out * -1, _in, autocvar_g_vehicle_bumblebee_cannon_turnspeed);
+ _out * -1, _in, autocvar_g_vehicle_bumblebee_cannon_turnspeed, dt);
if(!forbidWeaponUse(this))
if(PHYS_INPUT_BUTTON_ATCK(this))
gun.attack_finished_single[0] = time + autocvar_g_vehicle_bumblebee_cannon_refire;
}
- VEHICLE_UPDATE_PLAYER(this, vehic, health, bumblebee);
+ VEHICLE_UPDATE_PLAYER_RESOURCE(this, vehic, health, bumblebee, RESOURCE_HEALTH);
if(vehic.vehicle_flags & VHF_HASSHIELD)
VEHICLE_UPDATE_PLAYER(this, vehic, shield, bumblebee);
vehicles_regen(this, this.dmg_time, vehicle_shield, autocvar_g_vehicle_bumblebee_shield, autocvar_g_vehicle_bumblebee_shield_regen_pause, autocvar_g_vehicle_bumblebee_shield_regen, dt, true);
if(this.vehicle_flags & VHF_HEALTHREGEN)
- vehicles_regen(this, this.dmg_time, vehicle_health, autocvar_g_vehicle_bumblebee_health, autocvar_g_vehicle_bumblebee_health_regen_pause, autocvar_g_vehicle_bumblebee_health_regen, dt, false);
+ vehicles_regen_resource(this, this.dmg_time, vehicle_health, autocvar_g_vehicle_bumblebee_health, autocvar_g_vehicle_bumblebee_health_regen_pause, autocvar_g_vehicle_bumblebee_health_regen, dt, false, RESOURCE_HEALTH);
if(this.vehicle_flags & VHF_ENERGYREGEN)
vehicles_regen(this, this.wait, vehicle_energy, autocvar_g_vehicle_bumblebee_energy, autocvar_g_vehicle_bumblebee_energy_regen_pause, autocvar_g_vehicle_bumblebee_energy_regen, dt, false);
vang = vehicle_aimturret(vehic, trace_endpos, vehic.gun3, "fire",
autocvar_g_vehicle_bumblebee_raygun_pitchlimit_down * -1, autocvar_g_vehicle_bumblebee_raygun_pitchlimit_up,
- autocvar_g_vehicle_bumblebee_raygun_turnlimit_sides * -1, autocvar_g_vehicle_bumblebee_raygun_turnlimit_sides, autocvar_g_vehicle_bumblebee_raygun_turnspeed);
+ autocvar_g_vehicle_bumblebee_raygun_turnlimit_sides * -1, autocvar_g_vehicle_bumblebee_raygun_turnlimit_sides, autocvar_g_vehicle_bumblebee_raygun_turnspeed, dt);
if(!forbidWeaponUse(this))
if((PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_ATCK2(this)) && (vehic.vehicle_energy > autocvar_g_vehicle_bumblebee_raygun_dps * PHYS_INPUT_FRAMETIME || autocvar_g_vehicle_bumblebee_raygun == 0))
if(IS_VEHICLE(trace_ent))
{
- if(autocvar_g_vehicle_bumblebee_healgun_sps && trace_ent.vehicle_health <= trace_ent.max_health)
+ if(autocvar_g_vehicle_bumblebee_healgun_sps && GetResourceAmount(trace_ent, RESOURCE_HEALTH) <= trace_ent.max_health)
trace_ent.vehicle_shield = min(trace_ent.vehicle_shield + autocvar_g_vehicle_bumblebee_healgun_sps * dt, trace_ent.tur_head.max_health);
}
else if(IS_CLIENT(trace_ent))
}
*/
- VEHICLE_UPDATE_PLAYER(this, vehic, health, bumblebee);
+ VEHICLE_UPDATE_PLAYER_RESOURCE(this, vehic, health, bumblebee, RESOURCE_HEALTH);
VEHICLE_UPDATE_PLAYER(this, vehic, energy, bumblebee);
this.vehicle_ammo1 = (vehic.gun1.vehicle_energy / autocvar_g_vehicle_bumblebee_cannon_ammo) * 100;
if(!autocvar_g_vehicle_bumblebee_swim)
instance.dphitcontentsmask |= DPCONTENTS_LIQUIDSMASK;
- instance.vehicle_health = autocvar_g_vehicle_bumblebee_health;
+ SetResourceAmountExplicit(instance, RESOURCE_HEALTH, autocvar_g_vehicle_bumblebee_health);
instance.vehicle_shield = autocvar_g_vehicle_bumblebee_shield;
instance.solid = SOLID_BBOX;
set_movetype(instance, MOVETYPE_TOSS);
instance.vehicle_exit = bumblebee_exit;
instance.respawntime = autocvar_g_vehicle_bumblebee_respawntime;
- instance.vehicle_health = autocvar_g_vehicle_bumblebee_health;
- instance.max_health = instance.vehicle_health;
+ SetResourceAmountExplicit(instance, RESOURCE_HEALTH, autocvar_g_vehicle_bumblebee_health);
+ instance.max_health = GetResourceAmount(instance, RESOURCE_HEALTH);
instance.vehicle_shield = autocvar_g_vehicle_bumblebee_shield;
}
float hudAlpha = autocvar_hud_panel_fg_alpha;
float blinkValue = 0.55 + sin(time * 7) * 0.45;
vector tmpPos = '0 0 0';
- vector tmpSize = '1 1 1' * hud_fontsize;
+ vector tmpSize = hud_fontsize;
tmpPos.x = vehicleHud_Pos.x + vehicleHud_Size.x * (520/768);
if(!AuxiliaryXhair[1].draw2d)
float autocvar_g_vehicle_bumblebee_cannon_radius = 225;
float autocvar_g_vehicle_bumblebee_cannon_refire = 0.2;
float autocvar_g_vehicle_bumblebee_cannon_speed = 20000;
-float autocvar_g_vehicle_bumblebee_cannon_spread = 0.02;
+float autocvar_g_vehicle_bumblebee_cannon_spread = 0;
float autocvar_g_vehicle_bumblebee_cannon_force = -35;
#endif
vehicles_regen(vehic, vehic.dmg_time, vehicle_shield, autocvar_g_vehicle_racer_shield, autocvar_g_vehicle_racer_shield_regen_pause, autocvar_g_vehicle_racer_shield_regen, dt, true);
if(vehic.vehicle_flags & VHF_HEALTHREGEN)
- vehicles_regen(vehic, vehic.dmg_time, vehicle_health, autocvar_g_vehicle_racer_health, autocvar_g_vehicle_racer_health_regen_pause, autocvar_g_vehicle_racer_health_regen, dt, false);
+ vehicles_regen_resource(vehic, vehic.dmg_time, vehicle_health, autocvar_g_vehicle_racer_health, autocvar_g_vehicle_racer_health_regen_pause, autocvar_g_vehicle_racer_health_regen, dt, false, RESOURCE_HEALTH);
if(vehic.vehicle_flags & VHF_ENERGYREGEN)
vehicles_regen(vehic, vehic.wait, vehicle_energy, autocvar_g_vehicle_racer_energy, autocvar_g_vehicle_racer_energy_regen_pause, autocvar_g_vehicle_racer_energy_regen, dt, false);
- VEHICLE_UPDATE_PLAYER(player, vehic, health, racer);
+ VEHICLE_UPDATE_PLAYER_RESOURCE(player, vehic, health, racer, RESOURCE_HEALTH);
VEHICLE_UPDATE_PLAYER(player, vehic, energy, racer);
if(vehic.vehicle_flags & VHF_HASSHIELD)
{
#ifdef SVQC
set_movetype(instance, MOVETYPE_BOUNCE);
- instance.owner.vehicle_health = (instance.vehicle_health / autocvar_g_vehicle_racer_health) * 100;
+ instance.owner.vehicle_health = (GetResourceAmount(instance, RESOURCE_HEALTH) / autocvar_g_vehicle_racer_health) * 100;
instance.owner.vehicle_shield = (instance.vehicle_shield / autocvar_g_vehicle_racer_shield) * 100;
if(instance.owner.flagcarried)
setthink(instance, racer_think);
instance.nextthink = time;
- instance.vehicle_health = autocvar_g_vehicle_racer_health;
+ SetResourceAmountExplicit(instance, RESOURCE_HEALTH, autocvar_g_vehicle_racer_health);
instance.vehicle_shield = autocvar_g_vehicle_racer_shield;
set_movetype(instance, MOVETYPE_TOSS);
instance.bouncefactor = autocvar_g_vehicle_racer_bouncefactor;
instance.bouncestop = autocvar_g_vehicle_racer_bouncestop;
instance.damageforcescale = 0.5;
- instance.vehicle_health = autocvar_g_vehicle_racer_health;
+ SetResourceAmountExplicit(instance, RESOURCE_HEALTH, autocvar_g_vehicle_racer_health);
instance.vehicle_shield = autocvar_g_vehicle_racer_shield;
#endif
}
instance.vehicle_flags |= VHF_HEALTHREGEN;
instance.respawntime = autocvar_g_vehicle_racer_respawntime;
- instance.vehicle_health = autocvar_g_vehicle_racer_health;
+ SetResourceAmountExplicit(instance, RESOURCE_HEALTH, autocvar_g_vehicle_racer_health);
instance.vehicle_shield = autocvar_g_vehicle_racer_shield;
- instance.max_health = instance.vehicle_health;
+ instance.max_health = GetResourceAmount(instance, RESOURCE_HEALTH);
#endif
#ifdef CSQC
vehicle_aimturret(vehic, trace_endpos, vehic.gun1, "fire1",
autocvar_g_vehicle_raptor_cannon_pitchlimit_down * -1, autocvar_g_vehicle_raptor_cannon_pitchlimit_up,
- autocvar_g_vehicle_raptor_cannon_turnlimit * -1, autocvar_g_vehicle_raptor_cannon_turnlimit, autocvar_g_vehicle_raptor_cannon_turnspeed);
+ autocvar_g_vehicle_raptor_cannon_turnlimit * -1, autocvar_g_vehicle_raptor_cannon_turnlimit, autocvar_g_vehicle_raptor_cannon_turnspeed, dt);
vehicle_aimturret(vehic, trace_endpos, vehic.gun2, "fire1",
autocvar_g_vehicle_raptor_cannon_pitchlimit_down * -1, autocvar_g_vehicle_raptor_cannon_pitchlimit_up,
- autocvar_g_vehicle_raptor_cannon_turnlimit * -1, autocvar_g_vehicle_raptor_cannon_turnlimit, autocvar_g_vehicle_raptor_cannon_turnspeed);
+ autocvar_g_vehicle_raptor_cannon_turnlimit * -1, autocvar_g_vehicle_raptor_cannon_turnlimit, autocvar_g_vehicle_raptor_cannon_turnspeed, dt);
/*
ad = ad * 0.5;
vehicles_regen(vehic, vehic.dmg_time, vehicle_shield, autocvar_g_vehicle_raptor_shield, autocvar_g_vehicle_raptor_shield_regen_pause, autocvar_g_vehicle_raptor_shield_regen, dt, true);
if(vehic.vehicle_flags & VHF_HEALTHREGEN)
- vehicles_regen(vehic, vehic.dmg_time, vehicle_health, autocvar_g_vehicle_raptor_health, autocvar_g_vehicle_raptor_health_regen_pause, autocvar_g_vehicle_raptor_health_regen, dt, false);
+ vehicles_regen_resource(vehic, vehic.dmg_time, vehicle_health, autocvar_g_vehicle_raptor_health, autocvar_g_vehicle_raptor_health_regen_pause, autocvar_g_vehicle_raptor_health_regen, dt, false, RESOURCE_HEALTH);
if(vehic.vehicle_flags & VHF_ENERGYREGEN)
vehicles_regen(vehic, vehic.cnt, vehicle_energy, autocvar_g_vehicle_raptor_energy, autocvar_g_vehicle_raptor_energy_regen_pause, autocvar_g_vehicle_raptor_energy_regen, dt, false);
}
- VEHICLE_UPDATE_PLAYER(this, vehic, health, raptor);
+ VEHICLE_UPDATE_PLAYER_RESOURCE(this, vehic, health, raptor, RESOURCE_HEALTH);
VEHICLE_UPDATE_PLAYER(this, vehic, energy, raptor);
if(vehic.vehicle_flags & VHF_HASSHIELD)
VEHICLE_UPDATE_PLAYER(this, vehic, shield, raptor);
vehicles_regen(vehic, vehic.dmg_time, vehicle_shield, autocvar_g_vehicle_raptor_shield, autocvar_g_vehicle_raptor_shield_regen_pause, autocvar_g_vehicle_raptor_shield_regen, dt, true);
if(vehic.vehicle_flags & VHF_HEALTHREGEN)
- vehicles_regen(vehic, vehic.dmg_time, vehicle_health, autocvar_g_vehicle_raptor_health, autocvar_g_vehicle_raptor_health_regen_pause, autocvar_g_vehicle_raptor_health_regen, dt, false);
+ vehicles_regen_resource(vehic, vehic.dmg_time, vehicle_health, autocvar_g_vehicle_raptor_health, autocvar_g_vehicle_raptor_health_regen_pause, autocvar_g_vehicle_raptor_health_regen, dt, false, RESOURCE_HEALTH);
if(vehic.vehicle_flags & VHF_ENERGYREGEN)
vehicles_regen(vehic, vehic.cnt, vehicle_energy, autocvar_g_vehicle_raptor_energy, autocvar_g_vehicle_raptor_energy_regen_pause, autocvar_g_vehicle_raptor_energy_regen, dt, false);
this.vehicle_reload2 = bound(0, vehic.bomb1.alpha * 100, 100);
this.vehicle_ammo2 = (this.vehicle_reload2 == 100) ? 100 : 0;
- VEHICLE_UPDATE_PLAYER(this, vehic, health, raptor);
+ VEHICLE_UPDATE_PLAYER_RESOURCE(this, vehic, health, raptor, RESOURCE_HEALTH);
VEHICLE_UPDATE_PLAYER(this, vehic, energy, raptor);
if(vehic.vehicle_flags & VHF_HASSHIELD)
VEHICLE_UPDATE_PLAYER(this, vehic, shield, raptor);
instance.owner.PlayerPhysplug = raptor_takeoff;
set_movetype(instance, MOVETYPE_BOUNCEMISSILE);
instance.solid = SOLID_SLIDEBOX;
- instance.owner.vehicle_health = (instance.vehicle_health / autocvar_g_vehicle_raptor_health) * 100;
+ instance.owner.vehicle_health = (GetResourceAmount(instance, RESOURCE_HEALTH) / autocvar_g_vehicle_raptor_health) * 100;
instance.owner.vehicle_shield = (instance.vehicle_shield / autocvar_g_vehicle_raptor_shield) * 100;
instance.velocity = '0 0 1'; // nudge upwards so takeoff sequence can work
instance.tur_head.exteriormodeltoclient = instance.owner;
}
instance.frame = 0;
- instance.vehicle_health = autocvar_g_vehicle_raptor_health;
+ SetResourceAmountExplicit(instance, RESOURCE_HEALTH, autocvar_g_vehicle_raptor_health);
instance.vehicle_shield = autocvar_g_vehicle_raptor_shield;
set_movetype(instance, MOVETYPE_TOSS);
instance.solid = SOLID_SLIDEBOX;
instance.bouncefactor = autocvar_g_vehicle_raptor_bouncefactor;
instance.bouncestop = autocvar_g_vehicle_raptor_bouncestop;
instance.damageforcescale = 0.25;
- instance.vehicle_health = autocvar_g_vehicle_raptor_health;
+ SetResourceAmountExplicit(instance, RESOURCE_HEALTH, autocvar_g_vehicle_raptor_health);
instance.vehicle_shield = autocvar_g_vehicle_raptor_shield;
}
METHOD(Raptor, vr_setup, void(Raptor thisveh, entity instance))
instance.vehicle_exit = raptor_exit;
instance.respawntime = autocvar_g_vehicle_raptor_respawntime;
- instance.vehicle_health = autocvar_g_vehicle_raptor_health;
+ SetResourceAmountExplicit(instance, RESOURCE_HEALTH, autocvar_g_vehicle_raptor_health);
instance.vehicle_shield = autocvar_g_vehicle_raptor_shield;
- instance.max_health = instance.vehicle_health;
+ instance.max_health = GetResourceAmount(instance, RESOURCE_HEALTH);
if(!autocvar_g_vehicle_raptor_swim)
instance.dphitcontentsmask |= DPCONTENTS_LIQUIDSMASK;
.entity weaponentity = weaponentities[0]; // TODO: unhardcode
fireBullet(this, weaponentity, v, v_forward, autocvar_g_vehicle_spiderbot_minigun_spread, autocvar_g_vehicle_spiderbot_minigun_solidpenetration,
- autocvar_g_vehicle_spiderbot_minigun_damage, autocvar_g_vehicle_spiderbot_minigun_force, DEATH_VH_SPID_MINIGUN.m_id, 0);
+ autocvar_g_vehicle_spiderbot_minigun_damage, autocvar_g_vehicle_spiderbot_minigun_force, DEATH_VH_SPID_MINIGUN.m_id, EFFECT_BULLET);
sound (gun, CH_WEAPON_A, SND_UZI_FIRE, VOL_BASE, ATTEN_NORM);
//trailparticles(this, _particleeffectnum("spiderbot_minigun_trail"), v, trace_endpos);
vehicles_regen(vehic, vehic.dmg_time, vehicle_shield, autocvar_g_vehicle_spiderbot_shield, autocvar_g_vehicle_spiderbot_shield_regen_pause, autocvar_g_vehicle_spiderbot_shield_regen, dt, true);
if(vehic.vehicle_flags & VHF_HEALTHREGEN)
- vehicles_regen(vehic, vehic.dmg_time, vehicle_health, autocvar_g_vehicle_spiderbot_health, autocvar_g_vehicle_spiderbot_health_regen_pause, autocvar_g_vehicle_spiderbot_health_regen, dt, false);
+ vehicles_regen_resource(vehic, vehic.dmg_time, vehicle_health, autocvar_g_vehicle_spiderbot_health, autocvar_g_vehicle_spiderbot_health_regen_pause, autocvar_g_vehicle_spiderbot_health_regen, dt, false, RESOURCE_HEALTH);
PHYS_INPUT_BUTTON_ATCK(this) = PHYS_INPUT_BUTTON_ATCK2(this) = false;
//this.vehicle_ammo2 = vehic.tur_head.frame;
this.oldorigin = this.origin; // negate fall damage
this.velocity = vehic.velocity;
- VEHICLE_UPDATE_PLAYER(this, vehic, health, spiderbot);
+ VEHICLE_UPDATE_PLAYER_RESOURCE(this, vehic, health, spiderbot, RESOURCE_HEALTH);
if(vehic.vehicle_flags & VHF_HASSHIELD)
VEHICLE_UPDATE_PLAYER(this, vehic, shield, spiderbot);
STAT(VEHICLESTAT_W2MODE, instance) = SBRM_GUIDE;
set_movetype(instance, MOVETYPE_WALK);
CSQCVehicleSetup(instance.owner, 0);
- instance.owner.vehicle_health = (instance.vehicle_health / autocvar_g_vehicle_spiderbot_health) * 100;
+ instance.owner.vehicle_health = (GetResourceAmount(instance, RESOURCE_HEALTH) / autocvar_g_vehicle_spiderbot_health) * 100;
instance.owner.vehicle_shield = (instance.vehicle_shield / autocvar_g_vehicle_spiderbot_shield) * 100;
if(instance.owner.flagcarried)
setorigin(instance, instance.pos1 + '0 0 128');
instance.angles = instance.pos2;
instance.damageforcescale = 0.03;
- instance.vehicle_health = autocvar_g_vehicle_spiderbot_health;
+ SetResourceAmountExplicit(instance, RESOURCE_HEALTH, autocvar_g_vehicle_spiderbot_health);
instance.vehicle_shield = autocvar_g_vehicle_spiderbot_shield;
instance.PlayerPhysplug = spiderbot_frame;
instance.vehicle_flags |= VHF_HEALTHREGEN;
instance.respawntime = autocvar_g_vehicle_spiderbot_respawntime;
- instance.vehicle_health = autocvar_g_vehicle_spiderbot_health;
+ SetResourceAmountExplicit(instance, RESOURCE_HEALTH, autocvar_g_vehicle_spiderbot_health);
instance.vehicle_shield = autocvar_g_vehicle_spiderbot_shield;
- instance.max_health = instance.vehicle_health;
+ instance.max_health = GetResourceAmount(instance, RESOURCE_HEALTH);
instance.pushable = true; // spiderbot can use jumppads
}
if(PHYS_CS(this).movement_x > 0) // right
this.angles_y = forward.y;
}
-
+ #if 0
//if(!PHYS_INPUT_BUTTON_CROUCH(this) && !IS_DUCKED(this))
if(!(this.viewloc.spawnflags & VIEWLOC_FREEMOVE))
{
//else { input_buttons &= ~16; this.flags &= ~FL_DUCKED; }
#endif
}
+ #endif
}
}
Weapon Weapons_fromstr(string s)
{
FOREACH(Weapons, it != WEP_Null && it.netname == s, return it);
- return NULL;
+ return WEP_Null;
}
void Dump_Weapon_Settings()
{
int totalweapons = 0, totalsettings = 0;
+ int wepcount = 1;
FOREACH(Weapons, it != WEP_Null, {
+ if((it.spawnflags & WEP_FLAG_HIDDEN) && (it.spawnflags & WEP_FLAG_MUTATORBLOCKED) && !(it.spawnflags & WEP_FLAG_NORMAL))
+ continue; // never include the attacks
// step 1: clear the queue
WEP_CONFIG_COUNT = 0;
for (int x = 0; x <= MAX_CONFIG_SETTINGS; ++x)
// step 4: write queue
WEP_CONFIG_WRITETOFILE(sprintf(
"// {{{ #%d: %s%s\n",
- i,
+ wepcount,
it.m_name,
((it.spawnflags & WEP_FLAG_MUTATORBLOCKED) ? " (MUTATOR WEAPON)" : "")
));
LOG_INFOF("#%d: %s: %d settings...", i, it.m_name, WEP_CONFIG_COUNT);
totalweapons += 1;
totalsettings += WEP_CONFIG_COUNT;
+ wepcount += 1;
});
// clear queue now that we're finished
METHOD(WeaponPickup, giveTo, bool(entity this, entity item, entity player))
{
bool b = Item_GiveTo(item, player);
- if (b) {
- LOG_TRACEF("entity %i picked up %s", player, this.m_name);
- }
+ //if (b) {
+ //LOG_TRACEF("entity %i picked up %s", player, this.m_name);
+ //}
return b;
}
#endif
{
// note: this doesn't force the switch
W_SwitchToOtherWeapon(own, weaponentity);
- own.(weaponentity).arc_BUTTON_ATCK_prev = false; // hax
}
+ own.(weaponentity).arc_BUTTON_ATCK_prev = false; // allow switching weapons
delete(this);
return;
}
W_SetupShot_Range(
own,
- weaponentity, // TODO
+ weaponentity,
true,
0,
SND_Null,
0,
WEP_CVAR(arc, beam_damage) * coefficient,
WEP_CVAR(arc, beam_range),
- WEP_ARC.m_id
+ thiswep.m_id
);
// After teleport, "lock" the beam until the teleport is confirmed.
(1 - (WEP_CVAR(arc, beam_returnspeed) * frametime)),
min(WEP_CVAR(arc, beam_maxangle) / angle, 1)
);
- this.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (this.beam_dir * blendfactor));
+ if(vdist(this.beam_dir - w_shotdir, <, 0.01))
+ this.beam_dir = w_shotdir;
+ else
+ this.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (this.beam_dir * blendfactor));
}
else
{
(1 - (WEP_CVAR(arc, beam_returnspeed) * frametime)),
1
);
- this.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (this.beam_dir * blendfactor));
+ if(vdist(this.beam_dir - w_shotdir, <, 0.01))
+ this.beam_dir = w_shotdir;
+ else
+ this.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (this.beam_dir * blendfactor));
}
// network information: beam direction
{
accuracy_add(
own,
- WEP_ARC.m_id,
+ WEP_ARC,
0,
rootdamage * coefficient * falloff
);
getthink(beam)(beam);
}
-void Arc_Smoke(entity actor, .entity weaponentity)
+void W_Arc_Attack(Weapon thiswep, entity actor, .entity weaponentity, int fire)
+{
+ if(!actor.(weaponentity).arc_beam || wasfreed(actor.(weaponentity).arc_beam))
+ {
+ w_ready(thiswep, actor, weaponentity, fire);
+ return;
+ }
+
+ // attack handled by the beam itself, this is just a loop to keep the attack happening!
+
+ // NOTE: arc doesn't use a refire
+ //ATTACK_FINISHED(actor, weaponentity) = time + WEP_CVAR_PRI(arc, refire) * W_WeaponRateFactor(actor);
+ actor.(weaponentity).wframe = WFRAME_FIRE1;
+ weapon_thinkf(actor, weaponentity, WFRAME_DONTCHANGE, WEP_CVAR(arc, beam_animtime), W_Arc_Attack);
+}
+void Arc_Smoke(Weapon thiswep, entity actor, .entity weaponentity, int fire)
{
+ // TODO: spamming this without checking any refires is asking for trouble!
makevectors(actor.v_angle);
- W_SetupShot_Range(actor,weaponentity,true,0,SND_Null,0,0,0,WEP_ARC.m_id); // TODO: probably doesn't need deathtype, since this is just a prefire effect
+ W_SetupShot_Range(actor,weaponentity,false,0,SND_Null,0,0,0,thiswep.m_id); // TODO: probably doesn't need deathtype, since this is just a prefire effect
vector smoke_origin = w_shotorg + actor.velocity*frametime;
if ( actor.arc_overheat > time )
{
if ( random() < actor.(weaponentity).arc_heat_percent )
Send_Effect(EFFECT_ARC_SMOKE, smoke_origin, '0 0 0', 1 );
- if ( PHYS_INPUT_BUTTON_ATCK(actor) || PHYS_INPUT_BUTTON_ATCK2(actor) )
+ if ( (fire & 1) || (fire & 2) )
{
Send_Effect(EFFECT_ARC_OVERHEAT_FIRE, smoke_origin, w_shotdir, 1 );
if ( !actor.arc_smoke_sound )
}
if ( actor.arc_smoke_sound && ( actor.arc_overheat <= time ||
- !( PHYS_INPUT_BUTTON_ATCK(actor) || PHYS_INPUT_BUTTON_ATCK2(actor) ) ) || actor.(weaponentity).m_switchweapon != WEP_ARC )
+ !( PHYS_INPUT_BUTTON_ATCK(actor) || PHYS_INPUT_BUTTON_ATCK2(actor) ) ) || actor.(weaponentity).m_switchweapon != thiswep )
{
actor.arc_smoke_sound = 0;
sound(actor, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, ATTEN_NORM);
METHOD(Arc, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
{
Arc_Player_SetHeat(actor, weaponentity);
- Arc_Smoke(actor, weaponentity);
+ Arc_Smoke(thiswep, actor, weaponentity, fire);
bool beam_fire2 = ((fire & 2) && !WEP_CVAR(arc, bolt));
if (time >= actor.arc_overheat)
if ((fire & 1) || beam_fire2 || actor.(weaponentity).arc_beam.beam_bursting)
{
-
+ #if 0
if(actor.(weaponentity).arc_BUTTON_ATCK_prev)
{
#if 0
#endif
weapon_thinkf(actor, weaponentity, WFRAME_DONTCHANGE, WEP_CVAR(arc, beam_animtime), w_ready);
}
+ #endif
if((!actor.(weaponentity).arc_beam) || wasfreed(actor.(weaponentity).arc_beam))
{
if(!actor.(weaponentity).arc_BUTTON_ATCK_prev)
{
- weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready);
+ actor.(weaponentity).wframe = WFRAME_FIRE1;
+ weapon_thinkf(actor, weaponentity, WFRAME_DONTCHANGE, WEP_CVAR(arc, beam_animtime), W_Arc_Attack);
actor.(weaponentity).arc_BUTTON_ATCK_prev = true;
}
}
if(actor.(weaponentity).arc_BUTTON_ATCK_prev)
{
- int slot = weaponslot(weaponentity);
sound(actor, CH_WEAPON_A, SND_ARC_STOP, VOL_BASE, ATTN_NORM);
weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready);
- ATTACK_FINISHED(actor, slot) = time + WEP_CVAR(arc, beam_refire) * W_WeaponRateFactor(actor);
+ ATTACK_FINISHED(actor, weaponentity) = time + WEP_CVAR(arc, beam_refire) * W_WeaponRateFactor(actor);
}
actor.(weaponentity).arc_BUTTON_ATCK_prev = false;
if(WEP_CVAR_PRI(crylink, joinexplode))
maxdmg += WEP_CVAR_PRI(crylink, joinexplode_damage);
- W_SetupShot(actor, weaponentity, false, 2, SND_CRYLINK_FIRE, CH_WEAPON_A, maxdmg, WEP_CRYLINK.m_id);
+ W_SetupShot(actor, weaponentity, false, 2, SND_CRYLINK_FIRE, CH_WEAPON_A, maxdmg, thiswep.m_id);
forward = v_forward;
right = v_right;
up = v_up;
set_movetype(proj, MOVETYPE_BOUNCEMISSILE);
PROJECTILE_MAKETRIGGER(proj);
- proj.projectiledeathtype = WEP_CRYLINK.m_id;
+ proj.projectiledeathtype = thiswep.m_id;
//proj.gravity = 0.001;
setorigin(proj, w_shotorg);
if(WEP_CVAR_SEC(crylink, joinexplode))
maxdmg += WEP_CVAR_SEC(crylink, joinexplode_damage);
- W_SetupShot(actor, weaponentity, false, 2, SND_CRYLINK_FIRE2, CH_WEAPON_A, maxdmg, WEP_CRYLINK.m_id | HITTYPE_SECONDARY);
+ W_SetupShot(actor, weaponentity, false, 2, SND_CRYLINK_FIRE2, CH_WEAPON_A, maxdmg, thiswep.m_id | HITTYPE_SECONDARY);
forward = v_forward;
right = v_right;
up = v_up;
set_movetype(proj, MOVETYPE_BOUNCEMISSILE);
PROJECTILE_MAKETRIGGER(proj);
- proj.projectiledeathtype = WEP_CRYLINK.m_id | HITTYPE_SECONDARY;
+ proj.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
//proj.gravity = 0.001;
setorigin(proj, w_shotorg);
if(!(actor.items & IT_UNLIMITED_WEAPON_AMMO))
{
// ran out of ammo!
- actor.cnt = WEP_CRYLINK.m_id;
+ actor.cnt = thiswep.m_id;
actor.(weaponentity).m_switchweapon = w_getbestweapon(actor, weaponentity);
}
}
return true;
float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(crylink, ammo);
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_CRYLINK.m_id]) >= WEP_CVAR_PRI(crylink, ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(crylink, ammo);
return ammo_amount;
}
METHOD(Crylink, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
return true;
float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(crylink, ammo);
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_CRYLINK.m_id]) >= WEP_CVAR_SEC(crylink, ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(crylink, ammo);
return ammo_amount;
}
METHOD(Crylink, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
if(GetResourceAmount(this.realowner, thiswep.ammo_type) < WEP_CVAR(devastator, ammo))
if(!(this.realowner.items & IT_UNLIMITED_WEAPON_AMMO))
{
- this.realowner.cnt = WEP_DEVASTATOR.m_id;
- int slot = weaponslot(weaponentity);
- ATTACK_FINISHED(this.realowner, slot) = time;
+ this.realowner.cnt = thiswep.m_id;
+ ATTACK_FINISHED(this.realowner, weaponentity) = time;
this.realowner.(weaponentity).m_switchweapon = w_getbestweapon(this.realowner, weaponentity);
}
}
if(GetResourceAmount(this.realowner, thiswep.ammo_type) < WEP_CVAR(devastator, ammo))
if(!(this.realowner.items & IT_UNLIMITED_WEAPON_AMMO))
{
- this.realowner.cnt = WEP_DEVASTATOR.m_id;
- int slot = weaponslot(weaponentity);
- ATTACK_FINISHED(this.realowner, slot) = time;
+ this.realowner.cnt = thiswep.m_id;
+ ATTACK_FINISHED(this.realowner, weaponentity) = time;
this.realowner.(weaponentity).m_switchweapon = w_getbestweapon(this.realowner, weaponentity);
}
}
{
W_DecreaseAmmo(thiswep, actor, WEP_CVAR(devastator, ammo), weaponentity);
- W_SetupShot_ProjectileSize(actor, weaponentity, '-3 -3 -3', '3 3 3', false, 5, SND_ROCKET_FIRE, CH_WEAPON_A, WEP_CVAR(devastator, damage), WEP_DEVASTATOR.m_id);
+ W_SetupShot_ProjectileSize(actor, weaponentity, '-3 -3 -3', '3 3 3', false, 5, SND_ROCKET_FIRE, CH_WEAPON_A, WEP_CVAR(devastator, damage), thiswep.m_id);
Send_Effect(EFFECT_ROCKET_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
entity missile = WarpZone_RefSys_SpawnSameRefSys(actor);
set_movetype(missile, MOVETYPE_FLY);
PROJECTILE_MAKETRIGGER(missile);
- missile.projectiledeathtype = WEP_DEVASTATOR.m_id;
+ missile.projectiledeathtype = thiswep.m_id;
setsize(missile, '-3 -3 -3', '3 3 3'); // give it some size so it can be shot
setorigin(missile, w_shotorg - v_forward * 3); // move it back so it hits the wall at the right point
actor.(weaponentity).rl_release = 1;
if(fire & 2)
- if(actor.(weaponentity).m_switchweapon == WEP_DEVASTATOR)
+ if(actor.(weaponentity).m_switchweapon == thiswep)
{
bool rockfound = false;
IL_EACH(g_projectiles, it.realowner == actor && it.classname == "rocket",
{
#if 0
// don't switch while guiding a missile
- if(ATTACK_FINISHED(actor, slot) <= time || PS(actor).m_weapon != WEP_DEVASTATOR)
+ if(ATTACK_FINISHED(actor, weaponentity) <= time || PS(actor).m_weapon != WEP_DEVASTATOR)
{
ammo_amount = false;
if(WEP_CVAR(devastator, reload_ammo))
}
#else
float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(devastator, ammo);
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_DEVASTATOR.m_id]) >= WEP_CVAR(devastator, ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR(devastator, ammo);
return ammo_amount;
#endif
}
SND_ELECTRO_FIRE,
CH_WEAPON_A,
WEP_CVAR_PRI(electro, damage),
- WEP_ELECTRO.m_id
+ thiswep.m_id
);
Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
proj.nextthink = time;
proj.ltime = time + WEP_CVAR_PRI(electro, lifetime);
PROJECTILE_MAKETRIGGER(proj);
- proj.projectiledeathtype = WEP_ELECTRO.m_id;
+ proj.projectiledeathtype = thiswep.m_id;
proj.weaponentity_fld = weaponentity;
setorigin(proj, w_shotorg);
delete(this);
if(to)
- SetMovetypeFollow(this, to);
+ SetMovetypeFollow(newproj, to);
}
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); } }
+ if(toucher.takedamage == DAMAGE_AIM && WEP_CVAR_SEC(electro, touchexplode))
+ { W_Electro_Explode(this, toucher); }
else if(toucher.owner != this.owner && toucher.classname != this.classname) // don't stick to player's other projectiles!
{
//UpdateCSQCProjectile(this);
SND_ELECTRO_FIRE2,
CH_WEAPON_A,
WEP_CVAR_SEC(electro, damage),
- WEP_ELECTRO.m_id | HITTYPE_SECONDARY
+ thiswep.m_id | HITTYPE_SECONDARY
);
w_shotdir = v_forward; // no TrueAim for grenades please
proj.bot_dodgerating = WEP_CVAR_SEC(electro, damage);
proj.nextthink = time + WEP_CVAR_SEC(electro, lifetime);
PROJECTILE_MAKETRIGGER(proj);
- proj.projectiledeathtype = WEP_ELECTRO.m_id | HITTYPE_SECONDARY;
+ proj.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
proj.weaponentity_fld = weaponentity;
setorigin(proj, w_shotorg);
proj.bouncestop = WEP_CVAR_SEC(electro, bouncestop);
proj.missile_flags = MIF_SPLASH | MIF_ARC;
-#if 0
- entity p2;
- p2 = spawn();
- copyentity(proj, p2);
- setmodel(p2, MDL_PROJECTILE_ELECTRO);
- setsize(p2, proj.mins, proj.maxs);
-#endif
-
CSQCProjectile(proj, true, PROJECTILE_ELECTRO, false); // no culling, it has sound
MUTATOR_CALLHOOK(EditProjectile, actor, proj);
if(PHYS_INPUT_BUTTON_ATCK2(actor))
if(weapon_prepareattack(thiswep, actor, weaponentity, true, -1))
{
- W_Electro_Attack_Orb(WEP_ELECTRO, actor, weaponentity);
+ W_Electro_Attack_Orb(thiswep, actor, weaponentity);
actor.(weaponentity).electro_count -= 1;
weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(electro, animtime), W_Electro_CheckAttack);
return;
METHOD(Electro, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
{
float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(electro, ammo);
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_ELECTRO.m_id]) >= WEP_CVAR_PRI(electro, ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(electro, ammo);
return ammo_amount;
}
METHOD(Electro, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
if(WEP_CVAR(electro, combo_safeammocheck)) // true if you can fire at least one secondary blob AND one primary shot after it, otherwise false.
{
ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(electro, ammo) + WEP_CVAR_PRI(electro, ammo);
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_ELECTRO.m_id]) >= WEP_CVAR_SEC(electro, ammo) + WEP_CVAR_PRI(electro, ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(electro, ammo) + WEP_CVAR_PRI(electro, ammo);
}
else
{
ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(electro, ammo);
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_ELECTRO.m_id]) >= WEP_CVAR_SEC(electro, ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(electro, ammo);
}
return ammo_amount;
}
dir = normalize(e.origin + e.view_ofs - this.origin);
if(accuracy_isgooddamage(this.realowner, e))
- accuracy_add(this.realowner, WEP_FIREBALL.m_id, 0, WEP_CVAR_PRI(fireball, bfgdamage) * points);
+ accuracy_add(this.realowner, WEP_FIREBALL, 0, WEP_CVAR_PRI(fireball, bfgdamage) * points);
Damage(e, this, this.realowner, WEP_CVAR_PRI(fireball, bfgdamage) * points, this.projectiledeathtype | HITTYPE_BOUNCE | HITTYPE_SPLASH, this.weaponentity_fld, e.origin + e.view_ofs, WEP_CVAR_PRI(fireball, bfgforce) * dir);
Send_Effect(EFFECT_FIREBALL_BFGDAMAGE, e.origin, -1 * dir, 1);
W_DecreaseAmmo(thiswep, actor, WEP_CVAR_PRI(hagar, ammo), weaponentity);
- W_SetupShot(actor, weaponentity, false, 2, SND_HAGAR_FIRE, CH_WEAPON_A, WEP_CVAR_PRI(hagar, damage), WEP_HAGAR.m_id);
+ W_SetupShot(actor, weaponentity, false, 2, SND_HAGAR_FIRE, CH_WEAPON_A, WEP_CVAR_PRI(hagar, damage), thiswep.m_id);
Send_Effect(EFFECT_HAGAR_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
setthink(missile, adaptor_think2use_hittype_splash);
missile.nextthink = time + WEP_CVAR_PRI(hagar, lifetime);
PROJECTILE_MAKETRIGGER(missile);
- missile.projectiledeathtype = WEP_HAGAR.m_id;
+ missile.projectiledeathtype = thiswep.m_id;
missile.weaponentity_fld = weaponentity;
setorigin(missile, w_shotorg);
setsize(missile, '0 0 0', '0 0 0');
W_DecreaseAmmo(thiswep, actor, WEP_CVAR_SEC(hagar, ammo), weaponentity);
- W_SetupShot(actor, weaponentity, false, 2, SND_HAGAR_FIRE, CH_WEAPON_A, WEP_CVAR_SEC(hagar, damage), WEP_HAGAR.m_id | HITTYPE_SECONDARY);
+ W_SetupShot(actor, weaponentity, false, 2, SND_HAGAR_FIRE, CH_WEAPON_A, WEP_CVAR_SEC(hagar, damage), thiswep.m_id | HITTYPE_SECONDARY);
Send_Effect(EFFECT_HAGAR_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
setthink(missile, adaptor_think2use_hittype_splash);
missile.nextthink = time + WEP_CVAR_SEC(hagar, lifetime_min) + random() * WEP_CVAR_SEC(hagar, lifetime_rand);
PROJECTILE_MAKETRIGGER(missile);
- missile.projectiledeathtype = WEP_HAGAR.m_id | HITTYPE_SECONDARY;
+ missile.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
missile.weaponentity_fld = weaponentity;
setorigin(missile, w_shotorg);
setsize(missile, '0 0 0', '0 0 0');
}
.float hagar_loadstep, hagar_loadblock, hagar_loadbeep, hagar_warning;
-void W_Hagar_Attack2_Load_Release(entity actor, .entity weaponentity)
+void W_Hagar_Attack2_Load_Release(Weapon thiswep, entity actor, .entity weaponentity)
{
// time to release the rockets we've loaded
weapon_prepareattack_do(actor, weaponentity, true, WEP_CVAR_SEC(hagar, refire));
shots = actor.(weaponentity).hagar_load;
- W_SetupShot(actor, weaponentity, false, 2, SND_HAGAR_FIRE, CH_WEAPON_A, WEP_CVAR_SEC(hagar, damage) * shots, WEP_HAGAR.m_id | HITTYPE_SECONDARY);
+ W_SetupShot(actor, weaponentity, false, 2, SND_HAGAR_FIRE, CH_WEAPON_A, WEP_CVAR_SEC(hagar, damage) * shots, thiswep.m_id | HITTYPE_SECONDARY);
Send_Effect(EFFECT_HAGAR_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
forward = v_forward;
setthink(missile, adaptor_think2use_hittype_splash);
missile.nextthink = time + WEP_CVAR_SEC(hagar, lifetime_min) + random() * WEP_CVAR_SEC(hagar, lifetime_rand);
PROJECTILE_MAKETRIGGER(missile);
- missile.projectiledeathtype = WEP_HAGAR.m_id | HITTYPE_SECONDARY;
+ missile.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
missile.weaponentity_fld = weaponentity;
setorigin(missile, w_shotorg);
setsize(missile, '0 0 0', '0 0 0');
// loadable hagar secondary attack, must always run each frame
if(time < game_starttime || time < actor.race_penalty || timeout_status == TIMEOUT_ACTIVE)
return;
+ if (round_handler_IsActive() && !round_handler_IsRoundStarted())
+ return;
bool loaded = actor.(weaponentity).hagar_load >= WEP_CVAR_SEC(hagar, load_max);
if(actor.items & IT_UNLIMITED_WEAPON_AMMO)
enough_ammo = true;
else if(autocvar_g_balance_hagar_reload_ammo)
- enough_ammo = actor.(weaponentity).(weapon_load[WEP_HAGAR.m_id]) >= WEP_CVAR_SEC(hagar, ammo);
+ enough_ammo = actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(hagar, ammo);
else
enough_ammo = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(hagar, ammo);
if(!PHYS_INPUT_BUTTON_ATCK2(actor) || (stopped && actor.(weaponentity).hagar_loadstep < time && WEP_CVAR_SEC(hagar, load_hold) >= 0))
{
actor.(weaponentity).state = WS_READY;
- W_Hagar_Attack2_Load_Release(actor, weaponentity);
+ W_Hagar_Attack2_Load_Release(thiswep, actor, weaponentity);
}
}
else
void W_Hagar_Attack_Auto(Weapon thiswep, entity actor, .entity weaponentity, int fire)
{
- if(!(fire & 1) || actor.(weaponentity).hagar_load || actor.(weaponentity).hagar_loadblock || actor.(weaponentity).m_switchweapon != WEP_HAGAR || !weapon_prepareattack_check(thiswep, actor, weaponentity, false, -1))
+ if(!(fire & 1) || actor.(weaponentity).hagar_load || actor.(weaponentity).hagar_loadblock || actor.(weaponentity).m_switchweapon != thiswep || !weapon_prepareattack_check(thiswep, actor, weaponentity, false, -1))
{
w_ready(thiswep, actor, weaponentity, fire);
return;
W_Hagar_Attack(thiswep, actor, weaponentity);
- int slot = weaponslot(weaponentity);
- ATTACK_FINISHED(actor, slot) = time + WEP_CVAR_PRI(hagar, refire) * W_WeaponRateFactor(actor);
- int theframe = WFRAME_FIRE1;
- entity this = actor.(weaponentity);
- if(this)
- {
- if(this.wframe == WFRAME_FIRE1)
- theframe = WFRAME_DONTCHANGE;
- }
- weapon_thinkf(actor, weaponentity, theframe, WEP_CVAR_PRI(hagar, refire), W_Hagar_Attack_Auto);
+ ATTACK_FINISHED(actor, weaponentity) = time + WEP_CVAR_PRI(hagar, refire) * W_WeaponRateFactor(actor);
+ actor.(weaponentity).wframe = WFRAME_FIRE1;
+ weapon_thinkf(actor, weaponentity, WFRAME_DONTCHANGE, WEP_CVAR_PRI(hagar, refire), W_Hagar_Attack_Auto);
}
METHOD(Hagar, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
if(actor.(weaponentity).hagar_load)
{
actor.(weaponentity).state = WS_READY;
- W_Hagar_Attack2_Load_Release(actor, weaponentity);
+ W_Hagar_Attack2_Load_Release(thiswep, actor, weaponentity);
}
}
METHOD(Hagar, wr_setup, void(entity thiswep, entity actor, .entity weaponentity))
METHOD(Hagar, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
{
float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(hagar, ammo);
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_HAGAR.m_id]) >= WEP_CVAR_PRI(hagar, ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(hagar, ammo);
return ammo_amount;
}
METHOD(Hagar, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
{
float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(hagar, ammo);
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_HAGAR.m_id]) >= WEP_CVAR_SEC(hagar, ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(hagar, ammo);
return ammo_amount;
}
METHOD(Hagar, wr_resetplayer, void(entity thiswep, entity actor))
{
// if we have any rockets loaded when we die, release them
if(actor.(weaponentity).hagar_load && WEP_CVAR_SEC(hagar, load_releasedeath))
- W_Hagar_Attack2_Load_Release(actor, weaponentity);
+ W_Hagar_Attack2_Load_Release(thiswep, actor, weaponentity);
}
METHOD(Hagar, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
{
if(actor.crouch)
spread = spread * WEP_CVAR_PRI(hlac, spread_crouchmod);
- W_SetupShot(actor, weaponentity, false, 3, SND_LASERGUN_FIRE, CH_WEAPON_A, WEP_CVAR_PRI(hlac, damage), WEP_HLAC.m_id);
+ W_SetupShot(actor, weaponentity, false, 3, SND_LASERGUN_FIRE, CH_WEAPON_A, WEP_CVAR_PRI(hlac, damage), thiswep.m_id);
Send_Effect(EFFECT_BLASTER_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
if(!autocvar_g_norecoil)
{
missile.flags = FL_PROJECTILE;
IL_PUSH(g_projectiles, missile);
IL_PUSH(g_bot_dodge, missile);
- missile.projectiledeathtype = WEP_HLAC.m_id;
+ missile.projectiledeathtype = thiswep.m_id;
missile.weaponentity_fld = weaponentity;
CSQCProjectile(missile, true, PROJECTILE_HLAC, true);
MUTATOR_CALLHOOK(EditProjectile, actor, missile);
}
-void W_HLAC_Attack2(entity actor, .entity weaponentity)
+void W_HLAC_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
{
entity missile;
float spread;
if(actor.crouch)
spread = spread * WEP_CVAR_SEC(hlac, spread_crouchmod);
- W_SetupShot(actor, weaponentity, false, 3, SND_LASERGUN_FIRE, CH_WEAPON_A, WEP_CVAR_SEC(hlac, damage), WEP_HLAC.m_id | HITTYPE_SECONDARY);
+ W_SetupShot(actor, weaponentity, false, 3, SND_LASERGUN_FIRE, CH_WEAPON_A, WEP_CVAR_SEC(hlac, damage), thiswep.m_id | HITTYPE_SECONDARY);
Send_Effect(EFFECT_BLASTER_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
missile = new(hlacbolt);
IL_PUSH(g_projectiles, missile);
IL_PUSH(g_bot_dodge, missile);
missile.missile_flags = MIF_SPLASH;
- missile.projectiledeathtype = WEP_HLAC.m_id | HITTYPE_SECONDARY;
+ missile.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
missile.weaponentity_fld = weaponentity;
CSQCProjectile(missile, true, PROJECTILE_HLAC, true);
return;
}
- int slot = weaponslot(weaponentity);
- ATTACK_FINISHED(actor, slot) = time + WEP_CVAR_PRI(hlac, refire) * W_WeaponRateFactor(actor);
- W_HLAC_Attack(WEP_HLAC, actor, weaponentity);
+ ATTACK_FINISHED(actor, weaponentity) = time + WEP_CVAR_PRI(hlac, refire) * W_WeaponRateFactor(actor);
+ W_HLAC_Attack(thiswep, actor, weaponentity);
actor.(weaponentity).misc_bulletcounter = actor.(weaponentity).misc_bulletcounter + 1;
weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(hlac, refire), W_HLAC_Attack_Frame);
}
W_DecreaseAmmo(thiswep, actor, WEP_CVAR_SEC(hlac, ammo), weaponentity);
for(i=WEP_CVAR_SEC(hlac, shots);i>0;--i)
- W_HLAC_Attack2(actor, weaponentity);
+ W_HLAC_Attack2(thiswep, actor, weaponentity);
if(!autocvar_g_norecoil)
{
METHOD(HLAC, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
{
float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(hlac, ammo);
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_HLAC.m_id]) >= WEP_CVAR_PRI(hlac, ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(hlac, ammo);
return ammo_amount;
}
METHOD(HLAC, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
{
float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(hlac, ammo);
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_HLAC.m_id]) >= WEP_CVAR_SEC(hlac, ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(hlac, ammo);
return ammo_amount;
}
METHOD(HLAC, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
actor.punchangle_x = random() - 0.5;
actor.punchangle_y = random() - 0.5;
}
- int slot = weaponslot(weaponentity);
// this attack_finished just enforces a cooldown at the end of a burst
- ATTACK_FINISHED(actor, slot) = time + WEP_CVAR(machinegun, first_refire) * W_WeaponRateFactor(actor);
+ ATTACK_FINISHED(actor, weaponentity) = time + WEP_CVAR(machinegun, first_refire) * W_WeaponRateFactor(actor);
if(actor.(weaponentity).misc_bulletcounter == 1)
- fireBullet(actor, weaponentity, w_shotorg, w_shotdir, WEP_CVAR(machinegun, first_spread), WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, first_damage), WEP_CVAR(machinegun, first_force), deathtype, 0);
+ fireBullet(actor, weaponentity, w_shotorg, w_shotdir, WEP_CVAR(machinegun, first_spread), WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, first_damage), WEP_CVAR(machinegun, first_force), deathtype, EFFECT_BULLET);
else
- fireBullet(actor, weaponentity, w_shotorg, w_shotdir, WEP_CVAR(machinegun, sustained_spread), WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, sustained_damage), WEP_CVAR(machinegun, sustained_force), deathtype, 0);
+ fireBullet(actor, weaponentity, w_shotorg, w_shotdir, WEP_CVAR(machinegun, sustained_spread), WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, sustained_damage), WEP_CVAR(machinegun, sustained_force), deathtype, EFFECT_BULLET);
Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
return;
}
actor.(weaponentity).misc_bulletcounter = actor.(weaponentity).misc_bulletcounter + 1;
- W_MachineGun_Attack(WEP_MACHINEGUN, WEP_MACHINEGUN.m_id, actor, weaponentity);
+ W_MachineGun_Attack(thiswep, thiswep.m_id, actor, weaponentity);
weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(machinegun, sustained_refire), W_MachineGun_Attack_Frame);
}
else
return;
}
- W_DecreaseAmmo(WEP_MACHINEGUN, actor, WEP_CVAR(machinegun, sustained_ammo), weaponentity);
+ W_DecreaseAmmo(thiswep, actor, WEP_CVAR(machinegun, sustained_ammo), weaponentity);
- W_SetupShot(actor, weaponentity, true, 0, SND_UZI_FIRE, CH_WEAPON_A, WEP_CVAR(machinegun, sustained_damage), WEP_MACHINEGUN.m_id);
+ W_SetupShot(actor, weaponentity, true, 0, SND_UZI_FIRE, CH_WEAPON_A, WEP_CVAR(machinegun, sustained_damage), thiswep.m_id);
if(!autocvar_g_norecoil)
{
actor.punchangle_x = random() - 0.5;
}
machinegun_spread = bound(WEP_CVAR(machinegun, spread_min), WEP_CVAR(machinegun, spread_min) + (WEP_CVAR(machinegun, spread_add) * actor.(weaponentity).misc_bulletcounter), WEP_CVAR(machinegun, spread_max));
- fireBullet(actor, weaponentity, w_shotorg, w_shotdir, machinegun_spread, WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, sustained_damage), WEP_CVAR(machinegun, sustained_force), WEP_MACHINEGUN.m_id, 0);
+ fireBullet(actor, weaponentity, w_shotorg, w_shotdir, machinegun_spread, WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, sustained_damage), WEP_CVAR(machinegun, sustained_force), thiswep.m_id, EFFECT_BULLET);
actor.(weaponentity).misc_bulletcounter = actor.(weaponentity).misc_bulletcounter + 1;
SpawnCasing(((random() * 50 + 50) * v_right) - (v_forward * (random() * 25 + 25)) - ((random() * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, actor, weaponentity);
}
- int slot = weaponslot(weaponentity);
- ATTACK_FINISHED(actor, slot) = time + WEP_CVAR(machinegun, first_refire) * W_WeaponRateFactor(actor);
+ ATTACK_FINISHED(actor, weaponentity) = time + WEP_CVAR(machinegun, first_refire) * W_WeaponRateFactor(actor);
weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(machinegun, sustained_refire), W_MachineGun_Attack_Auto);
}
void W_MachineGun_Attack_Burst(Weapon thiswep, entity actor, .entity weaponentity, int fire)
{
- W_SetupShot(actor, weaponentity, true, 0, SND_UZI_FIRE, CH_WEAPON_A, WEP_CVAR(machinegun, sustained_damage), WEP_MACHINEGUN.m_id);
+ W_SetupShot(actor, weaponentity, true, 0, SND_UZI_FIRE, CH_WEAPON_A, WEP_CVAR(machinegun, sustained_damage), thiswep.m_id);
if(!autocvar_g_norecoil)
{
actor.punchangle_x = random() - 0.5;
actor.punchangle_y = random() - 0.5;
}
- fireBullet(actor, weaponentity, w_shotorg, w_shotdir, WEP_CVAR(machinegun, burst_speed), WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, sustained_damage), WEP_CVAR(machinegun, sustained_force), WEP_MACHINEGUN.m_id, 0);
+ fireBullet(actor, weaponentity, w_shotorg, w_shotdir, WEP_CVAR(machinegun, burst_speed), WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, sustained_damage), WEP_CVAR(machinegun, sustained_force), thiswep.m_id, EFFECT_BULLET);
Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
actor.(weaponentity).misc_bulletcounter = actor.(weaponentity).misc_bulletcounter + 1;
if(actor.(weaponentity).misc_bulletcounter == 0)
{
- int slot = weaponslot(weaponentity);
- ATTACK_FINISHED(actor, slot) = time + WEP_CVAR(machinegun, burst_refire2) * W_WeaponRateFactor(actor);
+ ATTACK_FINISHED(actor, weaponentity) = time + WEP_CVAR(machinegun, burst_refire2) * W_WeaponRateFactor(actor);
weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR(machinegun, burst_animtime), w_ready);
}
else
if(weapon_prepareattack(thiswep, actor, weaponentity, false, 0))
{
actor.(weaponentity).misc_bulletcounter = 1;
- W_MachineGun_Attack(WEP_MACHINEGUN, WEP_MACHINEGUN.m_id, actor, weaponentity); // sets attack_finished
+ W_MachineGun_Attack(thiswep, thiswep.m_id, actor, weaponentity); // sets attack_finished
weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(machinegun, sustained_refire), W_MachineGun_Attack_Frame);
}
if(weapon_prepareattack(thiswep, actor, weaponentity, true, 0))
{
actor.(weaponentity).misc_bulletcounter = 1;
- W_MachineGun_Attack(WEP_MACHINEGUN, WEP_MACHINEGUN.m_id | HITTYPE_SECONDARY, actor, weaponentity); // sets attack_finished
+ W_MachineGun_Attack(thiswep, thiswep.m_id | HITTYPE_SECONDARY, actor, weaponentity); // sets attack_finished
weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR(machinegun, first_refire), w_ready);
}
}
if(WEP_CVAR(machinegun, reload_ammo))
{
if(WEP_CVAR(machinegun, mode) == 1)
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, sustained_ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR(machinegun, sustained_ammo);
else
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, first_ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR(machinegun, first_ammo);
}
return ammo_amount;
}
if(WEP_CVAR(machinegun, reload_ammo))
{
if(WEP_CVAR(machinegun, mode) == 1)
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, burst_ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR(machinegun, burst_ammo);
else
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, first_ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR(machinegun, first_ammo);
}
return ammo_amount;
}
RadiusDamage(this, this.realowner, WEP_CVAR(minelayer, damage), WEP_CVAR(minelayer, edgedamage), WEP_CVAR(minelayer, radius), NULL, NULL, WEP_CVAR(minelayer, force), this.projectiledeathtype, this.weaponentity_fld, directhitentity);
.entity weaponentity = this.weaponentity_fld;
- if(this.realowner.(weaponentity).m_weapon == WEP_MINE_LAYER)
+ Weapon thiswep = WEP_MINE_LAYER;
+ if(this.realowner.(weaponentity).m_weapon == thiswep)
{
entity own = this.realowner;
- Weapon w = WEP_MINE_LAYER;
- if(!w.wr_checkammo1(w, own, weaponentity))
+ if(!thiswep.wr_checkammo1(thiswep, own, weaponentity))
{
- own.cnt = WEP_MINE_LAYER.m_id;
- int slot = weaponslot(weaponentity);
- ATTACK_FINISHED(own, slot) = time;
+ own.cnt = thiswep.m_id;
+ ATTACK_FINISHED(own, weaponentity) = time;
own.(weaponentity).m_switchweapon = w_getbestweapon(own, weaponentity);
}
}
- this.realowner.(weaponentity).minelayer_mines -= 1;
delete(this);
}
NULL, NULL, WEP_CVAR(minelayer, remote_force), this.projectiledeathtype | HITTYPE_BOUNCE, this.weaponentity_fld, NULL);
.entity weaponentity = this.weaponentity_fld;
- if(this.realowner.(weaponentity).m_weapon == WEP_MINE_LAYER)
+ Weapon thiswep = WEP_MINE_LAYER;
+ if(this.realowner.(weaponentity).m_weapon == thiswep)
{
entity own = this.realowner;
- Weapon w = WEP_MINE_LAYER;
- if(!w.wr_checkammo1(w, own, weaponentity))
+ if(!thiswep.wr_checkammo1(thiswep, own, weaponentity))
{
- own.cnt = WEP_MINE_LAYER.m_id;
- int slot = weaponslot(weaponentity);
- ATTACK_FINISHED(own, slot) = time;
+ own.cnt = thiswep.m_id;
+ ATTACK_FINISHED(own, weaponentity) = time;
own.(weaponentity).m_switchweapon = w_getbestweapon(own, weaponentity);
}
}
- this.realowner.(weaponentity).minelayer_mines -= 1;
delete(this);
}
if(this.move_movetype == MOVETYPE_NONE || this.move_movetype == MOVETYPE_FOLLOW)
return; // we're already a stuck mine, why do we get called? TODO does this even happen?
- if(WarpZone_Projectile_Touch(this, toucher))
- {
- if(wasfreed(this))
- {
- .entity weaponentity = this.weaponentity_fld;
- this.realowner.(weaponentity).minelayer_mines -= 1;
- }
- return;
- }
+ PROJECTILE_TOUCH(this, toucher);
if((toucher && IS_PLAYER(toucher) && !IS_DEAD(toucher)) || toucher.owner == this.owner)
{
// scan how many mines we placed, and return if we reached our limit
if(WEP_CVAR(minelayer, limit))
{
- if(actor.(weaponentity).minelayer_mines >= WEP_CVAR(minelayer, limit))
+ int minecount = W_MineLayer_Count(actor, weaponentity);
+ if(minecount >= WEP_CVAR(minelayer, limit))
{
// the refire delay keeps this message from being spammed
Send_Notification(NOTIF_ONE, actor, MSG_MULTI, WEAPON_MINELAYER_LIMIT, WEP_CVAR(minelayer, limit));
W_DecreaseAmmo(thiswep, actor, WEP_CVAR(minelayer, ammo), weaponentity);
- W_SetupShot_ProjectileSize(actor, weaponentity, '-4 -4 -4', '4 4 4', false, 5, SND_MINE_FIRE, CH_WEAPON_A, WEP_CVAR(minelayer, damage), WEP_MINE_LAYER.m_id);
+ W_SetupShot_ProjectileSize(actor, weaponentity, '-4 -4 -4', '4 4 4', false, 5, SND_MINE_FIRE, CH_WEAPON_A, WEP_CVAR(minelayer, damage), thiswep.m_id);
Send_Effect(EFFECT_ROCKET_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
mine = WarpZone_RefSys_SpawnSameRefSys(actor);
set_movetype(mine, MOVETYPE_TOSS);
PROJECTILE_MAKETRIGGER(mine);
- mine.projectiledeathtype = WEP_MINE_LAYER.m_id;
+ mine.projectiledeathtype = thiswep.m_id;
mine.weaponentity_fld = weaponentity;
setsize(mine, '-4 -4 -4', '4 4 4'); // give it some size so it can be shot
// common properties
MUTATOR_CALLHOOK(EditProjectile, actor, mine);
-
- actor.(weaponentity).minelayer_mines = W_MineLayer_Count(actor, weaponentity);
}
bool W_MineLayer_PlacedMines(entity this, .entity weaponentity, bool detonate)
METHOD(MineLayer, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
{
// aim and decide to fire if appropriate
- if(actor.(weaponentity).minelayer_mines >= WEP_CVAR(minelayer, limit))
+ int minecount = W_MineLayer_Count(actor, weaponentity);
+ if(minecount >= WEP_CVAR(minelayer, limit))
PHYS_INPUT_BUTTON_ATCK(actor) = false;
else
PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR(minelayer, speed), 0, WEP_CVAR(minelayer, lifetime), false);
}
METHOD(MineLayer, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
{
+ actor.(weaponentity).minelayer_mines = W_MineLayer_Count(actor, weaponentity);
+
if(autocvar_g_balance_minelayer_reload_ammo && actor.(weaponentity).clip_load < WEP_CVAR(minelayer, ammo)) // forced reload
{
// not if we're holding the minelayer without enough ammo, but can detonate existing mines
}
METHOD(MineLayer, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
{
- //int slot = 0; // TODO: unhardcode
// actually do // don't switch while placing a mine
- //if(ATTACK_FINISHED(actor, slot) <= time || PS(actor).m_weapon != WEP_MINE_LAYER)
+ //if(ATTACK_FINISHED(actor, weaponentity) <= time || PS(actor).m_weapon != WEP_MINE_LAYER)
//{
float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(minelayer, ammo);
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_MINE_LAYER.m_id]) >= WEP_CVAR(minelayer, ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR(minelayer, ammo);
return ammo_amount;
//}
//return true;
{
W_DecreaseAmmo(thiswep, actor, WEP_CVAR_PRI(mortar, ammo), weaponentity);
- W_SetupShot_ProjectileSize(actor, weaponentity, '-3 -3 -3', '3 3 3', false, 4, SND_GRENADE_FIRE, CH_WEAPON_A, WEP_CVAR_PRI(mortar, damage), WEP_MORTAR.m_id);
+ W_SetupShot_ProjectileSize(actor, weaponentity, '-3 -3 -3', '3 3 3', false, 4, SND_GRENADE_FIRE, CH_WEAPON_A, WEP_CVAR_PRI(mortar, damage), thiswep.m_id);
w_shotdir = v_forward; // no TrueAim for grenades please
Send_Effect(EFFECT_GRENADE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
gren.bouncefactor = WEP_CVAR(mortar, bouncefactor);
gren.bouncestop = WEP_CVAR(mortar, bouncestop);
PROJECTILE_MAKETRIGGER(gren);
- gren.projectiledeathtype = WEP_MORTAR.m_id;
+ gren.projectiledeathtype = thiswep.m_id;
gren.weaponentity_fld = weaponentity;
setorigin(gren, w_shotorg);
setsize(gren, '-3 -3 -3', '3 3 3');
W_DecreaseAmmo(thiswep, actor, WEP_CVAR_SEC(mortar, ammo), weaponentity);
- W_SetupShot_ProjectileSize(actor, weaponentity, '-3 -3 -3', '3 3 3', false, 4, SND_GRENADE_FIRE, CH_WEAPON_A, WEP_CVAR_SEC(mortar, damage), WEP_MORTAR.m_id | HITTYPE_SECONDARY);
+ W_SetupShot_ProjectileSize(actor, weaponentity, '-3 -3 -3', '3 3 3', false, 4, SND_GRENADE_FIRE, CH_WEAPON_A, WEP_CVAR_SEC(mortar, damage), thiswep.m_id | HITTYPE_SECONDARY);
w_shotdir = v_forward; // no TrueAim for grenades please
Send_Effect(EFFECT_GRENADE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
gren.bouncefactor = WEP_CVAR(mortar, bouncefactor);
gren.bouncestop = WEP_CVAR(mortar, bouncestop);
PROJECTILE_MAKETRIGGER(gren);
- gren.projectiledeathtype = WEP_MORTAR.m_id | HITTYPE_SECONDARY;
+ gren.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
gren.weaponentity_fld = weaponentity;
setorigin(gren, w_shotorg);
setsize(gren, '-3 -3 -3', '3 3 3');
METHOD(Mortar, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
{
float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(mortar, ammo);
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_MORTAR.m_id]) >= WEP_CVAR_PRI(mortar, ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(mortar, ammo);
return ammo_amount;
}
METHOD(Mortar, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
{
float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(mortar, ammo);
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_MORTAR.m_id]) >= WEP_CVAR_SEC(mortar, ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(mortar, ammo);
return ammo_amount;
}
METHOD(Mortar, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
}
}
-void W_Porto_Attack(entity actor, .entity weaponentity, float type)
+void W_Porto_Attack(Weapon thiswep, entity actor, .entity weaponentity, float type)
{
entity gren;
- W_SetupShot(actor, weaponentity, false, 4, SND_PORTO_FIRE, CH_WEAPON_A, 0, WEP_PORTO.m_id); // TODO: does the deathtype even need to be set here? porto can't hurt people
+ W_SetupShot(actor, weaponentity, false, 4, SND_PORTO_FIRE, CH_WEAPON_A, 0, thiswep.m_id); // TODO: does the deathtype even need to be set here? porto can't hurt people
// always shoot from the eye
w_shotdir = v_forward;
w_shotorg = actor.origin + actor.view_ofs + ((w_shotorg - actor.origin - actor.view_ofs) * v_forward) * v_forward;
if(!actor.porto_forbidden)
if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(porto, refire)))
{
- W_Porto_Attack(actor, weaponentity, 0);
+ W_Porto_Attack(thiswep, actor, weaponentity, 0);
weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(porto, animtime), w_ready);
}
if(!actor.porto_forbidden)
if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(porto, refire)))
{
- W_Porto_Attack(actor, weaponentity, 1);
+ W_Porto_Attack(thiswep, actor, weaponentity, 1);
weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(porto, animtime), w_ready);
}
}
if(!actor.porto_forbidden)
if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(porto, refire)))
{
- W_Porto_Attack(actor, weaponentity, -1);
+ W_Porto_Attack(thiswep, actor, weaponentity, -1);
weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(porto, animtime), w_ready);
}
}
}
for(i = 0; i < pShots; ++i)
- fireBullet(actor, weaponentity, w_shotorg, w_shotdir, pSpread, pSolidPenetration, pDamage, pForce, deathtype, (pTracer ? EF_RED : EF_BLUE));
+ fireBullet(actor, weaponentity, w_shotorg, w_shotdir, pSpread, pSolidPenetration, pDamage, pForce, deathtype, (pTracer ? EFFECT_RIFLE : EFFECT_RIFLE_WEAK));
if(autocvar_g_casings >= 2)
{
}
}
-void W_Rifle_Attack(entity actor, .entity weaponentity)
+void W_Rifle_Attack(Weapon thiswep, entity actor, .entity weaponentity)
{
- W_Rifle_FireBullet(WEP_RIFLE, weaponentity, WEP_CVAR_PRI(rifle, spread), WEP_CVAR_PRI(rifle, damage), WEP_CVAR_PRI(rifle, force), WEP_CVAR_PRI(rifle, solidpenetration), WEP_CVAR_PRI(rifle, ammo), WEP_RIFLE.m_id, WEP_CVAR_PRI(rifle, tracer), WEP_CVAR_PRI(rifle, shots), SND_CAMPINGRIFLE_FIRE, actor);
+ W_Rifle_FireBullet(thiswep, weaponentity, WEP_CVAR_PRI(rifle, spread), WEP_CVAR_PRI(rifle, damage), WEP_CVAR_PRI(rifle, force), WEP_CVAR_PRI(rifle, solidpenetration), WEP_CVAR_PRI(rifle, ammo), thiswep.m_id, WEP_CVAR_PRI(rifle, tracer), WEP_CVAR_PRI(rifle, shots), SND_CAMPINGRIFLE_FIRE, actor);
}
-void W_Rifle_Attack2(entity actor, .entity weaponentity)
+void W_Rifle_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
{
- W_Rifle_FireBullet(WEP_RIFLE, weaponentity, WEP_CVAR_SEC(rifle, spread), WEP_CVAR_SEC(rifle, damage), WEP_CVAR_SEC(rifle, force), WEP_CVAR_SEC(rifle, solidpenetration), WEP_CVAR_SEC(rifle, ammo), WEP_RIFLE.m_id | HITTYPE_SECONDARY, WEP_CVAR_SEC(rifle, tracer), WEP_CVAR_SEC(rifle, shots), SND_CAMPINGRIFLE_FIRE2, actor);
+ W_Rifle_FireBullet(thiswep, weaponentity, WEP_CVAR_SEC(rifle, spread), WEP_CVAR_SEC(rifle, damage), WEP_CVAR_SEC(rifle, force), WEP_CVAR_SEC(rifle, solidpenetration), WEP_CVAR_SEC(rifle, ammo), thiswep.m_id | HITTYPE_SECONDARY, WEP_CVAR_SEC(rifle, tracer), WEP_CVAR_SEC(rifle, shots), SND_CAMPINGRIFLE_FIRE2, actor);
}
-.void(entity actor, .entity weaponentity) rifle_bullethail_attackfunc;
+.void(Weapon thiswep, entity actor, .entity weaponentity) rifle_bullethail_attackfunc;
.WFRAME rifle_bullethail_frame;
.float rifle_bullethail_animtime;
.float rifle_bullethail_refire;
float r, af;
Weapon sw = actor.(weaponentity).m_switchweapon; // make it not detect weapon changes as reason to abort firing
- int slot = weaponslot(weaponentity);
- af = ATTACK_FINISHED(actor, slot);
+ af = ATTACK_FINISHED(actor, weaponentity);
actor.(weaponentity).m_switchweapon = actor.(weaponentity).m_weapon;
- ATTACK_FINISHED(actor, slot) = time;
+ ATTACK_FINISHED(actor, weaponentity) = time;
r = weapon_prepareattack(thiswep, actor, weaponentity, actor.rifle_bullethail_frame == WFRAME_FIRE2, actor.rifle_bullethail_refire);
if(actor.(weaponentity).m_switchweapon == actor.(weaponentity).m_weapon)
actor.(weaponentity).m_switchweapon = sw;
if(r)
{
- actor.rifle_bullethail_attackfunc(actor, weaponentity);
+ actor.rifle_bullethail_attackfunc(thiswep, actor, weaponentity);
weapon_thinkf(actor, weaponentity, actor.rifle_bullethail_frame, actor.rifle_bullethail_animtime, W_Rifle_BulletHail_Continue);
}
else
{
- ATTACK_FINISHED(actor, slot) = af; // reset attack_finished if we didn't fire, so the last shot enforces the refire time
+ ATTACK_FINISHED(actor, weaponentity) = af; // reset attack_finished if we didn't fire, so the last shot enforces the refire time
}
}
-void W_Rifle_BulletHail(entity actor, .entity weaponentity, float mode, void(entity actor, .entity weaponentity) AttackFunc, WFRAME fr, float animtime, float refire)
+void W_Rifle_BulletHail(Weapon thiswep, entity actor, .entity weaponentity, float mode, void(Weapon thiswep, entity actor, .entity weaponentity) AttackFunc, WFRAME fr, float animtime, float refire)
{
// if we get here, we have at least one bullet to fire
- AttackFunc(actor, weaponentity);
+ AttackFunc(thiswep, actor, weaponentity);
if(mode)
{
// continue hail
if(time >= actor.(weaponentity).rifle_accumulator + WEP_CVAR_PRI(rifle, burstcost))
{
weapon_prepareattack_do(actor, weaponentity, false, WEP_CVAR_PRI(rifle, refire));
- W_Rifle_BulletHail(actor, weaponentity, WEP_CVAR_PRI(rifle, bullethail), W_Rifle_Attack, WFRAME_FIRE1, WEP_CVAR_PRI(rifle, animtime), WEP_CVAR_PRI(rifle, refire));
+ W_Rifle_BulletHail(thiswep, actor, weaponentity, WEP_CVAR_PRI(rifle, bullethail), W_Rifle_Attack, WFRAME_FIRE1, WEP_CVAR_PRI(rifle, animtime), WEP_CVAR_PRI(rifle, refire));
actor.(weaponentity).rifle_accumulator += WEP_CVAR_PRI(rifle, burstcost);
}
if(fire & 2)
if(time >= actor.(weaponentity).rifle_accumulator + WEP_CVAR_SEC(rifle, burstcost))
{
weapon_prepareattack_do(actor, weaponentity, true, WEP_CVAR_SEC(rifle, refire));
- W_Rifle_BulletHail(actor, weaponentity, WEP_CVAR_SEC(rifle, bullethail), W_Rifle_Attack2, WFRAME_FIRE2, WEP_CVAR_SEC(rifle, animtime), WEP_CVAR_PRI(rifle, refire));
+ W_Rifle_BulletHail(thiswep, actor, weaponentity, WEP_CVAR_SEC(rifle, bullethail), W_Rifle_Attack2, WFRAME_FIRE2, WEP_CVAR_SEC(rifle, animtime), WEP_CVAR_PRI(rifle, refire));
actor.(weaponentity).rifle_accumulator += WEP_CVAR_SEC(rifle, burstcost);
}
}
METHOD(Rifle, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
{
float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(rifle, ammo);
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_RIFLE.m_id]) >= WEP_CVAR_PRI(rifle, ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(rifle, ammo);
return ammo_amount;
}
METHOD(Rifle, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
{
float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(rifle, ammo);
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_RIFLE.m_id]) >= WEP_CVAR_SEC(rifle, ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(rifle, ammo);
return ammo_amount;
}
METHOD(Rifle, wr_resetplayer, void(entity thiswep, entity actor))
W_DecreaseAmmo(thiswep, actor, WEP_CVAR(seeker, missile_ammo), weaponentity);
makevectors(actor.v_angle);
- W_SetupShot_ProjectileSize(actor, weaponentity, '-2 -2 -2', '2 2 2', false, 2, SND_SEEKER_FIRE, CH_WEAPON_A, 0, ((m_target != NULL) ? WEP_SEEKER.m_id | HITTYPE_SECONDARY : WEP_SEEKER.m_id));
+ W_SetupShot_ProjectileSize(actor, weaponentity, '-2 -2 -2', '2 2 2', false, 2, SND_SEEKER_FIRE, CH_WEAPON_A, 0, ((m_target != NULL) ? thiswep.m_id | HITTYPE_SECONDARY : thiswep.m_id));
w_shotorg += f_diff;
Send_Effect(EFFECT_SEEKER_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
//missile.think = W_Seeker_Missile_Animate; // csqc projectiles.
if(missile.enemy != NULL)
- missile.projectiledeathtype = WEP_SEEKER.m_id | HITTYPE_SECONDARY;
+ missile.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
else
- missile.projectiledeathtype = WEP_SEEKER.m_id;
+ missile.projectiledeathtype = thiswep.m_id;
setorigin(missile, w_shotorg);
f_diff = '+1.25 +3.75 0';
break;
}
- W_SetupShot_ProjectileSize(actor, weaponentity, '-2 -2 -2', '2 2 2', false, 2, SND_FLAC_FIRE, CH_WEAPON_A, WEP_CVAR(seeker, flac_damage), WEP_SEEKER.m_id | HITTYPE_SECONDARY);
+ W_SetupShot_ProjectileSize(actor, weaponentity, '-2 -2 -2', '2 2 2', false, 2, SND_FLAC_FIRE, CH_WEAPON_A, WEP_CVAR(seeker, flac_damage), thiswep.m_id | HITTYPE_SECONDARY);
w_shotorg += f_diff;
Send_Effect(EFFECT_HAGAR_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
missile.nextthink = time + WEP_CVAR(seeker, flac_lifetime) + WEP_CVAR(seeker, flac_lifetime_rand);
missile.solid = SOLID_BBOX;
set_movetype(missile, MOVETYPE_FLY);
- missile.projectiledeathtype = WEP_SEEKER.m_id | HITTYPE_SECONDARY;
+ missile.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
missile.weaponentity_fld = weaponentity;
missile.flags = FL_PROJECTILE;
IL_PUSH(g_projectiles, missile);
return NULL;
}
-void W_Seeker_Attack(entity actor, .entity weaponentity)
+void W_Seeker_Attack(Weapon thiswep, entity actor, .entity weaponentity)
{
entity closest_target = NULL;
closest_target = NULL;
}
- W_Seeker_Fire_Missile(WEP_SEEKER, actor, weaponentity, '0 0 0', closest_target);
+ W_Seeker_Fire_Missile(thiswep, actor, weaponentity, '0 0 0', closest_target);
}
void W_Seeker_Vollycontroller_Think(entity this) // TODO: Merge this with W_Seeker_Attack
Weapon thiswep = WEP_SEEKER;
.entity weaponentity = this.weaponentity_fld;
- if((!(this.realowner.items & IT_UNLIMITED_AMMO) && GetResourceAmount(this.realowner, thiswep.ammo_type) < WEP_CVAR(seeker, missile_ammo)) || (this.cnt <= -1) || (IS_DEAD(this.realowner)) || (this.realowner.(weaponentity).m_switchweapon != WEP_SEEKER))
+ if((!(this.realowner.items & IT_UNLIMITED_AMMO) && GetResourceAmount(this.realowner, thiswep.ammo_type) < WEP_CVAR(seeker, missile_ammo)) || (this.cnt <= -1) || (IS_DEAD(this.realowner)) || (this.realowner.(weaponentity).m_switchweapon != thiswep))
{
delete(this);
return;
switch(c)
{
case 0:
- W_Seeker_Fire_Missile(WEP_SEEKER, own, weaponentity, '-1.25 -3.75 0', own.enemy); // TODO
+ W_Seeker_Fire_Missile(thiswep, own, weaponentity, '-1.25 -3.75 0', own.enemy); // TODO
break;
case 1:
- W_Seeker_Fire_Missile(WEP_SEEKER, own, weaponentity, '+1.25 -3.75 0', own.enemy); // TODO
+ W_Seeker_Fire_Missile(thiswep, own, weaponentity, '+1.25 -3.75 0', own.enemy); // TODO
break;
case 2:
- W_Seeker_Fire_Missile(WEP_SEEKER, own, weaponentity, '-1.25 +3.75 0', own.enemy); // TODO
+ W_Seeker_Fire_Missile(thiswep, own, weaponentity, '-1.25 +3.75 0', own.enemy); // TODO
break;
case 3:
default:
- W_Seeker_Fire_Missile(WEP_SEEKER, own, weaponentity, '+1.25 +3.75 0', own.enemy); // TODO
+ W_Seeker_Fire_Missile(thiswep, own, weaponentity, '+1.25 +3.75 0', own.enemy); // TODO
break;
}
{
W_DecreaseAmmo(thiswep, actor, WEP_CVAR(seeker, tag_ammo), weaponentity);
- W_SetupShot_ProjectileSize(actor, weaponentity, '-2 -2 -2', '2 2 2', false, 2, SND_TAG_FIRE, CH_WEAPON_A, WEP_CVAR(seeker, missile_damage) * WEP_CVAR(seeker, missile_count), WEP_SEEKER.m_id | HITTYPE_BOUNCE | HITTYPE_SECONDARY);
+ W_SetupShot_ProjectileSize(actor, weaponentity, '-2 -2 -2', '2 2 2', false, 2, SND_TAG_FIRE, CH_WEAPON_A, WEP_CVAR(seeker, missile_damage) * WEP_CVAR(seeker, missile_count), thiswep.m_id | HITTYPE_BOUNCE | HITTYPE_SECONDARY);
entity missile = new(seeker_tag);
missile.weaponentity_fld = weaponentity;
{
if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR(seeker, missile_refire)))
{
- W_Seeker_Attack(actor, weaponentity);
+ W_Seeker_Attack(thiswep, actor, weaponentity);
weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR(seeker, missile_animtime), w_ready);
}
}
if(WEP_CVAR(seeker, type) == 1)
{
ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(seeker, missile_ammo);
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, missile_ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR(seeker, missile_ammo);
}
else
{
ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(seeker, tag_ammo);
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, tag_ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR(seeker, tag_ammo);
}
return ammo_amount;
}
if(WEP_CVAR(seeker, type) == 1)
{
ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(seeker, tag_ammo);
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, tag_ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR(seeker, tag_ammo);
}
else
{
ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(seeker, flac_ammo);
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, flac_ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR(seeker, flac_ammo);
}
return ammo_amount;
}
// handle accuracy
if(accuracy_isgooddamage(this.realowner, target_victim))
- { accuracy_add(this.realowner, WEP_SHOCKWAVE.m_id, 0, swing_damage); }
+ { accuracy_add(this.realowner, WEP_SHOCKWAVE, 0, swing_damage); }
#ifdef DEBUG_SHOCKWAVE
LOG_INFOF(
setthink(meleetemp, W_Shockwave_Melee_Think);
meleetemp.nextthink = time + WEP_CVAR(shockwave, melee_delay) * W_WeaponRateFactor(actor);
meleetemp.weaponentity_fld = weaponentity;
- W_SetupShot_Range(actor, weaponentity, true, 0, SND_Null, 0, WEP_CVAR(shockwave, melee_damage), WEP_CVAR(shockwave, melee_range), WEP_SHOCKWAVE.m_id | HITTYPE_SECONDARY);
+ W_SetupShot_Range(actor, weaponentity, true, 0, SND_Null, 0, WEP_CVAR(shockwave, melee_damage), WEP_CVAR(shockwave, melee_range), thiswep.m_id | HITTYPE_SECONDARY);
}
// SHOCKWAVE ATTACK MODE
WriteByte(MSG_BROADCAST, etof(actor));
}
-void W_Shockwave_Attack(entity actor, .entity weaponentity)
+void W_Shockwave_Attack(Weapon thiswep, entity actor, .entity weaponentity)
{
// declarations
float multiplier, multiplier_from_accuracy, multiplier_from_distance;
float i, queue = 0;
// set up the shot direction
- W_SetupShot(actor, weaponentity, true, 3, SND_LASERGUN_FIRE, CH_WEAPON_B, WEP_CVAR(shockwave, blast_damage), WEP_SHOCKWAVE.m_id);
+ W_SetupShot(actor, weaponentity, true, 3, SND_LASERGUN_FIRE, CH_WEAPON_B, WEP_CVAR(shockwave, blast_damage), thiswep.m_id);
vector attack_endpos = (w_shotorg + (w_shotdir * WEP_CVAR(shockwave, blast_distance)));
WarpZone_TraceLine(w_shotorg, attack_endpos, MOVE_NOMONSTERS, actor);
vector attack_hitpos = trace_endpos;
WEP_CVAR(shockwave, blast_splash_edgedamage),
WEP_CVAR(shockwave, blast_splash_radius),
w_shotdir * WEP_CVAR(shockwave, blast_splash_force),
- WEP_SHOCKWAVE.m_id,
+ thiswep.m_id,
0,
actor
);
actor,
actor,
final_damage,
- WEP_SHOCKWAVE.m_id,
+ thiswep.m_id,
weaponentity,
head.origin,
final_force
actor,
actor,
final_damage,
- WEP_SHOCKWAVE.m_id,
+ thiswep.m_id,
weaponentity,
head.origin,
final_force
);
if(accuracy_isgooddamage(actor, head))
- accuracy_add(actor, WEP_SHOCKWAVE.m_id, 0, final_damage);
+ accuracy_add(actor, thiswep, 0, final_damage);
#ifdef DEBUG_SHOCKWAVE
LOG_INFOF(
{
if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR(shockwave, blast_animtime)))
{
- W_Shockwave_Attack(actor, weaponentity);
+ W_Shockwave_Attack(thiswep, actor, weaponentity);
actor.(weaponentity).shockwave_blasttime = time + WEP_CVAR(shockwave, blast_refire) * W_WeaponRateFactor(actor);
weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(shockwave, blast_animtime), w_ready);
}
#ifdef SVQC
-void W_Shotgun_Attack(Weapon thiswep, entity actor, .entity weaponentity, float isprimary, float ammocount, float damage, float bullets, float spread, float solidpenetration, float force)
+void W_Shotgun_Attack(Weapon thiswep, entity actor, .entity weaponentity, float isprimary, float ammocount, float damage, float bullets, float spread, float solidpenetration, float force, entity bullet_trail_effect)
{
W_DecreaseAmmo(thiswep, actor, ammocount, weaponentity);
- W_SetupShot(actor, weaponentity, true, 5, SND_SHOTGUN_FIRE, ((isprimary) ? CH_WEAPON_A : CH_WEAPON_SINGLE), damage * bullets, WEP_SHOTGUN.m_id);
+ W_SetupShot(actor, weaponentity, true, 5, SND_SHOTGUN_FIRE, ((isprimary) ? CH_WEAPON_A : CH_WEAPON_SINGLE), damage * bullets, thiswep.m_id);
for(int sc = 0;sc < bullets;sc = sc + 1)
- fireBullet(actor, weaponentity, w_shotorg, w_shotdir, spread, solidpenetration, damage, force, WEP_SHOTGUN.m_id, 0);
-
+ fireBullet(actor, weaponentity, w_shotorg, w_shotdir, spread, solidpenetration, damage, force, thiswep.m_id, bullet_trail_effect);
Send_Effect(EFFECT_SHOTGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, ammocount);
this.realowner.origin + this.realowner.view_ofs,
v_forward * WEP_CVAR_SEC(shotgun, force));
- if(accuracy_isgooddamage(this.realowner, target_victim)) { accuracy_add(this.realowner, WEP_SHOTGUN.m_id, 0, swing_damage); }
+ if(accuracy_isgooddamage(this.realowner, target_victim)) { accuracy_add(this.realowner, WEP_SHOTGUN, 0, swing_damage); }
// draw large red flash for debugging
//te_customflash(targpos, 200, 2, '15 0 0');
}
sound(actor, CH_WEAPON_SINGLE, SND_Null, VOL_BASE, ATTN_NORM); // kill previous sound
- W_Shotgun_Attack(WEP_SHOTGUN, actor, weaponentity, true,
+ W_Shotgun_Attack(thiswep, actor, weaponentity, true,
WEP_CVAR_PRI(shotgun, ammo),
WEP_CVAR_PRI(shotgun, damage),
WEP_CVAR_PRI(shotgun, bullets),
WEP_CVAR_PRI(shotgun, spread),
WEP_CVAR_PRI(shotgun, solidpenetration),
- WEP_CVAR_PRI(shotgun, force)); // actually is secondary, but we trick the last shot into playing full reload sound
+ WEP_CVAR_PRI(shotgun, force),
+ EFFECT_BULLET_WEAK); // actually is secondary, but we trick the last shot into playing full reload sound
weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), w_ready);
}
void W_Shotgun_Attack3_Frame1(Weapon thiswep, entity actor, .entity weaponentity, int fire)
return;
}
- W_Shotgun_Attack(WEP_SHOTGUN, actor, weaponentity, false,
+ W_Shotgun_Attack(thiswep, actor, weaponentity, false,
WEP_CVAR_PRI(shotgun, ammo),
WEP_CVAR_PRI(shotgun, damage),
WEP_CVAR_PRI(shotgun, bullets),
WEP_CVAR_PRI(shotgun, spread),
WEP_CVAR_PRI(shotgun, solidpenetration),
- WEP_CVAR_PRI(shotgun, force));
+ WEP_CVAR_PRI(shotgun, force),
+ EFFECT_BULLET_WEAK);
weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), W_Shotgun_Attack3_Frame2);
}
WEP_CVAR_PRI(shotgun, bullets),
WEP_CVAR_PRI(shotgun, spread),
WEP_CVAR_PRI(shotgun, solidpenetration),
- WEP_CVAR_PRI(shotgun, force));
+ WEP_CVAR_PRI(shotgun, force),
+ EFFECT_BULLET_WEAK);
actor.(weaponentity).shotgun_primarytime = time + WEP_CVAR_PRI(shotgun, refire) * W_WeaponRateFactor(actor);
weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(shotgun, animtime), w_ready);
}
WEP_CVAR_PRI(shotgun, bullets),
WEP_CVAR_PRI(shotgun, spread),
WEP_CVAR_PRI(shotgun, solidpenetration),
- WEP_CVAR_PRI(shotgun, force));
+ WEP_CVAR_PRI(shotgun, force),
+ EFFECT_BULLET_WEAK);
actor.(weaponentity).shotgun_primarytime = time + WEP_CVAR_SEC(shotgun, alt_refire) * W_WeaponRateFactor(actor);
weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), W_Shotgun_Attack3_Frame1);
}
METHOD(Shotgun, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
{
float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(shotgun, ammo);
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_SHOTGUN.m_id]) >= WEP_CVAR_PRI(shotgun, ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(shotgun, ammo);
return ammo_amount;
}
METHOD(Shotgun, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
case 2: // secondary triple shot
{
float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(shotgun, ammo);
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_SHOTGUN.m_id]) >= WEP_CVAR_PRI(shotgun, ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(shotgun, ammo);
return ammo_amount;
}
default: return false; // secondary unavailable
void W_RocketMinsta_Explosion(entity actor, .entity weaponentity, vector loc)
{
if(accuracy_canbegooddamage(actor))
- accuracy_add(actor, WEP_DEVASTATOR.m_id, autocvar_g_rm_damage, 0);
+ accuracy_add(actor, WEP_DEVASTATOR, autocvar_g_rm_damage, 0);
entity dmgent = spawn();
dmgent.owner = dmgent.realowner = actor;
setorigin(dmgent, loc);
bool flying = IsFlying(actor); // do this BEFORE to make the trace values from FireRailgunBullet last
float vaporizer_damage = ((WEP_CVAR_PRI(vaporizer, damage) > 0) ? WEP_CVAR_PRI(vaporizer, damage) : 10000);
- W_SetupShot(actor, weaponentity, true, 0, SND_Null, CH_WEAPON_A, vaporizer_damage, WEP_VAPORIZER.m_id);
+ W_SetupShot(actor, weaponentity, true, 0, SND_Null, CH_WEAPON_A, vaporizer_damage, thiswep.m_id);
// handle sound separately so we can change the volume
// added bonus: no longer plays the strength sound (strength gives no bonus to instakill anyway)
sound (actor, CH_WEAPON_A, SND_MINSTANEXFIRE, VOL_BASE * 0.8, ATTEN_NORM);
yoda = 0;
damage_goodhits = 0;
- FireRailgunBullet(actor, weaponentity, w_shotorg, w_shotorg + w_shotdir * max_shot_distance, vaporizer_damage, WEP_CVAR_PRI(vaporizer, force), 0, 0, 0, 0, WEP_VAPORIZER.m_id);
+ FireRailgunBullet(actor, weaponentity, w_shotorg, w_shotorg + w_shotdir * max_shot_distance, vaporizer_damage, WEP_CVAR_PRI(vaporizer, force), 0, 0, 0, 0, thiswep.m_id);
// do this now, as goodhits is disabled below
SendCSQCVaporizerBeamParticle(actor, damage_goodhits);
{
float vaporizer_ammo = ((autocvar_g_instagib) ? 1 : WEP_CVAR_PRI(vaporizer, ammo));
float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= vaporizer_ammo;
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_VAPORIZER.m_id]) >= vaporizer_ammo;
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= vaporizer_ammo;
return ammo_amount;
}
METHOD(Vaporizer, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
if(!WEP_CVAR_SEC(vaporizer, ammo))
return true;
float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(vaporizer, ammo);
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_VAPORIZER.m_id]) >= WEP_CVAR_SEC(vaporizer, ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(vaporizer, ammo);
return ammo_amount;
}
METHOD(Vaporizer, wr_resetplayer, void(entity thiswep, entity actor))
myforcehalflife = WEP_CVAR_BOTH(vortex, !issecondary, damagefalloff_forcehalflife);
myammo = WEP_CVAR_BOTH(vortex, !issecondary, ammo);
- float dtype = WEP_VORTEX.m_id;
+ float dtype = thiswep.m_id;
if(WEP_CVAR_BOTH(vortex, !issecondary, armorpierce))
dtype |= HITTYPE_ARMORPIERCE;
{
actor.(weaponentity).clip_load = max(WEP_CVAR_SEC(vortex, ammo), actor.(weaponentity).clip_load - WEP_CVAR_SEC(vortex, ammo) * dt);
}
- actor.(weaponentity).(weapon_load[WEP_VORTEX.m_id]) = actor.(weaponentity).clip_load;
+ actor.(weaponentity).(weapon_load[thiswep.m_id]) = actor.(weaponentity).clip_load;
}
else
{
METHOD(Vortex, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
{
float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(vortex, ammo);
- ammo_amount += (autocvar_g_balance_vortex_reload_ammo && actor.(weaponentity).(weapon_load[WEP_VORTEX.m_id]) >= WEP_CVAR_PRI(vortex, ammo));
+ ammo_amount += (autocvar_g_balance_vortex_reload_ammo && actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(vortex, ammo));
return ammo_amount;
}
METHOD(Vortex, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
{
// don't allow charging if we don't have enough ammo
float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(vortex, ammo);
- ammo_amount += actor.(weaponentity).(weapon_load[WEP_VORTEX.m_id]) >= WEP_CVAR_SEC(vortex, ammo);
+ ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(vortex, ammo);
return ammo_amount;
}
else
\
PROP(false, m_alpha, WEPENT_SET_NORMAL, \
{ WriteByte(chan, rint(bound(-1, 254 * this.m_alpha, 254) - -1)); }, \
- { (viewmodels[this.m_wepent_slot]).alpha = (ReadByte() + -1) / 254; }) \
+ { (viewmodels[this.m_wepent_slot]).m_alpha = (ReadByte() + -1) / 254; }) \
\
PROP(false, vortex_charge, WEPENT_SET_NORMAL, \
{ WriteByte(chan, this.vortex_charge * 255); }, \
.Weapon switchingweapon;
.Weapon switchweapon;
+ .float m_alpha;
+
// only for Porto
.bool angles_held_status;
.vector angles_held;
/** Called once per CSQC_UpdateView() */
void CSQCPlayer_SetCamera()
{
- const vector v0 = ((intermission && !autocvar_cl_movement_intermissionrunning) ? '0 0 0' : pmove_vel); // TRICK: pmove_vel is set by the engine when we get here. No need to network velocity
- const float vh = PHYS_VIEWHEIGHT(NULL);
- const vector pl_viewofs = PHYS_PL_VIEWOFS(NULL);
- const vector pl_viewofs_crouch = PHYS_PL_CROUCH_VIEWOFS(NULL);
- const entity e = csqcplayer;
+ vector v0 = ((intermission && !autocvar_cl_movement_intermissionrunning) ? '0 0 0' : pmove_vel); // TRICK: pmove_vel is set by the engine when we get here. No need to network velocity
+ float vh = PHYS_VIEWHEIGHT(NULL);
+ vector pl_viewofs = PHYS_PL_VIEWOFS(NULL);
+ vector pl_viewofs_crouch = PHYS_PL_CROUCH_VIEWOFS(NULL);
+ entity e = csqcplayer;
if (e)
{
if (servercommandframe == 0 || clientcommandframe == 0)
}
else
{
- const int flg = e.iflags; e.iflags &= ~(IFLAG_ORIGIN | IFLAG_ANGLES);
+ int flg = e.iflags; e.iflags &= ~(IFLAG_ORIGIN | IFLAG_ANGLES);
InterpolateOrigin_Do(e);
e.iflags = flg;
if (csqcplayer_status == CSQCPLAYERSTATUS_FROMSERVER)
{
- const vector o = e.origin;
+ vector o = e.origin;
csqcplayer_status = CSQCPLAYERSTATUS_PREDICTED;
CSQCPlayer_PredictTo(e, servercommandframe + 1, false);
CSQCPlayer_SetPredictionError(e.origin - o, e.velocity - v0, pmove_onground - IS_ONGROUND(e));
void WarpZone_Touch(entity this, entity toucher);
NET_HANDLE(ENT_CLIENT_WARPZONE, bool isnew)
{
+ if(!warpzone_warpzones_exist)
+ cvar_settemp("r_water", "1"); // HACK for DarkPlaces: always enable reflections when a map has warpzones
warpzone_warpzones_exist = 1;
if (!this.enemy)
{
NET_HANDLE(ENT_CLIENT_WARPZONE_CAMERA, bool isnew)
{
+ if(!warpzone_cameras_exist)
+ cvar_settemp("r_water", "1"); // HACK for DarkPlaces: always enable reflections when a map has cameras
warpzone_cameras_exist = 1;
this.classname = "func_warpzone_camera";
LOG_INFO(_(" sync - reloads all cvars on the current menu page"));
LOG_INFO(_(" directmenu ITEM - select a menu item as main item"));
LOG_INFO(_(" dumptree - dump the state of the menu as a tree to the console"));
+ LOG_INFO("\n");
LOG_INFO("Generic commands shared by all programs:");
GenericCommand_macro_help();
#include "item/container.qh"
#include "item/borderimage.qh"
- METHOD(Item, destroy, void(Item this))
+ METHOD(MenuItem, destroy, void(MenuItem this))
{
// free memory associated with this
}
- METHOD(Item, relinquishFocus, void(Item this))
+ METHOD(MenuItem, relinquishFocus, void(MenuItem this))
{
entity par = this.parent;
if (!par) return;
if (par.instanceOfContainer) par.setFocus(par, NULL);
}
- METHOD(Item, resizeNotify, void(Item this, vector relOrigin, vector relSize, vector absOrigin, vector absSize))
+ METHOD(MenuItem, resizeNotify, void(MenuItem this, vector relOrigin, vector relSize, vector absOrigin, vector absSize))
{
this.origin = absOrigin;
this.size = absSize;
}
int autocvar_menu_showboxes;
- METHOD(Item, draw, void(Item this))
+ METHOD(MenuItem, draw, void(MenuItem this))
{
if (!autocvar_menu_showboxes) return;
vector rgb = '1 0 1';
}
}
- METHOD(Item, showNotify, void(Item this))
+ METHOD(MenuItem, showNotify, void(MenuItem this))
{}
- METHOD(Item, hideNotify, void(Item this))
+ METHOD(MenuItem, hideNotify, void(MenuItem this))
{}
- METHOD(Item, keyDown, float(Item this, float scan, float ascii, float shift))
+ METHOD(MenuItem, keyDown, float(MenuItem this, float scan, float ascii, float shift))
{
return 0; // unhandled
}
- METHOD(Item, keyUp, float(Item this, float scan, float ascii, float shift))
+ METHOD(MenuItem, keyUp, float(MenuItem this, float scan, float ascii, float shift))
{
return 0; // unhandled
}
- METHOD(Item, mouseMove, float(Item this, vector pos))
+ METHOD(MenuItem, mouseMove, float(MenuItem this, vector pos))
{
return 0; // unhandled
}
- METHOD(Item, mousePress, bool(Item this, vector pos))
+ METHOD(MenuItem, mousePress, bool(MenuItem this, vector pos))
{
return false; // unhandled
}
- METHOD(Item, mouseDrag, float(Item this, vector pos))
+ METHOD(MenuItem, mouseDrag, float(MenuItem this, vector pos))
{
return 0; // unhandled
}
- METHOD(Item, mouseRelease, float(Item this, vector pos))
+ METHOD(MenuItem, mouseRelease, float(MenuItem this, vector pos))
{
return 0; // unhandled
}
void m_play_focus_sound();
- METHOD(Item, focusEnter, void(Item this))
+ METHOD(MenuItem, focusEnter, void(MenuItem this))
{
if (this.allowFocusSound) m_play_focus_sound();
}
- METHOD(Item, focusLeave, void(Item this))
+ METHOD(MenuItem, focusLeave, void(MenuItem this))
{}
- METHOD(Item, toString, string(Item this))
+ METHOD(MenuItem, toString, string(MenuItem this))
{
return string_null;
}
#include "draw.qh"
#include "menu.qh"
-CLASS(Item, Object)
- METHOD(Item, draw, void(Item));
- METHOD(Item, keyDown, float(Item, float, float, float));
- METHOD(Item, keyUp, float(Item, float, float, float));
- METHOD(Item, mouseMove, float(Item, vector));
- METHOD(Item, mousePress, bool(Item this, vector pos));
- METHOD(Item, mouseDrag, float(Item, vector));
- METHOD(Item, mouseRelease, float(Item, vector));
- METHOD(Item, focusEnter, void(Item));
- METHOD(Item, focusLeave, void(Item));
- METHOD(Item, resizeNotify, void(Item, vector, vector, vector, vector));
- METHOD(Item, relinquishFocus, void(Item));
- METHOD(Item, showNotify, void(Item));
- METHOD(Item, hideNotify, void(Item));
- METHOD(Item, toString, string(Item));
- METHOD(Item, destroy, void(Item));
- ATTRIB(Item, focused, float, 0);
- ATTRIB(Item, focusable, float, 0);
- ATTRIB(Item, allowFocusSound, float, 0);
- ATTRIB(Item, parent, entity);
- ATTRIB(Item, preferredFocusPriority, float, 0);
- ATTRIB(Item, origin, vector, '0 0 0');
- ATTRIB(Item, size, vector, '0 0 0');
- ATTRIB(Item, tooltip, string);
-ENDCLASS(Item)
+CLASS(MenuItem, Object)
+ METHOD(MenuItem, draw, void(MenuItem));
+ METHOD(MenuItem, keyDown, float(MenuItem, float, float, float));
+ METHOD(MenuItem, keyUp, float(MenuItem, float, float, float));
+ METHOD(MenuItem, mouseMove, float(MenuItem, vector));
+ METHOD(MenuItem, mousePress, bool(MenuItem this, vector pos));
+ METHOD(MenuItem, mouseDrag, float(MenuItem, vector));
+ METHOD(MenuItem, mouseRelease, float(MenuItem, vector));
+ METHOD(MenuItem, focusEnter, void(MenuItem));
+ METHOD(MenuItem, focusLeave, void(MenuItem));
+ METHOD(MenuItem, resizeNotify, void(MenuItem, vector, vector, vector, vector));
+ METHOD(MenuItem, relinquishFocus, void(MenuItem));
+ METHOD(MenuItem, showNotify, void(MenuItem));
+ METHOD(MenuItem, hideNotify, void(MenuItem));
+ METHOD(MenuItem, toString, string(MenuItem));
+ METHOD(MenuItem, destroy, void(MenuItem));
+ ATTRIB(MenuItem, focused, float, 0);
+ ATTRIB(MenuItem, focusable, float, 0);
+ ATTRIB(MenuItem, allowFocusSound, float, 0);
+ ATTRIB(MenuItem, parent, entity);
+ ATTRIB(MenuItem, preferredFocusPriority, float, 0);
+ ATTRIB(MenuItem, origin, vector, '0 0 0');
+ ATTRIB(MenuItem, size, vector, '0 0 0');
+ ATTRIB(MenuItem, tooltip, string);
+ENDCLASS(MenuItem)
#include <menu/item.qh>
-CLASS(Container, Item)
+CLASS(Container, MenuItem)
METHOD(Container, draw, void(entity));
METHOD(Container, keyUp, float(entity, float, float, float));
METHOD(Container, keyDown, float(entity, float, float, float));
#pragma once
#include "../item.qh"
-CLASS(Image, Item)
+CLASS(Image, MenuItem)
METHOD(Image, configureImage, void(entity, string));
METHOD(Image, draw, void(entity));
METHOD(Image, toString, string(entity));
}
// skipping SUPER(InputBox).draw(me);
- Item_draw(me);
+ MenuItem_draw(me);
}
void InputBox_showNotify(entity me)
#pragma once
#include "../item.qh"
-CLASS(Label, Item)
+CLASS(Label, MenuItem)
METHOD(Label, configureLabel, void(entity, string, float, float));
METHOD(Label, draw, void(entity));
METHOD(Label, resizeNotify, void(entity, vector, vector, vector, vector));
}
}
AUTOCVAR(menu_scroll_averaging_time, float, 0.16, "smooth scroll averaging time");
-// scroll faster while dragging the scrollbar
+ // scroll faster while dragging the scrollbar
AUTOCVAR(menu_scroll_averaging_time_pressed, float, 0.06, "smooth scroll averaging time when dragging the scrollbar");
void ListBox_draw(entity me)
{
if (me.scrollPos != me.scrollPosTarget)
{
float averaging_time = (me.pressed == 1)
- ? autocvar_menu_scroll_averaging_time_pressed
+ ? autocvar_menu_scroll_averaging_time_pressed
: autocvar_menu_scroll_averaging_time;
// this formula works with whatever framerate
float f = averaging_time ? exp(-frametime / averaging_time) : 0;
#pragma once
#include "../item.qh"
-CLASS(ListBox, Item)
+CLASS(ListBox, MenuItem)
METHOD(ListBox, resizeNotify, void(entity, vector, vector, vector, vector));
METHOD(ListBox, configureListBox, void(entity, float, float));
METHOD(ListBox, draw, void(entity));
me.configureXonoticListBox(me);
me.campaignGlob = search_begin("maps/campaign*.txt", true, true);
me.loadCvars(me);
- me.campaignGo(me, 0); // takes care of enabling/disabling buttons too
+ me.campaignGo(me, 0); // it makes work buttons too
}
void XonoticCampaignList_destroy(entity me)
void XonoticCampaignList_campaignGo(entity me, float step)
{
- float canNext, canPrev;
string s;
float i, j, n;
- canNext = canPrev = 0;
-
if(me.campaignGlob >= 0)
{
n = search_getsize(me.campaignGlob);
s = substring(s, 13, strlen(s) - 17);
cvar_set("g_campaign_name", s);
me.loadCvars(me);
- canNext = (j != n - 1);
- canPrev = (j != 0);
+ me.hasNextCampaign = (j != n - 1);
+ me.hasPrevCampaign = (j != 0);
}
}
-
- if(me.buttonNext)
- me.buttonNext.disabled = !canNext;
- if(me.buttonPrev)
- me.buttonPrev.disabled = !canPrev;
}
void MultiCampaign_Next(entity btn, entity me)
void XonoticCampaignList_draw(entity me)
{
+ if(me.buttonNext)
+ me.buttonNext.disabled = !me.hasNextCampaign;
+ if(me.buttonPrev)
+ me.buttonPrev.disabled = !me.hasPrevCampaign;
+
if(cvar(me.cvarName) != me.campaignIndex || cvar_string("g_campaign_name") != campaign_name)
me.loadCvars(me);
SUPER(XonoticCampaignList).draw(me);
me.itemAbsSize = '0 0 0';
SUPER(XonoticCampaignList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
- me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
- me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
+ me.itemAbsSize.y = absSize.y * me.itemHeight;
+ me.itemAbsSize.x = absSize.x * (1 - me.controlWidth);
+ me.realFontSize.y = me.fontSize / me.itemAbsSize.y;
+ me.realFontSize.x = me.fontSize / me.itemAbsSize.x;
me.realUpperMargin1 = 0.5 * me.realFontSize.y;
me.realUpperMargin2 = me.realUpperMargin1 + 2 * me.realFontSize.y;
ATTRIB(XonoticCampaignList, cvarName, string);
METHOD(XonoticCampaignList, loadCvars, void(entity));
METHOD(XonoticCampaignList, saveCvars, void(entity));
+ ATTRIB(XonoticCampaignList, hasNextCampaign, bool, false);
+ ATTRIB(XonoticCampaignList, hasPrevCampaign, bool, false);
ATTRIB(XonoticCampaignList, buttonNext, entity);
ATTRIB(XonoticCampaignList, buttonPrev, entity);
#pragma once
#include "../item.qh"
-CLASS(XonoticCrosshairPreview, Item)
+CLASS(XonoticCrosshairPreview, MenuItem)
METHOD(XonoticCrosshairPreview, configureXonoticCrosshairPreview, void(entity));
METHOD(XonoticCrosshairPreview, draw, void(entity));
ATTRIB(XonoticCrosshairPreview, src, string);
me.itemAbsSize = '0 0 0';
SUPER(XonoticDemoList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
- me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
- me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
+ me.itemAbsSize.y = absSize.y * me.itemHeight;
+ me.itemAbsSize.x = absSize.x * (1 - me.controlWidth);
+ me.realFontSize.y = me.fontSize / me.itemAbsSize.y;
+ me.realFontSize.x = me.fontSize / me.itemAbsSize.x;
me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
me.columnNameOrigin = me.realFontSize.x;
void GameType_ConfigureSliders_for_CurrentGametype(entity me)
{
- switch(MapInfo_CurrentGametype())
- {
- case MAPINFO_TYPE_CA: GameType_ConfigureSliders(me, _("Frag limit:"), 5, 100, 5, "fraglimit_override", "g_ca_teams_override", _("The amount of frags needed before the match will end")); break;
- case MAPINFO_TYPE_FREEZETAG: GameType_ConfigureSliders(me, _("Frag limit:"), 5, 100, 5, "fraglimit_override", "g_freezetag_teams_override", _("The amount of frags needed before the match will end")); break;
- case MAPINFO_TYPE_CTF: GameType_ConfigureSliders(me, _("Capture limit:"), 1, 20, 1, "capturelimit_override", string_null, _("The amount of captures needed before the match will end")); break;
- case MAPINFO_TYPE_DOMINATION: GameType_ConfigureSliders(me, _("Point limit:"), 50, 500, 10, "g_domination_point_limit", "g_domination_teams_override", _("The amount of points needed before the match will end")); break;
- case MAPINFO_TYPE_KEYHUNT: GameType_ConfigureSliders(me, _("Point limit:"), 200, 1500, 50, "g_keyhunt_point_limit", "g_keyhunt_teams_override", _("The amount of points needed before the match will end")); break;
- case MAPINFO_TYPE_LMS: GameType_ConfigureSliders(me, _("Lives:"), 3, 50, 1, "g_lms_lives_override", string_null, string_null); break;
- case MAPINFO_TYPE_RACE: GameType_ConfigureSliders(me, _("Laps:"), 1, 25, 1, "g_race_laps_limit", string_null, string_null); break;
- case MAPINFO_TYPE_NEXBALL: GameType_ConfigureSliders(me, _("Goals:"), 1, 50, 1, "g_nexball_goallimit", string_null, _("The amount of goals needed before the match will end")); break;
- case MAPINFO_TYPE_ASSAULT: GameType_ConfigureSliders(me, _("Point limit:"), 50, 500, 10, string_null, string_null, string_null); break;
- case MAPINFO_TYPE_ONSLAUGHT: GameType_ConfigureSliders(me, _("Point limit:"), 50, 500, 10, string_null, string_null, string_null); break;
- case MAPINFO_TYPE_CTS: GameType_ConfigureSliders(me, _("Point limit:"), 50, 500, 10, string_null, string_null, string_null); break;
- case MAPINFO_TYPE_INVASION: GameType_ConfigureSliders(me, _("Point limit:"), 50, 500, 10, string_null, string_null, string_null); break;
- case MAPINFO_TYPE_TEAM_DEATHMATCH: GameType_ConfigureSliders(me, _("Point limit:"), 5, 100, 5, "g_tdm_point_limit", "g_tdm_teams_override", _("The amount of points needed before the match will end")); break;
- default: GameType_ConfigureSliders(me, _("Frag limit:"), 5, 100, 5, "fraglimit_override", string_null, _("The amount of frags needed before the match will end")); break;
- }
+ Gametype gt = MapInfo_CurrentGametype();
+ gt.m_configuremenu(gt, me, GameType_ConfigureSliders);
}
entity makeXonoticServerCreateTab()
s = "";
for(int j = 0; j < n; ++j)
{
- FOREACH(Weapons, it != WEP_Null, {
- if(argv(j) == it.netname)
- s = cons_mid(s, " & ", it.m_name);
- });
+ Weapon wep = Weapons_fromstr(argv(j));
+ if(wep != WEP_Null)
+ {
+ s = cons_mid(s, " & ", wep.m_name);
+ }
}
s = sprintf(_("%s Arena"), s);
me.TD(me, 1, 3, e = makeXonoticCheckBoxEx_T(2, 1, "notification_allow_chatboxprint", _("Display all info messages in the chatbox"), "-"));
me.TR(me);
me.TD(me, 1, 3, e = makeXonoticCheckBoxEx_T(2, 1, "notification_INFO_QUIT_DISCONNECT", _("Display player statuses in the chatbox"), "-"));
- makeMulti(e, "notification_INFO_QUIT_KICK_IDLING notification_INFO_JOIN_CONNECT_TEAM");
+ makeMulti(e, "notification_INFO_QUIT_KICK_IDLING notification_INFO_JOIN_CONNECT");
me.TR(me);
me.TR(me);
me.TD(me, 1, 3, e = makeXonoticCheckBox_T(0, "notification_CENTER_POWERUP_INVISIBILITY", _("Powerup notifications"), "-"));
e.configureXonoticTextSliderValues(e);
me.TR(me);
me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Input packets/s:")));
- me.TD(me, 1, 2, e = makeXonoticSlider_T(20, 100, 5, "cl_netfps",
+ me.TD(me, 1, 2, e = makeXonoticSlider_T(30, 180, 5, "cl_netfps",
_("How many input packets to send to the server each second")));
me.TR(me);
me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Server queries/s:")));
me.itemAbsSize = '0 0 0';
SUPER(XonoticGametypeList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
- me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
- me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
+ me.itemAbsSize.y = absSize.y * me.itemHeight;
+ me.itemAbsSize.x = absSize.x * (1 - me.controlWidth);
+ me.realFontSize.y = me.fontSize / me.itemAbsSize.y;
+ me.realFontSize.x = me.fontSize / me.itemAbsSize.x;
me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
me.columnIconOrigin = 0;
me.columnIconSize = me.itemAbsSize.y / me.itemAbsSize.x;
me.itemAbsSize = '0 0 0';
SUPER(XonoticHUDSkinList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
- me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
- me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
+ me.itemAbsSize.y = absSize.y * me.itemHeight;
+ me.itemAbsSize.x = absSize.x * (1 - me.controlWidth);
+ me.realFontSize.y = me.fontSize / me.itemAbsSize.y;
+ me.realFontSize.x = me.fontSize / me.itemAbsSize.x;
me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
me.columnNameOrigin = me.realFontSize.x;
KEYBIND_DEF("+use" , _("drop key / drop flag"));
KEYBIND_DEF("" , "");
KEYBIND_DEF("" , _("Misc"));
+ KEYBIND_DEF("kill" , _("respawn"));
KEYBIND_DEF("quickmenu" , _("quick menu"));
KEYBIND_DEF("menu_showsandboxtools" , _("sandbox menu"));
KEYBIND_DEF("+button8" , _("drag object"));
me.itemAbsSize = '0 0 0';
SUPER(XonoticMapList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
- me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
- me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
+ me.itemAbsSize.y = absSize.y * me.itemHeight;
+ me.itemAbsSize.x = absSize.x * (1 - me.controlWidth);
+ me.realFontSize.y = me.fontSize / me.itemAbsSize.y;
+ me.realFontSize.x = me.fontSize / me.itemAbsSize.x;
me.realUpperMargin1 = 0.5 * (1 - 2.5 * me.realFontSize.y);
me.realUpperMargin2 = me.realUpperMargin1 + 1.5 * me.realFontSize.y;
#pragma once
#include "../item.qh"
-CLASS(XonoticPicker, Item)
+CLASS(XonoticPicker, MenuItem)
METHOD(XonoticPicker, configureXonoticPicker, void(entity));
METHOD(XonoticPicker, mousePress, bool(XonoticPicker this, vector pos));
METHOD(XonoticPicker, mouseRelease, float(entity, vector));
me.itemAbsSize = '0 0 0';
SUPER(XonoticPlayerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
- me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
- me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
+ me.itemAbsSize.y = absSize.y * me.itemHeight;
+ me.itemAbsSize.x = absSize.x * (1 - me.controlWidth);
+ me.realFontSize.y = me.fontSize / me.itemAbsSize.y;
+ me.realFontSize.x = me.fontSize / me.itemAbsSize.x;
me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
// this list does 1 char left and right margin
me.itemAbsSize = '0 0 0';
SUPER(XonoticPlayList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
- me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
- me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
+ me.itemAbsSize.y = absSize.y * me.itemHeight;
+ me.itemAbsSize.x = absSize.x * (1 - me.controlWidth);
+ me.realFontSize.y = me.fontSize / me.itemAbsSize.y;
+ me.realFontSize.x = me.fontSize / me.itemAbsSize.x;
me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
me.columnNumberOrigin = 0;
me.itemAbsSize = '0 0 0';
SUPER(XonoticScreenshotList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
- me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
- me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
+ me.itemAbsSize.y = absSize.y * me.itemHeight;
+ me.itemAbsSize.x = absSize.x * (1 - me.controlWidth);
+ me.realFontSize.y = me.fontSize / me.itemAbsSize.y;
+ me.realFontSize.x = me.fontSize / me.itemAbsSize.x;
me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
me.columnNameOrigin = me.realFontSize.x;
me.itemAbsSize = '0 0 0';
SUPER(XonoticSkinList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
- me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
- me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
+ me.itemAbsSize.y = absSize.y * me.itemHeight;
+ me.itemAbsSize.x = absSize.x * (1 - me.controlWidth);
+ me.realFontSize.y = me.fontSize / me.itemAbsSize.y;
+ me.realFontSize.x = me.fontSize / me.itemAbsSize.x;
me.realUpperMargin1 = 0.5 * (1 - 2.5 * me.realFontSize.y);
me.realUpperMargin2 = me.realUpperMargin1 + 1.5 * me.realFontSize.y;
me.itemAbsSize = '0 0 0';
SUPER(XonoticSoundList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
- me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
- me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
+ me.itemAbsSize.y = absSize.y * me.itemHeight;
+ me.itemAbsSize.x = absSize.x * (1 - me.controlWidth);
+ me.realFontSize.y = me.fontSize / me.itemAbsSize.y;
+ me.realFontSize.x = me.fontSize / me.itemAbsSize.x;
me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
me.columnNumberOrigin = 0;
me.itemAbsSize = '0 0 0';
SUPER(XonoticStatsList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
- me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
- me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
+ me.itemAbsSize.y = absSize.y * me.itemHeight;
+ me.itemAbsSize.x = absSize.x * (1 - me.controlWidth);
+ me.realFontSize.y = me.fontSize / me.itemAbsSize.y;
+ me.realFontSize.x = me.fontSize / me.itemAbsSize.x;
me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
#if 0
GAMETYPE(MAPINFO_TYPE_NEXBALL) \
GAMETYPE(MAPINFO_TYPE_ONSLAUGHT) \
GAMETYPE(MAPINFO_TYPE_ASSAULT) \
+ /* GAMETYPE(MAPINFO_TYPE_DUEL) */ \
/* GAMETYPE(MAPINFO_TYPE_INVASION) */ \
/**/
#include <server/campaign.qc>
#include <server/cheats.qc>
#include <server/client.qc>
+#include <server/clientkill.qc>
#include <server/g_damage.qc>
#include <server/g_hook.qc>
#include <server/g_world.qc>
#include <server/handicap.qc>
#include <server/impulse.qc>
#include <server/ipban.qc>
-#include <server/item_key.qc>
#include <server/items.qc>
#include <server/mapvoting.qc>
#include <server/matrix.qc>
#include <server/campaign.qh>
#include <server/cheats.qh>
#include <server/client.qh>
+#include <server/clientkill.qh>
#include <server/g_damage.qh>
#include <server/g_hook.qh>
#include <server/g_world.qh>
#include <server/handicap.qh>
#include <server/impulse.qh>
#include <server/ipban.qh>
-#include <server/item_key.qh>
#include <server/items.qh>
#include <server/mapvoting.qh>
#include <server/matrix.qh>
//float autocvar_g_balance_powerup_strength_time;
float autocvar_g_balance_superweapons_time;
float autocvar_g_balance_selfdamagepercent;
-bool autocvar_g_balance_teams;
-bool autocvar_g_balance_teams_prevent_imbalance;
-//float autocvar_g_balance_teams_scorefactor;
float autocvar_g_ballistics_density_corpse;
float autocvar_g_ballistics_density_player;
float autocvar_g_ballistics_mindistance;
string autocvar_g_ban_sync_trusted_servers;
bool autocvar_g_ban_sync_trusted_servers_verify;
string autocvar_g_ban_sync_uri;
+bool autocvar_g_ban_telluser = true;
string autocvar_g_banned_list;
bool autocvar_g_banned_list_idmode;
bool autocvar_g_botclip_collisions;
#define autocvar_g_campaign_forceteam cvar("g_campaign_forceteam")
int autocvar_g_campaign_skill;
int autocvar_g_casings;
-bool autocvar_g_changeteam_banned;
float autocvar_g_chat_flood_burst;
float autocvar_g_chat_flood_burst_team;
float autocvar_g_chat_flood_burst_tell;
bool autocvar_g_chat_teamcolors;
bool autocvar_g_chat_tellprivacy;
bool autocvar_g_forced_respawn;
-string autocvar_g_forced_team_blue;
-string autocvar_g_forced_team_otherwise;
-string autocvar_g_forced_team_pink;
-string autocvar_g_forced_team_red;
-string autocvar_g_forced_team_yellow;
+string autocvar_g_forced_team_otherwise; // TODO: Move to teamplay.qc
#define autocvar_g_friendlyfire cvar("g_friendlyfire")
#define autocvar_g_friendlyfire_virtual cvar("g_friendlyfire_virtual")
#define autocvar_g_friendlyfire_virtual_force cvar("g_friendlyfire_virtual_force")
float autocvar_sv_maxspeed;
string autocvar_sv_motd;
bool autocvar_sv_precacheplayermodels;
-//float autocvar_sv_precacheweapons; // WEAPONTODO?
bool autocvar_sv_q3acompat_machineshotgunswap;
bool autocvar_sv_servermodelsonly;
int autocvar_sv_spectate;
string autocvar_sv_weaponstats_file;
float autocvar_sv_gibhealth;
float autocvar_sys_ticrate;
-bool autocvar_teamplay_lockonrestart;
-int autocvar_teamplay_mode;
#define autocvar_timelimit cvar("timelimit")
#define autocvar_timelimit_override cvar("timelimit_override")
float autocvar_timelimit_increment;
float autocvar_sv_track_canjump;
bool autocvar_sv_showspectators;
bool autocvar_g_weaponswitch_debug;
+bool autocvar_g_weaponswitch_debug_alternate;
bool autocvar_g_allow_checkpoints;
+bool autocvar_sv_vq3compat_changehitbox = false;
.entity bot_basewaypoint;
.bool navigation_dynamicgoal;
void navigation_dynamicgoal_init(entity this, bool initially_static);
-void navigation_dynamicgoal_set(entity this);
+void navigation_dynamicgoal_set(entity this, entity dropper);
void navigation_dynamicgoal_unset(entity this);
entity navigation_findnearestwaypoint(entity ent, float walkfromwp);
void navigation_goalrating_end(entity this);
void bot_lagfunc(entity this, float t, float f1, float f2, entity e1, vector v1, vector v2, vector v3, vector v4)
{
- if(this.flags & FL_INWATER)
- {
- this.bot_aimtarg = NULL;
- return;
- }
this.bot_aimtarg = e1;
this.bot_aimlatency = CS(this).ping; // FIXME? Shouldn't this be in the lag item?
//this.bot_aimorigin = v1;
float dist, delta_t, blend;
vector desiredang, diffang;
+ this.bot_aimdir_executed = true;
+
//dprint("aim ", this.netname, ": old:", vtos(this.v_angle));
// make sure v_angle is sane first
this.v_angle_y = this.v_angle.y - floor(this.v_angle.y / 360) * 360;
+ this.bot_4th_order_aimfilter * autocvar_bot_ai_aimskill_order_mix_4th
+ this.bot_5th_order_aimfilter * autocvar_bot_ai_aimskill_order_mix_5th
);
+ desiredang.x = bound(-90, desiredang.x, 90);
// calculate turn angles
diffang = desiredang - this.bot_mouseaim;
.vector lag5_vec3;
.vector lag5_vec4;
+.bool bot_aimdir_executed;
.float bot_badaimtime;
.float bot_aimthinktime;
.float bot_prevaimtime;
// if dead, just wait until we can respawn
if (IS_DEAD(this))
{
+ if (bot_waypoint_queue_owner == this)
+ bot_waypoint_queue_owner = NULL;
+ this.aistatus = 0;
CS(this).movement = '0 0 0';
if (this.deadflag == DEAD_DEAD)
{
this.bot_config_loaded = true;
- // this is really only a default, JoinBestTeam is called later
+ // this is really only a default, TeamBalance_JoinBestTeam is called later
setcolor(this, stof(bot_shirt) * 16 + stof(bot_pants));
this.bot_preferredcolors = this.clientcolors;
else if(this.bot_forced_team==4)
this.team = NUM_TEAM_4;
else
- JoinBestTeam(this, true);
+ TeamBalance_JoinBestTeam(this);
havocbot_setupbot(this);
}
void bot_removefromlargestteam()
{
- CheckAllowedTeams(NULL);
- GetTeamCounts(NULL);
+ entity balance = TeamBalance_CheckAllowedTeams(NULL);
+ TeamBalance_GetTeamCounts(balance, NULL);
entity best = NULL;
float besttime = 0;
int thiscount = 0;
- switch(it.team)
+ if (Team_IsValidTeam(it.team))
{
- case NUM_TEAM_1: thiscount = c1; break;
- case NUM_TEAM_2: thiscount = c2; break;
- case NUM_TEAM_3: thiscount = c3; break;
- case NUM_TEAM_4: thiscount = c4; break;
+ thiscount = TeamBalance_GetNumberOfPlayers(balance,
+ Team_TeamToIndex(it.team));
}
if(thiscount > bestcount)
best = it;
}
});
+ TeamBalance_Destroy(balance);
if(!bcount)
return; // no bots to remove
currentbots = currentbots - 1;
}
int bots;
- // add/remove bots if needed to make sure there are at least
- // minplayers+bot_number, or remove all bots if no one is playing
// But don't remove bots immediately on level change, as the real players
// usually haven't rejoined yet
bots_would_leave = false;
bots = min(ceil(fabs(autocvar_bot_vs_human) * activerealplayers), maxclients - realplayers);
else if ((realplayers || autocvar_bot_join_empty || (currentbots > 0 && time < 5)))
{
- float realminplayers, minplayers;
- realminplayers = autocvar_minplayers;
- minplayers = max(0, floor(realminplayers));
+ int minplayers = max(0, floor(autocvar_minplayers));
+ int minbots = max(0, floor(autocvar_bot_number));
- float realminbots, minbots;
- realminbots = autocvar_bot_number;
- minbots = max(0, floor(realminbots));
+ // add bots to reach minplayers if needed
+ bots = max(minbots, minplayers - activerealplayers);
+ // cap bots to the max players allowed by the server
+ int player_limit = GetPlayerLimit();
+ if(player_limit)
+ bots = min(bots, player_limit - activerealplayers);
+ bots = min(bots, maxclients - realplayers);
- bots = min(max(minbots, minplayers - activerealplayers), maxclients - realplayers);
if(bots > minbots)
bots_would_leave = true;
}
const int AI_STATUS_JETPACK_LANDING = BIT(10);
const int AI_STATUS_STUCK = BIT(11); // Cannot reach any goal
-.float isbot; // true if this client is actually a bot
+.bool isbot; // true if this client is actually a bot
.int aistatus;
// Skill system
#include <common/items/_mod.qh>
#include <common/wepent.qh>
+#include <common/mapobjects/func/ladder.qh>
#include <common/mapobjects/teleporters.qh>
#include <common/mapobjects/trigger/jumppads.qh>
}
havocbot_aim(this);
lag_update(this);
+
+ this.bot_aimdir_executed = false;
+
if (this.bot_aimtarg)
{
this.aistatus |= AI_STATUS_ATTACKING;
{
this.aistatus |= AI_STATUS_ROAMING;
this.aistatus &= ~AI_STATUS_ATTACKING;
-
- vector now, next;
- float aimdistance,skillblend,distanceblend,blend;
-
- 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);
-
- //dprint(this.goalstack01.classname,etos(this.goalstack01),"\n");
- if(
- this.goalstack01 != this && this.goalstack01 && !wasfreed(this.goalstack01) && ((this.aistatus & AI_STATUS_RUNNING) == 0) &&
- !(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
- )
- next = ((this.goalstack01.absmin + this.goalstack01.absmax) * 0.5) - (this.origin + this.view_ofs);
-
- skillblend=bound(0,(skill+this.bot_moveskill-2.5)*0.5,1); //lower skill player can't preturn
- distanceblend=bound(0,aimdistance/autocvar_bot_ai_keyboard_distance,1);
- blend = skillblend * (1-distanceblend);
- //v = (now * (distanceblend) + next * (1-distanceblend)) * (skillblend) + now * (1-skillblend);
- //v = now * (distanceblend) * (skillblend) + next * (1-distanceblend) * (skillblend) + now * (1-skillblend);
- //v = now * ((1-skillblend) + (distanceblend) * (skillblend)) + next * (1-distanceblend) * (skillblend);
- v = now + blend * (next - now);
- //dprint(etos(this), " ");
- //dprint(vtos(now), ":", vtos(next), "=", vtos(v), " (blend ", ftos(blend), ")\n");
- //v = now * (distanceblend) + next * (1-distanceblend);
- if (this.waterlevel < WATERLEVEL_SWIMMING)
- v.z = 0;
- //dprint("walk at:", vtos(v), "\n");
- //te_lightning2(NULL, this.origin, this.goalcurrent.origin);
- bot_aimdir(this, v, -1);
}
+
havocbot_movetogoal(this);
+ if (!this.bot_aimdir_executed && this.goalcurrent)
+ {
+ // Heading
+ vector dir = get_closer_dest(this.goalcurrent, this.origin);
+ dir -= this.origin + this.view_ofs;
+ dir.z = 0;
+ bot_aimdir(this, dir, -1);
+ }
// if the bot is not attacking, consider reloading weapons
if (!(this.aistatus & AI_STATUS_ATTACKING))
vector flatdir;
vector evadeobstacle;
vector evadelava;
+ float dodge_enemy_factor = 1;
float maxspeed;
//float dist;
vector dodge;
if(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
{
this.aistatus |= AI_STATUS_OUT_JUMPPAD;
- navigation_poptouchedgoals(this);
- return;
+ if(navigation_poptouchedgoals(this))
+ return;
}
else if(this.aistatus & AI_STATUS_OUT_JUMPPAD)
{
return;
}
- else if(GetResourceAmount(this, RESOURCE_HEALTH) + GetResourceAmount(this, RESOURCE_ARMOR) > ROCKETJUMP_DAMAGE())
+ else if(!this.jumppadcount && !this.goalcurrent.wphardwired
+ && GetResourceAmount(this, RESOURCE_HEALTH) + GetResourceAmount(this, RESOURCE_ARMOR) > ROCKETJUMP_DAMAGE())
{
if(this.velocity.z < 0)
{
if (this.goalcurrent == this.goalentity && this.goalentity_lock_timeout > time)
locked_goal = true;
- navigation_shortenpath(this);
+ if (navigation_shortenpath(this))
+ {
+ if (vdist(this.origin - this.goalcurrent_prev.origin, <, 50)
+ && navigation_goalrating_timeout_can_be_anticipated(this))
+ navigation_goalrating_timeout_force(this);
+ }
- if (IS_MOVABLE(this.goalcurrent))
+ bool goalcurrent_can_be_removed = false;
+ if (IS_PLAYER(this.goalcurrent) || IS_MONSTER(this.goalcurrent))
{
- if (IS_DEAD(this.goalcurrent))
+ bool freeze_state_changed = (boolean(STAT(FROZEN, this.goalentity)) != this.goalentity_shouldbefrozen);
+ if (IS_DEAD(this.goalcurrent) || (this.goalentity == this.goalcurrent && freeze_state_changed))
{
+ goalcurrent_can_be_removed = true;
+ // don't remove if not visible
if (checkpvs(this.origin + this.view_ofs, this.goalcurrent))
{
navigation_goalrating_timeout_force(this);
return;
}
}
- else if (this.bot_tracewalk_time < time)
+ else if (!(STAT(FROZEN, this.goalentity)) && 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,
this.bot_tracewalk_time = max(time, this.bot_tracewalk_time) + 0.25;
}
}
+
if(!locked_goal)
{
// optimize path finding by anticipating goalrating when bot is near a waypoint;
{
if (this.goalcurrent)
{
- if (IS_MOVABLE(this.goalcurrent) && IS_DEAD(this.goalcurrent))
+ if (goalcurrent_can_be_removed)
{
// remove even if not visible
navigation_goalrating_timeout_force(this);
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))
+ if (this.jumppadcount && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
{
- bunnyhop_forbidden = true;
+ // if bot used the jumppad, push towards jumppad origin until jumppad waypoint gets removed
destorg = this.goalcurrent.origin;
- if(destorg.z > this.origin.z)
- PHYS_INPUT_BUTTON_JUMP(this) = true;
+ }
+ else if (this.goalcurrent.wpisbox)
+ {
+ // if bot is inside the teleport waypoint, head to teleport origin until teleport gets used
+ // do it even if bot is on a ledge above a teleport/jumppad so it doesn't get stuck
+ if (boxesoverlap(this.goalcurrent.absmin, this.goalcurrent.absmax, this.origin + eZ * this.mins.z, this.origin + eZ * this.maxs.z)
+ || (this.absmin.z > destorg.z && destorg.x == this.origin.x && destorg.y == this.origin.y))
+ {
+ bunnyhop_forbidden = true;
+ destorg = this.goalcurrent.origin;
+ if(destorg.z > this.origin.z)
+ PHYS_INPUT_BUTTON_JUMP(this) = true;
+ }
}
diff = destorg - this.origin;
- // 1. stop if too close to target player (even if frozen)
- // 2. stop if the locked goal has been reached
- if ((IS_PLAYER(this.goalcurrent) && vdist(diff, <, 80))
- || (this.goalcurrent == this.goalentity && time < this.goalentity_lock_timeout && vdist(diff, <, 10)))
+ if (this.goalcurrent == this.goalentity && time < this.goalentity_lock_timeout && vdist(diff, <, 10))
{
+ // stop if the locked goal has been reached
destorg = this.origin;
- diff = '0 0 0';
+ diff = dir = '0 0 0';
}
-
- dir = normalize(diff);
+ else if (IS_PLAYER(this.goalcurrent) || IS_MONSTER(this.goalcurrent))
+ {
+ if (vdist(diff, <, 80))
+ {
+ // stop if too close to target player (even if frozen)
+ destorg = this.origin;
+ diff = dir = '0 0 0';
+ }
+ else
+ {
+ // move destorg out of target players, otherwise bot will consider them
+ // an obstacle that needs to be jumped (especially if frozen)
+ dir = normalize(diff);
+ destorg -= dir * PL_MAX_CONST.x * M_SQRT2;
+ diff = destorg - this.origin;
+ }
+ }
+ else
+ dir = normalize(diff);
flatdir = (diff.z == 0) ? dir : normalize(vec2(diff));
//if (this.bot_dodgevector_time < time)
this.aistatus &= ~AI_STATUS_DANGER_AHEAD;
makevectors(this.v_angle.y * '0 1 0');
- if (this.waterlevel)
+ if (this.waterlevel > WATERLEVEL_WETFEET)
{
- if(this.waterlevel>WATERLEVEL_SWIMMING)
+ if (this.waterlevel > WATERLEVEL_SWIMMING)
{
if(!this.goalcurrent)
this.aistatus |= AI_STATUS_OUT_WATER;
{
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))
+ (this.aistatus & AI_STATUS_OUT_WATER))
PHYS_INPUT_BUTTON_JUMP(this) = true;
else
PHYS_INPUT_BUTTON_JUMP(this) = false;
// jump if going toward an obstacle that doesn't look like stairs we
// can walk up directly
vector deviation = '0 0 0';
- if (this.velocity)
+ float current_speed = vlen(vec2(this.velocity));
+ if (current_speed < maxspeed * 0.2)
+ current_speed = maxspeed * 0.2;
+ else
{
deviation = vectoangles(diff) - vectoangles(this.velocity);
while (deviation.y < -180) deviation.y += 360;
while (deviation.y > 180) deviation.y -= 360;
}
+ float turning = false;
vector flat_diff = vec2(diff);
- offset = max(32, vlen(vec2(this.velocity)) * cos(deviation.y * DEG2RAD) * 0.2) * flatdir;
+ offset = max(32, current_speed * cos(deviation.y * DEG2RAD) * 0.3) * flatdir;
vector actual_destorg = this.origin + offset;
- if (!this.goalstack01 || this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
+ if (!this.goalstack01 || this.goalcurrent.wpflags & (WAYPOINTFLAG_TELEPORT | WAYPOINTFLAG_LADDER))
{
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;
+ float dist = vlen(vec2(this.origin + offset - destorg));
+ // if current and next goal are close to each other make sure
+ // actual_destorg isn't set beyond next_goal_org
+ if (dist ** 2 > vlen2(vec2(next_goal_org - destorg)))
+ actual_destorg = next_goal_org;
+ else
+ actual_destorg = vec2(destorg) + dist * next_dir;
actual_destorg.z = this.origin.z;
+ turning = true;
}
- tracebox(this.origin, this.mins, this.maxs, actual_destorg, false, this);
- if (trace_fraction < 1)
- if (trace_plane_normal.z < 0.7)
+ LABEL(jump_check);
+ dir = flatdir = normalize(actual_destorg - this.origin);
+
+ if (turning || fabs(deviation.y) < 50) // don't even try to jump if deviation is too high
{
- s = trace_fraction;
- 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)
+ tracebox(this.origin, this.mins, this.maxs, actual_destorg, false, this);
+ if (trace_fraction < 1 && trace_plane_normal.z < 0.7)
{
s = trace_fraction;
- tracebox(this.origin + jumpstepheightvec, this.mins, this.maxs, actual_destorg + jumpstepheightvec, false, this);
- if (trace_fraction > s)
- PHYS_INPUT_BUTTON_JUMP(this) = true;
+ tracebox(this.origin + stepheightvec, this.mins, this.maxs, actual_destorg + stepheightvec, false, this);
+ if (trace_fraction < s + 0.01 && trace_plane_normal.z < 0.7)
+ {
+ // found an obstacle
+ if (turning && fabs(deviation.y) > 5)
+ {
+ // check if the obstacle is still there without turning
+ actual_destorg = destorg;
+ turning = false;
+ this.bot_tracewalk_time = time + 0.25;
+ goto jump_check;
+ }
+ s = trace_fraction;
+ // don't artificially reduce max jump height in real-time
+ // (jumpstepheightvec is reduced a bit to make the jumps easy in tracewalk)
+ vector jump_height = (IS_ONGROUND(this)) ? stepheightvec + jumpheight_vec : jumpstepheightvec;
+ tracebox(this.origin + jump_height, this.mins, this.maxs, actual_destorg + jump_height, false, this);
+ if (trace_fraction > s)
+ PHYS_INPUT_BUTTON_JUMP(this) = true;
+ else
+ {
+ jump_height = stepheightvec + jumpheight_vec / 2;
+ tracebox(this.origin + jump_height, this.mins, this.maxs, actual_destorg + jump_height, false, this);
+ if (trace_fraction > s)
+ PHYS_INPUT_BUTTON_JUMP(this) = true;
+ }
+ }
}
}
if(IS_PLAYER(this.goalcurrent))
unreachable = true;
}
+
+ // slow down if bot is in the air and goal is under it
+ if (!this.goalcurrent.wphardwired
+ && vdist(flat_diff, <, 250) && this.origin.z - destorg.z > 120
+ && (!IS_ONGROUND(this) || vdist(vec2(this.velocity), >, maxspeed * 0.3)))
+ {
+ // tracebox wouldn't work when bot is still on the ledge
+ traceline(this.origin, this.origin - '0 0 200', true, this);
+ if (this.origin.z - trace_endpos.z > 120)
+ evadeobstacle = normalize(this.velocity) * -1;
+ }
+
if(unreachable)
{
navigation_clearroute(this);
}
dodge = havocbot_dodge(this);
- dodge = dodge * bound(0,0.5+(skill+this.bot_dodgeskill)*0.1,1);
+ if (dodge)
+ dodge *= bound(0, 0.5 + (skill + this.bot_dodgeskill) * 0.1, 1);
+ dodge += evadeobstacle + evadelava;
evadelava = evadelava * bound(1,3-(skill+this.bot_dodgeskill),3); //Noobs fear lava a lot and take more distance from it
- traceline(this.origin, ( ( this.enemy.absmin + this.enemy.absmax ) * 0.5 ), true, NULL);
- if(IS_PLAYER(trace_ent))
- dir = dir * bound(0,(skill+this.bot_dodgeskill)/7,1);
-
- dir = normalize(dir + dodge + evadeobstacle + evadelava);
+ if (this.enemy)
+ {
+ traceline(this.origin, (this.enemy.absmin + this.enemy.absmax) * 0.5, true, NULL);
+ if (IS_PLAYER(trace_ent))
+ dodge_enemy_factor = bound(0, (skill + this.bot_dodgeskill) / 7, 1);
+ }
// this.bot_dodgevector = dir;
// this.bot_dodgevector_jumpbutton = PHYS_INPUT_BUTTON_JUMP(this);
}
+ float ladder_zdir = 0;
if(time < this.ladder_time)
{
if(this.goalcurrent.origin.z + this.goalcurrent.mins.z > this.origin.z + this.mins.z)
{
if(this.origin.z + this.mins.z < this.ladder_entity.origin.z + this.ladder_entity.maxs.z)
- dir.z = 1;
+ ladder_zdir = 1;
}
else
{
if(this.origin.z + this.mins.z > this.ladder_entity.origin.z + this.ladder_entity.mins.z)
- dir.z = -1;
+ ladder_zdir = -1;
+ }
+ if (ladder_zdir)
+ {
+ if (vdist(flatdir, <, 15))
+ dir = ladder_zdir * '0 0 1';
+ else
+ {
+ dir.z = ladder_zdir * 1.3;
+ dir = normalize(dir);
+ }
}
}
+ if (this.goalcurrent.wpisbox
+ && boxesoverlap(this.goalcurrent.absmin, this.goalcurrent.absmax, this.origin, this.origin))
+ {
+ // bot is inside teleport waypoint but hasn't touched the real teleport yet
+ // head to teleport origin
+ dir = (this.goalcurrent.origin - this.origin);
+ dir.z = 0;
+ dir = normalize(dir);
+ }
+
+ if (!this.bot_aimdir_executed)
+ bot_aimdir(this, dir, -1);
+
+ if (!ladder_zdir)
+ {
+ dir *= dodge_enemy_factor;
+ dir = normalize(dir + dodge);
+ }
+
//dir = this.bot_dodgevector;
//if (this.bot_dodgevector_jumpbutton)
// PHYS_INPUT_BUTTON_JUMP(this) = true;
// Should it do a weapon combo?
float af, ct, combo_time, combo;
- af = ATTACK_FINISHED(this, 0);
+ af = ATTACK_FINISHED(this, weaponentity);
ct = autocvar_bot_ai_weapon_combo_threshold;
// Bots with no skill will be 4 times more slower than "godlike" bots when doing weapon combos
if(autocvar_bot_debug_goalstack)
debuggoalstack(this);
- // Heading
- vector dir = get_closer_dest(this.goalcurrent, this.origin);
- dir = dir - (this.origin + this.view_ofs);
- dir.z = 0;
- bot_aimdir(this, dir, -1);
// Go!
havocbot_movetogoal(this);
+ if (!this.bot_aimdir_executed && this.goalcurrent)
+ {
+ // Heading
+ vector dir = get_closer_dest(this.goalcurrent, this.origin);
+ dir -= this.origin + this.view_ofs;
+ dir.z = 0;
+ bot_aimdir(this, dir, -1);
+ }
+
if(this.aistatus & AI_STATUS_WAYPOINT_PERSONAL_REACHED)
{
// Step 5: Waypoint reached
this.cmd_moveto = havocbot_moveto;
this.cmd_resetgoal = havocbot_resetgoal;
+ // NOTE: bot is not player yet
havocbot_chooserole(this);
}
*/
.entity draggedby;
-.float ladder_time;
-.entity ladder_entity;
float enemy_distance = FLOAT_MAX;
float dist;
- FOREACH_CLIENT(IS_PLAYER(it) && it != this && !IS_DEAD(it),
+ FOREACH_CLIENT(IS_PLAYER(it) && it != this && !(IS_DEAD(it) || STAT(FROZEN, it)),
{
if (it.team == this.team)
{
{
float rating;
vector o;
- ratingscale = ratingscale * 0.0001; // items are rated around 10000 already
+ ratingscale = ratingscale * 0.0001;
IL_EACH(g_items, it.bot_pickup,
{
// 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)
{
if(!havocbot_goalrating_item_pickable_check_players(this, org, it, o))
continue;
+ it.bot_ratingscale_time = time;
+ it.bot_ratingscale = ratingscale;
rating = it.bot_pickupevalfunc(this, it);
if(rating > 0)
navigation_routerating(this, it, rating * ratingscale, 2000);
if(this.waterlevel>WATERLEVEL_WETFEET)
return;
- ratingscale = ratingscale * 0.00005; // enemies are rated around 20000 already
+ ratingscale = ratingscale * 0.0001;
float t;
FOREACH_CLIENT(IS_PLAYER(it) && bot_shouldattack(this, it), {
{
navigation_goalrating_start(this);
havocbot_goalrating_items(this, 10000, this.origin, 10000);
- havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
+ havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000);
havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
navigation_goalrating_end(this);
#include <common/constants.qh>
#include <common/net_linked.qh>
+#include <common/mapobjects/func/ladder.qh>
#include <common/mapobjects/trigger/jumppads.qh>
.float speed;
#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))
+ vector gco = (this.goalentity.absmin + this.goalentity.absmax) * 0.5;
+ if (vdist(gco - this.origin, >, autocvar_sv_maxspeed * 1.5)
+ && 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.nearestwaypointtimeout = time;
}
-void navigation_dynamicgoal_set(entity this)
+void navigation_dynamicgoal_set(entity this, entity dropper)
{
this.nearestwaypointtimeout = time;
+ if (dropper && dropper.nearestwaypointtimeout && dropper.nearestwaypointtimeout < time + 2)
+ this.nearestwaypoint = dropper.nearestwaypoint;
if (this.nearestwaypoint)
this.nearestwaypointtimeout += 2;
}
// 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
+ if ((IS_PLAYER(ent) || IS_MONSTER(ent))
+ && org.x == tracewalk_dest.x && org.y == tracewalk_dest.y && org.z > tracewalk_dest.z)
+ {
+ tracewalk_dest.z = wm2.z - PL_MIN_CONST.z;
+ tracewalk_dest_height = 0;
+ fix_player_dest = false;
+ }
+ else
+ {
+ tracewalk_dest.z = wm1.z;
+ tracewalk_dest_height = wm2.z - wm1.z;
+ }
}
else
{
int nav_action;
// Analyze starting point
- traceline(start, start, MOVE_NORMAL, e);
- if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA))
+ if (IN_LAVA(start))
ignorehazards = true;
tracebox(start, m1, m2, start, MOVE_NOMONSTERS, e);
this.goalcurrent_distance_z = FLOAT_MAX;
this.goalcurrent_distance_time = 0;
this.goalentity_lock_timeout = 0;
+ this.goalentity_shouldbefrozen = false;
this.goalentity = NULL;
this.goalcurrent = NULL;
this.goalstack01 = NULL;
vector pm2 = ent.origin + ent.maxs;
// do two scans, because box test is cheaper
- IL_EACH(g_waypoints, it != ent && it != except,
+ IL_EACH(g_waypoints, it != ent && it != except && !(it.wpflags & WAYPOINTFLAG_TELEPORT),
{
if(boxesoverlap(pm1, pm2, it.absmin, it.absmax))
{
// updates the best goal according to a weighted calculation of travel cost and item value of a new proposed item
void navigation_routerating(entity this, entity e, float f, float rangebias)
{
- if (!e)
- return;
-
- if(e.blacklisted)
- return;
+ if (!e || e.blacklisted) { return; }
rangebias = waypoint_getlinearcost(rangebias);
f = waypoint_getlinearcost(f);
if (IS_PLAYER(e))
{
bool rate_wps = false;
- if((e.flags & FL_INWATER) || (e.flags & FL_PARTIALGROUND))
+ if (e.watertype < CONTENT_WATER || (e.waterlevel > WATERLEVEL_WETFEET && !STAT(FROZEN, e))
+ || (e.flags & FL_PARTIALGROUND))
+ {
rate_wps = true;
+ }
if(!IS_ONGROUND(e))
{
nwp = e.nearestwaypoint;
}
- LOG_DEBUG("-- checking ", e.classname, " (with cost ", ftos(nwp.wpcost), ")");
if (nwp && nwp.wpcost < 10000000)
{
//te_wizspike(nwp.wpnearestpoint);
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), ") = ");
+ LOG_DEBUG("checking ^5", e.classname, "^7 with base rating ^xf04", ftos(f), "^7 and rangebias ^xf40", ftos(rangebias));
f = f * rangebias / (rangebias + cost);
- LOG_DEBUG("considering ", e.classname, " (with rating ", ftos(f), ")");
+ LOG_DEBUG(" ^5", e.classname, "^7 with cost ^6", ftos(cost), "^7 and final rating ^2", ftos(f));
if (navigation_bestrating < f)
{
- LOG_DEBUG("ground path: added goal ", e.classname, " (with rating ", ftos(f), ")");
+ LOG_DEBUG(" ground path: ^3added goal ^5", e.classname);
navigation_bestrating = f;
navigation_bestgoal = e;
}
}
// shorten path by removing intermediate goals
-void navigation_shortenpath(entity this)
+bool navigation_shortenpath(entity this)
{
if (!this.goalstack01 || wasfreed(this.goalstack01))
- return;
+ return false;
if (this.bot_tracewalk_time > time)
- return;
+ return false;
this.bot_tracewalk_time = max(time, this.bot_tracewalk_time) + 0.25;
bool cut_allowed = false;
navigation_poproute(this);
}
while (this.goalcurrent != next);
+ return true;
}
- return;
+ return false;
}
}
{
LOG_DEBUG("path optimized for ", this.netname, ", removed a goal from the queue");
navigation_poproute(this);
+ return true;
}
}
+ return false;
}
// removes any currently touching waypoints from the goal stack
this.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING;
this.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_REACHED;
}
+ if(this.jumppadcount)
+ {
+ // remove jumppad waypoint after a random delay to prevent bots getting
+ // stuck on certain jumppads that require an extra initial horizontal speed
+ float max_delay = 0.1;
+ if (vdist(vec2(this.velocity), >, 2 * autocvar_sv_maxspeed))
+ max_delay = 0.05;
+ if (time - this.lastteleporttime < random() * max_delay)
+ return removed_goals;
+ }
navigation_poproute(this);
this.lastteleporttime = 0;
++removed_goals;
gc_min = this.goalcurrent.origin - '1 1 1' * 12;
gc_max = this.goalcurrent.origin + '1 1 1' * 12;
}
- if(!boxesoverlap(this.absmin, this.absmax, gc_min, gc_max))
- break;
+ if (time < this.ladder_time)
+ {
+ if (!boxesoverlap(this.absmin, this.absmax - eZ * STAT(PL_MAX, this).z, gc_min, gc_max))
+ break;
+ }
+ else
+ {
+ if (!boxesoverlap(this.absmin, this.absmax, gc_min, gc_max))
+ break;
+ }
// Detect personal waypoints
if(this.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
this.aistatus |= AI_STATUS_STUCK;
}
}
+ this.goalentity_shouldbefrozen = boolean(STAT(FROZEN, this.goalentity));
}
void botframe_updatedangerousobjects(float maxupdate)
bot_waypoint_queue_bestgoal = bot_waypoint_queue_goal;
}
}
+
+ // move to a random waypoint while bot is searching for a walkable path;
+ // this is usually sufficient to unstuck bots from bad spots or when other
+ // bots of the same team block all their ways
+ if (!bot_waypoint_queue_bestgoal && (!this.goalentity || random() < 0.1))
+ {
+ navigation_clearroute(this);
+ navigation_routetogoal(this, bot_waypoint_queue_goal, this.origin);
+ navigation_goalrating_timeout_expire(this, 1 + random() * 2);
+ }
+
bot_waypoint_queue_goal = bot_waypoint_queue_goal.bot_waypoint_queue_nextgoal;
if (!bot_waypoint_queue_goal)
if (bot_waypoint_queue_bestgoal)
{
LOG_DEBUG(this.netname, " stuck, reachable waypoint found, heading to it");
+ navigation_clearroute(this);
navigation_routetogoal(this, bot_waypoint_queue_bestgoal, this.origin);
navigation_goalrating_timeout_set(this);
this.aistatus &= ~AI_STATUS_STUCK;
.float goalcurrent_distance_time;
.float goalentity_lock_timeout;
+.bool goalentity_shouldbefrozen;
.entity nearestwaypoint;
.float nearestwaypointtimeout;
.entity bot_basewaypoint;
.bool navigation_dynamicgoal;
void navigation_dynamicgoal_init(entity this, bool initially_static);
-void navigation_dynamicgoal_set(entity this);
+void navigation_dynamicgoal_set(entity this, entity dropper);
void navigation_dynamicgoal_unset(entity this);
.int nav_submerged_state;
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_shortenpath(entity this);
+bool navigation_shortenpath(entity this);
int navigation_poptouchedgoals(entity this);
void navigation_goalrating_start(entity this);
void navigation_goalrating_end(entity this);
LOG_INFO("Bot ", this.netname, " using ", this.(weaponentity).weaponname, " wants to fire, inhibited by weaponentity state");
}
}
- else if(ATTACK_FINISHED(this, slot) > time)
+ else if(ATTACK_FINISHED(this, weaponentity) > time)
{
if(f)
{
this.colormod = '8 0 8';
- LOG_INFO("Bot ", this.netname, " using ", this.(weaponentity).weaponname, " wants to fire, inhibited by ATTACK_FINISHED (", ftos(ATTACK_FINISHED(this, slot) - time), " seconds left)");
+ LOG_INFO("Bot ", this.netname, " using ", this.(weaponentity).weaponname, " wants to fire, inhibited by ATTACK_FINISHED (", ftos(ATTACK_FINISHED(this, weaponentity) - time), " seconds left)");
}
}
else if(this.(weaponentity).tuba_note)
if(!f)
{
this.colormod = '8 8 0';
- LOG_INFO("Bot ", this.netname, " using ", this.(weaponentity).weaponname, " thinks it has fired, but apparently did not; ATTACK_FINISHED says ", ftos(ATTACK_FINISHED(this, slot) - time), " seconds left");
+ LOG_INFO("Bot ", this.netname, " using ", this.(weaponentity).weaponname, " thinks it has fired, but apparently did not; ATTACK_FINISHED says ", ftos(ATTACK_FINISHED(this, weaponentity) - time), " seconds left");
}
}
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 waypoint_getSymmetricalPoint(vector org, int ctf_flags)
{
vector new_org = org;
if (fabs(autocvar_g_waypointeditor_symmetrical) == 1)
|| (autocvar_g_waypointeditor_symmetrical < 0));
if(autocvar_g_waypointeditor_symmetrical_order >= 2)
ctf_flags = autocvar_g_waypointeditor_symmetrical_order;
+ if (sym && ctf_flags < 2)
+ ctf_flags = 2;
int wp_num = ctf_flags;
if(!PHYS_INPUT_BUTTON_CROUCH(pl))
{
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)
+ if (vlen(item_org - org) < 20)
{
org = item_org;
break;
bprint(strcat("Waypoint spawned at ", vtos(e.origin), "\n"));
if(sym)
{
- org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
+ org = waypoint_getSymmetricalPoint(e.origin, ctf_flags);
if (vdist(org - pl.origin, >, 32))
{
if(wp_num > 2)
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))
|| (autocvar_g_waypointeditor_symmetrical < 0));
if(autocvar_g_waypointeditor_symmetrical_order >= 2)
ctf_flags = autocvar_g_waypointeditor_symmetrical_order;
+ if (sym && ctf_flags < 2)
+ ctf_flags = 2;
int wp_num = ctf_flags;
LABEL(remove_wp);
entity wp_sym = NULL;
if (sym)
{
- vector org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
+ vector org = waypoint_getSymmetricalPoint(e.origin, ctf_flags);
FOREACH_ENTITY_CLASS("waypoint", !(it.wpflags & WAYPOINTFLAG_GENERATED), {
if(vdist(org - it.origin, <, 3))
{
bool parse_comments = true;
float ver = 0;
+ string links_time = string_null;
while ((s = fgets(file)))
{
{
if(substring(s, 2, 17) == "WAYPOINT_VERSION ")
ver = stof(substring(s, 19, -1));
+ else if(substring(s, 2, 14) == "WAYPOINT_TIME ")
+ links_time = substring(s, 16, -1);
continue;
}
else
{
- if(ver < WAYPOINT_VERSION)
+ if(ver < WAYPOINT_VERSION || links_time != waypoint_time)
{
- LOG_TRACE("waypoint links for this map are outdated.");
+ if (links_time != waypoint_time)
+ LOG_TRACE("waypoint links for this map are not made for these waypoints.");
+ else
+ 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");
}
fputs(file, strcat("//", "WAYPOINT_VERSION ", ftos_decimals(WAYPOINT_VERSION, 2), "\n"));
+ if (waypoint_time != "")
+ fputs(file, strcat("//", "WAYPOINT_TIME ", waypoint_time, "\n"));
int c = 0;
IL_EACH(g_waypoints, true,
entity link = waypoint_get_link(it, j);
if(link)
{
+ // NOTE: vtos rounds vector components to 1 decimal place
string s = strcat(vtos(it.origin), "*", vtos(link.origin), "\n");
fputs(file, s);
++c;
// (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("//", "WAYPOINT_SYMMETRY ", sym_str, "\n"));
- fputs(file, strcat("//", "\n"));
+
+ strcpy(waypoint_time, strftime(true, "%Y-%m-%d %H:%M:%S"));
+ fputs(file, strcat("//", "WAYPOINT_TIME ", waypoint_time, "\n"));
+ //fputs(file, strcat("//", "\n"));
+ //fputs(file, strcat("//", "\n"));
+ //fputs(file, strcat("//", "\n"));
int c = 0;
IL_EACH(g_waypoints, true,
continue;
string s;
+ // NOTE: vtos rounds vector components to 1 decimal place
s = strcat(vtos(it.origin + it.mins), "\n");
s = strcat(s, vtos(it.origin + it.maxs));
s = strcat(s, "\n");
if (tokens > 2) { sym_param2 = stof(argv(2)); }
if (tokens > 3) { sym_param3 = stof(argv(3)); }
}
+ else if(substring(s, 2, 14) == "WAYPOINT_TIME ")
+ strcpy(waypoint_time, substring(s, 16, -1));
continue;
}
else
// 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.01
+const float WAYPOINT_VERSION = 1.01;
+string waypoint_time;
// 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;
#include <server/resources.qh>
#include "g_damage.qh"
+#include "clientkill.qh"
#include "player.qh"
#include "race.qh"
#include "../common/mapobjects/teleporters.qh"
#else
-.float maycheat;
+.bool maycheat;
float gamestart_sv_cheats;
{
}
-float CheatsAllowed(entity this, float i, float argc, float fr) // the cheat gets passed as argument for possible future ACL checking
+float CheatsAllowed(entity this, float i, int argc, float fr) // the cheat gets passed as argument for possible future ACL checking
{
// dead people cannot cheat
if(IS_DEAD(this))
}
// Find e and pick
if(e && pick)
- if(Drag_IsDraggable(e))
+ if(Drag_IsDraggable(e, this))
{
if(ischeat)
IS_CHEAT(this, 0, 0, CHRAME_DRAG);
}
}
-float Drag_IsDraggable(entity draggee)
+bool drag_undraggable(entity draggee, entity dragger)
+{
+ // stuff probably shouldn't need this, we should figure out why they do!
+ // exceptions of course are observers and weapon entities, where things mess up
+ return false;
+}
+
+float Drag_IsDraggable(entity draggee, entity dragger)
{
// TODO add more checks for bad stuff here
if(draggee == NULL)
return false;
- if(draggee.classname == "func_bobbing")
- return false;
if(draggee.classname == "door") // FIXME find out why these must be excluded, or work around the problem (trying to drag these causes like 4 fps)
- return false;
- if(draggee.classname == "plat")
- return false;
- if(draggee.classname == "func_button")
- return false;
+ return false; // probably due to BSP collision
// if(draggee.model == "")
// return false;
- if(IS_SPEC(draggee))
- return false;
- if(IS_OBSERVER(draggee))
- return false;
- if(draggee.classname == "exteriorweaponentity")
- return false;
- if(draggee.classname == "weaponentity")
- return false;
- return true;
+ return ((draggee.draggable) ? draggee.draggable(draggee, dragger) : true);
}
float Drag_MayChangeAngles(entity draggee)
dragger.dragentity = NULL;
return false;
}
- if(!Drag_CanDrag(dragger) || !Drag_IsDraggable(dragger.dragentity))
+ if(!Drag_CanDrag(dragger) || !Drag_IsDraggable(dragger.dragentity, dragger))
{
Drag_Finish(dragger);
return false;
const float CHRAME_DRAG = 8;
+bool drag_undraggable(entity draggee, entity dragger);
+
+.bool(entity this, entity dragger) draggable;
void Drag_MoveDrag(entity from, entity to); // call this from CopyBody
void DragBox_Think(entity this);
float Drag(entity this, float force_allow_pick, float ischeat);
void Drag_Begin(entity dragger, entity draggee, vector touchpoint);
void Drag_Finish(entity dragger);
-float Drag_IsDraggable(entity draggee);
+bool Drag_IsDraggable(entity draggee, entity dragger);
float Drag_MayChangeAngles(entity draggee);
void Drag_MoveForward(entity dragger);
void Drag_SetSpeed(entity dragger, float s);
#include "g_hook.qh"
#include "command/common.qh"
#include "command/vote.qh"
+#include "clientkill.qh"
#include "cheats.qh"
#include "g_world.qh"
#include "race.qh"
#include "../common/wepent.qh"
#include <common/state.qh>
+#include "compat/quake3.qh"
+
#include <common/effects/qc/globalsound.qh>
#include "../common/mapobjects/func/conveyor.qh"
#include "../common/mapobjects/teleporters.qh"
#include "../common/mapobjects/target/spawnpoint.qh"
+#include <common/mapobjects/trigger/counter.qh>
#include "../common/vehicles/all.qh"
RemoveGrapplingHooks(this);
Portal_ClearAll(this);
- Unfreeze(this);
+ Unfreeze(this, false);
SetSpectatee(this, NULL);
if (this.alivetime)
WaypointSprite_PlayerDead(this);
- if (mutator_returnvalue) {
- // mutator prevents resetting teams+score
- } else {
- int oldteam = this.team;
- this.team = -1; // move this as it is needed to log the player spectating in eventlog
- MUTATOR_CALLHOOK(Player_ChangedTeam, this, oldteam, this.team);
- this.frags = FRAGS_SPECTATOR;
- PlayerScore_Clear(this); // clear scores when needed
- }
-
if (CS(this).killcount != FRAGS_SPECTATOR)
{
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_SPECTATE, this.netname);
if(!game_stopped)
if(autocvar_g_chat_nospectators == 1 || (!warmup_stage && autocvar_g_chat_nospectators == 2))
Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_CHAT_NOSPECTATORS);
-
- if(!CS(this).just_joined)
- LogTeamchange(this.playerid, -1, 4);
- else
- CS(this).just_joined = false;
}
accuracy_resend(this);
this.strength_finished = 0;
this.invincible_finished = 0;
this.superweapons_finished = 0;
- this.dphitcontentsmask = 0;
+ //this.dphitcontentsmask = 0;
+ this.dphitcontentsmask = DPCONTENTS_SOLID;
+ if (autocvar_g_playerclip_collisions)
+ this.dphitcontentsmask |= DPCONTENTS_PLAYERCLIP;
this.pushltime = 0;
this.istypefrag = 0;
setthink(this, func_null);
this.crouch = false;
STAT(REVIVE_PROGRESS, this) = 0;
this.revival_time = 0;
+ this.draggable = drag_undraggable;
this.items = 0;
STAT(WEAPONS, this) = '0 0 0';
if(axh.owner == this && axh != NULL && !wasfreed(axh))
delete(axh);
}
+
+ if (mutator_returnvalue)
+ {
+ // mutator prevents resetting teams+score
+ }
+ else
+ {
+ SetPlayerTeam(this, -1, TEAM_CHANGE_SPECTATOR);
+ this.frags = FRAGS_SPECTATOR;
+ }
}
int player_getspecies(entity this)
accuracy_resend(this);
if (this.team < 0)
- JoinBestTeam(this, true);
+ TeamBalance_JoinBestTeam(this);
entity spot = SelectSpawnPoint(this, false);
if (!spot) {
this.event_damage = PlayerDamage;
this.event_heal = PlayerHeal;
+ this.draggable = func_null;
+
if(!this.bot_attack)
IL_PUSH(g_bot_targets, this);
this.bot_attack = true;
this.speedrunning = false;
+ this.counter_cnt = 0;
+ this.fragsfilter_cnt = 0;
+
target_voicescript_clear(this);
// reset fields the weapons may use
});
{
- string s = spot.target;
- spot.target = string_null;
+ //string s = spot.target;
+ //spot.target = string_null;
SUB_UseTargets(spot, this, NULL);
- spot.target = s;
+ //spot.target = s;
}
- Unfreeze(this);
+ Unfreeze(this, false);
MUTATOR_CALLHOOK(PlayerSpawn, this, spot);
MUTATOR_CALLHOOK(DecodeLevelParms);
}
-/*
-=============
-ClientKill
-
-Called when a client types 'kill' in the console
-=============
-*/
-
-.float clientkill_nexttime;
-void ClientKill_Now_TeamChange(entity this)
-{
- if(this.killindicator_teamchange == -1)
- {
- JoinBestTeam( this, true );
- }
- 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, this.killindicator_teamchange - 1);
- this.killindicator_teamchange = 0;
-}
-
-void ClientKill_Now(entity this)
-{
- if(this.vehicle)
- {
- vehicles_exit(this.vehicle, VHEF_RELEASE);
- if(!this.killindicator_teamchange)
- {
- this.vehicle_health = -1;
- Damage(this, this, this, 1 , DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0');
- }
- }
-
- if(this.killindicator && !wasfreed(this.killindicator))
- delete(this.killindicator);
-
- this.killindicator = NULL;
-
- if(this.killindicator_teamchange)
- ClientKill_Now_TeamChange(this);
-
- if (!IS_SPEC(this) && !IS_OBSERVER(this) && MUTATOR_CALLHOOK(ClientKill_Now, this) == false)
- {
- Damage(this, this, this, 100000, DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0');
- }
-
- // now I am sure the player IS dead
-}
-void KillIndicator_Think(entity this)
-{
- if (game_stopped)
- {
- this.owner.killindicator = NULL;
- delete(this);
- return;
- }
-
- if (this.owner.alpha < 0 && !this.owner.vehicle)
- {
- this.owner.killindicator = NULL;
- delete(this);
- return;
- }
-
- if(this.cnt <= 0)
- {
- ClientKill_Now(this.owner);
- return;
- }
- else if(this.count == 1) // count == 1 means that it's silent
- {
- this.nextthink = time + 1;
- this.cnt -= 1;
- }
- else
- {
- if(this.cnt <= 10)
- setmodel(this, MDL_NUM(this.cnt));
- if(IS_REAL_CLIENT(this.owner))
- {
- if(this.cnt <= 10)
- { Send_Notification(NOTIF_ONE, this.owner, MSG_ANNCE, Announcer_PickNumber(CNT_KILL, this.cnt)); }
- }
- this.nextthink = time + 1;
- this.cnt -= 1;
- }
-}
-
-float clientkilltime;
-void ClientKill_TeamChange (entity this, float targetteam) // 0 = don't change, -1 = auto, -2 = spec
-{
- float killtime;
- float starttime;
-
- if (game_stopped)
- return;
-
- killtime = autocvar_g_balance_kill_delay;
-
- if(MUTATOR_CALLHOOK(ClientKill, this, killtime))
- return;
- killtime = M_ARGV(1, float);
-
- this.killindicator_teamchange = targetteam;
-
- if(!this.killindicator)
- {
- if(!IS_DEAD(this))
- {
- killtime = max(killtime, this.clientkill_nexttime - time);
- this.clientkill_nexttime = time + killtime + autocvar_g_balance_kill_antispam;
- }
-
- if(killtime <= 0 || !IS_PLAYER(this) || IS_DEAD(this))
- {
- ClientKill_Now(this);
- }
- else
- {
- starttime = max(time, clientkilltime);
-
- this.killindicator = spawn();
- this.killindicator.owner = this;
- this.killindicator.scale = 0.5;
- setattachment(this.killindicator, this, "");
- setorigin(this.killindicator, '0 0 52');
- setthink(this.killindicator, KillIndicator_Think);
- this.killindicator.nextthink = starttime + (this.lip) * 0.05;
- clientkilltime = max(clientkilltime, this.killindicator.nextthink + 0.05);
- this.killindicator.cnt = ceil(killtime);
- this.killindicator.count = bound(0, ceil(killtime), 10);
- //sprint(this, strcat("^1You'll be dead in ", ftos(this.killindicator.cnt), " seconds\n"));
-
- IL_EACH(g_clones, it.enemy == this && !(it.effects & CSQCMODEL_EF_RESPAWNGHOST),
- {
- it.killindicator = spawn();
- it.killindicator.owner = it;
- it.killindicator.scale = 0.5;
- setattachment(it.killindicator, it, "");
- setorigin(it.killindicator, '0 0 52');
- setthink(it.killindicator, KillIndicator_Think);
- it.killindicator.nextthink = starttime + (it.lip) * 0.05;
- //clientkilltime = max(clientkilltime, it.killindicator.nextthink + 0.05);
- it.killindicator.cnt = ceil(killtime);
- });
- this.lip = 0;
- }
- }
- if(this.killindicator)
- {
- if(targetteam == 0) // just die
- {
- this.killindicator.colormod = '0 0 0';
- if(IS_REAL_CLIENT(this))
- if(this.killindicator.cnt > 0)
- Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_TEAMCHANGE_SUICIDE, this.killindicator.cnt);
- }
- else if(targetteam == -1) // auto
- {
- this.killindicator.colormod = '0 1 0';
- if(IS_REAL_CLIENT(this))
- if(this.killindicator.cnt > 0)
- Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_TEAMCHANGE_AUTO, this.killindicator.cnt);
- }
- else if(targetteam == -2) // spectate
- {
- this.killindicator.colormod = '0.5 0.5 0.5';
- if(IS_REAL_CLIENT(this))
- if(this.killindicator.cnt > 0)
- Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_TEAMCHANGE_SPECTATE, this.killindicator.cnt);
- }
- else
- {
- this.killindicator.colormod = Team_ColorRGB(targetteam);
- if(IS_REAL_CLIENT(this))
- if(this.killindicator.cnt > 0)
- Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, APP_TEAM_NUM(targetteam, CENTER_TEAMCHANGE), this.killindicator.cnt);
- }
- }
-
-}
-
-void ClientKill (entity this)
-{
- // TODO: once .health is removed, will need to check it here for the "already dead" message!
-
- if(game_stopped) return;
- if(this.player_blocked) return;
- if(STAT(FROZEN, this)) return;
-
- ClientKill_TeamChange(this, 0);
-}
-
void FixClientCvars(entity e)
{
// send prediction settings to the client
}
#endif
+string GetClientVersionMessage(entity this)
+{
+ if (CS(this).version_mismatch) {
+ if(CS(this).version < autocvar_gameversion) {
+ return strcat("This is Xonotic ", autocvar_g_xonoticversion,
+ "\n^3Your client version is outdated.\n\n\n### YOU WON'T BE ABLE TO PLAY ON THIS SERVER ###\n\n\nPlease update!!!^8");
+ } else {
+ return strcat("This is Xonotic ", autocvar_g_xonoticversion,
+ "\n^3This server is using an outdated Xonotic version.\n\n\n ### THIS SERVER IS INCOMPATIBLE AND THUS YOU CANNOT JOIN ###.^8");
+ }
+ } else {
+ return strcat("Welcome to Xonotic ", autocvar_g_xonoticversion);
+ }
+}
+
+string getwelcomemessage(entity this)
+{
+ MUTATOR_CALLHOOK(BuildMutatorsPrettyString, "");
+ string modifications = M_ARGV(0, string);
+
+ if(g_weaponarena)
+ {
+ if(g_weaponarena_random)
+ modifications = strcat(modifications, ", ", ftos(g_weaponarena_random), " of ", g_weaponarena_list, " Arena");
+ else
+ modifications = strcat(modifications, ", ", g_weaponarena_list, " Arena");
+ }
+ else if(cvar("g_balance_blaster_weaponstartoverride") == 0)
+ modifications = strcat(modifications, ", No start weapons");
+ if(cvar("sv_gravity") < stof(cvar_defstring("sv_gravity")))
+ modifications = strcat(modifications, ", Low gravity");
+ if(g_weapon_stay && !g_cts)
+ modifications = strcat(modifications, ", Weapons stay");
+ if(g_jetpack)
+ modifications = strcat(modifications, ", Jet pack");
+ if(autocvar_g_powerups == 0)
+ modifications = strcat(modifications, ", No powerups");
+ if(autocvar_g_powerups > 0)
+ modifications = strcat(modifications, ", Powerups");
+ modifications = substring(modifications, 2, strlen(modifications) - 2);
+
+ string versionmessage = GetClientVersionMessage(this);
+ string s = strcat(versionmessage, "^8\n^8\nmatch type is ^1", gamemode_name, "^8\n");
+
+ if(modifications != "")
+ s = strcat(s, "^8\nactive modifications: ^3", modifications, "^8\n");
+
+ if(cache_lastmutatormsg != autocvar_g_mutatormsg)
+ {
+ strcpy(cache_lastmutatormsg, autocvar_g_mutatormsg);
+ strcpy(cache_mutatormsg, cache_lastmutatormsg);
+ }
+
+ if (cache_mutatormsg != "") {
+ s = strcat(s, "\n\n^8special gameplay tips: ^7", cache_mutatormsg);
+ }
+
+ string mutator_msg = "";
+ MUTATOR_CALLHOOK(BuildGameplayTipsString, mutator_msg);
+ mutator_msg = M_ARGV(0, string);
+
+ s = strcat(s, mutator_msg); // trust that the mutator will do proper formatting
+
+ string motd = autocvar_sv_motd;
+ if (motd != "") {
+ s = strcat(s, "\n\n^8MOTD: ^7", strreplace("\\n", "\n", motd));
+ }
+ return s;
+}
+
/**
=============
ClientConnect
TRANSMUTE(Client, this);
CS(this).version_nagtime = time + 10 + random() * 10;
- // identify the right forced team
- if (autocvar_g_campaign)
- {
- if (IS_REAL_CLIENT(this)) // only players, not bots
- {
- switch (autocvar_g_campaign_forceteam)
- {
- case 1: this.team_forced = NUM_TEAM_1; break;
- case 2: this.team_forced = NUM_TEAM_2; break;
- case 3: this.team_forced = NUM_TEAM_3; break;
- case 4: this.team_forced = NUM_TEAM_4; break;
- default: this.team_forced = 0;
- }
- }
- }
- else if (PlayerInList(this, autocvar_g_forced_team_red)) this.team_forced = NUM_TEAM_1;
- else if (PlayerInList(this, autocvar_g_forced_team_blue)) this.team_forced = NUM_TEAM_2;
- else if (PlayerInList(this, autocvar_g_forced_team_yellow)) this.team_forced = NUM_TEAM_3;
- else if (PlayerInList(this, autocvar_g_forced_team_pink)) this.team_forced = NUM_TEAM_4;
- else switch (autocvar_g_forced_team_otherwise)
- {
- default: this.team_forced = 0; break;
- case "red": this.team_forced = NUM_TEAM_1; break;
- case "blue": this.team_forced = NUM_TEAM_2; break;
- case "yellow": this.team_forced = NUM_TEAM_3; break;
- case "pink": this.team_forced = NUM_TEAM_4; break;
- case "spectate":
- case "spectator":
- this.team_forced = -1;
- break;
- }
- if (!teamplay && this.team_forced > 0) this.team_forced = 0;
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_CONNECT, this.netname);
+
+ bot_clientconnect(this);
- int playerid_save = this.playerid;
- this.playerid = 0; // silent
- JoinBestTeam(this, false); // if the team number is valid, keep it
- this.playerid = playerid_save;
+ Player_DetermineForcedTeam(this);
TRANSMUTE(Observer, this);
if (autocvar_sv_eventlog)
GameLogEcho(strcat(":join:", ftos(this.playerid), ":", ftos(etof(this)), ":", ((IS_REAL_CLIENT(this)) ? this.netaddress : "bot"), ":", playername(this, false)));
- LogTeamchange(this.playerid, this.team, 1);
-
CS(this).just_joined = true; // stop spamming the eventlog with additional lines when the client connects
- if(teamplay && IS_PLAYER(this))
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_JOIN_CONNECT_TEAM), this.netname);
- else
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_CONNECT, this.netname);
-
stuffcmd(this, clientstuff, "\n");
stuffcmd(this, "cl_particles_reloadeffects\n"); // TODO do we still need this?
// notify about available teams
if (teamplay)
{
- CheckAllowedTeams(this);
- int t = 0;
- if (c1 >= 0) t |= BIT(0);
- if (c2 >= 0) t |= BIT(1);
- if (c3 >= 0) t |= BIT(2);
- if (c4 >= 0) t |= BIT(3);
+ entity balance = TeamBalance_CheckAllowedTeams(this);
+ int t = TeamBalance_GetAllowedTeams(balance);
+ TeamBalance_Destroy(balance);
stuffcmd(this, sprintf("set _teams_available %d\n", t));
}
else
if (IS_REAL_CLIENT(this))
sv_notice_join(this);
+ this.move_qcphysics = false;
+
// update physics stats (players can spawn before physics runs)
Physics_UpdateStats(this);
Portal_ClearAll(this);
- Unfreeze(this);
+ Unfreeze(this, false);
RemoveGrapplingHooks(this);
if ( !IS_DEAD(this.owner) && IS_PLAYER(this.owner) )
{
- if ( CS(this.owner).active_minigame )
+ if ( CS(this.owner).active_minigame && PHYS_INPUT_BUTTON_MINIGAME(this.owner) )
this.mdl = "models/sprites/minigame_busy.iqm";
else if (PHYS_INPUT_BUTTON_CHAT(this.owner))
this.mdl = "models/misc/chatbubble.spr";
if (time < this.superweapons_finished || (this.items & IT_UNLIMITED_SUPERWEAPONS))
{
this.items = this.items | IT_SUPERWEAPON;
- if(!g_cts)
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_PICKUP, this.netname);
- Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_PICKUP);
+ if(!(this.items & IT_UNLIMITED_SUPERWEAPONS))
+ {
+ if(!g_cts)
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_PICKUP, this.netname);
+ Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_PICKUP);
+ }
}
else
{
keys = BITSET(keys, KEY_LEFT, CS(this).movement.y < 0);
keys = BITSET(keys, KEY_JUMP, PHYS_INPUT_BUTTON_JUMP(this));
- keys = BITSET(keys, KEY_CROUCH, PHYS_INPUT_BUTTON_CROUCH(this));
+ keys = BITSET(keys, KEY_CROUCH, IS_DUCKED(this)); // workaround: player can't un-crouch until their path is clear, so we keep the button held here
keys = BITSET(keys, KEY_ATCK, PHYS_INPUT_BUTTON_ATCK(this));
keys = BITSET(keys, KEY_ATCK2, PHYS_INPUT_BUTTON_ATCK2(this));
CS(this).pressedkeys = keys; // store for other users
.bool team_selected;
bool ShowTeamSelection(entity this)
{
- if(!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || this.team_selected || (CS(this).wasplayer && autocvar_g_changeteam_banned) || this.team_forced > 0)
+ if (!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || this.team_selected || (CS(this).wasplayer && autocvar_g_changeteam_banned) || Player_HasRealForcedTeam(this))
return false;
stuffcmd(this, "menu_showteamselect\n");
return true;
if(!this.team_selected)
if(autocvar_g_campaign || autocvar_g_balance_teams)
- JoinBestTeam(this, true);
+ TeamBalance_JoinBestTeam(this);
if(autocvar_g_campaign)
campaign_bots_may_start = true;
if(IS_PLAYER(this))
if(teamplay && this.team != -1)
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_JOIN_PLAY_TEAM), this.netname);
+ {
+ }
else
Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_PLAY, this.netname);
this.team_selected = false;
}
+int GetPlayerLimit()
+{
+ int player_limit = autocvar_g_maxplayers;
+ MUTATOR_CALLHOOK(GetPlayerLimit, player_limit);
+ player_limit = M_ARGV(0, int);
+ return player_limit;
+}
+
/**
* Determines whether the player is allowed to join. This depends on cvar
* g_maxplayers, if it isn't used this function always return true, otherwise
return 0;
}
- if(this && this.team_forced < 0)
+ if(this && (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR))
return 0; // forced spectators can never join
// TODO simplify this
++currentlyPlaying;
});
+ int player_limit = GetPlayerLimit();
+
float free_slots = 0;
- if (!autocvar_g_maxplayers)
+ if (!player_limit)
free_slots = maxclients - totalClients;
- else if(currentlyPlaying < autocvar_g_maxplayers)
- free_slots = min(maxclients - totalClients, autocvar_g_maxplayers - currentlyPlaying);
+ else if(currentlyPlaying < player_limit)
+ free_slots = min(maxclients - totalClients, player_limit - currentlyPlaying);
static float join_prevent_msg_time = 0;
if(this && ignore && !free_slots && time > join_prevent_msg_time)
if(IS_PLAYER(this))
{
- if (STAT(FROZEN, this) == 2)
+ if (STAT(FROZEN, this) == FROZEN_TEMP_REVIVING)
{
STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) + frametime * this.revive_speed, 1);
SetResourceAmountExplicit(this, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, this) * start_health));
this.iceblock.alpha = bound(0.2, 1 - STAT(REVIVE_PROGRESS, this), 1);
if (STAT(REVIVE_PROGRESS, this) >= 1)
- Unfreeze(this);
+ Unfreeze(this, false);
}
- else if (STAT(FROZEN, this) == 3)
+ else if (STAT(FROZEN, this) == FROZEN_TEMP_DYING)
{
STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) - frametime * this.revive_speed, 1);
SetResourceAmountExplicit(this, RESOURCE_HEALTH, max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this)));
this.event_damage(this, this, this.frozen_by, 1, DEATH_NADE_ICE_FREEZE.m_id, DMG_NOWEP, this.origin, '0 0 0');
}
else if (STAT(REVIVE_PROGRESS, this) <= 0)
- Unfreeze(this);
+ Unfreeze(this, false);
}
}
// don't do this in ClientConnect
// many things can go wrong if a client is spawned as player on connection
if (MUTATOR_CALLHOOK(AutoJoinOnConnection, this)
- || (!(autocvar_sv_spectate || autocvar_g_campaign || this.team_forced < 0)
+ || (!(autocvar_sv_spectate || autocvar_g_campaign || (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR))
&& (!teamplay || autocvar_g_balance_teams)))
{
campaign_bots_may_start = true;
void Player_Physics(entity this)
{
- set_movetype(this, this.move_movetype);
+ this.movetype = (this.move_qcphysics) ? MOVETYPE_QCPLAYER : this.move_movetype;
if(!this.move_qcphysics)
return;
CSQCMODEL_AUTOUPDATE(this);
}
+/**
+ * message "": do not say, just test flood control
+ * return value:
+ * 1 = accept
+ * 0 = reject
+ * -1 = fake accept
+ */
+int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodcontrol)
+{
+ if (!teamsay && !privatesay && substring(msgin, 0, 1) == " ")
+ msgin = substring(msgin, 1, -1); // work around DP say bug (say_team does not have this!)
+
+ if (source)
+ msgin = formatmessage(source, msgin);
+
+ string colorstr;
+ if (!(IS_PLAYER(source) || source.caplayer))
+ colorstr = "^0"; // black for spectators
+ else if(teamplay)
+ colorstr = Team_ColorCode(source.team);
+ else
+ {
+ colorstr = "";
+ teamsay = false;
+ }
+
+ if(game_stopped)
+ teamsay = false;
+
+ if (!source) {
+ colorstr = "";
+ teamsay = false;
+ }
+
+ if(msgin != "")
+ msgin = trigger_magicear_processmessage_forallears(source, teamsay, privatesay, msgin);
+
+ /*
+ * using bprint solves this... me stupid
+ // how can we prevent the message from appearing in a listen server?
+ // for now, just give "say" back and only handle say_team
+ if(!teamsay)
+ {
+ clientcommand(source, strcat("say ", msgin));
+ return;
+ }
+ */
+
+ string namestr = "";
+ if (source)
+ namestr = playername(source, autocvar_g_chat_teamcolors);
+
+ string colorprefix = (strdecolorize(namestr) == namestr) ? "^3" : "^7";
+
+ string msgstr = "", cmsgstr = "";
+ string privatemsgprefix = string_null;
+ int privatemsgprefixlen = 0;
+ if (msgin != "")
+ {
+ if(privatesay)
+ {
+ msgstr = strcat("\{1}\{13}* ", colorprefix, namestr, "^3 tells you: ^7");
+ privatemsgprefixlen = strlen(msgstr);
+ msgstr = strcat(msgstr, msgin);
+ cmsgstr = strcat(colorstr, colorprefix, namestr, "^3 tells you:\n^7", msgin);
+ privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", playername(privatesay, autocvar_g_chat_teamcolors), ": ^7");
+ }
+ else if(teamsay)
+ {
+ if(strstrofs(msgin, "/me", 0) >= 0)
+ {
+ //msgin = strreplace("/me", "", msgin);
+ //msgin = substring(msgin, 3, strlen(msgin));
+ msgin = strreplace("/me", strcat(colorstr, "(", colorprefix, namestr, colorstr, ")^7"), msgin);
+ msgstr = strcat("\{1}\{13}^4* ", "^7", msgin);
+ }
+ else
+ msgstr = strcat("\{1}\{13}", colorstr, "(", colorprefix, namestr, colorstr, ") ^7", msgin);
+ cmsgstr = strcat(colorstr, "(", colorprefix, namestr, colorstr, ")\n^7", msgin);
+ }
+ else
+ {
+ if(strstrofs(msgin, "/me", 0) >= 0)
+ {
+ //msgin = strreplace("/me", "", msgin);
+ //msgin = substring(msgin, 3, strlen(msgin));
+ msgin = strreplace("/me", strcat(colorprefix, namestr), msgin);
+ msgstr = strcat("\{1}^4* ", "^7", msgin);
+ }
+ else {
+ msgstr = "\{1}";
+ msgstr = strcat(msgstr, (namestr != "") ? strcat(colorprefix, namestr, "^7: ") : "^7");
+ msgstr = strcat(msgstr, msgin);
+ }
+ cmsgstr = "";
+ }
+ msgstr = strcat(strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint
+ }
+
+ string fullmsgstr = msgstr;
+ string fullcmsgstr = cmsgstr;
+
+ // FLOOD CONTROL
+ int flood = 0;
+ var .float flood_field = floodcontrol_chat;
+ if(floodcontrol && source)
+ {
+ float flood_spl;
+ float flood_burst;
+ float flood_lmax;
+ float lines;
+ if(privatesay)
+ {
+ flood_spl = autocvar_g_chat_flood_spl_tell;
+ flood_burst = autocvar_g_chat_flood_burst_tell;
+ flood_lmax = autocvar_g_chat_flood_lmax_tell;
+ flood_field = floodcontrol_chattell;
+ }
+ else if(teamsay)
+ {
+ flood_spl = autocvar_g_chat_flood_spl_team;
+ flood_burst = autocvar_g_chat_flood_burst_team;
+ flood_lmax = autocvar_g_chat_flood_lmax_team;
+ flood_field = floodcontrol_chatteam;
+ }
+ else
+ {
+ flood_spl = autocvar_g_chat_flood_spl;
+ flood_burst = autocvar_g_chat_flood_burst;
+ flood_lmax = autocvar_g_chat_flood_lmax;
+ flood_field = floodcontrol_chat;
+ }
+ flood_burst = max(0, flood_burst - 1);
+ // to match explanation in default.cfg, a value of 3 must allow three-line bursts and not four!
+
+ // do flood control for the default line size
+ if(msgstr != "")
+ {
+ getWrappedLine_remaining = msgstr;
+ msgstr = "";
+ lines = 0;
+ while(getWrappedLine_remaining && (!flood_lmax || lines <= flood_lmax))
+ {
+ msgstr = strcat(msgstr, " ", getWrappedLineLen(82.4289758859709, strlennocol)); // perl averagewidth.pl < gfx/vera-sans.width
+ ++lines;
+ }
+ msgstr = substring(msgstr, 1, strlen(msgstr) - 1);
+
+ if(getWrappedLine_remaining != "")
+ {
+ msgstr = strcat(msgstr, "\n");
+ flood = 2;
+ }
+
+ if (time >= source.(flood_field))
+ {
+ source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + lines * flood_spl;
+ }
+ else
+ {
+ flood = 1;
+ msgstr = fullmsgstr;
+ }
+ }
+ else
+ {
+ if (time >= source.(flood_field))
+ source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + flood_spl;
+ else
+ flood = 1;
+ }
+
+ if (timeout_status == TIMEOUT_ACTIVE) // when game is paused, no flood protection
+ source.(flood_field) = flood = 0;
+ }
+
+ string sourcemsgstr, sourcecmsgstr;
+ if(flood == 2) // cannot happen for empty msgstr
+ {
+ if(autocvar_g_chat_flood_notify_flooder)
+ {
+ sourcemsgstr = strcat(msgstr, "\n^3FLOOD CONTROL: ^7message too long, trimmed\n");
+ sourcecmsgstr = "";
+ }
+ else
+ {
+ sourcemsgstr = fullmsgstr;
+ sourcecmsgstr = fullcmsgstr;
+ }
+ cmsgstr = "";
+ }
+ else
+ {
+ sourcemsgstr = msgstr;
+ sourcecmsgstr = cmsgstr;
+ }
+
+ if (!privatesay && source && !(IS_PLAYER(source) || source.caplayer))
+ {
+ if (!game_stopped)
+ if (teamsay || (autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !warmup_stage))
+ teamsay = -1; // spectators
+ }
+
+ if(flood)
+ LOG_INFO("NOTE: ", playername(source, true), "^7 is flooding.");
+
+ // build sourcemsgstr by cutting off a prefix and replacing it by the other one
+ if(privatesay)
+ sourcemsgstr = strcat(privatemsgprefix, substring(sourcemsgstr, privatemsgprefixlen, -1));
+
+ int ret;
+ if(source && CS(source).muted)
+ {
+ // always fake the message
+ ret = -1;
+ }
+ else if(flood == 1)
+ {
+ if (autocvar_g_chat_flood_notify_flooder)
+ {
+ sprint(source, strcat("^3FLOOD CONTROL: ^7wait ^1", ftos(source.(flood_field) - time), "^3 seconds\n"));
+ ret = 0;
+ }
+ else
+ ret = -1;
+ }
+ else
+ {
+ ret = 1;
+ }
+
+ if (privatesay && source && !(IS_PLAYER(source) || source.caplayer))
+ {
+ if (!game_stopped)
+ if ((privatesay && (IS_PLAYER(privatesay) || privatesay.caplayer)) && ((autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !warmup_stage)))
+ ret = -1; // just hide the message completely
+ }
+
+ MUTATOR_CALLHOOK(ChatMessage, source, ret);
+ ret = M_ARGV(1, int);
+
+ if(sourcemsgstr != "" && ret != 0)
+ {
+ if(ret < 0) // faked message, because the player is muted
+ {
+ sprint(source, sourcemsgstr);
+ if(sourcecmsgstr != "" && !privatesay)
+ centerprint(source, sourcecmsgstr);
+ }
+ else if(privatesay) // private message, between 2 people only
+ {
+ sprint(source, sourcemsgstr);
+ if (!autocvar_g_chat_tellprivacy) { dedicated_print(msgstr); } // send to server console too if "tellprivacy" is disabled
+ if(!MUTATOR_CALLHOOK(ChatMessageTo, privatesay, source))
+ {
+ sprint(privatesay, msgstr);
+ if(cmsgstr != "")
+ centerprint(privatesay, cmsgstr);
+ }
+ }
+ else if ( teamsay && CS(source).active_minigame )
+ {
+ sprint(source, sourcemsgstr);
+ dedicated_print(msgstr); // send to server console too
+ FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && CS(it).active_minigame == CS(source).active_minigame && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
+ sprint(it, msgstr);
+ });
+ }
+ else if(teamsay > 0) // team message, only sent to team mates
+ {
+ sprint(source, sourcemsgstr);
+ dedicated_print(msgstr); // send to server console too
+ if(sourcecmsgstr != "")
+ centerprint(source, sourcecmsgstr);
+ FOREACH_CLIENT((IS_PLAYER(it) || it.caplayer) && IS_REAL_CLIENT(it) && it != source && it.team == source.team && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
+ sprint(it, msgstr);
+ if(cmsgstr != "")
+ centerprint(it, cmsgstr);
+ });
+ }
+ else if(teamsay < 0) // spectator message, only sent to spectators
+ {
+ sprint(source, sourcemsgstr);
+ dedicated_print(msgstr); // send to server console too
+ FOREACH_CLIENT(!(IS_PLAYER(it) || it.caplayer) && IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
+ sprint(it, msgstr);
+ });
+ }
+ else
+ {
+ if (source) {
+ sprint(source, sourcemsgstr);
+ dedicated_print(msgstr); // send to server console too
+ MX_Say(strcat(playername(source, true), "^7: ", msgin));
+ }
+ FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
+ sprint(it, msgstr);
+ });
+ }
+ }
+
+ return ret;
+}
+
// hack to copy the button fields from the client entity to the Client State
void PM_UpdateButtons(entity this, entity store)
{
store.impulse = this.impulse;
this.impulse = 0;
- bool typing = this.buttonchat;
+ bool typing = this.buttonchat || this.button14;
store.button0 = (typing) ? 0 : this.button0;
//button1?!
/** Client IP */
ATTRIB(Client, netaddress, string, this.netaddress);
ATTRIB(Client, playermodel, string, this.playermodel);
- ATTRIB(Client, playerskin, int, this.playerskin);
+ ATTRIB(Client, playerskin, string, this.playerskin);
/** fingerprint of CA key the player used to authenticate */
ATTRIB(Client, crypto_keyfp, string, this.crypto_keyfp);
ATTRIB(Client, cvar_cl_accuracy_data_receive, bool, this.cvar_cl_accuracy_data_receive);
ATTRIBARRAY(Client, cvar_cl_weaponpriorities, string, 10);
ATTRIB(Client, cvar_cl_weaponpriority, string, this.cvar_cl_weaponpriority);
+ ATTRIB(Client, cvar_cl_cts_noautoswitch, bool, this.cvar_cl_cts_noautoswitch);
METHOD(Client, m_unwind, bool(Client this));
return false;
}
+bool PlayerInList(entity player, string list);
+
/// \brief Print the string to the client's chat.
/// \param[in] client Client to print to.
/// \param[in] text Text to print.
void ClientInit_misc(entity this);
-void ClientKill_TeamChange(entity this, float targetteam); // 0 = don't change, -1 = auto, -2 = spec
+int GetPlayerLimit();
bool joinAllowed(entity this);
void Join(entity this);
#define SPECTATE_COPY() ACCUMULATE void SpectateCopy(entity this, entity spectatee)
#define SPECTATE_COPYFIELD(fld) SPECTATE_COPY() { this.(fld) = spectatee.(fld); }
+
+int Say(entity source, float teamsay, entity privatesay, string msgin, float floodcontrol);
--- /dev/null
+#include "clientkill.qh"
+
+#include <server/defs.qh>
+
+#include "g_damage.qh"
+#include "teamplay.qh"
+
+#include <common/vehicles/sv_vehicles.qh>
+#include <common/notifications/all.qh>
+#include <common/stats.qh>
+
+void ClientKill_Now_TeamChange(entity this)
+{
+ if (this.killindicator_teamchange == -1)
+ {
+ TeamBalance_JoinBestTeam(this);
+ }
+ 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
+ {
+ Player_SetTeamIndexChecked(this, Team_TeamToIndex(
+ this.killindicator_teamchange));
+ }
+ this.killindicator_teamchange = 0;
+}
+
+void ClientKill_Now(entity this)
+{
+ if (this.vehicle)
+ {
+ vehicles_exit(this.vehicle, VHEF_RELEASE);
+ if (!this.killindicator_teamchange)
+ {
+ this.vehicle_health = -1;
+ Damage(this, this, this, 1 , DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0');
+ }
+ }
+
+ if (this.killindicator && !wasfreed(this.killindicator))
+ delete(this.killindicator);
+
+ this.killindicator = NULL;
+
+ if (this.killindicator_teamchange)
+ ClientKill_Now_TeamChange(this);
+
+ if (!IS_SPEC(this) && !IS_OBSERVER(this) && MUTATOR_CALLHOOK(ClientKill_Now, this) == false)
+ {
+ Damage(this, this, this, 100000, DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0');
+ }
+
+ // now I am sure the player IS dead
+}
+void KillIndicator_Think(entity this)
+{
+ if (game_stopped || (this.owner.alpha < 0 && !this.owner.vehicle))
+ {
+ this.owner.killindicator = NULL;
+ delete(this);
+ return;
+ }
+
+ if (this.cnt <= 0)
+ {
+ ClientKill_Now(this.owner);
+ return;
+ }
+
+ // count == 1 means that it's silent
+ if (this.count != 1)
+ {
+ if (this.cnt <= 10)
+ setmodel(this, MDL_NUM(this.cnt));
+ if (IS_REAL_CLIENT(this.owner))
+ {
+ if (this.cnt <= 10)
+ Send_Notification(NOTIF_ONE, this.owner, MSG_ANNCE, Announcer_PickNumber(CNT_KILL, this.cnt));
+ }
+ }
+ this.nextthink = time + 1;
+ this.cnt -= 1;
+}
+
+.float lip;
+float clientkilltime;
+.float clientkill_nexttime;
+void ClientKill_TeamChange(entity this, float targetteam) // 0 = don't change, -1 = auto, -2 = spec
+{
+ if (game_stopped)
+ return;
+
+ float killtime = autocvar_g_balance_kill_delay;
+
+ if (MUTATOR_CALLHOOK(ClientKill, this, killtime))
+ return;
+ killtime = M_ARGV(1, float);
+
+ this.killindicator_teamchange = targetteam;
+
+ // this.killindicator.count == 1 means that the kill indicator was spawned by ClientKill_Silent
+ if(killtime <= 0 && this.killindicator && this.killindicator.count == 1)
+ {
+ ClientKill_Now(this); // allow instant kill in this case
+ return;
+ }
+
+ if (!this.killindicator)
+ {
+ if (!IS_DEAD(this))
+ {
+ killtime = max(killtime, this.clientkill_nexttime - time);
+ this.clientkill_nexttime = time + killtime + autocvar_g_balance_kill_antispam;
+ }
+
+ if (killtime <= 0 || !IS_PLAYER(this) || IS_DEAD(this))
+ {
+ ClientKill_Now(this);
+ }
+ else
+ {
+ float starttime = max(time, clientkilltime);
+
+ this.killindicator = spawn();
+ this.killindicator.owner = this;
+ this.killindicator.scale = 0.5;
+ setattachment(this.killindicator, this, "");
+ setorigin(this.killindicator, '0 0 52');
+ setthink(this.killindicator, KillIndicator_Think);
+ this.killindicator.nextthink = starttime + (this.lip) * 0.05;
+ clientkilltime = max(clientkilltime, this.killindicator.nextthink + 0.05);
+ this.killindicator.cnt = ceil(killtime);
+ this.killindicator.count = bound(0, ceil(killtime), 10);
+ //sprint(this, strcat("^1You'll be dead in ", ftos(this.killindicator.cnt), " seconds\n"));
+
+ IL_EACH(g_clones, it.enemy == this && !(it.effects & CSQCMODEL_EF_RESPAWNGHOST),
+ {
+ it.killindicator = spawn();
+ it.killindicator.owner = it;
+ it.killindicator.scale = 0.5;
+ setattachment(it.killindicator, it, "");
+ setorigin(it.killindicator, '0 0 52');
+ setthink(it.killindicator, KillIndicator_Think);
+ it.killindicator.nextthink = starttime + (it.lip) * 0.05;
+ //clientkilltime = max(clientkilltime, it.killindicator.nextthink + 0.05);
+ it.killindicator.cnt = ceil(killtime);
+ });
+ this.lip = 0;
+ }
+ }
+ if (this.killindicator)
+ {
+ Notification notif;
+ if (targetteam == 0) // just die
+ {
+ this.killindicator.colormod = '0 0 0';
+ notif = CENTER_TEAMCHANGE_SUICIDE;
+ }
+ else if (targetteam == -1) // auto
+ {
+ this.killindicator.colormod = '0 1 0';
+ notif = CENTER_TEAMCHANGE_AUTO;
+ }
+ else if (targetteam == -2) // spectate
+ {
+ this.killindicator.colormod = '0.5 0.5 0.5';
+ notif = CENTER_TEAMCHANGE_SPECTATE;
+ }
+ else
+ {
+ this.killindicator.colormod = Team_ColorRGB(targetteam);
+ notif = APP_TEAM_NUM(targetteam, CENTER_TEAMCHANGE);
+ }
+ if (IS_REAL_CLIENT(this) && this.killindicator.cnt > 0)
+ Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, notif, this.killindicator.cnt);
+ }
+
+}
+
+void ClientKill_Silent(entity this, float _delay)
+{
+ this.killindicator = spawn();
+ this.killindicator.owner = this;
+ setthink(this.killindicator, KillIndicator_Think);
+ this.killindicator.nextthink = time + (this.lip) * 0.05;
+ this.killindicator.cnt = ceil(_delay);
+ this.killindicator.count = 1; // this is used to indicate that it should be silent
+ this.lip = 0;
+}
+
+// Called when a client types 'kill' in the console
+void ClientKill(entity this)
+{
+ if (game_stopped || this.player_blocked || STAT(FROZEN, this))
+ return;
+
+ ClientKill_TeamChange(this, 0);
+}
--- /dev/null
+
+// set when showing a kill countdown
+.entity killindicator;
+.int killindicator_teamchange;
+
+void ClientKill_Now_TeamChange(entity this);
+void ClientKill_Now(entity this);
+void KillIndicator_Think(entity this);
+void ClientKill_TeamChange(entity this, float targetteam); // 0 = don't change, -1 = auto, -2 = spec
+void ClientKill_Silent(entity this, float _delay);
+void ClientKill(entity this);
// Last updated: December 29th, 2011
// =====================================================
-void BanCommand_ban(float request, float argc, string command)
+void BanCommand_ban(int request, int argc, string command)
{
switch (request)
{
}
}
-void BanCommand_banlist(float request)
+void BanCommand_banlist(int request)
{
switch (request)
{
}
}
-void BanCommand_kickban(float request, float argc, string command)
+void BanCommand_kickban(int request, int argc, string command)
{
switch (request)
{
}
}
-void BanCommand_mute(float request, float argc, string command) // TODO: Add a sort of mute-"ban" which allows players to be muted based on IP/cryptokey
+void BanCommand_mute(int request, int argc, string command) // TODO: Add a sort of mute-"ban" which allows players to be muted based on IP/cryptokey
{
switch (request)
{
}
}
-void BanCommand_unban(float request, float argc)
+void BanCommand_unban(int request, int argc)
{
switch (request)
{
}
}
-void BanCommand_unmute(float request, float argc)
+void BanCommand_unmute(int request, int argc)
{
switch (request)
{
/* use this when creating a new command, making sure to place it in alphabetical order... also,
** ADD ALL NEW COMMANDS TO commands.cfg WITH PROPER ALIASES IN THE SAME FASHION!
-void BanCommand_(float request)
+void BanCommand_(int request)
{
switch(request)
{
#undef BAN_COMMAND
}
-float BanCommand_macro_command(float argc, string command)
+float BanCommand_macro_command(int argc, string command)
{
#define BAN_COMMAND(name, function, description) \
{ if (name == strtolower(argv(0))) { function; return true; } }
return false;
}
-float BanCommand_macro_usage(float argc)
+float BanCommand_macro_usage(int argc)
{
#define BAN_COMMAND(name, function, description) \
{ if (name == strtolower(argv(1))) { function; return true; } }
float BanCommand(string command)
{
- float argc = tokenize_console(command);
+ int argc = tokenize_console(command);
// Guide for working with argc arguments by example:
// argc: 1 - 2 - 3 - 4
void BanCommand_macro_write_aliases(float fh);
void BanCommand_macro_help();
-float BanCommand_macro_usage(float argc);
+float BanCommand_macro_usage(int argc);
#include "common.qh"
#include "vote.qh"
+#include "../bot/api.qh"
+
#include "../campaign.qh"
#include "../cheats.qh"
#include "../client.qh"
+#include "../clientkill.qh"
#include "../player.qh"
#include "../ipban.qh"
#include "../mapvoting.qh"
// Command Sub-Functions
// =======================
-void ClientCommand_autoswitch(entity caller, float request, float argc)
+void ClientCommand_autoswitch(entity caller, int request, int argc)
{
switch (request)
{
}
}
-void ClientCommand_clientversion(entity caller, float request, float argc) // internal command, used only by code
+void ClientCommand_clientversion(entity caller, int request, int argc) // internal command, used only by code
{
switch (request)
{
{
// JoinBestTeam(caller, false, true);
}
- else if (teamplay && !autocvar_sv_spectate && !(caller.team_forced > 0))
+ else if (teamplay && !autocvar_sv_spectate && !(Player_GetForcedTeamIndex(caller) > 0))
{
TRANSMUTE(Observer, caller); // really?
stuffcmd(caller, "menu_showteamselect\n");
}
}
-void ClientCommand_mv_getpicture(entity caller, float request, float argc) // internal command, used only by code
+void ClientCommand_mv_getpicture(entity caller, int request, int argc) // internal command, used only by code
{
switch (request)
{
}
}
-void ClientCommand_join(entity caller, float request)
+void ClientCommand_wpeditor(entity caller, int request, int argc)
+{
+ switch (request)
+ {
+ case CMD_REQUEST_COMMAND:
+ {
+ if (!autocvar_g_waypointeditor)
+ {
+ sprint(caller, "ERROR: this command works only if the waypoint editor is on\n");
+ return;
+ }
+
+ if (argv(1) != "")
+ {
+ if (argv(1) == "spawn")
+ {
+ if (!IS_PLAYER(caller))
+ sprint(caller, "ERROR: this command works only if you are player\n");
+ else
+ waypoint_spawn_fromeditor(caller);
+ }
+ else if (argv(1) == "remove")
+ {
+ if (!IS_PLAYER(caller))
+ sprint(caller, "ERROR: this command works only if you are player\n");
+ else
+ waypoint_remove_fromeditor(caller);
+ }
+ else if (argv(1) == "unreachable")
+ {
+ if (!IS_PLAYER(caller))
+ sprint(caller, "ERROR: this command works only if you are player\n");
+ else
+ waypoint_unreachable(caller);
+ }
+ else if (argv(1) == "saveall")
+ waypoint_saveall();
+ else if (argv(1) == "relinkall")
+ waypoint_schedulerelinkall();
+
+ return;
+ }
+ }
+
+ default:
+ sprint(caller, "Incorrect parameters for ^2wpeditor^7\n");
+ case CMD_REQUEST_USAGE:
+ {
+ sprint(caller, "\nUsage:^3 cmd wpeditor action\n");
+ sprint(caller, " Where 'action' can be: spawn, remove, unreachable, saveall, relinkall\n");
+ return;
+ }
+ }
+}
+
+void ClientCommand_join(entity caller, int request)
{
switch (request)
{
}
}
-void ClientCommand_physics(entity caller, float request, float argc)
+void ClientCommand_kill(entity caller, int request)
+{
+ switch (request)
+ {
+ case CMD_REQUEST_COMMAND:
+ {
+ if(IS_SPEC(caller) || IS_OBSERVER(caller))
+ return; // no point warning about this, command does nothing
+
+ if(GetResourceAmount(caller, RESOURCE_HEALTH) <= 0)
+ {
+ sprint(caller, "Can't die - you are already dead!\n");
+ return;
+ }
+
+ ClientKill(caller);
+
+ return; // never fall through to usage
+ }
+
+ default:
+ case CMD_REQUEST_USAGE:
+ {
+ sprint(caller, "\nUsage:^3 cmd kill\n");
+ sprint(caller, " No arguments required.\n");
+ return;
+ }
+ }
+}
+
+void ClientCommand_physics(entity caller, int request, int argc)
{
switch (request)
{
}
}
-void ClientCommand_ready(entity caller, float request) // todo: anti-spam for toggling readyness
+void ClientCommand_ready(entity caller, int request) // todo: anti-spam for toggling readyness
{
switch (request)
{
}
}
-void ClientCommand_say(entity caller, float request, float argc, string command)
+void ClientCommand_say(entity caller, int request, int argc, string command)
{
switch (request)
{
}
}
-void ClientCommand_say_team(entity caller, float request, float argc, string command)
+void ClientCommand_say_team(entity caller, int request, int argc, string command)
{
switch (request)
{
case CMD_REQUEST_COMMAND:
{
- if (argc >= 2) Say(caller, true, NULL, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1);
+ if (argc >= 2)
+ Say(caller, true, NULL, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1);
return; // never fall through to usage
}
}
.bool team_selected;
-void ClientCommand_selectteam(entity caller, float request, float argc)
+void ClientCommand_selectteam(entity caller, int request, int argc)
{
switch (request)
{
sprint(caller, "^7selectteam can only be used in teamgames\n");
return;
}
- if (caller.team_forced > 0)
+ if (Player_GetForcedTeamIndex(caller) > 0)
{
sprint(caller, "^7selectteam can not be used as your team is forced\n");
return;
if ((selection != -1) && autocvar_g_balance_teams &&
autocvar_g_balance_teams_prevent_imbalance)
{
- CheckAllowedTeams(caller);
- GetTeamCounts(caller);
- if ((BIT(Team_TeamToNumber(selection) - 1) & FindBestTeams(caller, false)) == 0)
+ entity balance = TeamBalance_CheckAllowedTeams(caller);
+ TeamBalance_GetTeamCounts(balance, caller);
+ if ((Team_IndexToBit(Team_TeamToIndex(selection)) &
+ TeamBalance_FindBestTeams(balance, caller, false)) == 0)
{
Send_Notification(NOTIF_ONE, caller, MSG_INFO, INFO_TEAMCHANGE_LARGERTEAM);
+ TeamBalance_Destroy(balance);
return;
}
+ TeamBalance_Destroy(balance);
}
ClientKill_TeamChange(caller, selection);
if (!IS_PLAYER(caller))
}
}
-void ClientCommand_selfstuff(entity caller, float request, string command)
+void ClientCommand_selfstuff(entity caller, int request, string command)
{
switch (request)
{
}
}
-void ClientCommand_sentcvar(entity caller, float request, float argc, string command)
+void ClientCommand_sentcvar(entity caller, int request, int argc, string command)
{
switch (request)
{
}
}
-void ClientCommand_spectate(entity caller, float request)
+void ClientCommand_spectate(entity caller, int request)
{
switch (request)
{
}
}
-void ClientCommand_suggestmap(entity caller, float request, float argc)
+void ClientCommand_suggestmap(entity caller, int request, int argc)
{
switch (request)
{
}
}
-void ClientCommand_tell(entity caller, float request, float argc, string command)
+void ClientCommand_tell(entity caller, int request, int argc, string command)
{
switch (request)
{
}
}
-void ClientCommand_voice(entity caller, float request, float argc, string command)
+void ClientCommand_voice(entity caller, int request, int argc, string command)
{
switch (request)
{
/* use this when creating a new command, making sure to place it in alphabetical order... also,
** ADD ALL NEW COMMANDS TO commands.cfg WITH PROPER ALIASES IN THE SAME FASHION!
-void ClientCommand_(entity caller, float request)
+void ClientCommand_(entity caller, int request)
{
switch(request)
{
#define CLIENT_COMMANDS(ent, request, arguments, command) \
CLIENT_COMMAND("autoswitch", ClientCommand_autoswitch(ent, request, arguments), "Whether or not to switch automatically when getting a better weapon") \
CLIENT_COMMAND("clientversion", ClientCommand_clientversion(ent, request, arguments), "Release version of the game") \
- CLIENT_COMMAND("mv_getpicture", ClientCommand_mv_getpicture(ent, request, arguments), "Retrieve mapshot picture from the server") \
CLIENT_COMMAND("join", ClientCommand_join(ent, request), "Become a player in the game") \
+ CLIENT_COMMAND("kill", ClientCommand_kill(ent, request), "Become a member of the dead") \
+ CLIENT_COMMAND("minigame", ClientCommand_minigame(ent, request, arguments, command), "Start a minigame") \
+ CLIENT_COMMAND("mv_getpicture", ClientCommand_mv_getpicture(ent, request, arguments), "Retrieve mapshot picture from the server") \
CLIENT_COMMAND("physics", ClientCommand_physics(ent, request, arguments), "Change physics set") \
CLIENT_COMMAND("ready", ClientCommand_ready(ent, request), "Qualify as ready to end warmup stage (or restart server if allowed)") \
CLIENT_COMMAND("say", ClientCommand_say(ent, request, arguments, command), "Print a message to chat to all players") \
CLIENT_COMMAND("suggestmap", ClientCommand_suggestmap(ent, request, arguments), "Suggest a map to the mapvote at match end") \
CLIENT_COMMAND("tell", ClientCommand_tell(ent, request, arguments, command), "Send a message directly to a player") \
CLIENT_COMMAND("voice", ClientCommand_voice(ent, request, arguments, command), "Send voice message via sound") \
- CLIENT_COMMAND("minigame", ClientCommand_minigame(ent, request, arguments, command), "Start a minigame") \
+ CLIENT_COMMAND("wpeditor", ClientCommand_wpeditor(ent, request, arguments), "Waypoint editor commands") \
/* nothing */
void ClientCommand_macro_help(entity caller)
#undef CLIENT_COMMAND
}
-float ClientCommand_macro_command(float argc, entity caller, string command)
+float ClientCommand_macro_command(int argc, entity caller, string command)
{
#define CLIENT_COMMAND(name, function, description) \
{ if (name == strtolower(argv(0))) { function; return true; } }
return false;
}
-float ClientCommand_macro_usage(float argc, entity caller)
+float ClientCommand_macro_usage(int argc, entity caller)
{
#define CLIENT_COMMAND(name, function, description) \
{ if (name == strtolower(argv(1))) { function; return true; } }
// if we're banned, don't even parse the command
if (Ban_MaybeEnforceBanOnce(this)) return;
- float argc = tokenize_console(command);
+ int argc = tokenize_console(command);
// Guide for working with argc arguments by example:
// argc: 1 - 2 - 3 - 4
case "begin": break; // handled by engine in host_cmd.c
case "download": break; // handled by engine in cl_parse.c
case "mv_getpicture": break; // handled by server in this file
+ case "wpeditor": break; // handled by server in this file
case "pause": break; // handled by engine in host_cmd.c
case "prespawn": break; // handled by engine in host_cmd.c
case "sentcvar": break; // handled by server in this file
else return true;
}
-entity GetIndexedEntity(float argc, float start_index)
+entity GetIndexedEntity(int argc, float start_index)
{
entity selection;
float tmp_number, index;
// Common commands used in both sv_cmd.qc and cmd.qc
// ===================================================
-void CommonCommand_cvar_changes(float request, entity caller)
+void CommonCommand_cvar_changes(int request, entity caller)
{
switch (request)
{
}
}
-void CommonCommand_cvar_purechanges(float request, entity caller)
+void CommonCommand_cvar_purechanges(int request, entity caller)
{
switch (request)
{
}
}
-void CommonCommand_info(float request, entity caller, float argc)
+void CommonCommand_info(int request, entity caller, int argc)
{
switch (request)
{
}
}
-void CommonCommand_ladder(float request, entity caller)
+void CommonCommand_ladder(int request, entity caller)
{
switch (request)
{
}
}
-void CommonCommand_lsmaps(float request, entity caller)
+void CommonCommand_lsmaps(int request, entity caller)
{
switch (request)
{
}
}
-void CommonCommand_printmaplist(float request, entity caller)
+void CommonCommand_printmaplist(int request, entity caller)
{
switch (request)
{
}
}
-void CommonCommand_rankings(float request, entity caller)
+void CommonCommand_rankings(int request, entity caller)
{
switch (request)
{
}
}
-void CommonCommand_records(float request, entity caller)
+void CommonCommand_records(int request, entity caller)
{
switch (request)
{
}
}
-void CommonCommand_teamstatus(float request, entity caller)
+void CommonCommand_teamstatus(int request, entity caller)
{
switch (request)
{
}
}
-void CommonCommand_time(float request, entity caller)
+void CommonCommand_time(int request, entity caller)
{
switch (request)
{
print_to(caller, strcat("realtime = ", ftos(gettime(GETTIME_REALTIME))));
print_to(caller, strcat("hires = ", ftos(gettime(GETTIME_HIRES))));
print_to(caller, strcat("uptime = ", ftos(gettime(GETTIME_UPTIME))));
- print_to(caller, strcat("localtime = ", strftime(true, "%a %b %e %H:%M:%S %Z %Y")));
- print_to(caller, strcat("gmtime = ", strftime(false, "%a %b %e %H:%M:%S %Z %Y")));
+ print_to(caller, strcat("localtime = ", strftime(true, "%a %b %d %H:%M:%S %Z %Y")));
+ print_to(caller, strcat("gmtime = ", strftime(false, "%a %b %d %H:%M:%S %Z %Y")));
return;
}
}
}
-void CommonCommand_timein(float request, entity caller)
+void CommonCommand_timein(int request, entity caller)
{
switch (request)
{
}
}
-void CommonCommand_timeout(float request, entity caller) // DEAR GOD THIS COMMAND IS TERRIBLE.
+void CommonCommand_timeout(int request, entity caller) // DEAR GOD THIS COMMAND IS TERRIBLE.
{
switch (request)
{
}
}
-void CommonCommand_who(float request, entity caller, float argc)
+void CommonCommand_who(int request, entity caller, int argc)
{
switch (request)
{
/* use this when creating a new command, making sure to place it in alphabetical order... also,
** ADD ALL NEW COMMANDS TO commands.cfg WITH PROPER ALIASES IN THE SAME FASHION!
-void CommonCommand_(float request, entity caller)
+void CommonCommand_(int request, entity caller)
{
switch(request)
{
// is this entity number even in the possible range of entities?
float VerifyClientNumber(float tmp_number);
-entity GetIndexedEntity(float argc, float start_index);
+entity GetIndexedEntity(int argc, float start_index);
// find a player which matches the input string, and return their entity
entity GetFilteredEntity(string input);
// Common commands used in both sv_cmd.qc and cmd.qc
// ===================================================
-void CommonCommand_cvar_changes(float request, entity caller);
+void CommonCommand_cvar_changes(int request, entity caller);
-void CommonCommand_cvar_purechanges(float request, entity caller);
+void CommonCommand_cvar_purechanges(int request, entity caller);
-void CommonCommand_editmob(float request, entity caller, float argc);
+void CommonCommand_editmob(int request, entity caller, int argc);
-void CommonCommand_info(float request, entity caller, float argc);
+void CommonCommand_info(int request, entity caller, int argc);
-void CommonCommand_ladder(float request, entity caller);
+void CommonCommand_ladder(int request, entity caller);
-void CommonCommand_lsmaps(float request, entity caller);
+void CommonCommand_lsmaps(int request, entity caller);
-void CommonCommand_printmaplist(float request, entity caller);
+void CommonCommand_printmaplist(int request, entity caller);
-void CommonCommand_rankings(float request, entity caller);
+void CommonCommand_rankings(int request, entity caller);
-void CommonCommand_records(float request, entity caller);
+void CommonCommand_records(int request, entity caller);
-void CommonCommand_teamstatus(float request, entity caller);
+void CommonCommand_teamstatus(int request, entity caller);
-void CommonCommand_time(float request, entity caller);
+void CommonCommand_time(int request, entity caller);
-void CommonCommand_timein(float request, entity caller);
+void CommonCommand_timein(int request, entity caller);
-void CommonCommand_timeout(float request, entity caller);
+void CommonCommand_timeout(int request, entity caller);
-void CommonCommand_who(float request, entity caller, float argc);
+void CommonCommand_who(int request, entity caller, int argc);
// ==================================
FOREACH(COMMON_COMMANDS, true, { print_to(caller, sprintf(" ^2%s^7: %s", it.m_name, it.m_description)); });
}
-float CommonCommand_macro_command(float argc, entity caller, string command)
+float CommonCommand_macro_command(int argc, entity caller, string command)
{
string c = strtolower(argv(0));
FOREACH(COMMON_COMMANDS, it.m_name == c, {
return false;
}
-float CommonCommand_macro_usage(float argc, entity caller)
+float CommonCommand_macro_usage(int argc, entity caller)
{
string c = strtolower(argv(1));
FOREACH(COMMON_COMMANDS, it.m_name == c, {
}
}
-bool RadarMap_Make(float argc)
+bool RadarMap_Make(int argc)
{
float i;
#pragma once
#ifndef RADARMAP
-bool RadarMap_Make(float argc) { LOG_INFO("radarmap is disabled, compile with -DRADARMAP to enable it."); return true; }
+bool RadarMap_Make(int argc) { LOG_INFO("radarmap is disabled, compile with -DRADARMAP to enable it."); return true; }
#else
// ===========================================
// FF is contained twice, to map 256 to FF too
// removes the need to bound()
-bool RadarMap_Make(float argc);
+bool RadarMap_Make(int argc);
#endif
// Command Sub-Functions
// =======================
-void GameCommand_adminmsg(float request, float argc)
+void GameCommand_adminmsg(int request, int argc)
{
switch (request)
{
}
}
-void GameCommand_allready(float request)
+void GameCommand_allready(int request)
{
switch (request)
{
}
}
-void GameCommand_allspec(float request, float argc)
+void GameCommand_allspec(int request, int argc)
{
switch (request)
{
}
}
-void GameCommand_anticheat(float request, float argc)
+void GameCommand_anticheat(int request, int argc)
{
switch (request)
{
}
}
-void GameCommand_bbox(float request)
+void GameCommand_bbox(int request)
{
switch (request)
{
}
}
-void GameCommand_bot_cmd(float request, float argc, string command)
+void GameCommand_bot_cmd(int request, int argc, string command)
{
switch (request)
{
}
}
-void GameCommand_cointoss(float request, float argc)
+void GameCommand_cointoss(int request, int argc)
{
switch (request)
{
}
}
-void GameCommand_database(float request, float argc)
+void GameCommand_database(int request, int argc)
{
switch (request)
{
}
}
-void GameCommand_defer_clear(float request, float argc)
+void GameCommand_defer_clear(int request, int argc)
{
switch (request)
{
}
}
-void GameCommand_defer_clear_all(float request)
+void GameCommand_defer_clear_all(int request)
{
switch (request)
{
case CMD_REQUEST_COMMAND:
{
int n = 0;
- float argc;
+ int argc;
FOREACH_CLIENT(true, {
argc = tokenize_console(strcat("defer_clear ", ftos(etof(it))));
}
}
-void GameCommand_delrec(float request, float argc) // perhaps merge later with records and printstats and such?
+void GameCommand_delrec(int request, int argc) // perhaps merge later with records and printstats and such?
{
switch (request)
{
}
}
-void GameCommand_effectindexdump(float request)
+void GameCommand_effectindexdump(int request)
{
switch (request)
{
}
}
-void GameCommand_extendmatchtime(float request)
+void GameCommand_extendmatchtime(int request)
{
switch (request)
{
}
}
-void GameCommand_gametype(float request, float argc)
+void GameCommand_gametype(int request, int argc)
{
switch (request)
{
}
}
-void GameCommand_gettaginfo(float request, float argc)
+void GameCommand_gettaginfo(int request, int argc)
{
switch (request)
{
}
}
-void GameCommand_animbench(float request, float argc)
+void GameCommand_animbench(int request, int argc)
{
switch (request)
{
}
}
-void GameCommand_gotomap(float request, float argc)
+void GameCommand_gotomap(int request, int argc)
{
switch (request)
{
}
}
-void GameCommand_lockteams(float request)
+void GameCommand_lockteams(int request)
{
switch (request)
{
}
}
-void GameCommand_make_mapinfo(float request)
+void GameCommand_make_mapinfo(int request)
{
switch (request)
{
}
}
-void GameCommand_moveplayer(float request, float argc)
+void GameCommand_moveplayer(int request, int argc)
{
switch (request)
{
{
// set up
float team_id;
- float save = client.team_forced;
- client.team_forced = 0;
+ int save = Player_GetForcedTeamIndex(client);
+ Player_SetForcedTeamIndex(client, TEAM_FORCE_DEFAULT);
// find the team to move the player to
team_id = Team_ColorToTeam(destination);
+ entity balance;
if (team_id == client.team) // already on the destination team
{
// keep the forcing undone
}
else if (team_id == 0) // auto team
{
- CheckAllowedTeams(client);
- team_id = Team_NumberToTeam(FindSmallestTeam(client, false));
+ balance = TeamBalance_CheckAllowedTeams(client);
+ team_id = Team_IndexToTeam(TeamBalance_FindBestTeam(balance, client, false));
}
else
{
- CheckAllowedTeams(client);
+ balance = TeamBalance_CheckAllowedTeams(client);
}
- client.team_forced = save;
+ Player_SetForcedTeamIndex(client, save);
// Check to see if the destination team is even available
switch (team_id)
{
- case NUM_TEAM_1: if (c1 == -1) { LOG_INFO("Sorry, can't move player to red team if it doesn't exist."); return; } break;
- case NUM_TEAM_2: if (c2 == -1) { LOG_INFO("Sorry, can't move player to blue team if it doesn't exist."); return; } break;
- case NUM_TEAM_3: if (c3 == -1) { LOG_INFO("Sorry, can't move player to yellow team if it doesn't exist."); return; } break;
- case NUM_TEAM_4: if (c4 == -1) { LOG_INFO("Sorry, can't move player to pink team if it doesn't exist."); return; } break;
-
- default: LOG_INFO("Sorry, can't move player here if team ", destination, " doesn't exist.");
+ case NUM_TEAM_1:
+ {
+ if (!TeamBalance_IsTeamAllowed(balance, 1))
+ {
+ LOG_INFO("Sorry, can't move player to red team if it doesn't exist.");
+ TeamBalance_Destroy(balance);
+ return;
+ }
+ TeamBalance_Destroy(balance);
+ break;
+ }
+ case NUM_TEAM_2:
+ {
+ if (!TeamBalance_IsTeamAllowed(balance, 2))
+ {
+ LOG_INFO("Sorry, can't move player to blue team if it doesn't exist.");
+ TeamBalance_Destroy(balance);
+ return;
+ }
+ TeamBalance_Destroy(balance);
+ break;
+ }
+ case NUM_TEAM_3:
+ {
+ if (!TeamBalance_IsTeamAllowed(balance, 3))
+ {
+ LOG_INFO("Sorry, can't move player to yellow team if it doesn't exist.");
+ TeamBalance_Destroy(balance);
+ return;
+ }
+ TeamBalance_Destroy(balance);
+ break;
+ }
+ case NUM_TEAM_4:
+ {
+ if (!TeamBalance_IsTeamAllowed(balance, 4))
+ {
+ LOG_INFO("Sorry, can't move player to pink team if it doesn't exist.");
+ TeamBalance_Destroy(balance);
+ return;
+ }
+ TeamBalance_Destroy(balance);
+ break;
+ }
+ default:
+ {
+ LOG_INFO("Sorry, can't move player here if team ", destination, " doesn't exist.");
return;
+ }
}
// If so, lets continue and finally move the player
- client.team_forced = 0;
- if (MoveToTeam(client, team_id, 6))
+ Player_SetForcedTeamIndex(client, TEAM_FORCE_DEFAULT);
+ if (MoveToTeam(client, Team_TeamToIndex(team_id), 6))
{
successful = strcat(successful, (successful ? ", " : ""), playername(client, false));
LOG_INFO("Player ", ftos(GetFilteredNumber(t)), " (", playername(client, false), ") has been moved to the ", Team_ColoredFullName(team_id), "^7.");
}
}
-void GameCommand_nospectators(float request)
+void GameCommand_nospectators(int request)
{
switch (request)
{
}
}
-void GameCommand_printstats(float request)
+void GameCommand_printstats(int request)
{
switch (request)
{
}
}
-void GameCommand_radarmap(float request, float argc)
+void GameCommand_radarmap(int request, int argc)
{
switch (request)
{
}
}
-void GameCommand_reducematchtime(float request)
+void GameCommand_reducematchtime(int request)
{
switch (request)
{
}
}
-void GameCommand_setbots(float request, float argc)
+void GameCommand_setbots(int request, int argc)
{
switch (request)
{
}
}
-void GameCommand_shuffleteams(float request)
+void GameCommand_shuffleteams(int request)
{
switch (request)
{
}
FOREACH_CLIENT(IS_PLAYER(it) || it.caplayer, {
- if (it.team_forced) {
+ if (Player_HasRealForcedTeam(it)) {
// we could theoretically assign forced players to their teams
// and shuffle the rest to fill the empty spots but in practise
// either all players or none are gonna have forced teams
});
int number_of_teams = 0;
- CheckAllowedTeams(NULL);
- if (c1 >= 0) number_of_teams = max(1, number_of_teams);
- if (c2 >= 0) number_of_teams = max(2, number_of_teams);
- if (c3 >= 0) number_of_teams = max(3, number_of_teams);
- if (c4 >= 0) number_of_teams = max(4, number_of_teams);
+ entity balance = TeamBalance_CheckAllowedTeams(NULL);
+ for (int i = 1; i <= NUM_TEAMS; ++i)
+ {
+ if (TeamBalance_IsTeamAllowed(balance, i))
+ {
+ number_of_teams = max(i, number_of_teams);
+ }
+ }
+ TeamBalance_Destroy(balance);
int team_index = 0;
FOREACH_CLIENT_RANDOM(IS_PLAYER(it) || it.caplayer, {
- int target_team_number = Team_NumberToTeam(team_index + 1);
- if (it.team != target_team_number) MoveToTeam(it, target_team_number, 6);
+ int target_team_index = team_index + 1;
+ if (Entity_GetTeamIndex(it) != target_team_index)
+ {
+ MoveToTeam(it, target_team_index, 6);
+ }
team_index = (team_index + 1) % number_of_teams;
});
}
}
-void GameCommand_stuffto(float request, float argc)
+void GameCommand_stuffto(int request, int argc)
{
// This... is a fairly dangerous and powerful command... - It allows any arguments to be sent to a client via rcon.
// Because of this, it is disabled by default and must be enabled by the server owner when doing compilation. That way,
#endif
}
-void GameCommand_trace(float request, float argc)
+void GameCommand_trace(int request, int argc)
{
switch (request)
{
}
}
-void GameCommand_unlockteams(float request)
+void GameCommand_unlockteams(int request)
{
switch (request)
{
}
}
-void GameCommand_warp(float request, float argc)
+void GameCommand_warp(int request, int argc)
{
switch (request)
{
/* use this when creating a new command, making sure to place it in alphabetical order... also,
** ADD ALL NEW COMMANDS TO commands.cfg WITH PROPER ALIASES IN THE SAME FASHION!
-void GameCommand_(float request)
+void GameCommand_(int request)
{
switch(request)
{
FOREACH(SERVER_COMMANDS, true, { LOG_INFOF(" ^2%s^7: %s", it.m_name, it.m_description); });
}
-float GameCommand_macro_command(float argc, string command)
+float GameCommand_macro_command(int argc, string command)
{
string c = strtolower(argv(0));
FOREACH(SERVER_COMMANDS, it.m_name == c, {
return false;
}
-float GameCommand_macro_usage(float argc)
+float GameCommand_macro_usage(int argc)
{
string c = strtolower(argv(1));
FOREACH(SERVER_COMMANDS, it.m_name == c, {
void GameCommand(string command)
{
- float argc = tokenize_console(command);
+ int argc = tokenize_console(command);
// Guide for working with argc arguments by example:
// argc: 1 - 2 - 3 - 4
#include "../g_damage.qh"
#include "../g_world.qh"
+#include "../teamplay.qh"
#include "../race.qh"
#include "../round_handler.qh"
#include "../scores.qh"
if (it.reset2) it.reset2(it);
});
- FOREACH_CLIENT(IS_PLAYER(it) && STAT(FROZEN, it), { Unfreeze(it); });
+ FOREACH_CLIENT(IS_PLAYER(it) && STAT(FROZEN, it), { Unfreeze(it, false); });
// Moving the player reset code here since the player-reset depends
// on spawnpoint entities which have to be reset first --blub
return false;
}
-string VoteCommand_extractcommand(string input, float startpos, float argc)
+string VoteCommand_extractcommand(string input, float startpos, int argc)
{
string output;
return validated_map;
}
-float VoteCommand_checkargs(float startpos, float argc)
+float VoteCommand_checkargs(float startpos, int argc)
{
float p, q, check, minargs;
string cvarname = strcat("sv_vote_command_restriction_", argv(startpos));
return true;
}
-int VoteCommand_parse(entity caller, string vote_command, string vote_list, float startpos, float argc)
+int VoteCommand_parse(entity caller, string vote_command, string vote_list, float startpos, int argc)
{
string first_command = argv(startpos);
int missing_chars = argv_start_index(startpos);
break;
}
+ case "restart":
+ {
+ // add a delay so that vote result can be seen and announcer can be heard
+ // if the vote is accepted
+ vote_parsed_command = strcat("defer 1 ", vote_command);
+ vote_parsed_display = strzone(strcat("^1", vote_command));
+
+ break;
+ }
+
default:
{
vote_parsed_command = vote_command;
// Command Sub-Functions
// =======================
-void VoteCommand_abstain(float request, entity caller) // CLIENT ONLY
+void VoteCommand_abstain(int request, entity caller) // CLIENT ONLY
{
switch (request)
{
}
}
-void VoteCommand_call(float request, entity caller, float argc, string vote_command) // BOTH
+void VoteCommand_call(int request, entity caller, int argc, string vote_command) // BOTH
{
switch (request)
{
}
FOREACH_CLIENT(IS_REAL_CLIENT(it), { ++tmp_playercount; });
- if (tmp_playercount > 1)
- Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_CALL);
bprint("\{1}^2* ^3", OriginalCallerName(), "^2 calls a vote for ", vote_called_display, "\n");
if (autocvar_sv_eventlog)
GameLogEcho(strcat(":vote:vcall:", ftos(vote_caller.playerid), ":", vote_called_display));
Nagger_VoteChanged();
VoteCount(true); // needed if you are the only one
+
+ if (tmp_playercount > 1 && vote_called != VOTE_NULL)
+ Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_CALL);
}
return;
}
}
-void VoteCommand_master(float request, entity caller, float argc, string vote_command) // CLIENT ONLY
+void VoteCommand_master(int request, entity caller, int argc, string vote_command) // CLIENT ONLY
{
switch (request)
{
}
}
-void VoteCommand_no(float request, entity caller) // CLIENT ONLY
+void VoteCommand_no(int request, entity caller) // CLIENT ONLY
{
switch (request)
{
}
}
-void VoteCommand_status(float request, entity caller) // BOTH
+void VoteCommand_status(int request, entity caller) // BOTH
{
switch (request)
{
}
}
-void VoteCommand_stop(float request, entity caller) // BOTH
+void VoteCommand_stop(int request, entity caller) // BOTH
{
switch (request)
{
}
}
-void VoteCommand_yes(float request, entity caller) // CLIENT ONLY
+void VoteCommand_yes(int request, entity caller) // CLIENT ONLY
{
switch (request)
{
/* use this when creating a new command, making sure to place it in alphabetical order... also,
** ADD ALL NEW COMMANDS TO commands.cfg WITH PROPER ALIASES IN THE SAME FASHION!
-void VoteCommand_(float request)
+void VoteCommand_(int request)
{
switch(request)
{
VOTE_COMMAND("yes", VoteCommand_yes(request, caller), "Select yes in current vote", VC_ASGNMNT_CLIENTONLY) \
/* nothing */
-void VoteCommand_macro_help(entity caller, float argc)
+void VoteCommand_macro_help(entity caller, int argc)
{
string command_origin = GetCommandPrefix(caller);
}
}
-float VoteCommand_macro_command(entity caller, float argc, string vote_command)
+float VoteCommand_macro_command(entity caller, int argc, string vote_command)
{
#define VOTE_COMMAND(name, function, description, assignment) \
{ if (Votecommand_check_assignment(caller, assignment)) { if (name == strtolower(argv(1))) { function; return true; } } }
// Main function handling vote commands
// ======================================
-void VoteCommand(float request, entity caller, float argc, string vote_command)
+void VoteCommand(int request, entity caller, int argc, string vote_command)
{
// Guide for working with argc arguments by example:
// argc: 1 - 2 - 3 - 4
// allow functions to be used in other code like g_world.qc and teamplay.qc
void VoteThink();
void VoteReset();
-void VoteCommand(float request, entity caller, float argc, string vote_command);
+void VoteCommand(int request, entity caller, int argc, string vote_command);
// warmup and nagger stuff
const float RESTART_COUNTDOWN = 10;
//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(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);}
+SPAWNFUNC_ITEM_COND(item_health, (this.spawnflags & 2), ITEM_HealthMega, ITEM_HealthMedium)
#include <server/resources.qh>
#include <common/t_items.qh>
#include <common/mapobjects/triggers.qh>
+#include <common/mapobjects/trigger/counter.qh>
#include <common/weapons/_all.qh>
//***********************
InitializeEntity(this, target_give_init, INITPRIO_FINDTARGET);
}
+void score_use(entity this, entity actor, entity trigger)
+{
+ if(!IS_PLAYER(actor))
+ return;
+ actor.fragsfilter_cnt += this.count;
+}
+spawnfunc(target_score)
+{
+ if(!g_cts) { delete(this); return; }
+
+ if(!this.count)
+ this.count = 1;
+ this.use = score_use;
+}
+
+void fragsfilter_use(entity this, entity actor, entity trigger)
+{
+ if(!IS_PLAYER(actor))
+ return;
+ if(actor.fragsfilter_cnt >= this.frags)
+ SUB_UseTargets(this, actor, trigger);
+}
+spawnfunc(target_fragsFilter)
+{
+ if(!g_cts) { delete(this); return; }
+
+ if(!this.frags)
+ this.frags = 1;
+ this.use = fragsfilter_use;
+}
+
//spawnfunc(item_flight) /* handled by buffs mutator */
//spawnfunc(item_haste) /* handled by buffs mutator */
//spawnfunc(item_health) /* handled in t_quake.qc */
gametypename = "team";
if(g_ctf)
gametypename = "ctf";
+ if(g_duel)
+ gametypename = "tournament";
if(maxclients == 1)
gametypename = "single";
// we do not have the other types (oneflag, obelisk, harvester, teamtournament)
#pragma once
bool DoesQ3ARemoveThisEntity(entity this);
+
+.int fragsfilter_cnt;
float bots_would_leave;
void UpdateFrags(entity player, int f);
-.float totalfrags;
-
-float team1_score, team2_score, team3_score, team4_score;
+.int totalfrags;
// flag set on worldspawn so that the code knows if it is dedicated or not
float server_is_dedicated;
//.float worldtype;
// Needed for dynamic clientwalls
-.float inactive; // Clientwall disappears when inactive
+.bool inactive; // Clientwall disappears when inactive
.float alpha_max, alpha_min;
.float fade_start, fade_end, fade_vertical_offset;
.float default_solid; // Variable to store default .solid for clientwalls
.float cvar_cl_jetpack_jump;
.float cvar_cl_movement_track_canjump;
.float cvar_cl_newusekeysupported;
+.float cvar_cl_cts_noautoswitch;
.string cvar_g_xonoticversion;
.string cvar_cl_weaponpriority;
#else
#define ATTACK_FINISHED_FOR(ent, w, slot) ((ent).attack_finished_single[slot])
#endif
-#define ATTACK_FINISHED(ent, slot) ATTACK_FINISHED_FOR(ent, ent.(weaponentity).m_weapon.m_id, slot)
-
-// assault game mode: Which team is attacking in this round?
-float assault_attacker_team;
+#define ATTACK_FINISHED(ent, w) ATTACK_FINISHED_FOR(ent, ent.(w).m_weapon.m_id, weaponslot(w))
// speedrun: when 1, player auto teleports back when capture timeout happens
.float speedrunning;
float ServerProgsDB;
float TemporaryDB;
-.float team_saved;
+.int team_saved;
bool some_spawn_has_been_used;
int have_team_spawns; // 0 = no team spawns requested, -1 = team spawns requested but none found, 1 = team spawns requested and found
int have_team_spawns_forteams; // if Xth bit is 1 then team X has spawns else it has no spawns; team 0 is the "no-team"
-// set when showing a kill countdown
-.entity killindicator;
-
.bool canteamdamage;
void Damage (entity targ, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force);
// WEAPONTODO
#define DMG_NOWEP (weaponentities[0])
-float lockteams;
-
float sv_maxidle;
float sv_maxidle_spectatorsareidle;
int sv_maxidle_slots;
.float weapon_load[Weapons_MAX];
.int ammo_none; // used by the reloading system, must always be 0
-.float clip_load;
-.float old_clip_load;
-.float clip_size;
+.int clip_load;
+.int old_clip_load;
+.int clip_size;
.int minelayer_mines;
.float vortex_charge;
// when doing this, hagar can go through clones
// #define PROJECTILE_MAKETRIGGER(e) (e).solid = SOLID_BBOX
-.float spectatee_status;
-.float zoomstate;
-.float restriction;
+.int spectatee_status;
+.bool zoomstate;
+.int restriction;
.entity clientdata;
.entity personal;
.bool just_joined;
.float cvar_cl_weaponimpulsemode;
-.float selectweapon; // last selected weapon of the player
+.int selectweapon; // last selected weapon of the player
.float ballistics_density; // wall piercing factor, larger = bullet can pass through more
-const float ACTIVE_NOT = 0;
-const float ACTIVE_ACTIVE = 1;
-const float ACTIVE_IDLE = 2;
-const float ACTIVE_BUSY = 2;
-const float ACTIVE_TOGGLE = 3;
-.float active;
+//const int FROZEN_NOT = 0;
+const int FROZEN_NORMAL = 1;
+const int FROZEN_TEMP_REVIVING = 2;
+const int FROZEN_TEMP_DYING = 3;
+
+const int ACTIVE_NOT = 0;
+const int ACTIVE_ACTIVE = 1;
+const int ACTIVE_IDLE = 2;
+const int ACTIVE_BUSY = 2;
+const int ACTIVE_TOGGLE = 3;
+.int active;
.void (entity this, int act_state) setactive;
.entity realowner;
//float serverflags;
-.float team_forced; // can be a team number to force a team, or 0 for default action, or -1 for forced spectator
-
-.float player_blocked;
+.bool player_blocked;
.float revival_time; // time at which player was last revived
.float revive_speed; // NOTE: multiplier (anything above 1 is instaheal)
.entity muzzle_flash;
.float misc_bulletcounter; // replaces uzi & hlac bullet counter.
-.int killindicator_teamchange;
-
void PlayerUseKey(entity this);
USING(spawn_evalfunc_t, vector(entity this, entity player, entity spot, vector current));
string modname;
-.float missile_flags;
+.int missile_flags;
const int MIF_SPLASH = BIT(1);
const int MIF_ARC = BIT(2);
const int MIF_PROXY = BIT(3);
#include "bot/api.qh"
#include "g_hook.qh"
#include <server/mutators/_mod.qh>
+#include "teamplay.qh"
#include "scores.qh"
#include "spawnpoints.qh"
#include "../common/state.qh"
float targ_maxhealth = ((IS_MONSTER(targ)) ? targ.max_health : start_health);
STAT(FROZEN, targ) = frozen_type;
- STAT(REVIVE_PROGRESS, targ) = ((frozen_type == 3) ? 1 : 0);
- SetResourceAmount(targ, RESOURCE_HEALTH, ((frozen_type == 3) ? targ_maxhealth : 1));
+ STAT(REVIVE_PROGRESS, targ) = ((frozen_type == FROZEN_TEMP_DYING) ? 1 : 0);
+ SetResourceAmount(targ, RESOURCE_HEALTH, ((frozen_type == FROZEN_TEMP_DYING) ? targ_maxhealth : 1));
targ.revive_speed = revivespeed;
if(targ.bot_attack)
IL_REMOVE(g_bot_targets, targ);
WaypointSprite_Spawn(WP_Frozen, 0, 0, targ, '0 0 64', NULL, targ.team, targ, waypointsprite_attached, true, RADARICON_WAYPOINT);
}
-void Unfreeze(entity targ)
+void Unfreeze(entity targ, bool reset_health)
{
if(!STAT(FROZEN, targ))
return;
- if(STAT(FROZEN, targ) && STAT(FROZEN, targ) != 3) // only reset health if target was frozen
- {
+ if (reset_health && STAT(FROZEN, targ) != FROZEN_TEMP_DYING)
SetResourceAmount(targ, RESOURCE_HEALTH, ((IS_PLAYER(targ)) ? start_health : targ.max_health));
- targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
- }
+
+ targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
STAT(FROZEN, targ) = 0;
STAT(REVIVE_PROGRESS, targ) = 0;
if(deathtype == DEATH_FALL.m_id)
if(damage >= autocvar_g_frozen_revive_falldamage)
{
- Unfreeze(targ);
+ Unfreeze(targ, false);
SetResourceAmount(targ, RESOURCE_HEALTH, autocvar_g_frozen_revive_falldamage_health);
Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3);
Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname);
RadiusDamage_running = 0;
if(!DEATH_ISSPECIAL(deathtype))
- accuracy_add(attacker, DEATH_WEAPONOF(deathtype).m_id, 0, min(coredamage, stat_damagedone));
+ accuracy_add(attacker, DEATH_WEAPONOF(deathtype), 0, min(coredamage, stat_damagedone));
return total_damage_to_creatures;
}
}
}
if(accuracy_isgooddamage(o, e))
- accuracy_add(o, DEATH_WEAPONOF(dt).m_id, 0, max(0, totaldamage - mindamage));
+ accuracy_add(o, DEATH_WEAPONOF(dt), 0, max(0, totaldamage - mindamage));
return max(0, totaldamage - mindamage); // can never be negative, but to make sure
}
else
e.fire_owner = o;
e.fire_hitsound = false;
if(accuracy_isgooddamage(o, e))
- accuracy_add(o, DEATH_WEAPONOF(dt).m_id, 0, d);
+ accuracy_add(o, DEATH_WEAPONOF(dt), 0, d);
return d;
}
}
}
e.fire_hitsound = true;
- if(!IS_INDEPENDENT_PLAYER(e))
- if(!STAT(FROZEN, e))
- FOREACH_CLIENT(IS_PLAYER(it) && it != e, {
- if(!IS_DEAD(it))
- if(!IS_INDEPENDENT_PLAYER(it))
+ if(!IS_INDEPENDENT_PLAYER(e) && !STAT(FROZEN, e))
+ {
+ IL_EACH(g_damagedbycontents, it.damagedbycontents && it != e,
+ {
+ if(!IS_DEAD(it) && it.takedamage && !IS_INDEPENDENT_PLAYER(it))
if(boxesoverlap(e.absmin, e.absmax, it.absmin, it.absmax))
{
t = autocvar_g_balance_firetransfer_time * (e.fire_endtime - time);
Fire_AddDamage(it, o, d, t, DEATH_FIRE.m_id);
}
});
+ }
}
void Fire_ApplyEffect(entity e)
.float teamkill_soundtime;
.entity teamkill_soundsource;
.entity pusher;
-.float istypefrag;
+.bool istypefrag;
.float taunt_soundtime;
float IsFlying(entity a);
void Freeze(entity targ, float freeze_time, int frozen_type, bool show_waypoint);
-void Unfreeze (entity targ);
+void Unfreeze(entity targ, bool reset_health);
// NOTE: the .weaponentity parameter can be set to DMG_NOWEP if the attack wasn't caused by a weapon or player
void Damage (entity targ, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force);
#include <server/mutators/_mod.qh>
#include "race.qh"
#include "scores.qh"
+#include "scores_rules.qh"
#include "teamplay.qh"
#include "weapons/weaponstats.qh"
#include "../common/constants.qh"
BADCVAR("g_dm");
BADCVAR("g_domination");
BADCVAR("g_domination_default_teams");
+ BADCVAR("g_duel");
BADCVAR("g_freezetag");
BADCVAR("g_freezetag_teams");
BADCVAR("g_invasion_teams");
}
}
+void default_delayedinit(entity this)
+{
+ if(!scores_initialized)
+ ScoreRules_generic();
+}
+
+void InitGameplayMode()
+{
+ VoteReset();
+
+ // find out good world mins/maxs bounds, either the static bounds found by looking for solid, or the mapinfo specified bounds
+ get_mi_min_max(1);
+ // assign reflectively to avoid "assignment to world" warning
+ int done = 0; for (int i = 0, n = numentityfields(); i < n; ++i) {
+ string k = entityfieldname(i); vector v = (k == "mins") ? mi_min : (k == "maxs") ? mi_max : '0 0 0';
+ if (v) {
+ putentityfieldstring(i, world, sprintf("%v", v));
+ if (++done == 2) break;
+ }
+ }
+ // currently, NetRadiant's limit is 131072 qu for each side
+ // distance from one corner of a 131072qu cube to the opposite corner is approx. 227023 qu
+ // set the distance according to map size but don't go over the limit to avoid issues with float precision
+ // in case somebody makes extremely large maps
+ max_shot_distance = min(230000, vlen(world.maxs - world.mins));
+
+ MapInfo_LoadMapSettings(mapname);
+ GameRules_teams(false);
+
+ if (!cvar_value_issafe(world.fog))
+ {
+ LOG_INFO("The current map contains a potentially harmful fog setting, ignored");
+ world.fog = string_null;
+ }
+ if(MapInfo_Map_fog != "")
+ if(MapInfo_Map_fog == "none")
+ world.fog = string_null;
+ else
+ world.fog = strzone(MapInfo_Map_fog);
+ clientstuff = strzone(MapInfo_Map_clientstuff);
+
+ MapInfo_ClearTemps();
+
+ gamemode_name = MapInfo_Type_ToText(MapInfo_LoadedGametype);
+
+ cache_mutatormsg = strzone("");
+ cache_lastmutatormsg = strzone("");
+
+ InitializeEntity(NULL, default_delayedinit, INITPRIO_GAMETYPE_FALLBACK);
+}
+
+void Map_MarkAsRecent(string m);
float world_already_spawned;
spawnfunc(worldspawn)
{
// - for this timelimit_overtime needs to be >0 of course
// - also check the winning condition calculated in the previous frame and only add normal overtime
// again, if at the point at which timelimit would be extended again, still no winner was found
- if (!autocvar_g_campaign && (checkrules_overtimesadded >= 0) && (checkrules_overtimesadded < autocvar_timelimit_overtimes || autocvar_timelimit_overtimes < 0) && autocvar_timelimit_overtime && !(g_race && !g_race_qualifying))
+ if (!autocvar_g_campaign && checkrules_overtimesadded >= 0
+ && (checkrules_overtimesadded < autocvar_timelimit_overtimes || autocvar_timelimit_overtimes < 0)
+ && autocvar_timelimit_overtime && !(g_race && !g_race_qualifying))
{
return 1; // need to call InitiateOvertime later
}
{
++checkrules_overtimesadded;
//add one more overtime by simply extending the timelimit
- float tl;
- tl = autocvar_timelimit;
- tl += autocvar_timelimit_overtime;
- cvar_set("timelimit", ftos(tl));
-
+ cvar_set("timelimit", ftos(autocvar_timelimit + autocvar_timelimit_overtime));
Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_OVERTIME_TIME, autocvar_timelimit_overtime * 60);
}
if(teamplay)
{
- team1_score = TeamScore_GetCompareValue(NUM_TEAM_1);
- team2_score = TeamScore_GetCompareValue(NUM_TEAM_2);
- team3_score = TeamScore_GetCompareValue(NUM_TEAM_3);
- team4_score = TeamScore_GetCompareValue(NUM_TEAM_4);
+ for (int i = 1; i < 5; ++i)
+ {
+ Team_SetTeamScore(Team_GetTeamFromIndex(i),
+ TeamScore_GetCompareValue(Team_IndexToTeam(i)));
+ }
}
ClearWinners();
if(!some_spawn_has_been_used)
return WINNING_NO;
- team1_score = team2_score = team3_score = team4_score = 0;
+ for (int i = 1; i < 5; ++i)
+ {
+ Team_SetTeamScore(Team_GetTeamFromIndex(i), 0);
+ }
- FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), {
- switch(it.team)
+ FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+ {
+ if (Team_IsValidTeam(it.team))
{
- case NUM_TEAM_1: team1_score = 1; break;
- case NUM_TEAM_2: team2_score = 1; break;
- case NUM_TEAM_3: team3_score = 1; break;
- case NUM_TEAM_4: team4_score = 1; break;
+ Team_SetTeamScore(Team_GetTeam(it.team), 1);
}
});
IL_EACH(g_spawnpoints, true,
{
- switch(it.team)
+ if (Team_IsValidTeam(it.team))
{
- case NUM_TEAM_1: team1_score = 1; break;
- case NUM_TEAM_2: team2_score = 1; break;
- case NUM_TEAM_3: team3_score = 1; break;
- case NUM_TEAM_4: team4_score = 1; break;
+ Team_SetTeamScore(Team_GetTeam(it.team), 1);
}
});
ClearWinners();
+ float team1_score = Team_GetTeamScore(Team_GetTeamFromIndex(1));
+ float team2_score = Team_GetTeamScore(Team_GetTeamFromIndex(2));
+ float team3_score = Team_GetTeamScore(Team_GetTeamFromIndex(3));
+ float team4_score = Team_GetTeamScore(Team_GetTeamFromIndex(4));
if(team1_score + team2_score + team3_score + team4_score == 0)
{
checkrules_equality = true;
{
float t, i;
if(team1_score)
- t = NUM_TEAM_1;
+ t = 1;
else if(team2_score)
- t = NUM_TEAM_2;
+ t = 2;
else if(team3_score)
- t = NUM_TEAM_3;
+ t = 3;
else // if(team4_score)
- t = NUM_TEAM_4;
- CheckAllowedTeams(NULL);
+ t = 4;
+ entity balance = TeamBalance_CheckAllowedTeams(NULL);
for(i = 0; i < MAX_TEAMSCORE; ++i)
{
- if(t != NUM_TEAM_1) if(c1 >= 0) TeamScore_AddToTeam(NUM_TEAM_1, i, -1000);
- if(t != NUM_TEAM_2) if(c2 >= 0) TeamScore_AddToTeam(NUM_TEAM_2, i, -1000);
- if(t != NUM_TEAM_3) if(c3 >= 0) TeamScore_AddToTeam(NUM_TEAM_3, i, -1000);
- if(t != NUM_TEAM_4) if(c4 >= 0) TeamScore_AddToTeam(NUM_TEAM_4, i, -1000);
+ for (int j = 1; j <= NUM_TEAMS; ++j)
+ {
+ if (t == j)
+ {
+ continue;
+ }
+ if (!TeamBalance_IsTeamAllowed(balance, j))
+ {
+ continue;
+ }
+ TeamScore_AddToTeam(Team_IndexToTeam(j), i, -1000);
+ }
}
AddWinners(team, t);
float checkrules_suddendeathend;
float checkrules_overtimesadded; //how many overtimes have been already added
+string cache_mutatormsg;
+string cache_lastmutatormsg;
+
const int WINNING_NO = 0; // no winner, but time limits may terminate the game
const int WINNING_YES = 1; // winner found
const int WINNING_NEVER = 2; // no winner, enter overtime if time limit is reached
#include "impulse.qh"
#include "round_handler.qh"
-#include "bot/api.qh"
-
#include "weapons/throwing.qh"
#include "command/common.qh"
#include "cheats.qh"
+#include "clientkill.qh"
#include "weapons/selection.qh"
#include "weapons/tracing.qh"
#include "weapons/weaponsystem.qh"
for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) \
{ \
.entity weaponentity = weaponentities[slot]; \
- W_SwitchWeapon(this, Weapons_from(WEP_FIRST + i), weaponentity); \
+ W_SwitchWeapon_TryOthers(this, Weapons_from(WEP_FIRST + i), weaponentity); \
if(slot == 0 && autocvar_g_weaponswitch_debug != 1) \
break; \
} \
}
sprint(this, "all waypoints cleared\n");
}
-
-IMPULSE(navwaypoint_spawn)
-{
- if (!autocvar_g_waypointeditor) return;
- waypoint_spawn_fromeditor(this);
-}
-
-IMPULSE(navwaypoint_remove)
-{
- if (!autocvar_g_waypointeditor) return;
- waypoint_remove_fromeditor(this);
-}
-
-IMPULSE(navwaypoint_relink)
-{
- if (!autocvar_g_waypointeditor) return;
- waypoint_schedulerelinkall();
-}
-
-IMPULSE(navwaypoint_save)
-{
- if (!autocvar_g_waypointeditor) return;
- waypoint_saveall();
-}
-
-IMPULSE(navwaypoint_unreachable)
-{
- if (!autocvar_g_waypointeditor) return;
- waypoint_unreachable(this);
-}
void OnlineBanList_Think(entity this)
{
- float argc;
+ int argc;
string uri;
float i, n;
if (Ban_IsClientBanned(client, -1))
{
string s = sprintf("^1NOTE:^7 banned client %s just tried to enter\n", client.netaddress);
+ if(autocvar_g_ban_telluser)
+ sprint(client, "You are banned from this server.\n");
dropclient(client);
bprint(s);
return true;
+++ /dev/null
-#include "item_key.qh"
-
-#include "../common/mapobjects/subs.qh"
-#include <common/mapobjects/triggers.qh>
-#include "../common/monsters/_mod.qh"
-#include "../common/notifications/all.qh"
-#include "../common/util.qh"
-#include "../lib/warpzone/util_server.qh"
-
-/*
-TODO:
-- add an unlock sound (here to trigger_keylock and to func_door)
-- display available keys on the HUD
-- make more tests
-- think about adding NOT_EASY/NOT_NORMAL/NOT_HARD for Q1 compatibility
-- should keys have a trigger?
-*/
-
-bool item_keys_usekey(entity l, entity p)
-{
- int valid = l.itemkeys & PS(p).itemkeys;
-
- if (!valid) {
- // player has none of the needed keys
- return false;
- } else if (l.itemkeys == valid) {
- // ALL needed keys were given
- l.itemkeys = 0;
- return true;
- } else {
- // only some of the needed keys were given
- l.itemkeys &= ~valid;
- return true;
- }
-}
-
-string item_keys_keylist(float keylist) {
- // no keys
- if (!keylist)
- return "";
-
- // one key
- if ((keylist & (keylist-1)) == 0)
- return strcat("the ", item_keys_names[lowestbit(keylist)]);
-
- string n = "";
- int base = 0;
- while (keylist) {
- int l = lowestbit(keylist);
- if (n)
- n = strcat(n, ", the ", item_keys_names[base + l]);
- else
- n = strcat("the ", item_keys_names[base + l]);
-
- keylist = bitshift(keylist, -(l + 1));
- base+= l + 1;
- }
-
- return n;
-}
-
-
-/*
-================================
-item_key
-================================
-*/
-
-/**
- * Key touch handler.
- */
-void item_key_touch(entity this, entity toucher)
-{
- if (!IS_PLAYER(toucher))
- return;
-
- // player already picked up this key
- if (PS(toucher).itemkeys & this.itemkeys)
- return;
-
- PS(toucher).itemkeys |= this.itemkeys;
- play2(toucher, this.noise);
-
- centerprint(toucher, this.message);
-
- string oldmsg = this.message;
- this.message = "";
- SUB_UseTargets(this, toucher, toucher); // TODO: should we be using toucher for the trigger here?
- this.message = oldmsg;
-}
-
-/**
- * Spawn a key with given model, key code and color.
- */
-void spawn_item_key(entity this)
-{
- precache_model(this.model);
-
- if (this.spawnflags & 1) // FLOATING
- this.noalign = 1;
-
- if (this.noalign)
- set_movetype(this, MOVETYPE_NONE);
- else
- set_movetype(this, MOVETYPE_TOSS);
-
- precache_sound(this.noise);
-
- this.mdl = this.model;
- this.effects = EF_LOWPRECISION;
- _setmodel(this, this.model);
- //setsize(this, '-16 -16 -24', '16 16 32');
- setorigin(this, this.origin + '0 0 32');
- setsize(this, '-16 -16 -56', '16 16 0');
- this.modelflags |= MF_ROTATE;
- this.solid = SOLID_TRIGGER;
-
- if (!this.noalign)
- {
- // first nudge it off the floor a little bit to avoid math errors
- setorigin(this, this.origin + '0 0 1');
- // note droptofloor returns false if stuck/or would fall too far
- droptofloor(this);
- }
-
- settouch(this, item_key_touch);
-}
-
-
-/*QUAKED item_key (0 .5 .8) (-16 -16 -24) (16 16 32) FLOATING
-A key entity.
-The itemkeys should contain one of the following key IDs:
-1 - GOLD key -
-2 - SILVER key
-4 - BRONZE key
-8 - RED keycard
-16 - BLUE keycard
-32 - GREEN keycard
-Custom keys:
-... - last key is 1<<23
-Keys with bigger Id than 32 don't have a default netname and model, if you use one of them, you MUST provide those.
------------KEYS------------
-colormod: color of the key (default: '.9 .9 .9').
-itemkeys: a key Id.
-message: message to print when player picks up this key.
-model: custom key model to use.
-netname: the display name of the key.
-noise: custom sound to play when player picks up the key.
--------- SPAWNFLAGS --------
-FLOATING: the item will float in air, instead of aligning to the floor by falling
----------NOTES----------
-This is the only correct way to put keys on the map!
-
-itemkeys MUST always have exactly one bit set.
-*/
-spawnfunc(item_key)
-{
- string _netname;
- vector _colormod;
-
- // reject this entity if more than one key was set!
- if (this.itemkeys>0 && (this.itemkeys & (this.itemkeys-1)) != 0) {
- objerror(this, "item_key.itemkeys must contain only 1 bit set specifying the key it represents!");
- delete(this);
- return;
- }
-
- // find default netname and colormod
- switch(this.itemkeys) {
- case BIT(0):
- _netname = "GOLD key";
- _colormod = '1 .9 0';
- break;
-
- case BIT(1):
- _netname = "SILVER key";
- _colormod = '.9 .9 .9';
- break;
-
- case BIT(2):
- _netname = "BRONZE key";
- _colormod = '.6 .25 0';
- break;
-
- case BIT(3):
- _netname = "RED keycard";
- _colormod = '.9 0 0';
- break;
-
- case BIT(4):
- _netname = "BLUE keycard";
- _colormod = '0 0 .9';
- break;
-
- case BIT(5):
- _netname = "GREEN keycard";
- _colormod = '0 .9 0';
- break;
-
- default:
- _netname = "FLUFFY PINK keycard";
- _colormod = '1 1 1';
-
- if (this.netname == "") {
- objerror(this, "item_key doesn't have a default name for this key and a custom one was not specified!");
- delete(this);
- return;
- }
- break;
-
- }
-
- // find default model
- string _model = string_null;
- if (this.itemkeys <= ITEM_KEY_BIT(2)) {
- _model = "models/keys/key.md3";
- } else if (this.itemkeys >= ITEM_KEY_BIT(3) && this.itemkeys <= ITEM_KEY_BIT(5)) {
- _model = "models/keys/key.md3"; // FIXME: replace it by a keycard model!
- } else if (this.model == "") {
- objerror(this, "item_key doesn't have a default model for this key and a custom one was not specified!");
- delete(this);
- return;
- }
-
- // set defailt netname
- if (this.netname == "")
- this.netname = _netname;
-
- // set default colormod
- if (!this.colormod)
- this.colormod = _colormod;
-
- // set default model
- if (this.model == "")
- this.model = _model;
-
- // set default pickup message
- if (this.message == "")
- this.message = strzone(strcat("You've picked up the ", this.netname, "!"));
-
- if (this.noise == "")
- this.noise = strzone(SND(ITEMPICKUP));
-
- // save the name for later
- item_keys_names[lowestbit(this.itemkeys)] = this.netname;
-
- // put the key on the map
- spawn_item_key(this);
-}
-
-/*QUAKED item_key1 (0 .5 .8) (-16 -16 -24) (16 16 32) FLOATING
-SILVER key.
------------KEYS------------
-colormod: color of the key (default: '.9 .9 .9').
-message: message to print when player picks up this key.
-model: custom model to use.
-noise: custom sound to play when player picks up the key.
--------- SPAWNFLAGS --------
-FLOATING: the item will float in air, instead of aligning to the floor by falling
----------NOTES----------
-Don't use this entity on new maps! Use item_key instead.
-*/
-spawnfunc(item_key1)
-{
- this.classname = "item_key";
- this.itemkeys = ITEM_KEY_BIT(1);
- spawnfunc_item_key(this);
-}
-
-/*QUAKED item_key2 (0 .5 .8) (-16 -16 -24) (16 16 32) FLOATING
-GOLD key.
------------KEYS------------
-colormod: color of the key (default: '1 .9 0').
-message: message to print when player picks up this key.
-model: custom model to use.
-noise: custom sound to play when player picks up the key.
--------- SPAWNFLAGS --------
-FLOATING: the item will float in air, instead of aligning to the floor by falling
----------NOTES----------
-Don't use this entity on new maps! Use item_key instead.
-*/
-spawnfunc(item_key2)
-{
- this.classname = "item_key";
- this.itemkeys = ITEM_KEY_BIT(0);
- spawnfunc_item_key(this);
-}
+++ /dev/null
-#pragma once
-
-/**
- * Returns the bit ID of a key
- */
-#define ITEM_KEY_BIT(n) ( bitshift(1, n) )
-
-#define ITEM_KEY_MAX 24
-
-/**
- * list of key names.
- */
-#ifdef SVQC
-string item_keys_names[ITEM_KEY_MAX];
-
-/**
- * Use keys from p on l.
- * Returns true if any new keys were given, false otherwise.
- */
-float item_keys_usekey(entity l, entity p);
-
-/**
- * Returns a string with a comma separated list of key names, as specified in keylist.
- */
-string item_keys_keylist(float keylist);
-#endif
#include "matrix.qh"
-#include "player.qh"
+#include "client.qh"
var void MX_Handle(int buf, string ancestor)
{
{
traceline_antilag(pl, CS(pl).cursor_trace_start, CS(pl).cursor_trace_start + normalize(CS(pl).cursor_trace_endpos - CS(pl).cursor_trace_start) * max_shot_distance, MOVE_NORMAL, pl, ANTILAG_LATENCY(pl));
}
-.bool ctrace_solidchanged;
+
void crosshair_trace_plusvisibletriggers(entity pl)
+{
+ crosshair_trace_plusvisibletriggers__is_wz(pl, false);
+}
+
+void WarpZone_crosshair_trace_plusvisibletriggers(entity pl)
+{
+ crosshair_trace_plusvisibletriggers__is_wz(pl, true);
+}
+
+void crosshair_trace_plusvisibletriggers__is_wz(entity pl, bool is_wz)
{
FOREACH_ENTITY_FLOAT(solid, SOLID_TRIGGER,
{
if(it.model != "")
{
it.solid = SOLID_BSP;
- it.ctrace_solidchanged = true;
IL_PUSH(g_ctrace_changed, it);
}
});
- crosshair_trace(pl);
+ if (is_wz)
+ WarpZone_crosshair_trace(pl);
+ else
+ crosshair_trace(pl);
- IL_EACH(g_ctrace_changed, it.ctrace_solidchanged,
- {
- it.solid = SOLID_TRIGGER;
- it.ctrace_solidchanged = false;
- });
+ IL_EACH(g_ctrace_changed, true, { it.solid = SOLID_TRIGGER; });
IL_CLEAR(g_ctrace_changed);
}
+
void WarpZone_crosshair_trace(entity pl)
{
WarpZone_traceline_antilag(pl, CS(pl).cursor_trace_start, CS(pl).cursor_trace_start + normalize(CS(pl).cursor_trace_endpos - CS(pl).cursor_trace_start) * max_shot_distance, MOVE_NORMAL, pl, ANTILAG_LATENCY(pl));
{
float p, p1, p2;
float n;
- vector cursor;
- entity cursor_ent;
+ vector cursor = '0 0 0';
+ entity cursor_ent = NULL;
string escape;
string replacement;
p = 0;
n = 7;
-
- WarpZone_crosshair_trace(this);
- cursor = trace_endpos;
- cursor_ent = trace_ent;
+ bool traced = false;
MUTATOR_CALLHOOK(PreFormatMessage, this, msg);
msg = M_ARGV(1, string);
if (p < 0)
break;
+ if(!traced)
+ {
+ WarpZone_crosshair_trace_plusvisibletriggers(this);
+ cursor = trace_endpos;
+ cursor_ent = trace_ent;
+ traced = true;
+ }
+
replacement = substring(msg, p, 2);
escape = substring(msg, p + 1, 1);
REPLICATE(cvar_g_xonoticversion, string, "g_xonoticversion");
+REPLICATE(cvar_cl_cts_noautoswitch, bool, "cl_cts_noautoswitch");
+
/**
* @param f -1: cleanup, 0: request, 1: receive
*/
for (i = 0; i < t; ++i)
{
s = argv(i);
- FOREACH(Weapons, it != WEP_Null, {
- if(it.netname == s)
- {
- g_weaponarena_weapons |= (it.m_wepset);
- g_weaponarena_list = strcat(g_weaponarena_list, it.m_name, " & ");
- break;
- }
- });
+ Weapon wep = Weapons_fromstr(s);
+ if(wep != WEP_Null)
+ {
+ g_weaponarena_weapons |= (wep.m_wepset);
+ g_weaponarena_list = strcat(g_weaponarena_list, wep.m_name, " & ");
+ }
}
g_weaponarena_list = strzone(substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3));
}
});
}
+ if(cvar("g_balance_superweapons_time") < 0)
+ start_items |= IT_UNLIMITED_SUPERWEAPONS;
+
if(!cvar("g_use_ammunition"))
start_items |= IT_UNLIMITED_AMMO;
builtin_remove(e);
}
-void InitializeEntity(entity e, void(entity this) func, float order)
+void InitializeEntity(entity e, void(entity this) func, int order)
{
entity prev, cur;
void crosshair_trace(entity pl);
void crosshair_trace_plusvisibletriggers(entity pl);
+void WarpZone_crosshair_trace_plusvisibletriggers(entity pl);
+void crosshair_trace_plusvisibletriggers__is_wz(entity pl, bool is_wz);
void detach_sameorigin(entity e);
string NearestLocation(vector p);
+string AmmoNameFromWeaponentity(Weapon wep);
+
void play2(entity e, string filename);
string playername(entity p, bool team_colorize);
//#NO AUTOCVARS END
-const float INITPRIO_FIRST = 0;
-const float INITPRIO_GAMETYPE = 0;
-const float INITPRIO_GAMETYPE_FALLBACK = 1;
-const float INITPRIO_FINDTARGET = 10;
-const float INITPRIO_DROPTOFLOOR = 20;
-const float INITPRIO_SETLOCATION = 90;
-const float INITPRIO_LINKDOORS = 91;
-const float INITPRIO_LAST = 99;
+const int INITPRIO_FIRST = 0;
+const int INITPRIO_GAMETYPE = 0;
+const int INITPRIO_GAMETYPE_FALLBACK = 1;
+const int INITPRIO_FINDTARGET = 10;
+const int INITPRIO_DROPTOFLOOR = 20;
+const int INITPRIO_SETLOCATION = 90;
+const int INITPRIO_LINKDOORS = 91;
+const int INITPRIO_LAST = 99;
.void(entity this) initialize_entity;
-.float initialize_entity_order;
+.int initialize_entity_order;
.entity initialize_entity_next;
entity initialize_entity_first;
-float sound_allowed(float dest, entity e);
-void InitializeEntity(entity e, void(entity this) func, float order);
+bool sound_allowed(int dest, entity e);
+void InitializeEntity(entity e, void(entity this) func, int order);
IntrusiveList g_ctrace_changed;
STATIC_INIT(g_ctrace_changed) { g_ctrace_changed = IL_NEW(); }
/** called when the match ends */
MUTATOR_HOOKABLE(MatchEnd, EV_NO_ARGS);
-/** allows adjusting allowed teams */
-#define EV_CheckAllowedTeams(i, o) \
+/** Allows adjusting allowed teams. Return true to use the bitmask value and set
+ * non-empty string to use team entity name. Both behaviors can be active at the
+ * same time and will stack allowed teams.
+ */
+#define EV_TeamBalance_CheckAllowedTeams(i, o) \
/** mask of teams */ i(float, MUTATOR_ARGV_0_float) \
/**/ o(float, MUTATOR_ARGV_0_float) \
/** team entity name */ i(string, MUTATOR_ARGV_1_string) \
/**/ o(string, MUTATOR_ARGV_1_string) \
/** player checked */ i(entity, MUTATOR_ARGV_2_entity) \
/**/
-MUTATOR_HOOKABLE(CheckAllowedTeams, EV_CheckAllowedTeams);
+MUTATOR_HOOKABLE(TeamBalance_CheckAllowedTeams,
+ EV_TeamBalance_CheckAllowedTeams);
/** return true to manually override team counts */
-MUTATOR_HOOKABLE(GetTeamCounts, EV_NO_ARGS);
+MUTATOR_HOOKABLE(TeamBalance_GetTeamCounts, EV_NO_ARGS);
-/** allow overriding of team counts */
-#define EV_GetTeamCount(i, o) \
- /** team to count */ i(float, MUTATOR_ARGV_0_float) \
+/** allows overriding of team counts */
+#define EV_TeamBalance_GetTeamCount(i, o) \
+ /** team index to count */ i(float, MUTATOR_ARGV_0_float) \
/** player to ignore */ i(entity, MUTATOR_ARGV_1_entity) \
- /** number of players in a team */ i(float, MUTATOR_ARGV_2_float) \
- /**/ o(float, MUTATOR_ARGV_2_float) \
- /** number of bots in a team */ i(float, MUTATOR_ARGV_3_float) \
- /**/ o(float, MUTATOR_ARGV_3_float) \
- /** lowest scoring human in a team */ i(entity, MUTATOR_ARGV_4_entity) \
- /**/ o(entity, MUTATOR_ARGV_4_entity) \
- /** lowest scoring bot in a team */ i(entity, MUTATOR_ARGV_5_entity) \
- /**/ o(entity, MUTATOR_ARGV_5_entity) \
- /**/
-MUTATOR_HOOKABLE(GetTeamCount, EV_GetTeamCount);
-
-/** allows overriding best teams */
-#define EV_FindBestTeams(i, o) \
+ /** number of players in a team */ o(float, MUTATOR_ARGV_2_float) \
+ /** number of bots in a team */ o(float, MUTATOR_ARGV_3_float) \
+ /**/
+MUTATOR_HOOKABLE(TeamBalance_GetTeamCount, EV_TeamBalance_GetTeamCount);
+
+/** allows overriding the teams that will make the game most balanced if the
+ * player joins any of them.
+ */
+#define EV_TeamBalance_FindBestTeams(i, o) \
/** player checked */ i(entity, MUTATOR_ARGV_0_entity) \
/** bitmask of teams */ o(float, MUTATOR_ARGV_1_float) \
/**/
-MUTATOR_HOOKABLE(FindBestTeams, EV_FindBestTeams);
+MUTATOR_HOOKABLE(TeamBalance_FindBestTeams, EV_TeamBalance_FindBestTeams);
+
+/** Called during autobalance. Return true to override the player that will be
+switched. */
+#define EV_TeamBalance_GetPlayerForTeamSwitch(i, o) \
+ /** source team index */ i(int, MUTATOR_ARGV_0_int) \
+ /** destination team index */ i(int, MUTATOR_ARGV_1_int) \
+ /** is looking for bot */ i(bool, MUTATOR_ARGV_2_bool) \
+ /** player to switch */ o(entity, MUTATOR_ARGV_3_entity) \
+ /**/
+MUTATOR_HOOKABLE(TeamBalance_GetPlayerForTeamSwitch,
+ EV_TeamBalance_GetPlayerForTeamSwitch);
/** copies variables for spectating "spectatee" to "this" */
#define EV_SpectateCopy(i, o) \
* Called before player changes their team. Return true to block team change.
*/
#define EV_Player_ChangeTeam(i, o) \
- /** player */ i(entity, MUTATOR_ARGV_0_entity) \
- /** current team */ i(float, MUTATOR_ARGV_1_float) \
- /** new team */ i(float, MUTATOR_ARGV_2_float) \
+ /** player */ i(entity, MUTATOR_ARGV_0_entity) \
+ /** current team index */ i(float, MUTATOR_ARGV_1_float) \
+ /** new team index */ i(float, MUTATOR_ARGV_2_float) \
/**/
MUTATOR_HOOKABLE(Player_ChangeTeam, EV_Player_ChangeTeam);
* Called after player has changed their team.
*/
#define EV_Player_ChangedTeam(i, o) \
- /** player */ i(entity, MUTATOR_ARGV_0_entity) \
- /** old team */ i(float, MUTATOR_ARGV_1_float) \
- /** current team */ i(float, MUTATOR_ARGV_2_float) \
+ /** player */ i(entity, MUTATOR_ARGV_0_entity) \
+ /** old team index */ i(float, MUTATOR_ARGV_1_float) \
+ /** current team index */ i(float, MUTATOR_ARGV_2_float) \
/**/
MUTATOR_HOOKABLE(Player_ChangedTeam, EV_Player_ChangedTeam);
/** targ */ i(entity, MUTATOR_ARGV_0_entity) \
/**/
MUTATOR_HOOKABLE(Unfreeze, EV_Unfreeze);
+
+/**
+ * Called when a player is trying to join, argument is the number of players allowed to join the match
+ */
+#define EV_GetPlayerLimit(i, o) \
+ /** g_maxplayers */ i(int, MUTATOR_ARGV_0_int) \
+ /**/ o(int, MUTATOR_ARGV_0_int) \
+ /**/
+MUTATOR_HOOKABLE(GetPlayerLimit, EV_GetPlayerLimit);
#include <common/effects/all.qh>
#include "bot/api.qh"
#include "cheats.qh"
+#include "clientkill.qh"
#include "g_damage.qh"
#include "handicap.qh"
#include "miscfunctions.qh"
#include "../common/minigames/sv_minigames.qh"
+#include <common/gamemodes/_mod.qh>
+
#include "../common/physics/player.qh"
#include "../common/effects/qc/_mod.qh"
#include "../common/mutators/mutator/waypoints/waypointsprites.qh"
if (this != attacker) {
float realdmg = damage - excess;
- if (IS_PLAYER(attacker)) {
+ if (IS_PLAYER(attacker) && DIFF_TEAM(attacker, this)) {
GameRules_scoring_add(attacker, DMG, realdmg);
}
if (IS_PLAYER(this)) {
if(this.classname != "body")
Obituary (attacker, inflictor, this, deathtype, weaponentity);
- // increment frag counter for used weapon type
- Weapon w = DEATH_WEAPONOF(deathtype);
+ // increment frag counter for used weapon type
+ Weapon w = DEATH_WEAPONOF(deathtype);
if(w != WEP_Null && accuracy_isgooddamage(attacker, this))
CS(attacker).accuracy.(accuracy_frags[w.m_id-1]) += 1;
// when we get here, player actually dies
- Unfreeze(this); // remove any icy remains
- SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0); // Unfreeze resets health, so we need to set it back
+ Unfreeze(this, false); // remove any icy remains
// clear waypoints
WaypointSprite_PlayerDead(this);
GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, limit);
return true;
}
-
-bool MoveToTeam(entity client, int team_colour, int type)
-{
- int lockteams_backup = lockteams; // backup any team lock
- lockteams = 0; // disable locked teams
- TeamchangeFrags(client); // move the players frags
- if (!SetPlayerTeamSimple(client, team_colour))
- {
- return false;
- }
- Damage(client, client, client, 100000, DEATH_AUTOTEAMCHANGE.m_id, DMG_NOWEP, client.origin, '0 0 0'); // kill the player
- lockteams = lockteams_backup; // restore the team lock
- LogTeamchange(client.playerid, client.team, type);
- return true;
-}
-
-/**
- * message "": do not say, just test flood control
- * return value:
- * 1 = accept
- * 0 = reject
- * -1 = fake accept
- */
-int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodcontrol)
-{
- if (!teamsay && !privatesay && substring(msgin, 0, 1) == " ")
- msgin = substring(msgin, 1, -1); // work around DP say bug (say_team does not have this!)
-
- if(source)
- msgin = formatmessage(source, msgin);
-
- string colorstr;
- if (!IS_PLAYER(source))
- colorstr = "^0"; // black for spectators
- else if(teamplay)
- colorstr = Team_ColorCode(source.team);
- else
- {
- colorstr = "";
- teamsay = false;
- }
-
- if(game_stopped)
- teamsay = false;
-
- if (!source) {
- colorstr = "";
- teamsay = false;
- }
-
- if(msgin != "")
- msgin = trigger_magicear_processmessage_forallears(source, teamsay, privatesay, msgin);
-
- /*
- * using bprint solves this... me stupid
- // how can we prevent the message from appearing in a listen server?
- // for now, just give "say" back and only handle say_team
- if(!teamsay)
- {
- clientcommand(source, strcat("say ", msgin));
- return;
- }
- */
-
- string namestr = "";
- if (source)
- namestr = playername(source, autocvar_g_chat_teamcolors);
-
- string colorprefix = (strdecolorize(namestr) == namestr) ? "^3" : "^7";
-
- string msgstr, cmsgstr;
- string privatemsgprefix = string_null;
- int privatemsgprefixlen = 0;
- if (msgin == "") {
- msgstr = cmsgstr = "";
- } else {
- if(privatesay)
- {
- msgstr = strcat("\{1}\{13}* ", colorprefix, namestr, "^3 tells you: ^7");
- privatemsgprefixlen = strlen(msgstr);
- msgstr = strcat(msgstr, msgin);
- cmsgstr = strcat(colorstr, colorprefix, namestr, "^3 tells you:\n^7", msgin);
- privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", playername(privatesay, autocvar_g_chat_teamcolors), ": ^7");
- }
- else if(teamsay)
- {
- if(strstrofs(msgin, "/me", 0) >= 0)
- {
- //msgin = strreplace("/me", "", msgin);
- //msgin = substring(msgin, 3, strlen(msgin));
- msgin = strreplace("/me", strcat(colorstr, "(", colorprefix, namestr, colorstr, ")^7"), msgin);
- msgstr = strcat("\{1}\{13}^4* ", "^7", msgin);
- }
- else
- msgstr = strcat("\{1}\{13}", colorstr, "(", colorprefix, namestr, colorstr, ") ^7", msgin);
- cmsgstr = strcat(colorstr, "(", colorprefix, namestr, colorstr, ")\n^7", msgin);
- }
- else
- {
- if(strstrofs(msgin, "/me", 0) >= 0)
- {
- //msgin = strreplace("/me", "", msgin);
- //msgin = substring(msgin, 3, strlen(msgin));
- msgin = strreplace("/me", strcat(colorprefix, namestr), msgin);
- msgstr = strcat("\{1}^4* ", "^7", msgin);
- }
- else {
- msgstr = "\{1}";
- msgstr = strcat(msgstr, (namestr != "") ? strcat(colorprefix, namestr, "^7: ") : "^7");
- msgstr = strcat(msgstr, msgin);
- }
- cmsgstr = "";
- }
- msgstr = strcat(strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint
- }
-
- string fullmsgstr = msgstr;
- string fullcmsgstr = cmsgstr;
-
- // FLOOD CONTROL
- int flood = 0;
- var .float flood_field = floodcontrol_chat;
- if(floodcontrol && source)
- {
- float flood_spl;
- float flood_burst;
- float flood_lmax;
- float lines;
- if(privatesay)
- {
- flood_spl = autocvar_g_chat_flood_spl_tell;
- flood_burst = autocvar_g_chat_flood_burst_tell;
- flood_lmax = autocvar_g_chat_flood_lmax_tell;
- flood_field = floodcontrol_chattell;
- }
- else if(teamsay)
- {
- flood_spl = autocvar_g_chat_flood_spl_team;
- flood_burst = autocvar_g_chat_flood_burst_team;
- flood_lmax = autocvar_g_chat_flood_lmax_team;
- flood_field = floodcontrol_chatteam;
- }
- else
- {
- flood_spl = autocvar_g_chat_flood_spl;
- flood_burst = autocvar_g_chat_flood_burst;
- flood_lmax = autocvar_g_chat_flood_lmax;
- flood_field = floodcontrol_chat;
- }
- flood_burst = max(0, flood_burst - 1);
- // to match explanation in default.cfg, a value of 3 must allow three-line bursts and not four!
-
- // do flood control for the default line size
- if(msgstr != "")
- {
- getWrappedLine_remaining = msgstr;
- msgstr = "";
- lines = 0;
- while(getWrappedLine_remaining && (!flood_lmax || lines <= flood_lmax))
- {
- msgstr = strcat(msgstr, " ", getWrappedLineLen(82.4289758859709, strlennocol)); // perl averagewidth.pl < gfx/vera-sans.width
- ++lines;
- }
- msgstr = substring(msgstr, 1, strlen(msgstr) - 1);
-
- if(getWrappedLine_remaining != "")
- {
- msgstr = strcat(msgstr, "\n");
- flood = 2;
- }
-
- if (time >= source.(flood_field))
- {
- source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + lines * flood_spl;
- }
- else
- {
- flood = 1;
- msgstr = fullmsgstr;
- }
- }
- else
- {
- if (time >= source.(flood_field))
- source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + flood_spl;
- else
- flood = 1;
- }
-
- if (timeout_status == TIMEOUT_ACTIVE) // when game is paused, no flood protection
- source.(flood_field) = flood = 0;
- }
-
- string sourcemsgstr, sourcecmsgstr;
- if(flood == 2) // cannot happen for empty msgstr
- {
- if(autocvar_g_chat_flood_notify_flooder)
- {
- sourcemsgstr = strcat(msgstr, "\n^3FLOOD CONTROL: ^7message too long, trimmed\n");
- sourcecmsgstr = "";
- }
- else
- {
- sourcemsgstr = fullmsgstr;
- sourcecmsgstr = fullcmsgstr;
- }
- cmsgstr = "";
- }
- else
- {
- sourcemsgstr = msgstr;
- sourcecmsgstr = cmsgstr;
- }
-
- if (!privatesay && source && !IS_PLAYER(source))
- {
- if (!game_stopped)
- if (teamsay || (autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !warmup_stage))
- teamsay = -1; // spectators
- }
-
- if(flood)
- LOG_INFO("NOTE: ", playername(source, true), "^7 is flooding.");
-
- // build sourcemsgstr by cutting off a prefix and replacing it by the other one
- if(privatesay)
- sourcemsgstr = strcat(privatemsgprefix, substring(sourcemsgstr, privatemsgprefixlen, -1));
-
- int ret;
- if(source && CS(source).muted)
- {
- // always fake the message
- ret = -1;
- }
- else if(flood == 1)
- {
- if (autocvar_g_chat_flood_notify_flooder)
- {
- sprint(source, strcat("^3FLOOD CONTROL: ^7wait ^1", ftos(source.(flood_field) - time), "^3 seconds\n"));
- ret = 0;
- }
- else
- ret = -1;
- }
- else
- {
- ret = 1;
- }
-
- if (privatesay && source && !IS_PLAYER(source))
- {
- if (!game_stopped)
- if ((privatesay && IS_PLAYER(privatesay)) && ((autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !warmup_stage)))
- ret = -1; // just hide the message completely
- }
-
- MUTATOR_CALLHOOK(ChatMessage, source, ret);
- ret = M_ARGV(1, int);
-
- if(sourcemsgstr != "" && ret != 0)
- {
- if(ret < 0) // faked message, because the player is muted
- {
- sprint(source, sourcemsgstr);
- if(sourcecmsgstr != "" && !privatesay)
- centerprint(source, sourcecmsgstr);
- }
- else if(privatesay) // private message, between 2 people only
- {
- sprint(source, sourcemsgstr);
- if (!autocvar_g_chat_tellprivacy) { dedicated_print(msgstr); } // send to server console too if "tellprivacy" is disabled
- if(!MUTATOR_CALLHOOK(ChatMessageTo, privatesay, source))
- {
- sprint(privatesay, msgstr);
- if(cmsgstr != "")
- centerprint(privatesay, cmsgstr);
- }
- }
- else if ( teamsay && CS(source).active_minigame )
- {
- sprint(source, sourcemsgstr);
- dedicated_print(msgstr); // send to server console too
- FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && CS(it).active_minigame == CS(source).active_minigame && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), sprint(it, msgstr));
- }
- else if(teamsay > 0) // team message, only sent to team mates
- {
- sprint(source, sourcemsgstr);
- dedicated_print(msgstr); // send to server console too
- if(sourcecmsgstr != "")
- centerprint(source, sourcecmsgstr);
- FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it) && it != source && it.team == source.team && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
- sprint(it, msgstr);
- if(cmsgstr != "")
- centerprint(it, cmsgstr);
- });
- }
- else if(teamsay < 0) // spectator message, only sent to spectators
- {
- sprint(source, sourcemsgstr);
- dedicated_print(msgstr); // send to server console too
- FOREACH_CLIENT(!IS_PLAYER(it) && IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), sprint(it, msgstr));
- }
- else
- {
- if (source) {
- sprint(source, sourcemsgstr);
- dedicated_print(msgstr); // send to server console too
- MX_Say(strcat(playername(source, true), "^7: ", msgin));
- }
- FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), sprint(it, msgstr));
- }
- }
-
- return ret;
-}
.entity pusher;
.float pushltime;
-.float istypefrag;
+.bool istypefrag;
.float CopyBody_nextthink;
.void(entity this) CopyBody_think;
void calculate_player_respawn_time(entity this);
-void ClientKill_Now_TeamChange(entity this);
-
-/// \brief Moves player to the specified team.
-/// \param[in,out] client Client to move.
-/// \param[in] team_colour Color of the team.
-/// \param[in] type ???
-/// \return True on success, false otherwise.
-bool MoveToTeam(entity client, float team_colour, float type);
-
void PlayerDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force);
bool PlayerHeal(entity targ, entity inflictor, float amount, float limit);
-
-int Say(entity source, float teamsay, entity privatesay, string msgin, float floodcontrol);
// reset fade counter
teleporter.portal_wants_to_vanish = 0;
- teleporter.fade_time = time + autocvar_g_balance_portal_lifetime;
+ teleporter.fade_time = ((autocvar_g_balance_portal_lifetime >= 0) ? time + autocvar_g_balance_portal_lifetime : 0);
SetResourceAmountExplicit(teleporter, RESOURCE_HEALTH, autocvar_g_balance_portal_health);
SetResourceAmountExplicit(teleporter.enemy, RESOURCE_HEALTH, autocvar_g_balance_portal_health);
destination.enemy = teleporter;
Portal_MakeInPortal(teleporter);
Portal_MakeOutPortal(destination);
- teleporter.fade_time = time + autocvar_g_balance_portal_lifetime;
+ teleporter.fade_time = ((autocvar_g_balance_portal_lifetime >= 0) ? time + autocvar_g_balance_portal_lifetime : 0);
destination.fade_time = teleporter.fade_time;
teleporter.portal_wants_to_vanish = 0;
destination.portal_wants_to_vanish = 0;
this.nextthink = time;
- if(time > this.fade_time)
+ if(this.fade_time && time > this.fade_time)
Portal_Remove(this, 0);
}
portal.portal_activatetime = time + 0.1;
portal.takedamage = DAMAGE_AIM;
portal.event_damage = Portal_Damage;
- portal.fade_time = time + autocvar_g_balance_portal_lifetime;
+ portal.fade_time = ((autocvar_g_balance_portal_lifetime >= 0) ? time + autocvar_g_balance_portal_lifetime : 0);
SetResourceAmountExplicit(portal, RESOURCE_HEALTH, autocvar_g_balance_portal_health);
setmodel(portal, MDL_PORTAL);
portal.savemodelindex = portal.modelindex;
this.nextthink = max(time, game_starttime);
}
-void round_handler_Spawn(float() canRoundStart_func, float() canRoundEnd_func, void() roundStart_func)
+void round_handler_Spawn(bool() canRoundStart_func, bool() canRoundEnd_func, void() roundStart_func)
{
if (round_handler)
{
entity round_handler;
.float delay; // stores delay from round end to countdown start
.float count; // stores initial number of the countdown
-.float wait; // it's set to true when round ends, to false when countdown starts
+.bool wait; // it's set to true when round ends, to false when countdown starts
.float cnt; // its initial value is .count + 1, then decreased while counting down
// reaches 0 when the round starts
.float round_timelimit;
.float round_endtime;
-.float() canRoundStart;
-.float() canRoundEnd;
+.bool() canRoundStart;
+.bool() canRoundEnd;
.void() roundStart;
void round_handler_Init(float the_delay, float the_count, float the_round_timelimit);
-void round_handler_Spawn(float() canRoundStart_func, float() canRoundEnd_func, void() roundStart_func);
+void round_handler_Spawn(bool() canRoundStart_func, bool() canRoundEnd_func, void() roundStart_func);
void round_handler_Reset(float next_think);
void round_handler_Remove();
int AvailableTeams()
{
return NumTeams(ScoreRules_teams);
- // NOTE: this method is unreliable, as forced teams set the c* globals to weird values
- //return boolean(c1 >= 0) + boolean(c2 >= 0) + boolean(c3 >= 0) + boolean(c4 >= 0);
}
// NOTE: ST_constants may not be >= MAX_TEAMSCORE
void ScoreRules_generic()
{
int teams = 0;
- if (teamplay) {
- CheckAllowedTeams(NULL);
- if (c1 >= 0) teams |= BIT(0);
- if (c2 >= 0) teams |= BIT(1);
- if (c3 >= 0) teams |= BIT(2);
- if (c4 >= 0) teams |= BIT(3);
+ if (teamplay)
+ {
+ entity balance = TeamBalance_CheckAllowedTeams(NULL);
+ teams = TeamBalance_GetAllowedTeams(balance);
+ TeamBalance_Destroy(balance);
}
GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, {});
}
vector spawn_score = prio * '1 0 0' + shortest * '0 1 0';
// filter out spots for assault
- if(spot.target != "")
+ if(spot.target && spot.target != "")
{
int found = 0;
for(entity targ = findchain(targetname, spot.target); targ; targ = targ.chain)
#include <common/gamemodes/_mod.qh>
#include "../common/teams.qh"
-void TeamchangeFrags(entity e)
+/// \brief Describes a state of team balance entity.
+enum
{
- PlayerScore_Clear(e);
-}
+ TEAM_BALANCE_UNINITIALIZED, ///< The team balance has not been initialized.
+ /// \brief TeamBalance_CheckAllowedTeams has been called.
+ TEAM_BALANCE_TEAMS_CHECKED,
+ /// \brief TeamBalance_GetTeamCounts has been called.
+ TEAM_BALANCE_TEAM_COUNTS_FILLED
+};
-void LogTeamchange(float player_id, float team_number, float type)
-{
- if(!autocvar_sv_eventlog)
- return;
+/// \brief Indicates that the player is not allowed to join a team.
+const int TEAM_NOT_ALLOWED = -1;
- if(player_id < 1)
- return;
+.float team_forced; // can be a team number to force a team, or 0 for default action, or -1 for forced spectator
- GameLogEcho(strcat(":team:", ftos(player_id), ":", ftos(team_number), ":", ftos(type)));
-}
+.int m_team_balance_state; ///< Holds the state of the team balance entity.
+.entity m_team_balance_team[NUM_TEAMS]; ///< ???
-void default_delayedinit(entity this)
-{
- if(!scores_initialized)
- ScoreRules_generic();
-}
+.float m_team_score; ///< The score of the team.
+.int m_num_players; ///< Number of players (both humans and bots) in a team.
+.int m_num_bots; ///< Number of bots in a team.
+.int m_num_players_alive; ///< Number of alive players in a team.
+.int m_num_control_points; ///< Number of control points owned by a team.
-void InitGameplayMode()
-{
- VoteReset();
-
- // find out good world mins/maxs bounds, either the static bounds found by looking for solid, or the mapinfo specified bounds
- get_mi_min_max(1);
- // assign reflectively to avoid "assignment to world" warning
- int done = 0; for (int i = 0, n = numentityfields(); i < n; ++i) {
- string k = entityfieldname(i); vector v = (k == "mins") ? mi_min : (k == "maxs") ? mi_max : '0 0 0';
- if (v) {
- putentityfieldstring(i, world, sprintf("%v", v));
- if (++done == 2) break;
- }
- }
- // currently, NetRadiant's limit is 131072 qu for each side
- // distance from one corner of a 131072qu cube to the opposite corner is approx. 227023 qu
- // set the distance according to map size but don't go over the limit to avoid issues with float precision
- // in case somebody makes extremely large maps
- max_shot_distance = min(230000, vlen(world.maxs - world.mins));
-
- MapInfo_LoadMapSettings(mapname);
- GameRules_teams(false);
-
- if (!cvar_value_issafe(world.fog))
- {
- LOG_INFO("The current map contains a potentially harmful fog setting, ignored");
- world.fog = string_null;
- }
- if(MapInfo_Map_fog != "")
- if(MapInfo_Map_fog == "none")
- world.fog = string_null;
- else
- world.fog = strzone(MapInfo_Map_fog);
- clientstuff = strzone(MapInfo_Map_clientstuff);
+string autocvar_g_forced_team_red;
+string autocvar_g_forced_team_blue;
+string autocvar_g_forced_team_yellow;
+string autocvar_g_forced_team_pink;
- MapInfo_ClearTemps();
+entity g_team_entities[NUM_TEAMS]; ///< Holds global team entities.
- gamemode_name = MapInfo_Type_ToText(MapInfo_LoadedGametype);
-
- cache_mutatormsg = strzone("");
- cache_lastmutatormsg = strzone("");
-
- InitializeEntity(NULL, default_delayedinit, INITPRIO_GAMETYPE_FALLBACK);
+STATIC_INIT(g_team_entities)
+{
+ for (int i = 0; i < NUM_TEAMS; ++i)
+ {
+ g_team_entities[i] = spawn();
+ }
}
-string GetClientVersionMessage(entity this)
+entity Team_GetTeamFromIndex(int index)
{
- if (CS(this).version_mismatch) {
- if(CS(this).version < autocvar_gameversion) {
- return strcat("This is Xonotic ", autocvar_g_xonoticversion,
- "\n^3Your client version is outdated.\n\n\n### YOU WON'T BE ABLE TO PLAY ON THIS SERVER ###\n\n\nPlease update!!!^8");
- } else {
- return strcat("This is Xonotic ", autocvar_g_xonoticversion,
- "\n^3This server is using an outdated Xonotic version.\n\n\n ### THIS SERVER IS INCOMPATIBLE AND THUS YOU CANNOT JOIN ###.^8");
- }
- } else {
- return strcat("Welcome to Xonotic ", autocvar_g_xonoticversion);
+ if (!Team_IsValidIndex(index))
+ {
+ LOG_FATALF("Team_GetTeamFromIndex: Index is invalid: %f", index);
}
+ return g_team_entities[index - 1];
}
-string getwelcomemessage(entity this)
+entity Team_GetTeam(int team_num)
{
- MUTATOR_CALLHOOK(BuildMutatorsPrettyString, "");
- string modifications = M_ARGV(0, string);
-
- if(g_weaponarena)
+ if (!Team_IsValidTeam(team_num))
{
- if(g_weaponarena_random)
- modifications = strcat(modifications, ", ", ftos(g_weaponarena_random), " of ", g_weaponarena_list, " Arena"); // TODO: somehow get this into the mutator
- else
- modifications = strcat(modifications, ", ", g_weaponarena_list, " Arena");
+ LOG_FATALF("Team_GetTeam: Value is invalid: %f", team_num);
}
- else if(cvar("g_balance_blaster_weaponstartoverride") == 0)
- modifications = strcat(modifications, ", No start weapons");
- if(cvar("sv_gravity") < stof(cvar_defstring("sv_gravity")))
- modifications = strcat(modifications, ", Low gravity");
- if(g_weapon_stay && !g_cts)
- modifications = strcat(modifications, ", Weapons stay");
- if(g_jetpack)
- modifications = strcat(modifications, ", Jetpack");
- if(autocvar_g_powerups == 0)
- modifications = strcat(modifications, ", No powerups");
- if(autocvar_g_powerups > 0)
- modifications = strcat(modifications, ", Powerups");
- modifications = substring(modifications, 2, strlen(modifications) - 2);
+ return g_team_entities[Team_TeamToIndex(team_num) - 1];
+}
- string versionmessage = GetClientVersionMessage(this);
- string s = strcat(versionmessage, "^8\n^8\nmatch type is ^1", gamemode_name, "^8\n");
+float Team_GetTeamScore(entity team_ent)
+{
+ return team_ent.m_team_score;
+}
- if(modifications != "")
- s = strcat(s, "^8\nactive modifications: ^3", modifications, "^8\n");
+void Team_SetTeamScore(entity team_ent, float score)
+{
+ team_ent.m_team_score = score;
+}
- if(cache_lastmutatormsg != autocvar_g_mutatormsg)
- {
- strcpy(cache_lastmutatormsg, autocvar_g_mutatormsg);
- strcpy(cache_mutatormsg, cache_lastmutatormsg);
- }
+int Team_GetNumberOfAlivePlayers(entity team_ent)
+{
+ return team_ent.m_num_players_alive;
+}
+
+void Team_SetNumberOfAlivePlayers(entity team_ent, int number)
+{
+ team_ent.m_num_players_alive = number;
+}
- if (cache_mutatormsg != "") {
- s = strcat(s, "\n\n^8special gameplay tips: ^7", cache_mutatormsg);
+int Team_GetNumberOfAliveTeams()
+{
+ int result = 0;
+ for (int i = 0; i < NUM_TEAMS; ++i)
+ {
+ if (g_team_entities[i].m_num_players_alive > 0)
+ {
+ ++result;
+ }
}
+ return result;
+}
- string mutator_msg = "";
- MUTATOR_CALLHOOK(BuildGameplayTipsString, mutator_msg);
- mutator_msg = M_ARGV(0, string);
+int Team_GetNumberOfControlPoints(entity team_ent)
+{
+ return team_ent.m_num_control_points;
+}
- s = strcat(s, mutator_msg); // trust that the mutator will do proper formatting
+void Team_SetNumberOfControlPoints(entity team_ent, int number)
+{
+ team_ent.m_num_control_points = number;
+}
- string motd = autocvar_sv_motd;
- if (motd != "") {
- s = strcat(s, "\n\n^8MOTD: ^7", strreplace("\\n", "\n", motd));
+int Team_GetNumberOfTeamsWithControlPoints()
+{
+ int result = 0;
+ for (int i = 0; i < NUM_TEAMS; ++i)
+ {
+ if (g_team_entities[i].m_num_control_points > 0)
+ {
+ ++result;
+ }
}
- return s;
+ return result;
}
void setcolor(entity this, int clr)
#endif
}
+bool Entity_HasValidTeam(entity this)
+{
+ return Team_IsValidTeam(this.team);
+}
+
+int Entity_GetTeamIndex(entity this)
+{
+ return Team_TeamToIndex(this.team);
+}
+
+entity Entity_GetTeam(entity this)
+{
+ int index = Entity_GetTeamIndex(this);
+ if (!Team_IsValidIndex(index))
+ {
+ return NULL;
+ }
+ return Team_GetTeamFromIndex(index);
+}
+
void SetPlayerColors(entity player, float _color)
{
float pants = _color & 0x0F;
}
}
-void KillPlayerForTeamChange(entity player)
+bool Player_SetTeamIndex(entity player, int index)
{
- if (IS_DEAD(player))
+ int new_team = Team_IndexToTeam(index);
+ if (player.team == new_team)
{
- return;
+ if (new_team != -1)
+ {
+ // This is important when players join the game and one of their
+ // color matches the team color while other doesn't. For example
+ // [BOT]Lion.
+ SetPlayerColors(player, new_team - 1);
+ }
+ return true;
}
- if (MUTATOR_CALLHOOK(Player_ChangeTeamKill, player) == true)
+ int old_index = Team_TeamToIndex(player.team);
+ if (MUTATOR_CALLHOOK(Player_ChangeTeam, player, old_index, index) == true)
{
- return;
+ // Mutator has blocked team change.
+ return false;
+ }
+ if (new_team == -1)
+ {
+ player.team = -1;
+ }
+ else
+ {
+ SetPlayerColors(player, new_team - 1);
}
- Damage(player, player, player, 100000, DEATH_TEAMCHANGE.m_id, DMG_NOWEP, player.origin,
- '0 0 0');
+ MUTATOR_CALLHOOK(Player_ChangedTeam, player, old_index, index);
+ return true;
}
-bool SetPlayerTeamSimple(entity player, int team_num)
+bool SetPlayerTeam(entity player, int team_index, int type)
{
- if (player.team == team_num)
+ int old_team_index = Entity_GetTeamIndex(player);
+ if (!Player_SetTeamIndex(player, team_index))
{
- // This is important when players join the game and one of their color
- // matches the team color while other doesn't. For example [BOT]Lion.
- SetPlayerColors(player, team_num - 1);
- return true;
+ return false;
}
- if (MUTATOR_CALLHOOK(Player_ChangeTeam, player, Team_TeamToNumber(
- player.team), Team_TeamToNumber(team_num)) == true)
+ LogTeamChange(player.playerid, player.team, type);
+ if (team_index != old_team_index)
{
- // Mutator has blocked team change.
- return false;
+ PlayerScore_Clear(player);
+ if (team_index != -1)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(
+ player.team, INFO_JOIN_PLAY_TEAM), player.netname);
+ }
+ else
+ {
+ if (!CS(player).just_joined)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_SPECTATE,
+ player.netname);
+ }
+ else
+ {
+ CS(player).just_joined = false;
+ }
+ }
+ KillPlayerForTeamChange(player);
+ if (!IS_BOT_CLIENT(player))
+ {
+ TeamBalance_AutoBalanceBots();
+ }
}
- int old_team = player.team;
- SetPlayerColors(player, team_num - 1);
- MUTATOR_CALLHOOK(Player_ChangedTeam, player, old_team, player.team);
return true;
}
-bool SetPlayerTeam(entity player, int destination_team, int source_team,
- bool no_print)
+void Player_SetTeamIndexChecked(entity player, int team_index)
{
- int team_num = Team_NumberToTeam(destination_team);
- if (!SetPlayerTeamSimple(player, team_num))
+ if (!teamplay)
{
- return false;
+ return;
}
- LogTeamchange(player.playerid, player.team, 3); // log manual team join
- if (no_print)
+ if (!Team_IsValidIndex(team_index))
{
- return true;
+ return;
}
- bprint(playername(player, false), "^7 has changed from ", Team_NumberToColoredFullName(source_team), "^7 to ", Team_NumberToColoredFullName(destination_team), "\n");
+ if ((autocvar_g_campaign) || (autocvar_g_changeteam_banned &&
+ CS(player).wasplayer))
+ {
+ Send_Notification(NOTIF_ONE, player, MSG_INFO,
+ INFO_TEAMCHANGE_NOTALLOWED);
+ return;
+ }
+ entity balance = TeamBalance_CheckAllowedTeams(player);
+ if (team_index == 1 && !TeamBalance_IsTeamAllowedInternal(balance, 1))
+ {
+ team_index = 4;
+ }
+ if (team_index == 4 && !TeamBalance_IsTeamAllowedInternal(balance, 4))
+ {
+ team_index = 3;
+ }
+ if (team_index == 3 && !TeamBalance_IsTeamAllowedInternal(balance, 3))
+ {
+ team_index = 2;
+ }
+ if (team_index == 2 && !TeamBalance_IsTeamAllowedInternal(balance, 2))
+ {
+ team_index = 1;
+ }
+ // autocvar_g_balance_teams_prevent_imbalance only makes sense if autocvar_g_balance_teams is on, as it makes the team selection dialog pointless
+ if (autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance)
+ {
+ TeamBalance_GetTeamCounts(balance, player);
+ if ((Team_IndexToBit(team_index) & TeamBalance_FindBestTeams(balance,
+ player, false)) == 0)
+ {
+ Send_Notification(NOTIF_ONE, player, MSG_INFO,
+ INFO_TEAMCHANGE_LARGERTEAM);
+ TeamBalance_Destroy(balance);
+ return;
+ }
+ }
+ TeamBalance_Destroy(balance);
+ SetPlayerTeam(player, team_index, TEAM_CHANGE_MANUAL);
+}
+
+bool MoveToTeam(entity client, int team_index, int type)
+{
+ //PrintToChatAll(sprintf("MoveToTeam: %s, %f", client.netname, team_index));
+ int lockteams_backup = lockteams; // backup any team lock
+ lockteams = 0; // disable locked teams
+ if (!SetPlayerTeam(client, team_index, type))
+ {
+ lockteams = lockteams_backup; // restore the team lock
+ return false;
+ }
+ lockteams = lockteams_backup; // restore the team lock
return true;
}
-// set c1...c4 to show what teams are allowed
-void CheckAllowedTeams(entity for_whom)
+bool Player_HasRealForcedTeam(entity player)
{
- int teams_mask = 0;
+ return player.team_forced > TEAM_FORCE_DEFAULT;
+}
- c1 = c2 = c3 = c4 = -1;
- num_bots_team1 = num_bots_team2 = num_bots_team3 = num_bots_team4 = 0;
+int Player_GetForcedTeamIndex(entity player)
+{
+ return player.team_forced;
+}
- string teament_name = string_null;
+void Player_SetForcedTeamIndex(entity player, int team_index)
+{
+ switch (team_index)
+ {
+ case TEAM_FORCE_SPECTATOR:
+ case TEAM_FORCE_DEFAULT:
+ {
+ player.team_forced = team_index;
+ break;
+ }
+ default:
+ {
+ if (!Team_IsValidIndex(team_index))
+ {
+ LOG_FATAL("Player_SetForcedTeamIndex: Invalid team index.");
+ }
+ else
+ {
+ player.team_forced = team_index;
+ break;
+ }
+ }
+ }
+}
- bool mutator_returnvalue = MUTATOR_CALLHOOK(CheckAllowedTeams, teams_mask, teament_name, for_whom);
+void Player_DetermineForcedTeam(entity player)
+{
+ if (autocvar_g_campaign)
+ {
+ if (IS_REAL_CLIENT(player)) // only players, not bots
+ {
+ if (Team_IsValidIndex(autocvar_g_campaign_forceteam))
+ {
+ player.team_forced = autocvar_g_campaign_forceteam;
+ }
+ else
+ {
+ player.team_forced = TEAM_FORCE_DEFAULT;
+ }
+ }
+ }
+ else if (PlayerInList(player, autocvar_g_forced_team_red))
+ {
+ player.team_forced = 1;
+ }
+ else if (PlayerInList(player, autocvar_g_forced_team_blue))
+ {
+ player.team_forced = 2;
+ }
+ else if (PlayerInList(player, autocvar_g_forced_team_yellow))
+ {
+ player.team_forced = 3;
+ }
+ else if (PlayerInList(player, autocvar_g_forced_team_pink))
+ {
+ player.team_forced = 4;
+ }
+ else
+ {
+ switch (autocvar_g_forced_team_otherwise)
+ {
+ case "red":
+ {
+ player.team_forced = 1;
+ break;
+ }
+ case "blue":
+ {
+ player.team_forced = 2;
+ break;
+ }
+ case "yellow":
+ {
+ player.team_forced = 3;
+ break;
+ }
+ case "pink":
+ {
+ player.team_forced = 4;
+ break;
+ }
+ case "spectate":
+ case "spectator":
+ {
+ player.team_forced = TEAM_FORCE_SPECTATOR;
+ break;
+ }
+ default:
+ {
+ player.team_forced = TEAM_FORCE_DEFAULT;
+ break;
+ }
+ }
+ }
+ if (!teamplay && Player_HasRealForcedTeam(player))
+ {
+ player.team_forced = TEAM_FORCE_DEFAULT;
+ }
+}
+
+void TeamBalance_JoinBestTeam(entity player)
+{
+ //PrintToChatAll(sprintf("TeamBalance_JoinBestTeam: %s", player.netname));
+ if (!teamplay)
+ {
+ return;
+ }
+ if (player.bot_forced_team)
+ {
+ return;
+ }
+ entity balance = TeamBalance_CheckAllowedTeams(player);
+ if (Player_HasRealForcedTeam(player))
+ {
+ int forced_team_index = player.team_forced;
+ bool is_team_allowed = TeamBalance_IsTeamAllowedInternal(balance,
+ forced_team_index);
+ TeamBalance_Destroy(balance);
+ if (!is_team_allowed)
+ {
+ return;
+ }
+ if (!SetPlayerTeam(player, forced_team_index, TEAM_CHANGE_AUTO))
+ {
+ return;
+ }
+ return;
+ }
+ int best_team_index = TeamBalance_FindBestTeam(balance, player, true);
+ TeamBalance_Destroy(balance);
+ if (!SetPlayerTeam(player, best_team_index, TEAM_CHANGE_AUTO))
+ {
+ return;
+ }
+}
+
+entity TeamBalance_CheckAllowedTeams(entity for_whom)
+{
+ entity balance = spawn();
+ for (int i = 0; i < NUM_TEAMS; ++i)
+ {
+ entity team_ent = balance.m_team_balance_team[i] = spawn();
+ team_ent.m_team_score = g_team_entities[i].m_team_score;
+ team_ent.m_num_players = TEAM_NOT_ALLOWED;
+ team_ent.m_num_bots = 0;
+ }
+ setthink(balance, TeamBalance_Destroy);
+
+ int teams_mask = 0;
+ string teament_name = string_null;
+ bool mutator_returnvalue = MUTATOR_CALLHOOK(TeamBalance_CheckAllowedTeams,
+ teams_mask, teament_name, for_whom);
teams_mask = M_ARGV(0, float);
teament_name = M_ARGV(1, string);
-
- if(!mutator_returnvalue)
+ if (mutator_returnvalue)
{
- if(teams_mask & BIT(0)) c1 = 0;
- if(teams_mask & BIT(1)) c2 = 0;
- if(teams_mask & BIT(2)) c3 = 0;
- if(teams_mask & BIT(3)) c4 = 0;
+ for (int i = 0; i < NUM_TEAMS; ++i)
+ {
+ if (teams_mask & BIT(i))
+ {
+ balance.m_team_balance_team[i].m_num_players = 0;
+ }
+ }
}
- // find out what teams are allowed if necessary
- if(teament_name)
+ if (teament_name)
{
entity head = find(NULL, classname, teament_name);
- while(head)
+ while (head)
{
- switch(head.team)
+ if (Team_IsValidTeam(head.team))
{
- case NUM_TEAM_1: c1 = 0; break;
- case NUM_TEAM_2: c2 = 0; break;
- case NUM_TEAM_3: c3 = 0; break;
- case NUM_TEAM_4: c4 = 0; break;
+ TeamBalance_GetTeam(balance, head.team).m_num_players = 0;
}
-
head = find(head, classname, teament_name);
}
}
// TODO: Balance quantity of bots across > 2 teams when bot_vs_human is set (and remove next line)
- if(AvailableTeams() == 2)
- if(autocvar_bot_vs_human && for_whom)
+ if (AvailableTeams() == 2)
+ if (autocvar_bot_vs_human && for_whom)
{
- if(autocvar_bot_vs_human > 0)
+ if (autocvar_bot_vs_human > 0)
{
// find last team available
-
- if(IS_BOT_CLIENT(for_whom))
+ if (IS_BOT_CLIENT(for_whom))
{
- if(c4 >= 0) { c3 = c2 = c1 = -1; }
- else if(c3 >= 0) { c4 = c2 = c1 = -1; }
- else { c4 = c3 = c1 = -1; }
+ if (TeamBalance_IsTeamAllowedInternal(balance, 4))
+ {
+ TeamBalance_BanTeamsExcept(balance, 4);
+ }
+ else if (TeamBalance_IsTeamAllowedInternal(balance, 3))
+ {
+ TeamBalance_BanTeamsExcept(balance, 3);
+ }
+ else
+ {
+ TeamBalance_BanTeamsExcept(balance, 2);
+ }
// no further cases, we know at least 2 teams exist
}
else
{
- if(c1 >= 0) { c2 = c3 = c4 = -1; }
- else if(c2 >= 0) { c1 = c3 = c4 = -1; }
- else { c1 = c2 = c4 = -1; }
+ if (TeamBalance_IsTeamAllowedInternal(balance, 1))
+ {
+ TeamBalance_BanTeamsExcept(balance, 1);
+ }
+ else if (TeamBalance_IsTeamAllowedInternal(balance, 2))
+ {
+ TeamBalance_BanTeamsExcept(balance, 2);
+ }
+ else
+ {
+ TeamBalance_BanTeamsExcept(balance, 3);
+ }
// no further cases, bots have one of the teams
}
}
else
{
// find first team available
-
- if(IS_BOT_CLIENT(for_whom))
+ if (IS_BOT_CLIENT(for_whom))
{
- if(c1 >= 0) { c2 = c3 = c4 = -1; }
- else if(c2 >= 0) { c1 = c3 = c4 = -1; }
- else { c1 = c2 = c4 = -1; }
+ if (TeamBalance_IsTeamAllowedInternal(balance, 1))
+ {
+ TeamBalance_BanTeamsExcept(balance, 1);
+ }
+ else if (TeamBalance_IsTeamAllowedInternal(balance, 2))
+ {
+ TeamBalance_BanTeamsExcept(balance, 2);
+ }
+ else
+ {
+ TeamBalance_BanTeamsExcept(balance, 3);
+ }
// no further cases, we know at least 2 teams exist
}
else
{
- if(c4 >= 0) { c3 = c2 = c1 = -1; }
- else if(c3 >= 0) { c4 = c2 = c1 = -1; }
- else { c4 = c3 = c1 = -1; }
+ if (TeamBalance_IsTeamAllowedInternal(balance, 4))
+ {
+ TeamBalance_BanTeamsExcept(balance, 4);
+ }
+ else if (TeamBalance_IsTeamAllowedInternal(balance, 3))
+ {
+ TeamBalance_BanTeamsExcept(balance, 3);
+ }
+ else
+ {
+ TeamBalance_BanTeamsExcept(balance, 2);
+ }
// no further cases, bots have one of the teams
}
}
}
- if(!for_whom)
- return;
+ if (!for_whom)
+ {
+ balance.m_team_balance_state = TEAM_BALANCE_TEAMS_CHECKED;
+ return balance;
+ }
// if player has a forced team, ONLY allow that one
- if(for_whom.team_forced == NUM_TEAM_1 && c1 >= 0)
- c2 = c3 = c4 = -1;
- else if(for_whom.team_forced == NUM_TEAM_2 && c2 >= 0)
- c1 = c3 = c4 = -1;
- else if(for_whom.team_forced == NUM_TEAM_3 && c3 >= 0)
- c1 = c2 = c4 = -1;
- else if(for_whom.team_forced == NUM_TEAM_4 && c4 >= 0)
- c1 = c2 = c3 = -1;
+ for (int i = 1; i <= NUM_TEAMS; ++i)
+ {
+ if (for_whom.team_forced == i &&
+ TeamBalance_IsTeamAllowedInternal(balance, i))
+ {
+ TeamBalance_BanTeamsExcept(balance, i);
+ break;
+ }
+ }
+ balance.m_team_balance_state = TEAM_BALANCE_TEAMS_CHECKED;
+ return balance;
}
-float PlayerValue(entity p)
+void TeamBalance_Destroy(entity balance)
{
- return 1;
- // FIXME: it always returns 1...
+ if (balance == NULL)
+ {
+ return;
+ }
+ for (int i = 0; i < NUM_TEAMS; ++i)
+ {
+ delete(balance.(m_team_balance_team[i]));
+ }
+ delete(balance);
}
-// c1...c4 should be set to -1 (not allowed) or 0 (allowed).
-// teams that are allowed will now have their player counts stored in c1...c4
-void GetTeamCounts(entity ignore)
+int TeamBalance_GetAllowedTeams(entity balance)
{
- if (MUTATOR_CALLHOOK(GetTeamCounts) == true)
+ if (balance == NULL)
+ {
+ LOG_FATAL("TeamBalance_GetAllowedTeams: Team balance entity is NULL.");
+ }
+ if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED)
+ {
+ LOG_FATAL("TeamBalance_GetAllowedTeams: "
+ "Team balance entity is not initialized.");
+ }
+ int result = 0;
+ for (int i = 1; i <= NUM_TEAMS; ++i)
+ {
+ if (TeamBalance_IsTeamAllowedInternal(balance, i))
+ {
+ result |= Team_IndexToBit(i);
+ }
+ }
+ return result;
+}
+
+bool TeamBalance_IsTeamAllowed(entity balance, int index)
+{
+ if (balance == NULL)
+ {
+ LOG_FATAL("TeamBalance_IsTeamAllowed: Team balance entity is NULL.");
+ }
+ if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED)
+ {
+ LOG_FATAL("TeamBalance_IsTeamAllowed: "
+ "Team balance entity is not initialized.");
+ }
+ if (!Team_IsValidIndex(index))
+ {
+ LOG_FATALF("TeamBalance_IsTeamAllowed: Team index is invalid: %f",
+ index);
+ }
+ return TeamBalance_IsTeamAllowedInternal(balance, index);
+}
+
+void TeamBalance_GetTeamCounts(entity balance, entity ignore)
+{
+ if (balance == NULL)
+ {
+ LOG_FATAL("TeamBalance_GetTeamCounts: Team balance entity is NULL.");
+ }
+ if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED)
+ {
+ LOG_FATAL("TeamBalance_GetTeamCounts: "
+ "Team balance entity is not initialized.");
+ }
+ if (MUTATOR_CALLHOOK(TeamBalance_GetTeamCounts) == true)
{
- if (c1 >= 0)
+ // Mutator has overriden the configuration.
+ for (int i = 1; i <= NUM_TEAMS; ++i)
{
- MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_1, ignore, c1,
- num_bots_team1, lowest_human_team1, lowest_bot_team1);
- c1 = M_ARGV(2, float);
- num_bots_team1 = M_ARGV(3, float);
- lowest_human_team1 = M_ARGV(4, entity);
- lowest_bot_team1 = M_ARGV(5, entity);
- }
- if (c2 >= 0)
- {
- MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_2, ignore, c2,
- num_bots_team2, lowest_human_team2, lowest_bot_team2);
- c2 = M_ARGV(2, float);
- num_bots_team2 = M_ARGV(3, float);
- lowest_human_team2 = M_ARGV(4, entity);
- lowest_bot_team2 = M_ARGV(5, entity);
- }
- if (c3 >= 0)
- {
- MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_3, ignore, c3,
- num_bots_team3, lowest_human_team3, lowest_bot_team3);
- c3 = M_ARGV(2, float);
- num_bots_team3 = M_ARGV(3, float);
- lowest_human_team3 = M_ARGV(4, entity);
- lowest_bot_team3 = M_ARGV(5, entity);
- }
- if (c4 >= 0)
- {
- MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_4, ignore,
- c4, num_bots_team4, lowest_human_team4, lowest_bot_team4);
- c4 = M_ARGV(2, float);
- num_bots_team4 = M_ARGV(3, float);
- lowest_human_team4 = M_ARGV(4, entity);
- lowest_bot_team4 = M_ARGV(5, entity);
+ entity team_ent = TeamBalance_GetTeamFromIndex(balance, i);
+ if (TeamBalanceTeam_IsAllowed(team_ent))
+ {
+ MUTATOR_CALLHOOK(TeamBalance_GetTeamCount, i, ignore);
+ team_ent.m_num_players = M_ARGV(2, float);
+ team_ent.m_num_bots = M_ARGV(3, float);
+ }
}
}
else
{
- float value, bvalue;
- // now count how many players are on each team already
- float lowest_human_score1 = FLOAT_MAX;
- float lowest_bot_score1 = FLOAT_MAX;
- float lowest_human_score2 = FLOAT_MAX;
- float lowest_bot_score2 = FLOAT_MAX;
- float lowest_human_score3 = FLOAT_MAX;
- float lowest_bot_score3 = FLOAT_MAX;
- float lowest_human_score4 = FLOAT_MAX;
- float lowest_bot_score4 = FLOAT_MAX;
+ // Manually count all players.
FOREACH_CLIENT(true,
{
- float t;
- if (IS_PLAYER(it) || it.caplayer)
+ if (it == ignore)
{
- t = it.team;
+ continue;
}
- else if (it.team_forced > 0)
+ int team_num;
+ // TODO: Reconsider when the player is truly on the team.
+ if (IS_CLIENT(it) || (it.caplayer))
{
- t = it.team_forced; // reserve the spot
+ team_num = it.team;
}
- else
+ else if (Player_HasRealForcedTeam(it))
{
- continue;
+ // Do we really need this? Probably not.
+ team_num = Team_IndexToTeam(it.team_forced); // reserve the spot
}
- if (it == ignore)
+ else
{
continue;
}
- value = PlayerValue(it);
- if (IS_BOT_CLIENT(it))
- {
- bvalue = value;
- }
- else
+ if (!Team_IsValidTeam(team_num))
{
- bvalue = 0;
+ continue;
}
- if (value == 0)
+ entity team_ent = TeamBalance_GetTeam(balance, team_num);
+ if (!TeamBalanceTeam_IsAllowed(team_ent))
{
continue;
}
- switch (t)
+ ++team_ent.m_num_players;
+ if (IS_BOT_CLIENT(it))
{
- case NUM_TEAM_1:
- {
- if (c1 < 0)
- {
- break;
- }
- c1 += value;
- num_bots_team1 += bvalue;
- float temp_score = PlayerScore_Get(it, SP_SCORE);
- if (!bvalue)
- {
- if (temp_score < lowest_human_score1)
- {
- lowest_human_team1 = it;
- lowest_human_score1 = temp_score;
- }
- break;
- }
- if (temp_score < lowest_bot_score1)
- {
- lowest_bot_team1 = it;
- lowest_bot_score1 = temp_score;
- }
- break;
- }
- case NUM_TEAM_2:
- {
- if (c2 < 0)
- {
- break;
- }
- c2 += value;
- num_bots_team2 += bvalue;
- float temp_score = PlayerScore_Get(it, SP_SCORE);
- if (!bvalue)
- {
- if (temp_score < lowest_human_score2)
- {
- lowest_human_team2 = it;
- lowest_human_score2 = temp_score;
- }
- break;
- }
- if (temp_score < lowest_bot_score2)
- {
- lowest_bot_team2 = it;
- lowest_bot_score2 = temp_score;
- }
- break;
- }
- case NUM_TEAM_3:
- {
- if (c3 < 0)
- {
- break;
- }
- c3 += value;
- num_bots_team3 += bvalue;
- float temp_score = PlayerScore_Get(it, SP_SCORE);
- if (!bvalue)
- {
- if (temp_score < lowest_human_score3)
- {
- lowest_human_team3 = it;
- lowest_human_score3 = temp_score;
- }
- break;
- }
- if (temp_score < lowest_bot_score3)
- {
- lowest_bot_team3 = it;
- lowest_bot_score3 = temp_score;
- }
- break;
- }
- case NUM_TEAM_4:
- {
- if (c4 < 0)
- {
- break;
- }
- c4 += value;
- num_bots_team4 += bvalue;
- float temp_score = PlayerScore_Get(it, SP_SCORE);
- if (!bvalue)
- {
- if (temp_score < lowest_human_score4)
- {
- lowest_human_team4 = it;
- lowest_human_score4 = temp_score;
- }
- break;
- }
- if (temp_score < lowest_bot_score4)
- {
- lowest_bot_team4 = it;
- lowest_bot_score4 = temp_score;
- }
- break;
- }
+ ++team_ent.m_num_bots;
}
});
}
// if the player who has a forced team has not joined yet, reserve the spot
- if(autocvar_g_campaign)
+ if (autocvar_g_campaign)
{
- switch(autocvar_g_campaign_forceteam)
+ if (Team_IsValidIndex(autocvar_g_campaign_forceteam))
{
- case 1: if(c1 == num_bots_team1) ++c1; break;
- case 2: if(c2 == num_bots_team2) ++c2; break;
- case 3: if(c3 == num_bots_team3) ++c3; break;
- case 4: if(c4 == num_bots_team4) ++c4; break;
+ entity team_ent = TeamBalance_GetTeamFromIndex(balance,
+ autocvar_g_campaign_forceteam);
+ if (team_ent.m_num_players == team_ent.m_num_bots)
+ {
+ ++team_ent.m_num_players;
+ }
}
}
+ balance.m_team_balance_state = TEAM_BALANCE_TEAM_COUNTS_FILLED;
}
-bool IsTeamSmallerThanTeam(int team_a, int team_b, entity player,
- bool use_score)
+int TeamBalance_GetNumberOfPlayers(entity balance, int index)
{
- if (!Team_IsValidNumber(team_a))
- {
- LOG_FATALF("IsTeamSmallerThanTeam: team_a is invalid: %f", team_a);
- }
- if (!Team_IsValidNumber(team_b))
+ if (balance == NULL)
{
- LOG_FATALF("IsTeamSmallerThanTeam: team_b is invalid: %f", team_b);
+ LOG_FATAL("TeamBalance_GetNumberOfPlayers: "
+ "Team balance entity is NULL.");
}
- if (team_a == team_b)
+ if (balance.m_team_balance_state != TEAM_BALANCE_TEAM_COUNTS_FILLED)
{
- return false;
+ LOG_FATAL("TeamBalance_GetNumberOfPlayers: "
+ "TeamBalance_GetTeamCounts has not been called.");
}
- // we assume that CheckAllowedTeams and GetTeamCounts have already been called
- int num_players_team_a = -1, num_players_team_b = -1;
- int num_bots_team_a = 0, num_bots_team_b = 0;
- float score_team_a = 0, score_team_b = 0;
- switch (team_a)
+ if (!Team_IsValidIndex(index))
{
- case 1:
- {
- num_players_team_a = c1;
- num_bots_team_a = num_bots_team1;
- score_team_a = team1_score;
- break;
- }
- case 2:
- {
- num_players_team_a = c2;
- num_bots_team_a = num_bots_team2;
- score_team_a = team2_score;
- break;
- }
- case 3:
- {
- num_players_team_a = c3;
- num_bots_team_a = num_bots_team3;
- score_team_a = team3_score;
- break;
- }
- case 4:
- {
- num_players_team_a = c4;
- num_bots_team_a = num_bots_team4;
- score_team_a = team4_score;
- break;
- }
+ LOG_FATALF("TeamBalance_GetNumberOfPlayers: Team index is invalid: %f",
+ index);
}
- switch (team_b)
+ return balance.m_team_balance_team[index - 1].m_num_players;
+}
+
+int TeamBalance_FindBestTeam(entity balance, entity player, bool ignore_player)
+{
+ if (balance == NULL)
{
- case 1:
- {
- num_players_team_b = c1;
- num_bots_team_b = num_bots_team1;
- score_team_b = team1_score;
- break;
- }
- case 2:
- {
- num_players_team_b = c2;
- num_bots_team_b = num_bots_team2;
- score_team_b = team2_score;
- break;
- }
- case 3:
- {
- num_players_team_b = c3;
- num_bots_team_b = num_bots_team3;
- score_team_b = team3_score;
- break;
- }
- case 4:
- {
- num_players_team_b = c4;
- num_bots_team_b = num_bots_team4;
- score_team_b = team4_score;
- break;
- }
+ LOG_FATAL("TeamBalance_FindBestTeam: Team balance entity is NULL.");
}
- // invalid
- if (num_players_team_a < 0 || num_players_team_b < 0)
+ if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED)
{
- return false;
+ LOG_FATAL("TeamBalance_FindBestTeam: "
+ "Team balance entity is not initialized.");
}
- if (IS_REAL_CLIENT(player) && bots_would_leave)
+ // count how many players are in each team
+ if (ignore_player)
{
- num_players_team_a -= num_bots_team_a;
- num_players_team_b -= num_bots_team_b;
+ TeamBalance_GetTeamCounts(balance, player);
}
- if (!use_score)
+ else
{
- return num_players_team_a < num_players_team_b;
+ TeamBalance_GetTeamCounts(balance, NULL);
}
- if (num_players_team_a < num_players_team_b)
+ int team_bits = TeamBalance_FindBestTeams(balance, player, true);
+ if (team_bits == 0)
{
- return true;
+ LOG_FATALF("TeamBalance_FindBestTeam: No teams available for %s\n",
+ MapInfo_Type_ToString(MapInfo_CurrentGametype()));
}
- if (num_players_team_a > num_players_team_b)
+ RandomSelection_Init();
+ for (int i = 1; i <= NUM_TEAMS; ++i)
{
- return false;
+ if (team_bits & Team_IndexToBit(i))
+ {
+ RandomSelection_AddFloat(i, 1, 1);
+ }
}
- return score_team_a < score_team_b;
+ return RandomSelection_chosen_float;
}
-bool IsTeamEqualToTeam(int team_a, int team_b, entity player, bool use_score)
+int TeamBalance_FindBestTeams(entity balance, entity player, bool use_score)
{
- if (!Team_IsValidNumber(team_a))
+ if (balance == NULL)
{
- LOG_FATALF("IsTeamEqualToTeam: team_a is invalid: %f", team_a);
+ LOG_FATAL("TeamBalance_FindBestTeams: Team balance entity is NULL.");
}
- if (!Team_IsValidNumber(team_b))
+ if (balance.m_team_balance_state != TEAM_BALANCE_TEAM_COUNTS_FILLED)
{
- LOG_FATALF("IsTeamEqualToTeam: team_b is invalid: %f", team_b);
+ LOG_FATAL("TeamBalance_FindBestTeams: "
+ "TeamBalance_GetTeamCounts has not been called.");
}
- if (team_a == team_b)
+ if (MUTATOR_CALLHOOK(TeamBalance_FindBestTeams, player) == true)
{
- return true;
+ return M_ARGV(1, float);
}
- // we assume that CheckAllowedTeams and GetTeamCounts have already been called
- int num_players_team_a = -1, num_players_team_b = -1;
- int num_bots_team_a = 0, num_bots_team_b = 0;
- float score_team_a = 0, score_team_b = 0;
- switch (team_a)
+ int team_bits = 0;
+ int previous_team = 0;
+ for (int i = 1; i <= NUM_TEAMS; ++i)
{
- case 1:
+ if (!TeamBalance_IsTeamAllowedInternal(balance, i))
{
- num_players_team_a = c1;
- num_bots_team_a = num_bots_team1;
- score_team_a = team1_score;
- break;
+ continue;
}
- case 2:
+ if (previous_team == 0)
{
- num_players_team_a = c2;
- num_bots_team_a = num_bots_team2;
- score_team_a = team2_score;
- break;
+ team_bits = Team_IndexToBit(i);
+ previous_team = i;
+ continue;
}
- case 3:
+ int compare = TeamBalance_CompareTeams(balance, i, previous_team,
+ player, use_score);
+ if (compare == TEAMS_COMPARE_LESS)
{
- num_players_team_a = c3;
- num_bots_team_a = num_bots_team3;
- score_team_a = team3_score;
- break;
+ team_bits = Team_IndexToBit(i);
+ previous_team = i;
+ continue;
}
- case 4:
+ if (compare == TEAMS_COMPARE_EQUAL)
{
- num_players_team_a = c4;
- num_bots_team_a = num_bots_team4;
- score_team_a = team4_score;
- break;
+ team_bits |= Team_IndexToBit(i);
+ previous_team = i;
}
}
- switch (team_b)
+ return team_bits;
+}
+
+int TeamBalance_CompareTeams(entity balance, int team_index_a, int team_index_b,
+ entity player, bool use_score)
+{
+ if (balance == NULL)
{
- case 1:
- {
- num_players_team_b = c1;
- num_bots_team_b = num_bots_team1;
- score_team_b = team1_score;
- break;
- }
- case 2:
- {
- num_players_team_b = c2;
- num_bots_team_b = num_bots_team2;
- score_team_b = team2_score;
- break;
- }
- case 3:
- {
- num_players_team_b = c3;
- num_bots_team_b = num_bots_team3;
- score_team_b = team3_score;
- break;
- }
- case 4:
- {
- num_players_team_b = c4;
- num_bots_team_b = num_bots_team4;
- score_team_b = team4_score;
- break;
- }
+ LOG_FATAL("TeamBalance_CompareTeams: Team balance entity is NULL.");
}
- // invalid
- if (num_players_team_a < 0 || num_players_team_b < 0)
- return false;
-
- if (IS_REAL_CLIENT(player) && bots_would_leave)
+ if (balance.m_team_balance_state != TEAM_BALANCE_TEAM_COUNTS_FILLED)
{
- num_players_team_a -= num_bots_team_a;
- num_players_team_b -= num_bots_team_b;
+ LOG_FATAL("TeamBalance_CompareTeams: "
+ "TeamBalance_GetTeamCounts has not been called.");
}
- if (!use_score)
+ if (!Team_IsValidIndex(team_index_a))
{
- return num_players_team_a == num_players_team_b;
+ LOG_FATALF("TeamBalance_CompareTeams: team_index_a is invalid: %f",
+ team_index_a);
}
- if (num_players_team_a != num_players_team_b)
+ if (!Team_IsValidIndex(team_index_b))
{
- return false;
+ LOG_FATALF("TeamBalance_CompareTeams: team_index_b is invalid: %f",
+ team_index_b);
+ }
+ if (team_index_a == team_index_b)
+ {
+ return TEAMS_COMPARE_EQUAL;
}
- return score_team_a == score_team_b;
+ entity team_a = TeamBalance_GetTeamFromIndex(balance, team_index_a);
+ entity team_b = TeamBalance_GetTeamFromIndex(balance, team_index_b);
+ return TeamBalance_CompareTeamsInternal(team_a, team_b, player, use_score);
}
-int FindBestTeams(entity player, bool use_score)
+void TeamBalance_AutoBalanceBots()
{
- if (MUTATOR_CALLHOOK(FindBestTeams, player) == true)
- {
- return M_ARGV(1, float);
- }
- int team_bits = 0;
- int previous_team = 0;
- if (c1 >= 0)
+ if (!autocvar_g_balance_teams ||
+ !autocvar_g_balance_teams_prevent_imbalance)
{
- team_bits = BIT(0);
- previous_team = 1;
+ return;
}
- if (c2 >= 0)
+ //PrintToChatAll("TeamBalance_AutoBalanceBots");
+ entity balance = TeamBalance_CheckAllowedTeams(NULL);
+ TeamBalance_GetTeamCounts(balance, NULL);
+ int smallest_team_index = 0;
+ int smallest_team_player_count = 0;
+ for (int i = 1; i <= NUM_TEAMS; ++i)
{
- if (previous_team == 0)
+ entity team_ = TeamBalance_GetTeamFromIndex(balance, i);
+ if (!TeamBalanceTeam_IsAllowed(team_))
{
- team_bits = BIT(1);
- previous_team = 2;
+ continue;
}
- else if (IsTeamSmallerThanTeam(2, previous_team, player, use_score))
+ int playercount = TeamBalanceTeam_GetNumberOfPlayers(team_);
+ if (smallest_team_index == 0)
{
- team_bits = BIT(1);
- previous_team = 2;
+ smallest_team_index = i;
+ smallest_team_player_count = playercount;
}
- else if (IsTeamEqualToTeam(2, previous_team, player, use_score))
+ else if (playercount < smallest_team_player_count)
{
- team_bits |= BIT(1);
- previous_team = 2;
+ smallest_team_index = i;
+ smallest_team_player_count = playercount;
}
}
- if (c3 >= 0)
+ //PrintToChatAll(sprintf("Smallest team: %f", smallest_team_index));
+ //PrintToChatAll(sprintf("Smallest team players: %f", smallest_team_player_count));
+ entity switchable_bot = NULL;
+ int teams = BITS(NUM_TEAMS);
+ while (teams != 0)
{
- if (previous_team == 0)
+ int largest_team_index = TeamBalance_GetLargestTeamIndex(balance,
+ teams);
+ if (smallest_team_index == largest_team_index)
{
- team_bits = BIT(2);
- previous_team = 3;
+ TeamBalance_Destroy(balance);
+ return;
}
- else if (IsTeamSmallerThanTeam(3, previous_team, player, use_score))
+ entity largest_team = TeamBalance_GetTeamFromIndex(balance,
+ largest_team_index);
+ int largest_team_player_count = TeamBalanceTeam_GetNumberOfPlayers(
+ largest_team);
+ if (largest_team_player_count - smallest_team_player_count < 2)
{
- team_bits = BIT(2);
- previous_team = 3;
+ TeamBalance_Destroy(balance);
+ return;
}
- else if (IsTeamEqualToTeam(3, previous_team, player, use_score))
+ //PrintToChatAll(sprintf("Largest team: %f", largest_team_index));
+ //PrintToChatAll(sprintf("Largest team players: %f", largest_team_player_count));
+ switchable_bot = TeamBalance_GetPlayerForTeamSwitch(largest_team_index,
+ smallest_team_index, true);
+ if (switchable_bot != NULL)
{
- team_bits |= BIT(2);
- previous_team = 3;
+ break;
}
+ teams &= ~Team_IndexToBit(largest_team_index);
}
- if (c4 >= 0)
+ TeamBalance_Destroy(balance);
+ if (switchable_bot == NULL)
{
- if (previous_team == 0)
+ //PrintToChatAll("No bot found after searching through all the teams");
+ return;
+ }
+ SetPlayerTeam(switchable_bot, smallest_team_index, TEAM_CHANGE_AUTO);
+}
+
+int TeamBalance_GetLargestTeamIndex(entity balance, int teams)
+{
+ int largest_team_index = 0;
+ int largest_team_player_count = 0;
+ for (int i = 1; i <= NUM_TEAMS; ++i)
+ {
+ if (!(Team_IndexToBit(i) & teams))
{
- team_bits = BIT(3);
+ continue;
}
- else if (IsTeamSmallerThanTeam(4, previous_team, player, use_score))
+ entity team_ = TeamBalance_GetTeamFromIndex(balance, i);
+ if (!TeamBalanceTeam_IsAllowed(team_))
{
- team_bits = BIT(3);
+ continue;
}
- else if (IsTeamEqualToTeam(4, previous_team, player, use_score))
+ int playercount = TeamBalanceTeam_GetNumberOfPlayers(team_);
+ if (largest_team_index == 0)
{
- team_bits |= BIT(3);
+ largest_team_index = i;
+ largest_team_player_count = playercount;
+ }
+ else if (playercount > largest_team_player_count)
+ {
+ largest_team_index = i;
+ largest_team_player_count = playercount;
}
}
- return team_bits;
-}
-
-// returns # of smallest team (1, 2, 3, 4)
-// NOTE: Assumes CheckAllowedTeams has already been called!
-int FindSmallestTeam(entity player, float ignore_player)
-{
- // count how many players are in each team
- if (ignore_player)
- {
- GetTeamCounts(player);
- }
- else
- {
- GetTeamCounts(NULL);
- }
- int team_bits = FindBestTeams(player, true);
- if (team_bits == 0)
- {
- error(sprintf("No teams available for %s\n", MapInfo_Type_ToString(MapInfo_CurrentGametype())));
- }
- RandomSelection_Init();
- if ((team_bits & BIT(0)) != 0)
- {
- RandomSelection_AddFloat(1, 1, 1);
- }
- if ((team_bits & BIT(1)) != 0)
- {
- RandomSelection_AddFloat(2, 1, 1);
- }
- if ((team_bits & BIT(2)) != 0)
- {
- RandomSelection_AddFloat(3, 1, 1);
- }
- if ((team_bits & BIT(3)) != 0)
- {
- RandomSelection_AddFloat(4, 1, 1);
- }
- return RandomSelection_chosen_float;
+ return largest_team_index;
}
-void JoinBestTeam(entity this, bool force_best_team)
+entity TeamBalance_GetPlayerForTeamSwitch(int source_team_index,
+ int destination_team_index, bool is_bot)
{
- // don't join a team if we're not playing a team game
- if (!teamplay)
+ if (MUTATOR_CALLHOOK(TeamBalance_GetPlayerForTeamSwitch, source_team_index,
+ destination_team_index, is_bot))
{
- return;
+ return M_ARGV(3, entity);
}
-
- // find out what teams are available
- CheckAllowedTeams(this);
-
- // 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)
+ entity lowest_player = NULL;
+ float lowest_score = FLOAT_MAX;
+ FOREACH_CLIENT(Entity_GetTeamIndex(it) == source_team_index,
{
- int selected_team;
- if ((c1 >= 0) && (this.team == NUM_TEAM_1))
- {
- selected_team = this.team;
- }
- else if ((c2 >= 0) && (this.team == NUM_TEAM_2))
+ if (IS_BOT_CLIENT(it) != is_bot)
{
- selected_team = this.team;
+ continue;
}
- else if ((c3 >= 0) && (this.team == NUM_TEAM_3))
+ float temp_score = PlayerScore_Get(it, SP_SCORE);
+ if (temp_score >= lowest_score)
{
- selected_team = this.team;
+ continue;
}
- else if ((c4 >= 0) && (this.team == NUM_TEAM_4))
+ //PrintToChatAll(sprintf(
+ // "Found %s with lowest score, checking allowed teams", it.netname));
+ entity balance = TeamBalance_CheckAllowedTeams(it);
+ if (TeamBalance_IsTeamAllowed(balance, source_team_index))
{
- selected_team = this.team;
+ //PrintToChatAll("Allowed");
+ lowest_player = it;
+ lowest_score = temp_score;
}
else
{
- selected_team = -1;
+ //PrintToChatAll("Not allowed");
}
+ TeamBalance_Destroy(balance);
+ });
+ return lowest_player;
+}
- if (selected_team > 0)
- {
- SetPlayerTeamSimple(this, selected_team);
- LogTeamchange(this.playerid, this.team, 99);
- return;
- }
- }
- // otherwise end up on the smallest team (handled below)
- if (this.bot_forced_team)
+void LogTeamChange(float player_id, float team_number, int type)
+{
+ if (!autocvar_sv_eventlog)
{
return;
}
- int best_team = FindSmallestTeam(this, true);
- best_team = Team_NumberToTeam(best_team);
- if (best_team == -1)
- {
- error("JoinBestTeam: invalid team\n");
- }
- int old_team = Team_TeamToNumber(this.team);
- TeamchangeFrags(this);
- SetPlayerTeamSimple(this, best_team);
- LogTeamchange(this.playerid, this.team, 2); // log auto join
- if ((old_team != -1) && !IS_BOT_CLIENT(this))
+ if (player_id < 1)
{
- AutoBalanceBots(old_team, Team_TeamToNumber(best_team));
+ return;
}
- KillPlayerForTeamChange(this);
+ GameLogEcho(sprintf(":team:%f:%f:%f", player_id, team_number, type));
}
-void SV_ChangeTeam(entity this, float _color)
+void KillPlayerForTeamChange(entity player)
{
- float source_color, destination_color, source_team, destination_team;
-
- // in normal deathmatch we can just apply the color and we're done
- if(!teamplay)
- SetPlayerColors(this, _color);
-
- if(!IS_CLIENT(this))
+ if (IS_DEAD(player))
{
- // since this is an engine function, and gamecode doesn't have any calls earlier than this, do the connecting message here
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CONNECTING, this.netname);
return;
}
-
- if(!teamplay)
+ if (MUTATOR_CALLHOOK(Player_ChangeTeamKill, player) == true)
+ {
return;
+ }
+ Damage(player, player, player, 100000, DEATH_TEAMCHANGE.m_id, DMG_NOWEP,
+ player.origin, '0 0 0');
+}
- source_color = this.clientcolors & 0x0F;
- destination_color = _color & 0x0F;
+bool TeamBalance_IsTeamAllowedInternal(entity balance, int index)
+{
+ return balance.m_team_balance_team[index - 1].m_num_players !=
+ TEAM_NOT_ALLOWED;
+}
- source_team = Team_TeamToNumber(source_color + 1);
- destination_team = Team_TeamToNumber(destination_color + 1);
+void TeamBalance_BanTeamsExcept(entity balance, int index)
+{
+ for (int i = 1; i <= NUM_TEAMS; ++i)
+ {
+ if (i != index)
+ {
+ balance.m_team_balance_team[i - 1].m_num_players = TEAM_NOT_ALLOWED;
+ }
+ }
+}
- if (destination_team == -1)
+entity TeamBalance_GetTeamFromIndex(entity balance, int index)
+{
+ if (!Team_IsValidIndex(index))
{
- return;
+ LOG_FATALF("TeamBalance_GetTeamFromIndex: Index is invalid: %f", index);
}
+ return balance.m_team_balance_team[index - 1];
+}
- CheckAllowedTeams(this);
+entity TeamBalance_GetTeam(entity balance, int team_num)
+{
+ return TeamBalance_GetTeamFromIndex(balance, Team_TeamToIndex(team_num));
+}
- if (destination_team == 1 && c1 < 0) destination_team = 4;
- if (destination_team == 4 && c4 < 0) destination_team = 3;
- if (destination_team == 3 && c3 < 0) destination_team = 2;
- if (destination_team == 2 && c2 < 0) destination_team = 1;
+bool TeamBalanceTeam_IsAllowed(entity team_ent)
+{
+ return team_ent.m_num_players != TEAM_NOT_ALLOWED;
+}
- // not changing teams
- if (source_color == destination_color)
- {
- SetPlayerTeam(this, destination_team, source_team, true);
- return;
- }
+int TeamBalanceTeam_GetNumberOfPlayers(entity team_ent)
+{
+ return team_ent.m_num_players;
+}
- if((autocvar_g_campaign) || (autocvar_g_changeteam_banned && CS(this).wasplayer)) {
- Send_Notification(NOTIF_ONE, this, MSG_INFO, INFO_TEAMCHANGE_NOTALLOWED);
- return; // changing teams is not allowed
- }
+int TeamBalanceTeam_GetNumberOfBots(entity team_ent)
+{
+ return team_ent.m_num_bots;
+}
- // autocvar_g_balance_teams_prevent_imbalance only makes sense if autocvar_g_balance_teams is on, as it makes the team selection dialog pointless
- if (autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance)
+int TeamBalance_CompareTeamsInternal(entity team_a, entity team_b,
+ entity player, bool use_score)
+{
+ if (team_a == team_b)
{
- GetTeamCounts(this);
- if ((BIT(destination_team - 1) & FindBestTeams(this, false)) == 0)
- {
- Send_Notification(NOTIF_ONE, this, MSG_INFO, INFO_TEAMCHANGE_LARGERTEAM);
- return;
- }
+ return TEAMS_COMPARE_EQUAL;
}
- if(IS_PLAYER(this) && source_team != destination_team)
+ if (!TeamBalanceTeam_IsAllowed(team_a) ||
+ !TeamBalanceTeam_IsAllowed(team_b))
{
- // reduce frags during a team change
- TeamchangeFrags(this);
+ return TEAMS_COMPARE_INVALID;
}
- if (!SetPlayerTeam(this, destination_team, source_team, !IS_CLIENT(this)))
+ int num_players_team_a = team_a.m_num_players;
+ int num_players_team_b = team_b.m_num_players;
+ if (IS_REAL_CLIENT(player) && bots_would_leave)
{
- return;
+ num_players_team_a -= team_a.m_num_bots;
+ num_players_team_b -= team_b.m_num_bots;
}
- AutoBalanceBots(source_team, destination_team);
- if (!IS_PLAYER(this) || (source_team == destination_team))
+ if (num_players_team_a < num_players_team_b)
{
- return;
+ return TEAMS_COMPARE_LESS;
}
- KillPlayerForTeamChange(this);
-}
-
-void AutoBalanceBots(int source_team, int destination_team)
-{
- if (!Team_IsValidNumber(source_team))
+ if (num_players_team_a > num_players_team_b)
{
- LOG_WARNF("AutoBalanceBots: Source team is invalid: %f", source_team);
- return;
+ return TEAMS_COMPARE_GREATER;
}
- if (!Team_IsValidNumber(destination_team))
+ if (!use_score)
{
- LOG_WARNF("AutoBalanceBots: Destination team is invalid: %f",
- destination_team);
- return;
+ return TEAMS_COMPARE_EQUAL;
}
- if (!autocvar_g_balance_teams ||
- !autocvar_g_balance_teams_prevent_imbalance)
+ if (team_a.m_team_score < team_b.m_team_score)
{
- return;
+ return TEAMS_COMPARE_LESS;
}
- int num_players_source_team = 0;
- int num_players_destination_team = 0;
- entity lowest_bot_destination_team = NULL;
- switch (source_team)
+ if (team_a.m_team_score > team_b.m_team_score)
{
- case 1:
- {
- num_players_source_team = c1;
- break;
- }
- case 2:
- {
- num_players_source_team = c2;
- break;
- }
- case 3:
- {
- num_players_source_team = c3;
- break;
- }
- case 4:
- {
- num_players_source_team = c4;
- break;
- }
+ return TEAMS_COMPARE_GREATER;
}
- if (num_players_source_team < 0)
+ return TEAMS_COMPARE_EQUAL;
+}
+
+void SV_ChangeTeam(entity player, int new_color)
+{
+ if (!teamplay)
{
- return;
+ SetPlayerColors(player, new_color);
}
- switch (destination_team)
+ // TODO: Should we really bother with this?
+ if(!IS_CLIENT(player))
{
- case 1:
- {
- num_players_destination_team = c1;
- lowest_bot_destination_team = lowest_bot_team1;
- break;
- }
- case 2:
- {
- num_players_destination_team = c2;
- lowest_bot_destination_team = lowest_bot_team2;
- break;
- }
- case 3:
- {
- num_players_destination_team = c3;
- lowest_bot_destination_team = lowest_bot_team3;
- break;
- }
- case 4:
- {
- num_players_destination_team = c4;
- lowest_bot_destination_team = lowest_bot_team4;
- break;
- }
+ // since this is an engine function, and gamecode doesn't have any calls earlier than this, do the connecting message here
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CONNECTING,
+ player.netname);
+ return;
}
- if ((num_players_destination_team <= num_players_source_team) ||
- (lowest_bot_destination_team == NULL))
+ if (!teamplay)
{
return;
}
- SetPlayerTeamSimple(lowest_bot_destination_team,
- Team_NumberToTeam(source_team));
- KillPlayerForTeamChange(lowest_bot_destination_team);
+ Player_SetTeamIndexChecked(player, Team_TeamToIndex((new_color & 0x0F) +
+ 1));
}
#pragma once
-string cache_mutatormsg;
-string cache_lastmutatormsg;
+int autocvar_teamplay_mode;
-// The following variables are used for balancing. They are not updated
-// automatically. You need to call CheckAllowedTeams and GetTeamCounts to get
-// proper values.
+bool autocvar_g_changeteam_banned;
+bool autocvar_teamplay_lockonrestart;
-// These four have 2 different states. If they are equal to -1, it means that
-// the player can't join the team. Zero or positive value means that player can
-// join the team and means the number of players on that team.
-float c1;
-float c2;
-float c3;
-float c4;
-float num_bots_team1; ///< Number of bots in the first team.
-float num_bots_team2; ///< Number of bots in the second team.
-float num_bots_team3; ///< Number of bots in the third team.
-float num_bots_team4; ///< Number of bots in the fourth team.
-entity lowest_human_team1; ///< Human with the lowest score in the first team.
-entity lowest_human_team2; ///< Human with the lowest score in the second team.
-entity lowest_human_team3; ///< Human with the lowest score in the third team.
-entity lowest_human_team4; ///< Human with the lowest score in the fourth team.
-entity lowest_bot_team1; ///< Bot with the lowest score in the first team.
-entity lowest_bot_team2; ///< Bot with the lowest score in the second team.
-entity lowest_bot_team3; ///< Bot with the lowest score in the third team.
-entity lowest_bot_team4; ///< Bot with the lowest score in the fourth team.
+bool autocvar_g_balance_teams;
+bool autocvar_g_balance_teams_prevent_imbalance;
-int redowned, blueowned, yellowowned, pinkowned;
+bool lockteams;
-//float audit_teams_time;
+// ========================== Global teams API ================================
-void TeamchangeFrags(entity e);
+/// \brief Returns the global team entity at the given index.
+/// \param[in] index Index of the team.
+/// \return Global team entity at the given index.
+entity Team_GetTeamFromIndex(int index);
-void LogTeamchange(float player_id, float team_number, float type);
+/// \brief Returns the global team entity that corresponds to the given TEAM_NUM
+/// value.
+/// \param[in] team_num Team value. See TEAM_NUM constants.
+/// \return Global team entity that corresponds to the given TEAM_NUM value.
+entity Team_GetTeam(int team_num);
-void default_delayedinit(entity this);
+// ========================= Team specific API ================================
-void InitGameplayMode();
+/// \brief Returns the score of the team.
+/// \param[in] team_ent Team entity.
+/// \return Score of the team.
+float Team_GetTeamScore(entity team_ent);
-string GetClientVersionMessage(entity this);
+/// \brief Sets the score of the team.
+/// \param[in,out] team_ent Team entity.
+/// \param[in] score Score to set.
+void Team_SetTeamScore(entity team_ent, float score);
-string getwelcomemessage(entity this);
+/// \brief Returns the number of alive players in a team.
+/// \param[in] team_ent Team entity.
+/// \return Number of alive players in a team.
+int Team_GetNumberOfAlivePlayers(entity team_ent);
-void SetPlayerColors(entity player, float _color);
+/// \brief Sets the number of alive players in a team.
+/// \param[in,out] team_ent Team entity.
+/// \param[in] number Number of players to set.
+void Team_SetNumberOfAlivePlayers(entity team_ent, int number);
-/// \brief Kills player as a result of team change.
-/// \param[in,out] player Player to kill.
-/// \return No return.
-void KillPlayerForTeamChange(entity player);
+/// \brief Returns the number of alive teams.
+/// \return Number of alive teams.
+int Team_GetNumberOfAliveTeams();
-/// \brief Sets the team of the player.
+/// \brief Returns the number of control points owned by a team.
+/// \param[in] team_ent Team entity.
+/// \return Number of control points owned by a team.
+int Team_GetNumberOfControlPoints(entity team_ent);
+
+/// \brief Sets the number of control points owned by a team.
+/// \param[in,out] team_ent Team entity.
+/// \param[in] number Number of control points to set.
+void Team_SetNumberOfControlPoints(entity team_ent, int number);
+
+/// \brief Returns the number of teams that own control points.
+/// \return Number of teams that own control points.
+int Team_GetNumberOfTeamsWithControlPoints();
+
+// ======================= Entity specific API ================================
+
+void setcolor(entity this, int clr);
+
+/// \brief Returns whether the given entity belongs to a valid team.
+/// \param[in] this Entity to check.
+/// \return True if entity belongs to a valid team, false otherwise.
+bool Entity_HasValidTeam(entity this);
+
+/// \brief Returns the team index of the given entity.
+/// \param[in] this Entity to check.
+/// \return Team index of the entity.
+int Entity_GetTeamIndex(entity this);
+
+/// \brief Returns the team entity of the given entity.
+/// \param[in] this Entity to check.
+/// \return Team entity of the given entity or NULL if the entity doesn't belong
+/// to any team.
+entity Entity_GetTeam(entity this);
+
+void SetPlayerColors(entity player, float _color);
+
+/// \brief Sets the team of the player using its index.
/// \param[in,out] player Player to adjust.
-/// \param[in] team_num Team number to set. See TEAM_NUM constants.
+/// \param[in] index Index of the team to set.
/// \return True if team switch was successful, false otherwise.
-bool SetPlayerTeamSimple(entity player, int team_num);
+bool Player_SetTeamIndex(entity player, int index);
+
+enum
+{
+ TEAM_CHANGE_AUTO = 2, ///< The team was selected by autobalance.
+ TEAM_CHANGE_MANUAL = 3, ///< Player has manually selected their team.
+ TEAM_CHANGE_SPECTATOR = 4 ///< Player is joining spectators. //TODO: Remove?
+};
/// \brief Sets the team of the player.
/// \param[in,out] player Player to adjust.
-/// \param[in] destination_team Team to set.
-/// \param[in] source_team Previous team of the player.
-/// \param[in] no_print Whether to print this event to players' console.
+/// \param[in] team_index Index of the team to set.
+/// \param[in] type Type of the team change. See TEAM_CHANGE constants.
/// \return True if team switch was successful, false otherwise.
-bool SetPlayerTeam(entity player, int destination_team, int source_team,
- bool no_print);
+bool SetPlayerTeam(entity player, int team_index, int type);
-// set c1...c4 to show what teams are allowed
-void CheckAllowedTeams(entity for_whom);
+/// \brief Sets the team of the player with all sanity checks.
+/// \param[in,out] player Player to adjust.
+/// \param[in] team_index Index of the team to set.
+void Player_SetTeamIndexChecked(entity player, int team_index);
-float PlayerValue(entity p);
+/// \brief Moves player to the specified team.
+/// \param[in,out] client Client to move.
+/// \param[in] team_index Index of the team.
+/// \param[in] type ???
+/// \return True on success, false otherwise.
+bool MoveToTeam(entity client, int team_index, int type);
-// c1...c4 should be set to -1 (not allowed) or 0 (allowed).
-// teams that are allowed will now have their player counts stored in c1...c4
-void GetTeamCounts(entity ignore);
+enum
+{
+ TEAM_FORCE_SPECTATOR = -1, ///< Force the player to spectator team.
+ TEAM_FORCE_DEFAULT = 0 ///< Don't force any team.
+};
-/// \brief Returns whether one team is smaller than the other.
-/// \param[in] team_a First team.
-/// \param[in] team_b Second team.
+/// \brief Returns whether player has real forced team. Spectator team is
+/// ignored.
/// \param[in] player Player to check.
-/// \param[in] use_score Whether to take into account team scores.
-/// \return True if first team is smaller than the second one, false otherwise.
-/// \note This function assumes that CheckAllowedTeams and GetTeamCounts have
-/// been called.
-bool IsTeamSmallerThanTeam(int team_a, int team_b, entity player,
- bool use_score);
+/// \return True if player has real forced team, false otherwise.
+bool Player_HasRealForcedTeam(entity player);
-/// \brief Returns whether one team is equal to the other.
-/// \param[in] team_a First team.
-/// \param[in] team_b Second team.
+/// \brief Returns the index of the forced team of the given player.
+/// \param[in] player Player to check.
+/// \return Index of the forced team.
+int Player_GetForcedTeamIndex(entity player);
+
+/// \brief Sets the index of the forced team of the given player.
+/// \param[in,out] player Player to adjust.
+/// \param[in] team_index Index of the team to set.
+void Player_SetForcedTeamIndex(entity player, int team_index);
+
+/// \brief Determines the forced team of the player using current global config.
+/// \param[in,out] player Player to adjust.
+void Player_DetermineForcedTeam(entity player);
+
+// ========================= Team balance API =================================
+
+/// \brief Assigns the given player to a team that will make the game most
+/// balanced.
+/// \param[in,out] player Player to assign.
+void TeamBalance_JoinBestTeam(entity player);
+
+/// \brief Checks whether the player can join teams according to global
+/// configuration and mutator settings.
+/// \param[in] for_whom Player to check for. Pass NULL for global rules.
+/// \return Team balance entity that holds information about teams. This entity
+/// will be automatically destroyed on the next frame but you are encouraged to
+/// manually destroy it by calling TeamBalance_Destroy for performance reasons.
+entity TeamBalance_CheckAllowedTeams(entity for_whom);
+
+/// \brief Destroy the team balance entity.
+/// \param[in,out] balance Team balance entity to destroy.
+/// \note Team balance entity is allowed to be NULL.
+void TeamBalance_Destroy(entity balance);
+
+/// \brief Returns the bitmask of allowed teams.
+/// \param[in] balance Team balance entity.
+/// \return Bitmask of allowed teams.
+int TeamBalance_GetAllowedTeams(entity balance);
+
+/// \brief Returns whether the team change to the specified team is allowed.
+/// \param[in] balance Team balance entity.
+/// \param[in] index Index of the team.
+/// \return True if team change to the specified team is allowed, false
+/// otherwise.
+bool TeamBalance_IsTeamAllowed(entity balance, int index);
+
+/// \brief Counts the number of players and various other information about
+/// each team.
+/// \param[in,out] balance Team balance entity.
+/// \param[in] ignore Player to ignore. This is useful if you plan to switch the
+/// player's team. Pass NULL for global information.
+/// \note This function updates the internal state of the team balance entity.
+void TeamBalance_GetTeamCounts(entity balance, entity ignore);
+
+/// \brief Returns the number of players (both humans and bots) in a team.
+/// \param[in] balance Team balance entity.
+/// \param[in] index Index of the team.
+/// \return Number of player (both humans and bots) in a team.
+/// \note You need to call TeamBalance_GetTeamCounts before calling this
+/// function.
+int TeamBalance_GetNumberOfPlayers(entity balance, int index);
+
+/// \brief Finds the team that will make the game most balanced if the player
+/// joins it.
+/// \param[in] balance Team balance entity.
+/// \param[in] player Player to check.
+/// \param[in] ignore_player ???
+/// \return Index of the team that will make the game most balanced if the
+/// player joins it. If there are several equally good teams available, the
+/// function will pick a random one.
+int TeamBalance_FindBestTeam(entity balance, entity player, bool ignore_player);
+
+/// \brief Returns the bitmask of the teams that will make the game most
+/// balanced if the player joins any of them.
+/// \param[in] balance Team balance entity.
/// \param[in] player Player to check.
/// \param[in] use_score Whether to take into account team scores.
-/// \return True if first team is equal to the second one, false otherwise.
-/// \note This function assumes that CheckAllowedTeams and GetTeamCounts have
-/// been called.
-bool IsTeamEqualToTeam(int team_a, int team_b, entity player, bool use_score);
+/// \return Bitmask of the teams that will make the game most balanced if the
+/// player joins any of them.
+/// \note You need to call TeamBalance_GetTeamCounts before calling this
+/// function.
+int TeamBalance_FindBestTeams(entity balance, entity player, bool use_score);
+
+/// \brief Describes the result of comparing teams.
+enum
+{
+ TEAMS_COMPARE_INVALID, ///< One or both teams are invalid.
+ TEAMS_COMPARE_LESS, ///< First team is less than the second one.
+ TEAMS_COMPARE_EQUAL, ///< Both teams are equal.
+ TEAMS_COMPARE_GREATER ///< First team the greater than the second one.
+};
-/// \brief Returns the bitmask of the best teams for the player to join.
+/// \brief Compares two teams for the purposes of game balance.
+/// \param[in] balance Team balance entity.
+/// \param[in] team_index_a Index of the first team.
+/// \param[in] team_index_b Index of the second team.
/// \param[in] player Player to check.
/// \param[in] use_score Whether to take into account team scores.
-/// \return Bitmask of the best teams for the player to join.
-/// \note This function assumes that CheckAllowedTeams and GetTeamCounts have
-/// been called.
-int FindBestTeams(entity player, bool use_score);
+/// \return TEAMS_COMPARE value. See above.
+/// \note You need to call TeamBalance_GetTeamCounts before calling this
+/// function.
+int TeamBalance_CompareTeams(entity balance, int team_index_a, int team_index_b,
+ entity player, bool use_score);
-// returns # of smallest team (1, 2, 3, 4)
-// NOTE: Assumes CheckAllowedTeams has already been called!
-int FindSmallestTeam(entity player, float ignore_player);
+/// \brief Switches a bot from one team to another if teams are not balanced.
+void TeamBalance_AutoBalanceBots();
-void JoinBestTeam(entity this, bool force_best_team);
+/// \brief Returns the index of the team with most players that is contained in
+/// the given bitmask of teams.
+/// \param[in] balance Team balance entity.
+/// \param[in] teams Bitmask of teams to search in.
+/// \return Index of the team with most players.
+int TeamBalance_GetLargestTeamIndex(entity balance, int teams);
-/// \brief Auto balances bots in teams after the player has changed team.
-/// \param[in] source_team Previous team of the player (1, 2, 3, 4).
-/// \param[in] destination_team Current team of the player (1, 2, 3, 4).
-/// \return No return.
-/// \note This function assumes that CheckAllowedTeams and GetTeamCounts have
-/// been called.
-void AutoBalanceBots(int source_team, int destination_team);
+/// \brief Returns the player who is the most suitable for switching between
+/// the given teams.
+/// \param[in] source_team_index Index of the team to search in.
+/// \param[in] destination_team_index Index of the team to switch to.
+/// \param[in] is_bot True to search for bot, false for human.
+/// \return Player who is the most suitable for switching between the given
+/// teams or NULL if not found.
+entity TeamBalance_GetPlayerForTeamSwitch(int source_team_index,
+ int destination_team_index, bool is_bot);
-void setcolor(entity this, int clr);
+// ============================ Internal API ==================================
+
+void LogTeamChange(float player_id, float team_number, int type);
+
+/// \brief Kills player as a result of team change.
+/// \param[in,out] player Player to kill.
+void KillPlayerForTeamChange(entity player);
+
+/// \brief Returns whether the team change to the specified team is allowed.
+/// \param[in] balance Team balance entity.
+/// \param[in] index Index of the team.
+/// \return True if team change to the specified team is allowed, false
+/// otherwise.
+/// \note This function bypasses all the sanity checks.
+bool TeamBalance_IsTeamAllowedInternal(entity balance, int index);
+
+/// \brief Bans team change to all teams except the given one.
+/// \param[in,out] balance Team balance entity.
+/// \param[in] index Index of the team.
+void TeamBalance_BanTeamsExcept(entity balance, int index);
+
+/// \brief Returns the team entity of the team balance entity at the given
+/// index.
+/// \param[in] balance Team balance entity.
+/// \param[in] index Index of the team.
+/// \return Team entity of the team balance entity at the given index.
+entity TeamBalance_GetTeamFromIndex(entity balance, int index);
+
+/// \brief Returns the team entity of the team balance entity that corresponds
+/// to the given TEAM_NUM value.
+/// \param[in] balance Team balance entity.
+/// \param[in] team_num Team value. See TEAM_NUM constants.
+/// \return Team entity of the team balance entity that corresponds to the given
+/// TEAM_NUM value.
+entity TeamBalance_GetTeam(entity balance, int team_num);
+
+/// \brief Returns whether the team is allowed.
+/// \param[in] team_ent Team entity.
+/// \return True if team is allowed, false otherwise.
+bool TeamBalanceTeam_IsAllowed(entity team_ent);
+
+/// \brief Returns the number of players (both humans and bots) in a team.
+/// \param[in] team_ent Team entity.
+/// \return Number of player (both humans and bots) in a team.
+/// \note You need to call TeamBalance_GetTeamCounts before calling this
+/// function.
+int TeamBalanceTeam_GetNumberOfPlayers(entity team_ent);
+
+/// \brief Returns the number of bots in a team.
+/// \param[in] team_ent Team entity.
+/// \return Number of bots in a team.
+/// \note You need to call TeamBalance_GetTeamCounts before calling this
+/// function.
+int TeamBalanceTeam_GetNumberOfBots(entity team_ent);
+
+/// \brief Compares two teams for the purposes of game balance.
+/// \param[in] team_a First team.
+/// \param[in] team_b Second team.
+/// \param[in] player Player to check.
+/// \param[in] use_score Whether to take into account team scores.
+/// \return TEAMS_COMPARE value. See above.
+/// \note You need to call TeamBalance_GetTeamCounts before calling this
+/// function.
+int TeamBalance_CompareTeamsInternal(entity team_a, entity team_index_b,
+ entity player, bool use_score);
+
+/// \brief Called when the player connects or when they change their color with
+/// the "color" command.
+/// \param[in,out] player Player that requested a new color.
+/// \param[in] new_color Requested color.
+void SV_ChangeTeam(entity player, int new_color);
//.float hit_time;
.float fired_time;
-void accuracy_add(entity this, int w, int fired, int hit)
+void accuracy_add(entity this, Weapon w, int fired, int hit)
{
if (IS_INDEPENDENT_PLAYER(this)) return;
entity a = CS(this).accuracy;
if (!a) return;
if (!hit && !fired) return;
- if (w == WEP_Null.m_id) return;
- w -= WEP_FIRST;
- int b = accuracy_byte(a.accuracy_hit[w], a.accuracy_fired[w]);
- if (hit) a.accuracy_hit [w] += hit;
- if (fired) a.accuracy_fired[w] += fired;
+ if (w == WEP_Null) return;
+ int wepid = w.m_id;
+ wepid -= WEP_FIRST;
+ int b = accuracy_byte(a.accuracy_hit[wepid], a.accuracy_fired[wepid]);
+ if (hit) a.accuracy_hit [wepid] += hit;
+ if (fired) a.accuracy_fired[wepid] += fired;
if (hit && STAT(HIT_TIME, a) != time) { // only run this once per frame
- a.accuracy_cnt_hit[w] += 1;
+ a.accuracy_cnt_hit[wepid] += 1;
STAT(HIT_TIME, a) = time;
}
if (fired && a.fired_time != time) { // only run this once per frame
- a.accuracy_cnt_fired[w] += 1;
+ a.accuracy_cnt_fired[wepid] += 1;
a.fired_time = time;
}
- if (b == accuracy_byte(a.accuracy_hit[w], a.accuracy_fired[w])) return; // no change
- int sf = 1 << (w % 24);
+ if (b == accuracy_byte(a.accuracy_hit[wepid], a.accuracy_fired[wepid])) return; // no change
+ int sf = 1 << (wepid % 24);
a.SendFlags |= sf;
FOREACH_CLIENT(IS_SPEC(it) && it.enemy == this, { CS(it).accuracy.SendFlags |= sf; });
}
if (mutator_check == MUT_ACCADD_INVALID) return true;
if (mutator_check != MUT_ACCADD_VALID) return false;
- if (!IS_CLIENT(targ)) return false;
+ if (!IS_CLIENT(targ) || !IS_CLIENT(attacker)) return false;
return true;
}
bool accuracy_canbegooddamage(entity attacker)
{
- return !warmup_stage;
+ return !warmup_stage && IS_CLIENT(attacker);
}
void accuracy_resend(entity e);
// update accuracy stats
-void accuracy_add(entity e, float w, float fired, float hit);
+void accuracy_add(entity e, Weapon w, float fired, float hit);
// helper
bool accuracy_isgooddamage(entity attacker, entity targ);
W_SwitchWeapon_Force(this, ww, weaponentity);
}
-void W_SwitchWeapon(entity this, Weapon w, .entity weaponentity)
+bool W_SwitchWeapon(entity this, Weapon w, .entity weaponentity)
{
if(this.(weaponentity).m_switchweapon != w)
{
if(client_hasweapon(this, w, weaponentity, true, true))
+ {
W_SwitchWeapon_Force(this, w, weaponentity);
+ return true;
+ }
else
+ {
this.(weaponentity).selectweapon = w.m_id; // update selectweapon anyway
+ return false;
+ }
}
else if(!forbidWeaponUse(this))
{
entity actor = this;
w.wr_reload(w, actor, weaponentity);
}
+
+ return true; // player already has the weapon out or needs to reload
+}
+
+void W_SwitchWeapon_TryOthers(entity this, Weapon w, .entity weaponentity)
+{
+ if(!W_SwitchWeapon(this, w, weaponentity))
+ W_NextWeaponOnImpulse(this, w.impulse, weaponentity);
}
void W_CycleWeapon(entity this, string weaponorder, float dir, .entity weaponentity)
// perform weapon to attack (weaponstate and attack_finished check is here)
void W_SwitchToOtherWeapon(entity this, .entity weaponentity);
-void W_SwitchWeapon(entity this, Weapon imp, .entity weaponentity);
+bool W_SwitchWeapon(entity this, Weapon imp, .entity weaponentity); // returns false if the player does not have the weapon
+void W_SwitchWeapon_TryOthers(entity this, Weapon imp, .entity weaponentity);
void W_CycleWeapon(entity this, string weaponorder, float dir, .entity weaponentity);
for (int i = 1; i < t; ++i)
{
s = argv(i);
- FOREACH(Weapons, it != WEP_Null, {
- if(it.netname == s)
- {
- entity replacement = spawn();
- copyentity(this, replacement);
- replacement.m_isreplaced = true;
- weapon_defaultspawnfunc(replacement, it);
- break;
- }
- });
+ Weapon wep = Weapons_fromstr(s);
+ if(wep != WEP_Null)
+ {
+ entity replacement = spawn();
+ copyentity(this, replacement);
+ replacement.m_isreplaced = true;
+ weapon_defaultspawnfunc(replacement, wep);
+ }
}
}
if (t >= 1) // always the case!
{
s = argv(0);
- wpn = WEP_Null;
- FOREACH(Weapons, it != WEP_Null, {
- if(it.netname == s)
- {
- wpn = it;
- break;
- }
- });
+ wpn = Weapons_fromstr(s);
}
if (wpn == WEP_Null)
{
SUB_VanishOrRemove(this);
}
-// returns amount of ammo used as string, or -1 for failure, or 0 for no ammo count
-string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector velo, .entity weaponentity)
+// returns amount of ammo used, or -1 for failure, or 0 for no ammo count
+float W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector velo, .entity weaponentity)
{
- float thisammo;
- string s;
Weapon info = Weapons_from(wpn);
int ammotype = info.ammo_type;
weapon_defaultspawnfunc(wep, info);
if(startitem_failed)
- return string_null;
+ return -1;
setthink(wep, thrown_wep_think);
wep.savenextthink = wep.nextthink;
wep.nextthink = min(wep.nextthink, time + 0.5);
//wa = W_AmmoItemCode(wpn);
if(ammotype == RESOURCE_NONE)
{
- return "";
+ return 0;
}
else
{
- s = "";
-
if(doreduce && g_weapon_stay == 2)
{
// if our weapon is loaded, give its load back to the player
}
float ownderammo = GetResourceAmount(own, ammotype);
- thisammo = min(ownderammo, GetResourceAmount(wep, ammotype));
+ float thisammo = min(ownderammo, GetResourceAmount(wep, ammotype));
SetResourceAmount(wep, ammotype, thisammo);
SetResourceAmount(own, ammotype, ownderammo - thisammo);
- switch (ammotype)
- {
- case RESOURCE_SHELLS: s = sprintf("%s and %d shells", s, thisammo); break;
- case RESOURCE_BULLETS: s = sprintf("%s and %d nails", s, thisammo); break;
- case RESOURCE_ROCKETS: s = sprintf("%s and %d rockets", s, thisammo); break;
- case RESOURCE_CELLS: s = sprintf("%s and %d cells", s, thisammo); break;
- case RESOURCE_PLASMA: s = sprintf("%s and %d plasma", s, thisammo); break;
- case RESOURCE_FUEL: s = sprintf("%s and %d fuel", s, thisammo); break;
- }
-
- s = substring(s, 5, -1);
+ return thisammo;
}
- return s;
+ return 0;
}
}
STAT(WEAPONS, this) &= ~set;
W_SwitchWeapon_Force(this, w_getbestweapon(this, weaponentity), weaponentity);
- string a = W_ThrowNewWeapon(this, w.m_id, doreduce, this.origin + delta, velo, weaponentity);
+ float a = W_ThrowNewWeapon(this, w.m_id, doreduce, this.origin + delta, velo, weaponentity);
- if(!a) return;
- Send_Notification(NOTIF_ONE, this, MSG_MULTI, ITEM_WEAPON_DROP, a, w.m_id);
+ if(a < 0) return;
+ Send_Notification(NOTIF_ONE, this, MSG_MULTI, ITEM_WEAPON_DROP, w.m_id, a);
}
void SpawnThrownWeapon(entity this, vector org, Weapon wep, .entity weaponentity)
.float savenextthink;
void thrown_wep_think(entity this);
-// returns amount of ammo used as string, or -1 for failure, or 0 for no ammo count
-string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector velo, .entity weaponentity);
+// returns amount of ammo used, or -1 for failure, or 0 for no ammo count
+float W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector velo, .entity weaponentity);
bool W_IsWeaponThrowable(entity this, int w);
void W_SetupShot_Dir_ProjectileSize_Range(entity ent, .entity weaponentity, vector s_forward, vector mi, vector ma, float antilag, float recoil, Sound snd, float chan, float maxdamage, float range, int deathtype)
{
TC(Sound, snd);
- float nudge = 1; // added to traceline target and subtracted from result TOOD(divVerent): do we still need this? Doesn't the engine do this now for us?
float oldsolid = ent.dphitcontentsmask;
Weapon wep = DEATH_WEAPONOF(deathtype);
if(!IS_CLIENT(ent))
// track max damage
if (IS_PLAYER(ent) && accuracy_canbegooddamage(ent))
- accuracy_add(ent, wep.m_id, maxdamage, 0);
+ accuracy_add(ent, wep, maxdamage, 0);
if(IS_PLAYER(ent))
W_HitPlotAnalysis(ent, wep, v_forward, v_right, v_up);
vector md = ent.(weaponentity).movedir;
vector vecs = ((md.x > 0) ? md : '0 0 0');
- vector dv = v_right * -vecs.y + v_up * vecs.z;
- w_shotorg = ent.origin + ent.view_ofs + dv;
+ vector dv = v_forward * vecs.x + v_right * -vecs.y + v_up * vecs.z;
+ w_shotorg = ent.origin + ent.view_ofs;
// now move the shotorg forward as much as requested if possible
if(antilag)
{
if(CS(ent).antilag_debug)
- tracebox_antilag(ent, w_shotorg, mi, ma, w_shotorg + v_forward * (vecs.x + nudge), MOVE_NORMAL, ent, CS(ent).antilag_debug);
+ tracebox_antilag(ent, w_shotorg, mi, ma, w_shotorg + dv, MOVE_NORMAL, ent, CS(ent).antilag_debug);
else
- tracebox_antilag(ent, w_shotorg, mi, ma, w_shotorg + v_forward * (vecs.x + nudge), MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
+ tracebox_antilag(ent, w_shotorg, mi, ma, w_shotorg + dv, MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
}
else
- tracebox(w_shotorg, mi, ma, w_shotorg + v_forward * (vecs.x + nudge), MOVE_NORMAL, ent);
- w_shotorg = trace_endpos - v_forward * nudge;
+ tracebox(w_shotorg, mi, ma, w_shotorg + dv, MOVE_NORMAL, ent);
+ w_shotorg = trace_endpos;
// calculate the shotdir from the chosen shotorg
if(W_DualWielding(ent))
w_shotdir = s_forward;
}
// nudge w_shotend so a trace to w_shotend hits
- w_shotend = w_shotend + normalize(w_shotend - w_shotorg) * nudge;
+ w_shotend = w_shotend + normalize(w_shotend - w_shotorg);
//if(w_shotend != prevend) { printf("SERVER: shotEND differs: %s - %s\n", vtos(w_shotend), vtos(prevend)); }
//if(w_shotorg != prevorg) { printf("SERVER: shotORG differs: %s - %s\n", vtos(w_shotorg), vtos(prevorg)); }
//if(w_shotdir != prevdir) { printf("SERVER: shotDIR differs: %s - %s\n", vtos(w_shotdir), vtos(prevdir)); }
//explosion = spawn();
// Find all non-hit players the beam passed close by
- if(deathtype == WEP_VAPORIZER.m_id || deathtype == WEP_VORTEX.m_id)
+ if(deathtype == WEP_VAPORIZER.m_id || deathtype == WEP_VORTEX.m_id) // WEAPONTODO
{
FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != this, {
if(!it.railgunhit)
IL_CLEAR(g_railgunhit);
// calculate hits and fired shots for hitscan
- accuracy_add(this, this.(weaponentity).m_weapon.m_id, 0, min(bdamage, totaldmg));
+ if(this.(weaponentity))
+ accuracy_add(this, this.(weaponentity).m_weapon, 0, min(bdamage, totaldmg));
trace_endpos = endpoint;
trace_ent = endent;
fireBullet_last_hit = NULL;
}
-void fireBullet(entity this, .entity weaponentity, vector start, vector dir, float spread, float max_solid_penetration, float damage, float force, float dtype, int tracereffects)
+void fireBullet(entity this, .entity weaponentity, vector start, vector dir, float spread, float max_solid_penetration, float damage, float force, float dtype, entity tracer_effect)
{
vector end;
end = start + dir * max_shot_distance;
fireBullet_last_hit = NULL;
+ fireBullet_trace_callback_eff = tracer_effect;
+
float solid_penetration_left = 1;
float total_damage = 0;
- if(tracereffects & EF_RED)
- fireBullet_trace_callback_eff = EFFECT_RIFLE;
- else if(tracereffects & EF_BLUE)
- fireBullet_trace_callback_eff = EFFECT_RIFLE_WEAK;
- else
- fireBullet_trace_callback_eff = EFFECT_BULLET;
-
float lag = ((IS_REAL_CLIENT(this)) ? ANTILAG_LATENCY(this) : 0);
if(lag < 0.001)
lag = 0;
// do not exceed 100%
float added_damage = min(damage - total_damage, damage * solid_penetration_left);
total_damage += damage * solid_penetration_left;
- accuracy_add(this, this.(weaponentity).m_weapon.m_id, 0, added_damage);
+ accuracy_add(this, this.(weaponentity).m_weapon, 0, added_damage);
}
}
entity fireBullet_trace_callback_eff;
entity fireBullet_last_hit;
void fireBullet_trace_callback(vector start, vector hit, vector end);
-void fireBullet(entity this, .entity weaponentity, vector start, vector dir, float spread, float max_solid_penetration, float damage, float force, float dtype, int tracereffects);
+void fireBullet(entity this, .entity weaponentity, vector start, vector dir, float spread, float max_solid_penetration, float damage, float force, float dtype, entity tracer_effect);
#include "../command/common.qh"
#include <server/mutators/_mod.qh>
#include "../round_handler.qh"
+#include <server/cheats.qh>
#include <server/resources.qh>
#include <common/t_items.qh>
#include <common/animdecide.qh>
setthink(view, CL_Weaponentity_Think);
view.nextthink = time;
view.viewmodelforclient = actor;
+ view.draggable = drag_undraggable;
setcefc(view, CL_Weaponentity_CustomizeEntityForClient);
wepent_link(view);
entity exterior = actor.exteriorweaponentity = new(exteriorweaponentity);
exterior.solid = SOLID_NOT;
exterior.owner = actor;
+ exterior.draggable = drag_undraggable;
exterior.weaponentity_fld = weaponentity;
setorigin(exterior, '0 0 0');
setthink(exterior, CL_ExteriorWeaponentity_Think);
if (attacktime >= 0)
{
- int slot = weaponslot(weaponentity);
// don't fire if previous attack is not finished
- if (ATTACK_FINISHED(actor, slot) > time + actor.(weaponentity).weapon_frametime * 0.5) return false;
+ if (ATTACK_FINISHED(actor, weaponentity) > time + actor.(weaponentity).weapon_frametime * 0.5) return false;
entity this = actor.(weaponentity);
// don't fire while changing weapon
if (this.state != WS_READY) return false;
// if the weapon hasn't been firing continuously, reset the timer
if (attacktime >= 0)
{
- int slot = weaponslot(weaponentity);
- if (ATTACK_FINISHED(actor, slot) < time - this.weapon_frametime * 1.5)
+ if (ATTACK_FINISHED(actor, weaponentity) < time - this.weapon_frametime * 1.5)
{
- ATTACK_FINISHED(actor, slot) = time;
+ ATTACK_FINISHED(actor, weaponentity) = time;
// dprint("resetting attack finished to ", ftos(time), "\n");
}
- ATTACK_FINISHED(actor, slot) = ATTACK_FINISHED(actor, slot) + attacktime * W_WeaponRateFactor(actor);
+ float arate = W_WeaponRateFactor(actor);
+ ATTACK_FINISHED(actor, weaponentity) = ATTACK_FINISHED(actor, weaponentity) + attacktime * arate;
+
+ if(autocvar_g_weaponswitch_debug_alternate && W_DualWielding(actor))
+ {
+ int slot = weaponslot(weaponentity);
+ for(int wepslot = 0; wepslot < MAX_WEAPONSLOTS; ++wepslot)
+ {
+ if(slot == wepslot)
+ continue;
+ .entity wepent = weaponentities[wepslot];
+ if(actor.(wepent) && actor.(wepent).m_weapon != WEP_Null)
+ {
+ if(ATTACK_FINISHED(actor, wepent) > time + actor.(wepent).weapon_frametime * 0.5)
+ continue; // still cooling down!
+ if (ATTACK_FINISHED(actor, wepent) < time - actor.(wepent).weapon_frametime * 1.5)
+ ATTACK_FINISHED(actor, wepent) = time;
+ ATTACK_FINISHED(actor, wepent) = ATTACK_FINISHED(actor, wepent) + (attacktime * arate) / MAX_WEAPONSLOTS;
+ }
+ }
+ }
}
this.bulletcounter += 1;
- // dprint("attack finished ", ftos(ATTACK_FINISHED(actor, slot)), "\n");
+ // dprint("attack finished ", ftos(ATTACK_FINISHED(actor, weaponentity)), "\n");
}
bool weapon_prepareattack(Weapon thiswep, entity actor, .entity weaponentity, bool secondary, float attacktime)
entity oldwep = this.m_weapon;
// set up weapon switch think in the future, and start drop anim
- if (INDEPENDENT_ATTACK_FINISHED || ATTACK_FINISHED(actor, weaponslot(weaponentity)) <= time + this.weapon_frametime * 0.5)
+ if (INDEPENDENT_ATTACK_FINISHED || ATTACK_FINISHED(actor, weaponentity) <= time + this.weapon_frametime * 0.5)
{
sound(actor, CH_WEAPON_SINGLE, SND_WEAPON_SWITCH, VOL_BASE, ATTN_NORM);
this.state = WS_DROP;
// LordHavoc: network timing test code
// if (actor.button0)
- // print(ftos(frametime), " ", ftos(time), " >= ", ftos(ATTACK_FINISHED(actor, slot)), " >= ", ftos(this.weapon_nextthink), "\n");
+ // print(ftos(frametime), " ", ftos(time), " >= ", ftos(ATTACK_FINISHED(actor, weaponentity)), " >= ", ftos(this.weapon_nextthink), "\n");
Weapon w = this.m_weapon;
{
backtrace(sprintf(
"W_DecreaseAmmo(%.2f): '%s' subtracted too much %s from '%s', resulting with '%.2f' left... "
- "Please notify Samual immediately with a copy of this backtrace!\n",
+ "Please notify the developers immediately with a copy of this backtrace!\n",
ammo_use,
wep.netname,
GetAmmoPicture(wep.ammo_type),
// then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
// so your weapon is disabled for a few seconds without reason
- // ATTACK_FINISHED(actor, slot) -= w_ent.reload_time - 1;
+ // ATTACK_FINISHED(actor, weaponentity) -= w_ent.reload_time - 1;
w_ready(wpn, actor, weaponentity, PHYS_INPUT_BUTTON_ATCK(actor) | (PHYS_INPUT_BUTTON_ATCK2(actor) << 1));
}
// then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
// so your weapon is disabled for a few seconds without reason
- // ATTACK_FINISHED(actor, slot) = max(time, ATTACK_FINISHED(actor, slot)) + this.reload_time + 1;
+ // ATTACK_FINISHED(actor, weaponentity) = max(time, ATTACK_FINISHED(actor, weaponentity)) + this.reload_time + 1;
weapon_thinkf(actor, weaponentity, WFRAME_RELOAD, this.reload_time, W_ReloadedAndReady);
set sv_vote_nospectators 1
set g_chat_nospectators 2
set g_warmup 1
+set g_warmup_limit 0
set g_balance_teams 0
set g_spawnshieldtime 0
set g_spawn_furthest 1
exec balance-overkill.cfg
exec physicsOverkill.cfg
exec randomitems-overkill.cfg
+if_dedicated exec help-overkill.cfg
// general gameplay
set g_overkill 1
--- /dev/null
+cellammo\r
+{\r
+ dpreflectcube cubemaps/default/sky\r
+ dpoffsetmapping - 0.5 match8 128\r
+ {\r
+ map textures/items/cellammo\r
+ rgbgen lightingDiffuse\r
+ }\r
+}
\ No newline at end of file
--- /dev/null
+crylink_new
+{
+ dpreflectcube cubemaps/default/sky
+ {
+ map textures/crylink_new.tga
+ rgbgen lightingDiffuse
+ }
+}
+
rgbGen Vertex
}
}
+
+electro
+{
+ dpreflectcube cubemaps/default/sky
+ {
+ map textures/electronew.tga
+ rgbgen lightingDiffuse
+ }
+}
\ No newline at end of file
-electro
-{
- dpreflectcube cubemaps/default/sky
- {
- map textures/electro.tga
- rgbgen lightingDiffuse
- }
-}
nexgun
{
dpreflectcube cubemaps/default/sky
--- /dev/null
+set g_vehicles 1
+
+set g_vehicles_enter 0 "require pressing use key to enter a vehicle"
+set g_vehicles_enter_radius 250
+set g_vehicles_steal 1 "allow stealing enemy vehicles in teamplay modes"
+set g_vehicles_steal_show_waypoint 1 "show a waypoint above the thief"
+set g_vehicles_delayspawn 1
+set g_vehicles_delayspawn_jitter 10
+set g_vehicles_teams 1 "allow team specific vehicles"
+
+set g_vehicles_teleportable 0
+set g_vehicles_crush_dmg 70
+set g_vehicles_crush_force 50
+set g_vehicles_allow_bots 0
+set g_vehicles_exit_attempts 25
+set g_vehicles_thinkrate 0.1
+
+set g_vehicles_vortex_damagerate 0.75
+set g_vehicles_machinegun_damagerate 0.75
+set g_vehicles_rifle_damagerate 0.75
+set g_vehicles_vaporizer_damagerate 0.5
+set g_vehicles_tag_damagerate 5
+set g_vehicles_weapon_damagerate 2
+
+// {{{ #1: Bumblebee
+set g_vehicle_bumblebee 1
+set g_vehicle_bumblebee_respawntime 60
+
+set g_vehicle_bumblebee_speed_forward 350
+set g_vehicle_bumblebee_speed_strafe 350
+set g_vehicle_bumblebee_speed_up 350
+set g_vehicle_bumblebee_speed_down 350
+set g_vehicle_bumblebee_turnspeed 120
+set g_vehicle_bumblebee_pitchspeed 60
+set g_vehicle_bumblebee_pitchlimit 60
+set g_vehicle_bumblebee_friction 0.5
+set g_vehicle_bumblebee_swim 0
+
+set g_vehicle_bumblebee_energy 500
+set g_vehicle_bumblebee_energy_regen 50
+set g_vehicle_bumblebee_energy_regen_pause 1
+
+set g_vehicle_bumblebee_health 1000
+set g_vehicle_bumblebee_health_regen 65
+set g_vehicle_bumblebee_health_regen_pause 10
+
+set g_vehicle_bumblebee_shield 400
+set g_vehicle_bumblebee_shield_regen 150
+set g_vehicle_bumblebee_shield_regen_pause 0.75
+
+set g_vehicle_bumblebee_cannon_ammo 100
+set g_vehicle_bumblebee_cannon_ammo_regen 100
+set g_vehicle_bumblebee_cannon_ammo_regen_pause 1
+
+set g_vehicle_bumblebee_cannon_lock 1
+
+set g_vehicle_bumblebee_cannon_turnspeed 260
+set g_vehicle_bumblebee_cannon_pitchlimit_down 60
+set g_vehicle_bumblebee_cannon_pitchlimit_up 60
+set g_vehicle_bumblebee_cannon_turnlimit_in 20
+set g_vehicle_bumblebee_cannon_turnlimit_out 80
+
+
+set g_vehicle_bumblebee_raygun_turnspeed 180
+set g_vehicle_bumblebee_raygun_pitchlimit_down 20
+set g_vehicle_bumblebee_raygun_pitchlimit_up 5
+set g_vehicle_bumblebee_raygun_turnlimit_sides 35
+
+set g_vehicle_bumblebee_raygun 0
+set g_vehicle_bumblebee_raygun_range 2048
+set g_vehicle_bumblebee_raygun_dps 250
+set g_vehicle_bumblebee_raygun_aps 100
+set g_vehicle_bumblebee_raygun_fps 100
+
+set g_vehicle_bumblebee_healgun_hps 150
+set g_vehicle_bumblebee_healgun_hmax 100
+set g_vehicle_bumblebee_healgun_aps 75
+set g_vehicle_bumblebee_healgun_amax 100
+set g_vehicle_bumblebee_healgun_sps 100
+set g_vehicle_bumblebee_healgun_locktime 2.5
+
+set g_vehicle_bumblebee_blowup_radius 500
+set g_vehicle_bumblebee_blowup_coredamage 500
+set g_vehicle_bumblebee_blowup_edgedamage 100
+set g_vehicle_bumblebee_blowup_forceintensity 600
+set g_vehicle_bumblebee_bouncepain "1 100 200"
+
+set g_vehicle_bumblebee_cannon_cost 2
+set g_vehicle_bumblebee_cannon_damage 60
+set g_vehicle_bumblebee_cannon_radius 225
+set g_vehicle_bumblebee_cannon_refire 0.2
+set g_vehicle_bumblebee_cannon_speed 20000
+set g_vehicle_bumblebee_cannon_spread 0
+set g_vehicle_bumblebee_cannon_force -35
+// }}}
+// {{{ #2: Racer
+set g_vehicle_racer 1
+set g_vehicle_racer_respawntime 35
+
+set g_vehicle_racer_thinkrate 0.05 // TODO: any higher causes it to sink in liquids
+
+set g_vehicle_racer_speed_afterburn 3000
+set g_vehicle_racer_afterburn_cost 130 "energy consumed per second"
+
+set g_vehicle_racer_waterburn_cost 5
+set g_vehicle_racer_waterburn_speed 750
+
+set g_vehicle_racer_water_speed_forward 600
+set g_vehicle_racer_water_speed_strafe 600
+
+set g_vehicle_racer_pitchlimit 30
+
+set g_vehicle_racer_water_downforce 0.03
+set g_vehicle_racer_water_upforcedamper 15
+
+set g_vehicle_racer_anglestabilizer 1.75
+set g_vehicle_racer_downforce 0.01
+
+set g_vehicle_racer_speed_forward 650
+set g_vehicle_racer_speed_strafe 650
+set g_vehicle_racer_springlength 90
+set g_vehicle_racer_upforcedamper 2
+set g_vehicle_racer_friction 0.45
+
+set g_vehicle_racer_water_time 5
+
+set g_vehicle_racer_hovertype 0 "0 = hover, otherwise = maglev"
+set g_vehicle_racer_hoverpower 8000 "this is multiplied by 4 for the 4 engines"
+
+set g_vehicle_racer_turnroll 30
+set g_vehicle_racer_turnspeed 220
+set g_vehicle_racer_pitchspeed 125
+
+set g_vehicle_racer_energy 100
+set g_vehicle_racer_energy_regen 90
+set g_vehicle_racer_energy_regen_pause 0.35
+
+set g_vehicle_racer_health 200
+set g_vehicle_racer_health_regen 0
+set g_vehicle_racer_health_regen_pause 0
+
+set g_vehicle_racer_shield 100
+set g_vehicle_racer_shield_regen 30
+set g_vehicle_racer_shield_regen_pause 1
+
+set g_vehicle_racer_rocket_locktarget 1
+set g_vehicle_racer_rocket_locking_time 0.35
+set g_vehicle_racer_rocket_locking_releasetime 0.5
+set g_vehicle_racer_rocket_locked_time 4
+
+set g_vehicle_racer_blowup_radius 250
+set g_vehicle_racer_blowup_coredamage 250
+set g_vehicle_racer_blowup_edgedamage 15
+set g_vehicle_racer_blowup_forceintensity 250
+
+set g_vehicle_racer_bouncefactor 0.25 "factor of old velocity to keep after collision"
+set g_vehicle_racer_bouncestop 0 "if not 0, new velocity after bounce is 0 if new velocity is smaller than this"
+set g_vehicle_racer_bouncepain "200 0.15 150" "minspeed_for_pain speedchange_to_pain_factor max_damage"
+
+set g_vehicle_racer_cannon_cost 1.5
+set g_vehicle_racer_cannon_damage 15
+set g_vehicle_racer_cannon_radius 100
+set g_vehicle_racer_cannon_refire 0.05
+set g_vehicle_racer_cannon_speed 15000
+set g_vehicle_racer_cannon_spread 0.0125
+set g_vehicle_racer_cannon_force 50
+
+set g_vehicle_racer_rocket_accel 1600
+set g_vehicle_racer_rocket_damage 100
+set g_vehicle_racer_rocket_radius 125
+set g_vehicle_racer_rocket_force 350
+set g_vehicle_racer_rocket_speed 900
+set g_vehicle_racer_rocket_turnrate 0.2
+set g_vehicle_racer_rocket_refire 3
+
+set g_vehicle_racer_rocket_climbspeed 1600
+set g_vehicle_racer_rocket_locked_maxangle 1.8
+// }}}
+// {{{ #3: Raptor
+set g_vehicle_raptor 1
+set g_vehicle_raptor_respawntime 40
+
+set g_vehicle_raptor_takeofftime 1.5
+
+set g_vehicle_raptor_movestyle 1 "0: move relative to player angles, 1: ignore aiming for up/down movement"
+set g_vehicle_raptor_turnspeed 200
+set g_vehicle_raptor_pitchspeed 50
+set g_vehicle_raptor_pitchlimit 45
+
+set g_vehicle_raptor_speed_forward 1700
+set g_vehicle_raptor_speed_strafe 2200
+set g_vehicle_raptor_speed_up 2300
+set g_vehicle_raptor_speed_down 2000
+set g_vehicle_raptor_friction 2
+
+set g_vehicle_raptor_swim 0
+
+set g_vehicle_raptor_cannon_turnspeed 120
+set g_vehicle_raptor_cannon_turnlimit 20
+set g_vehicle_raptor_cannon_pitchlimit_up 12
+set g_vehicle_raptor_cannon_pitchlimit_down 32
+
+set g_vehicle_raptor_cannon_locktarget 1
+set g_vehicle_raptor_cannon_locking_time 0.2
+set g_vehicle_raptor_cannon_locking_releasetime 0.45
+set g_vehicle_raptor_cannon_locked_time 1
+set g_vehicle_raptor_cannon_predicttarget 1
+
+set g_vehicle_raptor_energy 100
+set g_vehicle_raptor_energy_regen 25
+set g_vehicle_raptor_energy_regen_pause 0.25
+
+set g_vehicle_raptor_health 250
+set g_vehicle_raptor_health_regen 0
+set g_vehicle_raptor_health_regen_pause 0
+
+set g_vehicle_raptor_shield 200
+set g_vehicle_raptor_shield_regen 25
+set g_vehicle_raptor_shield_regen_pause 1.5
+
+set g_vehicle_raptor_bouncefactor 0.2
+set g_vehicle_raptor_bouncestop 0
+set g_vehicle_raptor_bouncepain "1 4 1000"
+
+set g_vehicle_raptor_cannon_cost 1
+set g_vehicle_raptor_cannon_damage 10
+set g_vehicle_raptor_cannon_radius 60
+set g_vehicle_raptor_cannon_refire 0.03
+set g_vehicle_raptor_cannon_speed 24000
+set g_vehicle_raptor_cannon_spread 0.01
+set g_vehicle_raptor_cannon_force 25
+
+set g_vehicle_raptor_bomblets 8
+set g_vehicle_raptor_bomblet_alt 750
+set g_vehicle_raptor_bomblet_time 0.5
+set g_vehicle_raptor_bomblet_damage 55
+set g_vehicle_raptor_bomblet_spread 0.4
+set g_vehicle_raptor_bomblet_edgedamage 25
+set g_vehicle_raptor_bomblet_radius 350
+set g_vehicle_raptor_bomblet_force 150
+set g_vehicle_raptor_bomblet_explode_delay 0.4
+
+set g_vehicle_raptor_bombs_refire 5
+
+set g_vehicle_raptor_flare_refire 5
+set g_vehicle_raptor_flare_lifetime 10
+set g_vehicle_raptor_flare_chase 0.9
+set g_vehicle_raptor_flare_range 2000
+// }}}
+// {{{ #4: Spiderbot
+set g_vehicle_spiderbot 1
+set g_vehicle_spiderbot_respawntime 45
+
+set g_vehicle_spiderbot_speed_stop 50
+set g_vehicle_spiderbot_speed_strafe 400
+set g_vehicle_spiderbot_speed_walk 500
+set g_vehicle_spiderbot_speed_run 700
+set g_vehicle_spiderbot_turnspeed 90
+set g_vehicle_spiderbot_turnspeed_strafe 300
+set g_vehicle_spiderbot_movement_inertia 0.15
+
+set g_vehicle_spiderbot_springlength 150
+set g_vehicle_spiderbot_springup 20
+set g_vehicle_spiderbot_springblend 0.1
+set g_vehicle_spiderbot_tiltlimit 90
+
+set g_vehicle_spiderbot_head_pitchlimit_down -20
+set g_vehicle_spiderbot_head_pitchlimit_up 30
+set g_vehicle_spiderbot_head_turnlimit 90
+set g_vehicle_spiderbot_head_turnspeed 110
+
+set g_vehicle_spiderbot_health 800
+set g_vehicle_spiderbot_health_regen 10
+set g_vehicle_spiderbot_health_regen_pause 5
+
+set g_vehicle_spiderbot_shield 200
+set g_vehicle_spiderbot_shield_regen 25
+set g_vehicle_spiderbot_shield_regen_pause 0.35
+
+set g_vehicle_spiderbot_bouncepain "0 0 0" "minspeed_for_pain speedchange_to_pain_factor max_damage"
+
+set g_vehicle_spiderbot_minigun_damage 16
+set g_vehicle_spiderbot_minigun_refire 0.06
+set g_vehicle_spiderbot_minigun_spread 0.012
+set g_vehicle_spiderbot_minigun_ammo_cost 1
+set g_vehicle_spiderbot_minigun_ammo_max 100
+set g_vehicle_spiderbot_minigun_ammo_regen 40
+set g_vehicle_spiderbot_minigun_ammo_regen_pause 1
+set g_vehicle_spiderbot_minigun_force 9
+set g_vehicle_spiderbot_minigun_solidpenetration 32
+
+set g_vehicle_spiderbot_rocket_damage 50
+set g_vehicle_spiderbot_rocket_force 150
+set g_vehicle_spiderbot_rocket_radius 250
+set g_vehicle_spiderbot_rocket_speed 3500
+set g_vehicle_spiderbot_rocket_spread 0.05
+set g_vehicle_spiderbot_rocket_refire 0.1
+// volley
+set g_vehicle_spiderbot_rocket_refire2 0.025
+set g_vehicle_spiderbot_rocket_reload 4
+set g_vehicle_spiderbot_rocket_health 100
+set g_vehicle_spiderbot_rocket_noise 0.2
+set g_vehicle_spiderbot_rocket_turnrate 0.25
+set g_vehicle_spiderbot_rocket_lifetime 20
+// }}}
seta cl_hitsound_max_pitch 1.5 "maximum pitch of hit sound"
seta cl_hitsound_nom_damage 25 "damage amount at which hitsound bases pitch off"
+seta cl_eventchase_spectated_change 1 "camera goes into 3rd person mode for a moment when changing spectated player"
+seta cl_eventchase_spectated_change_time 1 "how much time the effect lasts when changing spectated player"
seta cl_eventchase_death 1 "camera goes into 3rd person mode when the player is dead; set to 2 to active the effect only when the corpse doesn't move anymore"
seta cl_eventchase_frozen 0 "camera goes into 3rd person mode when the player is frozen"
seta cl_eventchase_nexball 1 "camera goes into 3rd person mode when in nexball game-mode"
cl_movement_track_canjump 0
cl_stairsmoothspeed 200
-alias g_waypointeditor_spawn "impulse 103"
-alias g_waypointeditor_remove "impulse 104"
-alias g_waypointeditor_relinkall "impulse 105"
-alias g_waypointeditor_saveall "impulse 106"
-alias g_waypointeditor_unreachable "impulse 107"
+alias g_waypointeditor_spawn "wpeditor spawn"
+alias g_waypointeditor_remove "wpeditor remove"
+alias g_waypointeditor_relinkall "wpeditor relinkall"
+alias g_waypointeditor_saveall "wpeditor saveall"
+alias g_waypointeditor_unreachable "wpeditor unreachable"
+
+alias navwaypoint_relink g_waypointeditor_spawn
+alias navwaypoint_remove g_waypointeditor_remove
+alias navwaypoint_save g_waypointeditor_relinkall
+alias navwaypoint_spawn g_waypointeditor_saveall
+alias navwaypoint_unreachable g_waypointeditor_unreachable
seta menu_sandbox_spawn_model ""
seta menu_sandbox_attach_bone ""
set g_waypointsprite_timealphaexponent 1
seta g_waypointsprite_turrets 1 "disable turret waypoints"
seta g_waypointsprite_turrets_maxdist 5000 "max distance for turret waypoints"
+seta g_waypointsprite_turrets_text 0 "show the turret's name in the waypoint"
seta g_waypointsprite_uppercase 1
seta g_waypointsprite_text 0 "Always show text instead of icons, setting this to 0 will still use text if the icon is unavailable"
seta g_waypointsprite_iconsize 32
seta cl_race_cptimes_showself 1 "Always show your own times as well as the current best on checkpoints in Race/CTS"
seta cl_race_cptimes_onlyself 0 "Only show your own times on checkpoints in Race/CTS"
+seta cl_cts_noautoswitch 0 "Prevent forced switching to new weapons in CTS"
+
set cl_stripcolorcodes 0 "experimental feature (notes: strips ALL color codes from messages!)"
// Demo camera
seta cl_movement_errorcompensation 1 "try to compensate for prediction errors and reduce perceived lag"
seta cl_movement_intermissionrunning 0 "keep velocity after the match ends, players may appear to continue running while stationary"
+seta cl_viewmodel_alpha 0 "Maximum transparency of the view model, set to 0 to disable"
+
set debugdraw 0
set debugdraw_filter ""
set debugdraw_filterout ""
r_shadow_glossexact 1
r_shadow_glossintensity 1
-// use fake light if map has no lightmaps
-r_fakelight 1
+// use slightly better lighting than r_fullbright if map has no lightmaps, and for fullbrightplayers
+r_fullbright_directed 1
r_water_hideplayer 1 // hide your own feet/player model in refraction views, this way you don't see half of your body under water
r_water_refractdistort 0.019
set cl_rainsnow_maxdrawdist 2048
-// equalize looks better than fullbright
-r_equalize_entities_fullbright 1
-
// safe font defaults
r_font_hinting 1
r_font_disable_freetype 0
// Change g_start_delay based upon if the server is local or not.
if_client set g_start_delay 0 "delay before the game starts, so everyone can join; recommended to set this to like 15 on a public server"
if_dedicated set g_start_delay 15 "delay before the game starts, so everyone can join; recommended to set this to like 15 on a public server"
+
+// this should be execed only once even on ruleset-votable servers, otherwise the tips would always start from 0
+if_dedicated exec help.cfg
// server settings
hostname "Xonotic $g_xonoticversion Server"
set sv_mapchange_delay 5
-set minplayers 0 "number of players playing at the same time (if not enough real players are there the remaining slots are filled with bots)"
+set minplayers 0 "fill server with bots to reach this number of players (if bot_number is not enough)"
// restart server if all players hit "ready"-button
set sv_ready_restart 0 "allow a map to be restarted once all players pressed the \"ready\" button"
// tournament mod
set g_warmup 0 "split the game into a warmup- and match-stage"
-set g_warmup_limit 0 "limit warmup-stage to this time (in seconds); if set to -1 the warmup-stage is not affected by any timelimit, if set to 0 the usual timelimit also affects warmup-stage"
+set g_warmup_limit 180 "limit warmup-stage to this time (in seconds); if set to -1 the warmup-stage is not affected by any timelimit, if set to 0 the usual timelimit also affects warmup-stage"
set g_warmup_allow_timeout 0 "allow calling timeouts in the warmup-stage (if sv_timeout is set to 1)"
set g_warmup_allguns 1 "provide more weapons on start while in warmup: 0 = normal start weapons, 1 = all guns available on the map, 2 = all normal weapons"
set g_warmup_majority_factor 0.8 "minimum percentage of players ready needed for warmup to end"
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 timelimit_suddendeath 5 "number of minutes suddendeath mode lasts after all overtimes were added and still no winner was found"
// common team values
-set g_tdm 0 "Team Deathmatch: the team who kills their opponents most often wins"
-set g_tdm_on_dm_maps 0 "when this is set, all DM maps automatically support TDM"
set teamplay_mode 4 "default teamplay setting in team games. 1 = no friendly fire, self damage. 2 = friendly fire and self damage enabled. 3 = no friendly fire, but self damage enabled. 4 = obey the cvars g_mirrordamage*, g_friendlyfire* and g_teamdamage*"
set g_mirrordamage 0.7 "for teamplay_mode 4: mirror damage factor"
set g_balance_teams 1 "automatically balance out players entering instead of asking them for their preferred team"
set g_balance_teams_prevent_imbalance 1 "prevent players from changing to larger teams"
-set g_balance_teams_scorefactor 0.25 "at the end of the game, take score into account instead of team size by this amount (beware: values over 0.5 mean that a x:0 score imbalance will cause ALL new players to prefer the losing team at the end, despite numbers)"
set g_changeteam_banned 0 "not allowed to change team"
-set g_changeteam_fragtransfer 0 "% of frags you get to keep when you change teams (rounded down)"
set sv_teamnagger 1 "enable a nag message when the teams are unbalanced"
set g_maplist_votable_abstain 0 "when 1, you can abstain from your vote"
set g_maplist_votable_screenshot_dir "maps levelshots" "where to look for map screenshots"
-set sv_vote_gametype 1 "show a vote screen for gametypes before map vote screen"
+set sv_vote_gametype 0 "show a vote screen for gametypes before map vote screen"
set sv_vote_gametype_keeptwotime 10 "show only 2 options after this amount of time during gametype vote screen"
set sv_vote_gametype_options "dm tdm ctf" "Keep the identifiers short, otherwise you'll run into issues with too long alias names (max is 31 characters) when using sv_vote_gametype_hook_*"
set sv_vote_gametype_timeout 20
set g_ban_default_bantime 5400 "90 minutes"
set g_ban_default_masksize 3 "masksize 0 means banning by UID only, 1 means banning by /8 (IPv6: /32) network, 2 means banning by /16 (IPv6: /48) network, 3 means banning by /24 (IPv6: /56) network, 4 means banning by single IP (IPv6: /64 network)"
+set g_ban_telluser 1 "notify the banned player about it when they try to join"
set g_banned_list "" "format: IP remainingtime IP remainingtime ..."
set g_banned_list_idmode "1" "when set, the IP banning system always uses the ID over the IP address (so a user in a banned IP range can connect if they have a valid signed ID)"
set g_hitplots 0 "when set to 1, hitplots are stored by the server to provide a means of proving that a triggerbot was used"
set g_hitplots_individuals "" "the individuals, by IP, that should have their hitplots recorded"
+// set it to 1 to "fix bot moveto command and routing... now all bots can get to their seats" (Nexuiz repo, commit 2c9873e6)
set bot_navigation_ignoreplayers 0 // FIXME remove this once the issue is solved
set bot_sound_monopoly 0 "when enabled, only bots can make any noise"
sv_gameplayfix_nogravityonground 1
set sv_q3acompat_machineshotgunswap 0 "shorthand for swapping machinegun and shotgun (for Q3A map compatibility in mapinfo files)"
+set sv_vq3compat 0 "toggle for some compatibility hacks (for VQ3 and CPM map compatibility in mapinfo files)"
set g_movement_highspeed 1 "movement speed modification factor (only changes movement when above maxspeed)"
exec balance-xonotic.cfg
exec physicsX.cfg
exec turrets.cfg
+exec vehicles.cfg
exec gamemodes-server.cfg
exec mutators.cfg
exec monsters.cfg
exec minigames.cfg
exec physics.cfg
+if_dedicated exec help-xonotic.cfg
set sv_join_notices ""
set sv_join_notices_time 15