- wget -nv -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints
- wget -nv -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache
- - EXPECT=f69ef2a13dbe594a0284660e60bdc903
+ - EXPECT=0697ef57c3dca0ff122074b29495c5da
- HASH=$(${ENGINE} +exec serverbench.cfg
| tee /dev/stderr
| grep '^:'
-Sat 03 Jun 2023 07:22:57 AM CEST
+Sun 18 Jun 2023 07:22:56 AM CEST
alias bans "qc_cmd_sv banlist ${* ?}"
alias muteban "qc_cmd_sv mute ${* ?}"
alias unmuteban "qc_cmd_sv unmute ${* ?}"
+alias chatban "qc_cmd_sv mute ${* ?}"
+alias unchatban "qc_cmd_sv unmute ${* ?}"
alias unvoteban "qc_cmd_sv unvoteban ${* ?}" // Remove an existing vote ban client
// other aliases for muteban, playban and voteban lists
-alias mutebans "g_muteban_list ${* ?}"
+alias mutebans "g_chatban_list ${* ?}"
+alias chatbans "g_chatban_list ${* ?}"
alias playbans "g_playban_list ${* ?}"
alias votebans "g_voteban_list ${* ?}"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-03 07:22+0200\n"
"PO-Revision-Date: 2013-09-12 16:53+0000\n"
-"Last-Translator: Yllelder, 2016\n"
+"Last-Translator: LegendGuard, 2020-2023\n"
"Language-Team: Spanish (http://app.transifex.com/team-xonotic/xonotic/"
"language/es/)\n"
"Language: es\n"
#: qcsrc/client/hud/panel/scoreboard.qc:142
msgid "SCO^frags"
-msgstr "eliminaciones"
+msgstr "bajas"
#: qcsrc/client/hud/panel/scoreboard.qc:143
msgid "Number of goals scored"
#: qcsrc/common/gamemodes/gamemode/deathmatch/deathmatch.qh:8
msgid "Score as many frags as you can"
-msgstr "Marca tantas eliminaciones como puedas"
+msgstr "Marca tantas bajas como puedas"
#: qcsrc/common/gamemodes/gamemode/domination/domination.qh:12
msgid "Capture and defend all the control points to win"
#: qcsrc/common/gamemodes/gamemode/mayhem/mayhem.qh:10
msgid "Compete for the most damage dealt and frags in this chaotic mayhem!"
msgstr ""
-"¡Compite por causar el mayor daño y obtener la mayor cantidad de "
-"eliminaciones en este caótico Mayhem!"
+"¡Compite por causar el mayor daño y obtener la mayor cantidad de bajas en "
+"este caótico Mayhem!"
#: qcsrc/common/gamemodes/gamemode/mayhem/mayhem.qh:10
msgid "Mayhem"
#: qcsrc/common/gamemodes/gamemode/survival/survival.qh:12
msgid "Identify and eliminate all the hunters before all your allies are gone"
msgstr ""
+"Identifica y elimina a todos los cazadores antes de que todos tus aliados "
+"desaparezcan"
#: qcsrc/common/gamemodes/gamemode/survival/survival.qh:12
msgid "Survival"
-msgstr ""
+msgstr "Supervivencia"
#: qcsrc/common/gamemodes/gamemode/tdm/tdm.qh:9
msgid "Help your team score the most frags against the enemy team"
msgstr ""
-"Ayuda a tu equipo a acertar tantas eliminaciones como puedas contra el "
-"equipo enemigo"
+"Ayuda a tu equipo a acertar tantas bajas como puedas contra el equipo enemigo"
#: qcsrc/common/gamemodes/gamemode/tdm/tdm.qh:9
msgid "Team Deathmatch"
#: qcsrc/common/gamemodes/gamemode/tka/tka.qh:13
msgid "Keep the ball in your team's possession to get points for kills"
msgstr ""
+"Mantén el balón en posesión de tu equipo para obtener puntos por asesinatos"
#: qcsrc/common/gamemodes/gamemode/tka/tka.qh:13
msgid "Team Keepaway"
-msgstr ""
+msgstr "Keepaway por equipos"
#: qcsrc/common/gamemodes/gamemode/tmayhem/tmayhem.qh:10
msgid ""
"Compete with your team for the most damage dealt and frags in this chaotic "
"mayhem!"
msgstr ""
-"¡Compite con tu equipo por causar el mayor daño y obtener frags en este "
+"¡Compite con tu equipo por causar el mayor daño y obtener bajas en este "
"caótico Mayhem!"
#: qcsrc/common/gamemodes/gamemode/tmayhem/tmayhem.qh:10
#: qcsrc/common/mapinfo.qh:89
#: qcsrc/menu/xonotic/dialog_multiplayer_create.qc:107
msgid "Frag limit:"
-msgstr "Límite de eliminaciones:"
+msgstr "Límite de bajas:"
#: qcsrc/common/mapinfo.qh:89
msgid "The amount of frags needed before the match will end"
-msgstr "Cantidad de eliminaciones necesarias antes de que termine el combate"
+msgstr "Cantidad de bajas necesarias antes de que termine el combate"
#: qcsrc/common/minigames/cl_minigames.qc:379
msgid "It's your turn"
#: qcsrc/common/notifications/all.inc:721
msgid "^BGGet the ball to score points for frags!"
-msgstr "^BG¡Obtén la pelota para ganar puntos por eliminaciones!"
+msgstr "^BG¡Obtén la pelota para ganar puntos por bajas!"
#: qcsrc/common/notifications/all.inc:723
msgid ""
#: qcsrc/common/notifications/all.qh:467
#, c-format
msgid "%s^K1 made a TRIPLE FRAG! %s^BG"
-msgstr "¡%s^K1 ha hecho una TRIPLE ELIMINACIÓN! %s^BG"
+msgstr "¡%s^K1 ha hecho una TRIPLE BAJA! %s^BG"
#: qcsrc/common/notifications/all.qh:467
#, c-format
#: qcsrc/common/notifications/all.qh:467
msgid "TRIPLE FRAG! "
-msgstr "¡TRIPLE ELIMINACIÓN! "
+msgstr "¡TRIPLE BAJA! "
#: qcsrc/common/notifications/all.qh:468
#, c-format
#: qcsrc/common/notifications/all.qh:524
#, c-format
msgid "%d frag spree! "
-msgstr "¡%d eliminaciones seguidas! "
+msgstr "¡%d bajas seguidas! "
#: qcsrc/common/notifications/all.qh:537
msgid "First blood! "
#: qcsrc/common/notifications/all.qh:582
#, c-format
msgid "%s^K1 has %d frags in a row! %s^BG"
-msgstr "¡%s^K1 tiene %d eliminaciones seguidas! %s^BG"
+msgstr "¡%s^K1 tiene %d bajas seguidas! %s^BG"
#: qcsrc/common/notifications/all.qh:583
#, c-format
#: qcsrc/common/notifications/all.qh:618
#, c-format
msgid ", ending their %d frag spree"
-msgstr ", finalizando sus %d eliminaciones seguidas"
+msgstr ", finalizando sus %d bajas seguidas"
#: qcsrc/common/notifications/all.qh:619
#, c-format
#: qcsrc/common/notifications/all.qh:633
#, c-format
msgid ", losing their %d frag spree"
-msgstr ", perdiendo sus %d eliminaciones seguidas"
+msgstr ", perdiendo sus %d bajas seguidas"
#: qcsrc/common/notifications/all.qh:634
#, c-format
#: qcsrc/menu/xonotic/dialog_settings_audio.qc:178
msgid "Automatically taunt enemies after fragging them"
-msgstr "Burlarse automáticamente de los enemigos al acertarlos"
+msgstr "Burlarse automáticamente de los enemigos al eliminarlos"
#: qcsrc/menu/xonotic/dialog_settings_audio.qc:180
msgid "Sometimes"
#: qcsrc/menu/xonotic/dialog_settings_game_messages.qc:24
msgid "Frag Information"
-msgstr "Información de Eliminación"
+msgstr "Información de Baja"
#: qcsrc/menu/xonotic/dialog_settings_game_messages.qc:26
msgid "Display information about killing sprees"
#: qcsrc/menu/xonotic/dialog_settings_game_messages.qc:58
msgid "Add extra frag information to centerprint when available"
msgstr ""
-"Añadir información adicional de eliminación a la visualización central "
-"cuando sea posible"
+"Añadir información adicional de la baja a la visualización central cuando "
+"sea posible"
#: qcsrc/menu/xonotic/dialog_settings_game_messages.qc:62
msgid "Add frag location to death messages when available"
msgstr ""
-"Añadir localización de la eliminación en los mensajes de muertes cuando sea "
-"posible"
+"Añadir localización de la baja en los mensajes de muertes cuando sea posible"
#: qcsrc/menu/xonotic/dialog_settings_game_messages.qc:65
msgid "Gamemode Settings"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-03 07:22+0200\n"
"PO-Revision-Date: 2013-09-12 16:53+0000\n"
-"Last-Translator: RYU N. <ryusho2523@yahoo.co.jp>, 2021\n"
+"Last-Translator: LegendGuard, 2020-2023\n"
"Language-Team: Japanese (Japan) (http://app.transifex.com/team-xonotic/"
"xonotic/language/ja_JP/)\n"
"Language: ja_JP\n"
#: qcsrc/common/gamemodes/gamemode/survival/survival.qh:12
msgid "Identify and eliminate all the hunters before all your allies are gone"
-msgstr ""
+msgstr "仲間がすべて消える前に、すべてのハンターを特定し排除して"
#: qcsrc/common/gamemodes/gamemode/survival/survival.qh:12
msgid "Survival"
-msgstr ""
+msgstr "サバイバル"
#: qcsrc/common/gamemodes/gamemode/tdm/tdm.qh:9
msgid "Help your team score the most frags against the enemy team"
#: qcsrc/common/gamemodes/gamemode/tka/tka.qh:13
msgid "Keep the ball in your team's possession to get points for kills"
-msgstr ""
+msgstr "相手チームよりもボールをキープし続けて、キルポイントを獲得して"
#: qcsrc/common/gamemodes/gamemode/tka/tka.qh:13
msgid "Team Keepaway"
-msgstr ""
+msgstr "チームキープアウェイ (TEAM KEEPAWAY)"
#: qcsrc/common/gamemodes/gamemode/tmayhem/tmayhem.qh:10
msgid ""
#: qcsrc/client/hud/panel/scoreboard.qc:144
msgid "Number of hunts (Survival)"
-msgstr ""
+msgstr "Quotiens venatus est (Superstites)"
#: qcsrc/client/hud/panel/scoreboard.qc:144
msgid "SCO^hunts"
-msgstr ""
+msgstr "venatus"
#: qcsrc/client/hud/panel/scoreboard.qc:145
msgid "Number of keys carrier kills"
#: qcsrc/client/hud/panel/scoreboard.qc:164
msgid "Number of rounds played"
-msgstr ""
+msgstr "Quot temporibus lusit"
#: qcsrc/client/hud/panel/scoreboard.qc:164
msgid "SCO^rounds played"
-msgstr ""
+msgstr "quot lusit"
#: qcsrc/client/hud/panel/scoreboard.qc:165
msgid "SCO^score"
#: qcsrc/client/hud/panel/scoreboard.qc:168
msgid "Number of survivals"
-msgstr ""
+msgstr "Quotiens superstes fuit"
#: qcsrc/client/hud/panel/scoreboard.qc:168
msgid "SCO^survivals"
-msgstr ""
+msgstr "superstes"
#: qcsrc/client/hud/panel/scoreboard.qc:169
msgid "Number of domination points taken (Domination)"
#: qcsrc/common/gamemodes/gamemode/survival/cl_survival.qc:16
msgid "Hunter"
-msgstr ""
+msgstr "Venator"
#: qcsrc/common/gamemodes/gamemode/survival/cl_survival.qc:22
msgid "Survivor"
-msgstr ""
+msgstr "Superstes"
#: qcsrc/common/gamemodes/gamemode/survival/survival.qh:12
msgid "Identify and eliminate all the hunters before all your allies are gone"
msgstr ""
+"Supertites et venatores discerne, posteriores occide, ne omnes collegae tui "
+"moriantur"
#: qcsrc/common/gamemodes/gamemode/survival/survival.qh:12
msgid "Survival"
-msgstr ""
+msgstr "Supertites"
#: qcsrc/common/gamemodes/gamemode/tdm/tdm.qh:9
msgid "Help your team score the most frags against the enemy team"
#: qcsrc/common/gamemodes/gamemode/tka/tka.qh:13
msgid "Keep the ball in your team's possession to get points for kills"
-msgstr ""
+msgstr "Age ut manus tua pilam teneat, ut rationi addetur cum interficies"
#: qcsrc/common/gamemodes/gamemode/tka/tka.qh:13
msgid "Team Keepaway"
-msgstr ""
+msgstr "Manuum Abhibitio"
#: qcsrc/common/gamemodes/gamemode/tmayhem/tmayhem.qh:10
msgid ""
#: qcsrc/common/notifications/all.inc:234
msgid "^F4NOTE: ^BGChat is currently disabled on this server"
-msgstr ""
+msgstr "^F4NOTA: ^BGLocutorium hoc moderatro nunc vetatur"
#: qcsrc/common/notifications/all.inc:235
msgid "^F4NOTE: ^BGSpectator chat is not sent to players during the match"
#: qcsrc/common/notifications/all.inc:236
msgid "^F4NOTE: ^BGPrivate chat is currently disabled on this server"
-msgstr ""
+msgstr "^F4NOTA: ^BGLocutorium privatum hoc moderatro nunc vetatur"
#: qcsrc/common/notifications/all.inc:237
msgid "^F4NOTE: ^BGSpectator chat is currently disabled on this server"
-msgstr ""
+msgstr "^F4NOTA: ^BGSpectatorum locutorium hoc moderatro nunc vetatur"
#: qcsrc/common/notifications/all.inc:238
msgid "^F4NOTE: ^BGTeam chat is currently disabled on this server"
-msgstr ""
+msgstr "^F4NOTA: ^BGManus locutorium hoc moderatro nunc vetatur"
#: qcsrc/common/notifications/all.inc:240
#, c-format
#: qcsrc/common/notifications/all.inc:429
#, c-format
msgid "^BG%s^F3 was forced to spectate for excessive teamkilling"
-msgstr ""
+msgstr "^BG%s^F3 spectare debet quia manus collegas nimis interficiebat"
#: qcsrc/common/notifications/all.inc:430
#, c-format
#: qcsrc/common/notifications/all.inc:450
#: qcsrc/common/notifications/all.inc:789
msgid "^K1Hunters^BG win the round"
-msgstr ""
+msgstr "^K1Venatores^BG tempore vincunt"
#: qcsrc/common/notifications/all.inc:451
#: qcsrc/common/notifications/all.inc:791
msgid "^F1Survivors^BG win the round"
-msgstr ""
+msgstr "^F1Superstites^BG tempore vincunt"
#: qcsrc/common/notifications/all.inc:453
msgid "^BGYou cannot change to a larger team"
#, c-format
msgid ""
"^BG%s^K1 blew themself up with their Overkill Rocket Propelled Chainsaw%s%s"
-msgstr ""
+msgstr "^BG%s^K1 Missilium Pellentium Serra Immoderata sua se rupit%s%s"
#: qcsrc/common/notifications/all.inc:507
#, c-format
#: qcsrc/common/notifications/all.inc:519
#, c-format
msgid "^BG%s^K1 is now thinking with portals%s%s"
-msgstr ""
+msgstr "^BG%s^K1 cum teleportis nunc cogitat%s%s"
#: qcsrc/common/notifications/all.inc:520
#, c-format
"^K1You aren't allowed to play because you are banned in this server, but you "
"can play minigames"
msgstr ""
+"^K1Te ludere vetatur quia hoc moderatro expulsus es, sed ludos parvos ludere "
+"potes"
#: qcsrc/common/notifications/all.inc:720
msgid "^BGYou picked up the ball"
#: qcsrc/common/notifications/all.inc:721
msgid "^BGGet the ball to score points for frags!"
-msgstr ""
+msgstr "^BGPilam cape ut rationi addetur cum interficies!"
#: qcsrc/common/notifications/all.inc:723
msgid ""
msgid ""
"^K1You are forced to spectate and you aren't allowed to play because you are "
"banned in this server"
-msgstr ""
+msgstr "^K1Spectare debes et te ludere vetatur quia hoc moderatro expulsus es"
#: qcsrc/common/notifications/all.inc:778
msgid "^F2The race is over, finish your lap!"
msgid ""
"^BGYou are a ^K1hunter^BG! Eliminate the survivor(s) without raising "
"suspicion!"
-msgstr ""
+msgstr "^BGEs ^K1venator^BG! Superstites occide, age ut ei non suspicentur!"
#: qcsrc/common/notifications/all.inc:790
msgid "^BGYou are a ^F1survivor^BG! Identify and eliminate the hunter(s)!"
-msgstr ""
+msgstr "^BGEs ^F1superstes^BG! Venatores discerne et occide!"
#: qcsrc/common/notifications/all.inc:793
msgid "^K1Changing to ^TC^TT^K1 in ^COUNT"
#: qcsrc/common/notifications/all.inc:809
msgid ""
"^K1You aren't allowed to call a vote because you are banned in this server"
-msgstr ""
+msgstr "^K1Te suffragia diribere vetatur quia hoc moderatro expulsus es"
#: qcsrc/common/notifications/all.inc:810
msgid "^K1You aren't allowed to vote because you are banned in this server"
-msgstr ""
+msgstr "^K1Suffragio privaris quia hoc moderatro expulsus es"
#: qcsrc/common/notifications/all.qh:420 qcsrc/common/notifications/all.qh:421
#, c-format
#: qcsrc/menu/xonotic/credits.qc:180
msgid "Chinese (Hong Kong)"
-msgstr ""
+msgstr "Sinica (Hong Cong)"
#: qcsrc/menu/xonotic/credits.qc:193
msgid "Chinese (Taiwan)"
#: qcsrc/menu/xonotic/credits.qc:275
msgid "Indonesian"
-msgstr ""
+msgstr "Indonesica"
#: qcsrc/menu/xonotic/credits.qc:280
msgid "Irish"
#: qcsrc/menu/xonotic/credits.qc:307
msgid "Latin"
-msgstr ""
+msgstr "Latina"
#: qcsrc/menu/xonotic/credits.qc:310
msgid "Polish"
#: qcsrc/client/hud/panel/scoreboard.qc:144
msgid "Number of hunts (Survival)"
-msgstr "ç\8c\8eæ\9d\80数(生存模式)"
+msgstr "ç\8b©ç\8c\8e次数(生存模式)"
#: qcsrc/client/hud/panel/scoreboard.qc:144
msgid "SCO^hunts"
-msgstr "ç\8c\8eæ\9d\80"
+msgstr "ç\8b©ç\8c\8e"
#: qcsrc/client/hud/panel/scoreboard.qc:145
msgid "Number of keys carrier kills"
#: qcsrc/client/hud/panel/scoreboard.qc:168
msgid "Number of survivals"
-msgstr "生还数"
+msgstr "ç\94\9fè¿\98次æ\95°"
#: qcsrc/client/hud/panel/scoreboard.qc:168
msgid "SCO^survivals"
#: qcsrc/client/hud/panel/scoreboard.qc:144
msgid "Number of hunts (Survival)"
-msgstr "ç\8dµæ®º數(生存模式)"
+msgstr "ç\8b©ç\8dµæ¬¡數(生存模式)"
#: qcsrc/client/hud/panel/scoreboard.qc:144
msgid "SCO^hunts"
-msgstr "ç\8dµæ®º"
+msgstr "ç\8b©ç\8dµ"
#: qcsrc/client/hud/panel/scoreboard.qc:145
msgid "Number of keys carrier kills"
#: qcsrc/client/hud/panel/scoreboard.qc:168
msgid "Number of survivals"
-msgstr "生還數"
+msgstr "ç\94\9fé\82\84次æ\95¸"
#: qcsrc/client/hud/panel/scoreboard.qc:168
msgid "SCO^survivals"
#: qcsrc/client/hud/panel/scoreboard.qc:144
msgid "Number of hunts (Survival)"
-msgstr "ç\8dµæ®º數(生存模式)"
+msgstr "ç\8b©ç\8dµæ¬¡數(生存模式)"
#: qcsrc/client/hud/panel/scoreboard.qc:144
msgid "SCO^hunts"
-msgstr "ç\8dµæ®º"
+msgstr "ç\8b©ç\8dµ"
#: qcsrc/client/hud/panel/scoreboard.qc:145
msgid "Number of keys carrier kills"
#: qcsrc/client/hud/panel/scoreboard.qc:168
msgid "Number of survivals"
-msgstr "生還數"
+msgstr "ç\94\9fé\82\84次æ\95¸"
#: qcsrc/client/hud/panel/scoreboard.qc:168
msgid "SCO^survivals"
originoffset 0 0 -8
originjitter 28 28 16
velocityjitter 0 0 256
+effect item_despawn
+ type snow
+// type smoke
+ blend add
+ alpha 192 256 256
+ color 0xff9600 0xffefb8
+ count 32
+ originjitter 8 8 8
+ sizeincrease 1
+ size 0.5 1
+ tex 48 55
+ velocityjitter 16 16 32
+ lightradius 48
+ lightradiusfade 64
+ lightcolor 1 0.75 0.36
+ lightshadow 1
de_CH "German (Switzerland)" "Deutsch (Schweiz)" 100%
en "English" "English" 100%
en_AU "English (Australia)" "English (Australia)" 69%
-es "Spanish" "Español" 99%
+es "Spanish" "Español" 100%
fr "French" "Français" 100%
ga "Irish" "Irish" 29%
it "Italian" "Italiano" 100%
-la "Latin" "Lingua Latina" 62%
+la "Latin" "Lingua Latina" 63%
hu "Hungarian" "Magyar" 43%
nl "Dutch" "Nederlands" 61%
pl "Polish" "Polski" 79%
zh_TW "Chinese (Taiwan)" "中文(正體字)" 100%
zh_CN "Chinese (China)" "中文(简体字)" 100%
zh_HK "Chinese (Hong Kong)" "中文(香港字)" 100%
-ja_JP "Japanese" "日本語" 99%
+ja_JP "Japanese" "日本語" 100%
ko "Korean" "한국의" 35%
--- /dev/null
+//campaign:Xonotic 战役
+//"game","mapname","bots","skill","fraglimit","timelimit","mutator-sets","description","long description"
+//fraglimit: Score limit + lead limit, use "default" to leave the implicit value in effect, leave unset for no limit. eg: 50, 30+5, +10, 0+default
+//timelimit: Match duration in minutes, use "default" to leave the implicit value in effect, leave unset for no limit.
+"dm","boil","5","2","10",,,"死亡竞赛:怒火中烧","欢迎来到 Xonotic!\n我们从简出发,在《怒火中烧》地图对付 5 只简单的机器人。\nXonotic 主要是多人游戏, 不过在开始在线游戏之前, 练习对付机器人更好。"
+"tdm","stormkeep","5","2",,"5",,"团队死亡竞赛:风暴要塞","下一步我们在《风暴要塞》练习 3 对 3 的团队死亡竞赛。\n你需要在这 5 分钟内做到最好, 因为得分最高的团队获胜!"
+"mayhem","warfare","5","2",,"5",,"大乱斗:针锋相对","持续五分钟的大乱斗!\n在这里,你全副武装,带着所有武器加入游戏。武器对自己没有伤害;造成最多伤害、杀敌最多的玩家获胜!"
+"ctf","dance","9","3","3",,,"夺旗战:起舞","在夺旗战中,你要拿到对方的旗帜、把它带到你的基地。同时也得保护你自己的旗帜、不让对方拿走,才能成功夺取!\n夺取 3 次即获胜。"
+"dm","finalrage","6","3","15",,,"死亡竞赛:最终狂怒","另一场死亡竞赛,不过机器人的水平比之前要好。"
+"dom","atelier","11","4","50",,,"统治:工作坊","占领并保卫控制点、驱离进犯者,得分以获胜。"
+"kh","runningmanctf","5","5","500",,,"钥匙猎取:向前冲","每个团队都有一把钥匙。你要把它们都拿到手(杀死钥匙携带者,捡起掉落的钥匙),之后与团队内其他携带者会面。"
+"ctf","geoplanetary","13","5","5",,,"夺旗战:行星地带","一场很“拥挤”的夺旗战。\n开始在线游戏前,值得一练。"
+"dm","darkzone","4","5","15",,"g_instagib 1","【特别】死亡竞赛:暗黑地带","InstaGib(瞬杀)! 每个人都有一把武器——汽化者。\n瞄准你的敌人,一击必杀。(除非他获得如“额外生命”等加成)"
+"ctf","vorix","11","6","5",,"g_grappling_hook 1","【特别】夺旗战:Vorix","钩爪:按 e 键或‘鼠标按键 5’来使用。 勾住一面墙,感受强大的拉力。"
+//"rc","leave_em_behind","0","0","2400",,,"竞速:统统甩开","这是竞速,跑得越快越好!靠近墙壁或地面时,使用镭射枪(第一把武器)击打之可获得加速度。按住空格以连续加速跳跃(“兔兔蹦”)。\n夺得 24 秒以下的记录以通过这关。"
+"dm","solarium","8","6","15",,"g_nix 1","【特别】死亡竞赛:日光浴场","NIX(无物品 Xonotic): 每个人都有相同的武器, 不过每隔一段时间会随机更换。"
+"ft","erbium","5","6","5",,,"冰封战:废弃铒工厂","当你被杀死时,你被冻结。站在被冻结的队友身旁以复活他们。如果一个团队的玩家都被冻结,此轮结束。"
+"lms","opium","6","6","5",,,"笑到最后:麻木","在此游戏模式中,你有 5 条生命。 目标是顽强斗争、活到最后!"
+"tdm","afterslime","5","7","20",,"sv_gravity 200","【特别】团队死亡竞赛:史莱姆研究室","另一场团队死亡竞赛。\n不过,这里的重力低一些。"
+"ctf","go","5","7","5",,,"夺旗战:勇往直前","又一次在太空中的夺旗战,这次机器人的水平要稍微高一些。当心脚下,不要掉入虚空!"
+"tka","runningman","5","7","15",,,"团队驱离:向前冲","为团队争夺光球,拿起来,开始杀敌!\n提示:你可以向传送门内射弹,命中另一边的敌人。"
+//"nb","nexballarena","5","7","5",,,"Nex 球赛:Nex 球赛竞技场","这个有点像足球。把球踢进对方的球门里。率先得 5 分的团队获胜!"
+"kh","silentsiege","8","8","600",,,"钥匙猎取:静谧围城","再一场钥匙猎取!得 600 分获胜。"
+"ca","bromine","5","8","5",,,"组队竞技:废弃铒工厂","这是轮流制的团队竞争。武器对自己无伤害,但如果你死亡,你就需要等到下一轮才能再次加入游戏。竞争后仍有幸存玩家的队伍获胜!"
+"ctf","implosion","7","8","5",,"g_jetpack 1","【特别】夺旗战:聚爆实验场","喷气背包:在空中时跳跃(空格)以使用,享受飞行!"
+"as","techassault","7","8","30",,"g_campaign_forceteam 1","突击:科技突袭","你是进攻方,需要摧毁所有目标。摧毁一个目标后,会出现下一目标。"
+"tmayhem","trident","7","8",,"5",,"团队大乱斗:三叉戟","在这里,你全副武装,带着所有武器加入游戏。武器对己方团队没有伤害;造成最多伤害、杀敌最多的团队获胜!"
+//"cts","courtfun","0","0","6000",,,"CTS 竞速:殿堂欢乐","CTS 竞速 (Complete the Stage) 类似于普通竞速,不过只需要到达终点。你需要在 1 分钟内完成。"
+"ft","xoylent","5","8","5",,,"冰封战:Xoylent","当你被杀死时,你被冻结。站在被冻结的队友身旁以复活他们。如果一个团队的玩家都被冻结,此轮结束。"
+"ons","runningmanctf","7","8","1","10",,"猛攻:向前冲","猛攻:贴近已连接但还没有被占领的控制点以占领它;摧毁并占领所有敌方的控制点。最终摧毁敌方的发电核心以取胜!"
+"ctf","catharsis","9","9","5",,,"夺旗战:水晶地带","夺旗战,有到目前为止最难的机器人!5 次夺取以获胜。"
+"dm","stormkeep","5","9","15",,"g_new_toys 1; g_cloaked 1","【特别】死亡竞赛:风暴要塞","新玩意:这里会生成一些新武器,像地雷放置器,重型镭射突击大炮,和 T.A.G. 瞄准者。\n此外还有另一个修改:近乎隐形的玩家。"
+"ctf","space-elevator","7","10","3",,"g_instagib 1; g_grappling_hook 1","【特别】夺旗战:太空电梯","这次是瞬杀夺旗战。同时你会体验使用非手持抓钩!(按 e 键或‘鼠标按键 5’来使用)"
+"duel","fuse","1","10","10","5","g_forced_respawn 1","【特别】决斗:导火索","这次你只面对一个机器人——一场所谓的“决斗”。多人游戏非常流行决斗。"
+"ca","warfare","5","10","5",,"g_vampire 1; g_vampire_factor 0.75; g_balance_health_limit 400; g_ca_start_health 400; g_ca_start_armor 0","【特别】组队竞技:针锋相对","启用了吸血修改,造成伤害可以恢复血量!\n竞争后仍有幸存玩家的队伍获胜!"
+"dm","glowplant","5","11","20",,,"【最终】死亡竞赛:荧光反应堆","在这场最终竞赛,你要打败 5 只能力最强的机器人。你需要 20 次杀敌以完结这场战役!此后你就有能力和真人对抗!"
+"mayhem","stormkeep","9","11","300",,"g_weaponarena devastator; g_mayhem_selfdamage 1","【附加】大乱斗:风暴要塞","惊喜!\n欢迎来到火箭地狱!\n这里只有灭世火箭炮,可以对自己造成伤害——不要炸飞自己!玩得开心!"
+// Dr. Jaska: fix bot weapon priority and weapon balance before enabling full weapon arsenal for this one
+"mayhem","trident","6","1337",,"5","g_weaponarena \"blaster mortar crylink devastator \"","【终结】大乱斗:三叉戟","惊喜!\n欢迎来到这场灵魂出窍的大乱斗!\n没有多年的游戏经验和出色的战斗技巧,是无法在这一关取胜的。"
+
+// rc & cts disabled, since we hide these modes
+
+// note that currently any cvars changed in mutator-sets must be accessed in code using the cvar() function (or autocvar aliases to that), otherwise they will not work
+// see https://gitlab.com/xonotic/xonotic-data.pk3dir/issues/1976 for details
--- /dev/null
+//campaign:Xonotic 戰役
+//"game","mapname","bots","skill","fraglimit","timelimit","mutator-sets","description","long description"
+//fraglimit: Score limit + lead limit, use "default" to leave the implicit value in effect, leave unset for no limit. eg: 50, 30+5, +10, 0+default
+//timelimit: Match duration in minutes, use "default" to leave the implicit value in effect, leave unset for no limit.
+"dm","boil","5","2","10",,,"死亡競賽:怒火中燒","歡迎來到 Xonotic!\n我們從簡出發,在《怒火中燒》地圖對付 5 隻簡單的機器人。\nXonotic 主要是多人遊戲, 不過在開始在線遊戲之前, 練習對付機器人更好。"
+"tdm","stormkeep","5","2",,"5",,"團隊死亡競賽:風暴要塞","下一步我們在《風暴要塞》練習 3 對 3 的團隊死亡競賽。\n你需要在這 5 分鐘內做到最好, 因為得分最高的團隊獲勝!"
+"mayhem","warfare","5","2",,"5",,"大亂鬥:針鋒相對","持續五分鐘的大亂鬥!\n在這裏,你全副武裝,帶着所有武器加入遊戲。武器對自己沒有傷害;造成最多傷害、殺敵最多的玩家獲勝!"
+"ctf","dance","9","3","3",,,"奪旗戰:起舞","在奪旗戰中,你要拿到對方的旗幟、把它帶到你的基地。同時也得保護你自己的旗幟、不讓對方拿走,才能成功奪取!\n奪取 3 次即獲勝。"
+"dm","finalrage","6","3","15",,,"死亡競賽:最終狂怒","另一場死亡競賽,不過機器人的水平比之前要好。"
+"dom","atelier","11","4","50",,,"統治:工作坊","佔領並保衞控制點、驅離進犯者,得分以獲勝。"
+"kh","runningmanctf","5","5","500",,,"鑰匙獵取:向前衝","每個團隊都有一把鑰匙。你要把它們都拿到手(殺死鑰匙攜帶者,撿起掉落的鑰匙),之後與團隊內其他攜帶者會面。"
+"ctf","geoplanetary","13","5","5",,,"奪旗戰:行星地帶","一場很“擁擠”的奪旗戰。\n開始在線遊戲前,值得一練。"
+"dm","darkzone","4","5","15",,"g_instagib 1","【特別】死亡競賽:暗黑地帶","InstaGib(瞬殺)! 每個人都有一把武器——汽化者。\n瞄準你的敵人,一擊必殺。(除非他獲得如“額外生命”等加成)"
+"ctf","vorix","11","6","5",,"g_grappling_hook 1","【特別】奪旗戰:Vorix","鈎爪:按 e 鍵或‘鼠標按鍵 5’來使用。 勾住一面牆,感受強大的拉力。"
+//"rc","leave_em_behind","0","0","2400",,,"競速:統統甩開","這是競速,跑得越快越好!靠近牆壁或地面時,使用鐳射槍(第一把武器)擊打之可獲得加速度。按住空格以連續加速跳躍(“兔兔蹦”)。\n奪得 24 秒以下的記錄以通過這關。"
+"dm","solarium","8","6","15",,"g_nix 1","【特別】死亡競賽:日光浴場","NIX(無物品 Xonotic): 每個人都有相同的武器, 不過每隔一段時間會隨機更換。"
+"ft","erbium","5","6","5",,,"冰封戰:廢棄鉺工廠","當你被殺死時,你被凍結。站在被凍結的隊友身旁以復活他們。如果一個團隊的玩家都被凍結,此輪結束。"
+"lms","opium","6","6","5",,,"笑到最後:麻木","在此遊戲模式中,你有 5 條生命。 目標是頑強鬥爭、活到最後!"
+"tdm","afterslime","5","7","20",,"sv_gravity 200","【特別】團隊死亡競賽:史萊姆研究室","另一場團隊死亡競賽。\n不過,這裏的重力低一些。"
+"ctf","go","5","7","5",,,"奪旗戰:勇往直前","又一次在太空中的奪旗戰,這次機器人的水平要稍微高一些。當心腳下,不要掉入虛空!"
+"tka","runningman","5","7","15",,,"團隊驅離:向前衝","為團隊爭奪光球,拿起來,開始殺敵!\n提示:你可以向傳送門內射彈,命中另一邊的敵人。"
+//"nb","nexballarena","5","7","5",,,"Nex 球賽:Nex 球賽競技場","這個有點像足球。把球踢進對方的球門裏。率先得 5 分的團隊獲勝!"
+"kh","silentsiege","8","8","600",,,"鑰匙獵取:靜謐圍城","再一場鑰匙獵取!得 600 分獲勝。"
+"ca","bromine","5","8","5",,,"組隊競技:廢棄鉺工廠","這是輪流制的團隊競爭。武器對自己無傷害,但如果你死亡,你就需要等到下一輪才能再次加入遊戲。競爭後仍有倖存玩家的隊伍獲勝!"
+"ctf","implosion","7","8","5",,"g_jetpack 1","【特別】奪旗戰:聚爆實驗場","噴氣揹包:在空中時跳躍(空格)以使用,享受飛行!"
+"as","techassault","7","8","30",,"g_campaign_forceteam 1","突擊:科技突襲","你是進攻方,需要摧毀所有目標。摧毀一個目標後,會出現下一目標。"
+"tmayhem","trident","7","8",,"5",,"團隊大亂鬥:三叉戟","在這裏,你全副武裝,帶着所有武器加入遊戲。武器對己方團隊沒有傷害;造成最多傷害、殺敵最多的團隊獲勝!"
+//"cts","courtfun","0","0","6000",,,"CTS 競速:殿堂歡樂","CTS 競速 (Complete the Stage) 類似於普通競速,不過只需要到達終點。你需要在 1 分鐘內完成。"
+"ft","xoylent","5","8","5",,,"冰封戰:Xoylent","當你被殺死時,你被凍結。站在被凍結的隊友身旁以復活他們。如果一個團隊的玩家都被凍結,此輪結束。"
+"ons","runningmanctf","7","8","1","10",,"猛攻:向前衝","猛攻:貼近已連接但還沒有被佔領的控制點以佔領它;摧毀並佔領所有敵方的控制點。最終摧毀敵方的發電核心以取勝!"
+"ctf","catharsis","9","9","5",,,"奪旗戰:水晶地帶","奪旗戰,有到目前為止最難的機器人!5 次奪取以獲勝。"
+"dm","stormkeep","5","9","15",,"g_new_toys 1; g_cloaked 1","【特別】死亡競賽:風暴要塞","新玩意:這裏會生成一些新武器,像地雷放置器,重型鐳射突擊大炮,和 T.A.G. 瞄準者。\n此外還有另一個修改:近乎隱形的玩家。"
+"ctf","space-elevator","7","10","3",,"g_instagib 1; g_grappling_hook 1","【特別】奪旗戰:太空電梯","這次是瞬殺奪旗戰。同時你會體驗使用非手持抓鈎!(按 e 鍵或‘鼠標按鍵 5’來使用)"
+"duel","fuse","1","10","10","5","g_forced_respawn 1","【特別】決鬥:導火索","這次你只面對一個機器人——一場所謂的“決鬥”。多人遊戲非常流行決鬥。"
+"ca","warfare","5","10","5",,"g_vampire 1; g_vampire_factor 0.75; g_balance_health_limit 400; g_ca_start_health 400; g_ca_start_armor 0","【特別】組隊競技:針鋒相對","啓用了吸血修改,造成傷害可以恢復血量!\n競爭後仍有倖存玩家的隊伍獲勝!"
+"dm","glowplant","5","11","20",,,"【最終】死亡競賽:熒光反應堆","在這場最終競賽,你要打敗 5 隻能力最強的機器人。你需要 20 次殺敵以完結這場戰役!此後你就有能力和真人對抗!"
+"mayhem","stormkeep","9","11","300",,"g_weaponarena devastator; g_mayhem_selfdamage 1","【附加】大亂鬥:風暴要塞","驚喜!\n歡迎來到火箭地獄!\n這裏只有滅世火箭炮,可以對自己造成傷害——不要炸飛自己!玩得開心!"
+// Dr. Jaska: fix bot weapon priority and weapon balance before enabling full weapon arsenal for this one
+"mayhem","trident","6","1337",,"5","g_weaponarena \"blaster mortar crylink devastator \"","【終結】大亂鬥:三叉戟","驚喜!\n歡迎來到這場靈魂出竅的大亂鬥!\n沒有多年的遊戲經驗和出色的戰鬥技巧,是無法在這一關取勝的。"
+
+// rc & cts disabled, since we hide these modes
+
+// note that currently any cvars changed in mutator-sets must be accessed in code using the cvar() function (or autocvar aliases to that), otherwise they will not work
+// see https://gitlab.com/xonotic/xonotic-data.pk3dir/issues/1976 for details
--- /dev/null
+//campaign:Xonotic 戰役
+//"game","mapname","bots","skill","fraglimit","timelimit","mutator-sets","description","long description"
+//fraglimit: Score limit + lead limit, use "default" to leave the implicit value in effect, leave unset for no limit. eg: 50, 30+5, +10, 0+default
+//timelimit: Match duration in minutes, use "default" to leave the implicit value in effect, leave unset for no limit.
+"dm","boil","5","2","10",,,"死亡競賽:怒火中燒","歡迎來到 Xonotic!\n我們從簡出發,在《怒火中燒》地圖對付 5 隻簡單的機器人。\nXonotic 主要是多人遊戲, 不過在開始線上遊戲之前, 練習對付機器人更好。"
+"tdm","stormkeep","5","2",,"5",,"團隊死亡競賽:風暴要塞","下一步我們在《風暴要塞》練習 3 對 3 的團隊死亡競賽。\n你需要在這 5 分鐘內做到最好, 因為得分最高的團隊獲勝!"
+"mayhem","warfare","5","2",,"5",,"大亂鬥:針鋒相對","持續五分鐘的大亂鬥!\n在這裡,你全副武裝,帶著所有武器加入遊戲。武器對自己沒有傷害;造成最多傷害、殺敵最多的玩家獲勝!"
+"ctf","dance","9","3","3",,,"奪旗戰:起舞","在奪旗戰中,你要拿到對方的旗子、把它帶到你的基地。同時也得保護你自己的旗子、不讓對方拿走,才能成功奪取!\n奪取 3 次即獲勝。"
+"dm","finalrage","6","3","15",,,"死亡競賽:最終狂怒","另一場死亡競賽,不過機器人的水平比之前要好。"
+"dom","atelier","11","4","50",,,"統治:工作坊","佔領並保衛控制點、驅離進犯者,得分以獲勝。"
+"kh","runningmanctf","5","5","500",,,"鑰匙獵取:向前衝","每個團隊都有一把鑰匙。你要把它們都拿到手(殺死鑰匙攜帶者,撿起掉落的鑰匙),之後與團隊內其他攜帶者會面。"
+"ctf","geoplanetary","13","5","5",,,"奪旗戰:行星地帶","一場很「擁擠」的奪旗戰。\n開始線上遊戲前,值得一練。"
+"dm","darkzone","4","5","15",,"g_instagib 1","【特別】死亡競賽:暗黑地帶","InstaGib(瞬殺)! 每個人都有一把武器——汽化者。\n瞄準你的敵人,一擊必殺。(除非他獲得如「額外生命」等加成)"
+"ctf","vorix","11","6","5",,"g_grappling_hook 1","【特別】奪旗戰:Vorix","鉤爪:按 e 鍵或‘滑鼠按鍵 5’來使用。 勾住一面牆,感受強大的拉力。"
+//"rc","leave_em_behind","0","0","2400",,,"競速:統統甩開","這是競速,跑得越快越好!靠近牆壁或地面時,使用鐳射槍(第一把武器)擊打之可獲得加速度。按住空格以連續加速跳躍(「兔兔蹦」)。\n奪得 24 秒以下的記錄以透過這關。"
+"dm","solarium","8","6","15",,"g_nix 1","【特別】死亡競賽:日光浴場","NIX(無物品 Xonotic): 每個人都有相同的武器, 不過每隔一段時間會隨機更換。"
+"ft","erbium","5","6","5",,,"冰封戰:廢棄鉺工廠","當你被殺死時,你被凍結。站在被凍結的隊友身旁以復活他們。如果一個團隊的玩家都被凍結,此輪結束。"
+"lms","opium","6","6","5",,,"適者生存:麻木","在此遊戲類型中,你有 5 條生命。 目標是頑強鬥爭、活到最後!"
+"tdm","afterslime","5","7","20",,"sv_gravity 200","【特別】團隊死亡競賽:史萊姆研究室","另一場團隊死亡競賽。\n不過,這裡的重力低一些。"
+"ctf","go","5","7","5",,,"奪旗戰:勇往直前","又一次在太空中的奪旗戰,這次機器人的水平要稍微高一些。當心腳下,不要掉入虛空!"
+"tka","runningman","5","7","15",,,"團隊驅離:向前衝","為團隊爭奪光球,拿起來,開始殺敵!\n提示:你可以向傳送門內射彈,命中另一邊的敵人。"
+//"nb","nexballarena","5","7","5",,,"Nex 球賽:Nex 球賽競技場","這個有點像足球。把球踢進對方的球門裡。率先得 5 分的團隊獲勝!"
+"kh","silentsiege","8","8","600",,,"鑰匙獵取:靜謐圍城","再一場鑰匙獵取!得 600 分獲勝。"
+"ca","bromine","5","8","5",,,"組隊競技:廢棄鉺工廠","這是輪流制的團隊競爭。武器對自己無傷害,但如果你死亡,你就需要等到下一輪才能再次加入遊戲。競爭後仍有倖存玩家的隊伍獲勝!"
+"ctf","implosion","7","8","5",,"g_jetpack 1","【特別】奪旗戰:聚爆實驗場","噴氣揹包:在空中時跳躍(空格)以使用,享受飛行!"
+"as","techassault","7","8","30",,"g_campaign_forceteam 1","突擊:科技突襲","你是進攻方,需要摧毀所有目標。摧毀一個目標後,會出現下一目標。"
+"tmayhem","trident","7","8",,"5",,"團隊大亂鬥:三叉戟","在這裡,你全副武裝,帶著所有武器加入遊戲。武器對己方團隊沒有傷害;造成最多傷害、殺敵最多的團隊獲勝!"
+//"cts","courtfun","0","0","6000",,,"CTS 競速:殿堂歡樂","CTS 競速 (Complete the Stage) 類似於普通競速,不過只需要到達終點。你需要在 1 分鐘內完成。"
+"ft","xoylent","5","8","5",,,"冰封戰:Xoylent","當你被殺死時,你被凍結。站在被凍結的隊友身旁以復活他們。如果一個團隊的玩家都被凍結,此輪結束。"
+"ons","runningmanctf","7","8","1","10",,"猛攻:向前衝","猛攻:貼近已連線但還沒有被佔領的控制點以佔領它;摧毀並佔領所有敵方的控制點。最終摧毀敵方的發電核心以取勝!"
+"ctf","catharsis","9","9","5",,,"奪旗戰:水晶地帶","奪旗戰,有到目前為止最難的機器人!5 次奪取以獲勝。"
+"dm","stormkeep","5","9","15",,"g_new_toys 1; g_cloaked 1","【特別】死亡競賽:風暴要塞","新玩意:這裡會生成一些新武器,像地雷放置器,重型鐳射突擊大炮,和 T.A.G. 瞄準者。\n此外還有另一個修改:近乎隱形的玩家。"
+"ctf","space-elevator","7","10","3",,"g_instagib 1; g_grappling_hook 1","【特別】奪旗戰:太空電梯","這次是瞬殺奪旗戰。同時你會體驗使用非手持抓鉤!(按 e 鍵或‘滑鼠按鍵 5’來使用)"
+"duel","fuse","1","10","10","5","g_forced_respawn 1","【特別】決鬥:導火索","這次你只面對一個機器人——一場所謂的「決鬥」。多人遊戲非常流行決鬥。"
+"ca","warfare","5","10","5",,"g_vampire 1; g_vampire_factor 0.75; g_balance_health_limit 400; g_ca_start_health 400; g_ca_start_armor 0","【特別】組隊競技:針鋒相對","啟用了吸血修改,造成傷害可以恢復血量!\n競爭後仍有倖存玩家的隊伍獲勝!"
+"dm","glowplant","5","11","20",,,"【最終】死亡競賽:熒光反應堆","在這場最終競賽,你要打敗 5 隻能力最強的機器人。你需要 20 次殺敵以完結這場戰役!此後你就有能力和真人對抗!"
+"mayhem","stormkeep","9","11","300",,"g_weaponarena devastator; g_mayhem_selfdamage 1","【附加】大亂鬥:風暴要塞","驚喜!\n歡迎來到火箭地獄!\n這裡只有滅世火箭炮,可以對自己造成傷害——不要炸飛自己!玩得開心!"
+// Dr. Jaska: fix bot weapon priority and weapon balance before enabling full weapon arsenal for this one
+"mayhem","trident","6","1337",,"5","g_weaponarena \"blaster mortar crylink devastator \"","【終結】大亂鬥:三叉戟","驚喜!\n歡迎來到這場靈魂出竅的大亂鬥!\n沒有多年的遊戲經驗和出色的戰鬥技巧,是無法在這一關取勝的。"
+
+// rc & cts disabled, since we hide these modes
+
+// note that currently any cvars changed in mutator-sets must be accessed in code using the cvar() function (or autocvar aliases to that), otherwise they will not work
+// see https://gitlab.com/xonotic/xonotic-data.pk3dir/issues/1976 for details
if (mode == 1)
label = "bckills"; // first case in the switch
+#define SCO_LABEL(strlabel, label, padding, help) \
+ case label: \
+ if (!mode) \
+ return CTX(strlabel); \
+ LOG_HELP("^3", label, padding, "^7", help);
+
switch(label)
{
- case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
- case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
- case "caps": if (!mode) return CTX(_("SCO^caps")); else LOG_HELP(strcat("^3", "caps", " ^7", _("How often a flag (CTF) or a key (KeyHunt) was captured")));
- case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
- case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
- case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
- case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
- case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
- case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
- case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
- case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
- case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
- case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
- case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
- case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
- case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
- case "hunts": if (!mode) return CTX(_("SCO^hunts")); else LOG_HELP(strcat("^3", "hunts", " ^7", _("Number of hunts (Survival)")));
- case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
- case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
- case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
- case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
- case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
- case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
- case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
- case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
- case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
- case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
- case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
- case "pickups": if (!mode) return CTX(_("SCO^pickups")); else LOG_HELP(strcat("^3", "pickups", " ^7", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up")));
- case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
- case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
- case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
- case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
- case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
- case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
- case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
- case "rounds_pl": if (!mode) return CTX(_("SCO^rounds played"));else LOG_HELP(strcat("^3", "rounds_pl", " ^7", _("Number of rounds played")));
- case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
- case "avgspeed": if (!mode) return CTX(_("SCO^average speed"));else LOG_HELP(strcat("^3", "avgspeed", " ^7", _("Average speed (CTS)")));
- case "topspeed": if (!mode) return CTX(_("SCO^top speed")); else LOG_HELP(strcat("^3", "topspeed", " ^7", _("Top speed (CTS)")));
- case "startspeed": if (!mode) return CTX(_("SCO^start speed")); else LOG_HELP(strcat("^3", "startspeed", " ^7", _("Start speed (CTS)")));
- case "strafe": if (!mode) return CTX(_("SCO^strafe")); else LOG_HELP(strcat("^3", "strafe", " ^7", _("Strafe efficiency (CTS)")));
- case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
- case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
- case "survivals": if (!mode) return CTX(_("SCO^survivals")); else LOG_HELP(strcat("^3", "survivals", " ^7", _("Number of survivals")));
- case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
- case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
- case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
- case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
- default: return label;
+ SCO_LABEL(_("SCO^bckills"), "bckills", " ", _("Number of ball carrier kills"));
+ SCO_LABEL(_("SCO^bctime"), "bctime", " ", _("Total amount of time holding the ball in Keepaway"));
+ SCO_LABEL(_("SCO^caps"), "caps", " ", _("How often a flag (CTF) or a key (KeyHunt) was captured"));
+ SCO_LABEL(_("SCO^captime"), "captime", " ", _("Time of fastest capture (CTF)"));
+ SCO_LABEL(_("SCO^deaths"), "deaths", " ", _("Number of deaths"));
+ SCO_LABEL(_("SCO^destroyed"), "destroyed", " ", _("Number of keys destroyed by pushing them into void"));
+ SCO_LABEL(_("SCO^damage"), "dmg", " ", _("The total damage done"));
+ SCO_LABEL(_("SCO^dmgtaken"), "dmgtaken", " ", _("The total damage taken"));
+ SCO_LABEL(_("SCO^drops"), "drops", " ", _("Number of flag drops"));
+ SCO_LABEL(_("SCO^elo"), "elo", " ", _("Player ELO"));
+ SCO_LABEL(_("SCO^fastest"), "fastest", " ", _("Time of fastest lap (Race/CTS)"));
+ SCO_LABEL(_("SCO^faults"), "faults", " ", _("Number of faults committed"));
+ SCO_LABEL(_("SCO^fckills"), "fckills", " ", _("Number of flag carrier kills"));
+ SCO_LABEL(_("SCO^fps"), "fps", " ", _("FPS"));
+ SCO_LABEL(_("SCO^frags"), "frags", " ", _("Number of kills minus suicides"));
+ SCO_LABEL(_("SCO^goals"), "goals", " ", _("Number of goals scored"));
+ SCO_LABEL(_("SCO^hunts"), "hunts", " ", _("Number of hunts (Survival)"));
+ SCO_LABEL(_("SCO^kckills"), "kckills", " ", _("Number of keys carrier kills"));
+ SCO_LABEL(_("SCO^k/d"), "kd", " ", _("The kill-death ratio"));
+ SCO_LABEL(_("SCO^kdr"), "kdr", " ", _("The kill-death ratio"));
+ SCO_LABEL(_("SCO^kdratio"), "kdratio", " ", _("The kill-death ratio"));
+ SCO_LABEL(_("SCO^kills"), "kills", " ", _("Number of kills"));
+ SCO_LABEL(_("SCO^laps"), "laps", " ", _("Number of laps finished (Race/CTS)"));
+ SCO_LABEL(_("SCO^lives"), "lives", " ", _("Number of lives (LMS)"));
+ SCO_LABEL(_("SCO^losses"), "losses", " ", _("Number of times a key was lost"));
+ SCO_LABEL(_("SCO^name"), "name", " ", _("Player name"));
+ SCO_LABEL(_("SCO^nick"), "nick", " ", _("Player name"));
+ SCO_LABEL(_("SCO^objectives"), "objectives", " ", _("Number of objectives destroyed"));
+ SCO_LABEL(_("SCO^pickups"), "pickups", " ", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up"));
+ SCO_LABEL(_("SCO^ping"), "ping", " ", _("Ping time"));
+ SCO_LABEL(_("SCO^pl"), "pl", " ", _("Packet loss"));
+ SCO_LABEL(_("SCO^pushes"), "pushes", " ", _("Number of players pushed into void"));
+ SCO_LABEL(_("SCO^rank"), "rank", " ", _("Player rank"));
+ SCO_LABEL(_("SCO^returns"), "returns", " ", _("Number of flag returns"));
+ SCO_LABEL(_("SCO^revivals"), "revivals", " ", _("Number of revivals"));
+ SCO_LABEL(_("SCO^rounds won"), "rounds", " ", _("Number of rounds won"));
+ SCO_LABEL(_("SCO^rounds played"), "rounds_pl", " ", _("Number of rounds played"));
+ SCO_LABEL(_("SCO^score"), "score", " ", _("Total score"));
+ SCO_LABEL(_("SCO^average speed"), "avgspeed", " ", _("Average speed (CTS)"));
+ SCO_LABEL(_("SCO^top speed"), "topspeed", " ", _("Top speed (CTS)"));
+ SCO_LABEL(_("SCO^start speed"), "startspeed", " ", _("Start speed (CTS)"));
+ SCO_LABEL(_("SCO^strafe"), "strafe", " ", _("Strafe efficiency (CTS)"));
+ SCO_LABEL(_("SCO^suicides"), "suicides", " ", _("Number of suicides"));
+ SCO_LABEL(_("SCO^sum"), "sum", " ", _("Number of kills minus deaths"));
+ SCO_LABEL(_("SCO^survivals"), "survivals", " ", _("Number of survivals"));
+ SCO_LABEL(_("SCO^takes"), "takes", " ", _("Number of domination points taken (Domination)"));
+ SCO_LABEL(_("SCO^teamkills"), "teamkills", " ", _("Number of teamkills"));
+ SCO_LABEL(_("SCO^ticks"), "ticks", " ", _("Number of ticks (Domination)"));
+ SCO_LABEL(_("SCO^time"), "time", " ", _("Total time raced (Race/CTS)"));
}
return label;
+#undef SCO_LABEL
}
bool scoreboard_ui_disabling;
// e.g. -teams,rc,cts,lms/kills ?+rc/kills
#define SCOREBOARD_DEFAULT_COLUMNS \
"ping pl fps name |" \
-" -teams,rc,cts,inv,lms/kills +ft,tdm,tmayhem/kills ?+rc,inv/kills" \
-" -teams,lms/deaths +ft,tdm,tmayhem/deaths" \
+" -teams,rc,cts,surv,inv,lms/kills +ft,tdm,tmayhem/kills ?+rc,inv/kills" \
+" -teams,surv,lms/deaths +ft,tdm,tmayhem/deaths" \
" +tdm/sum" \
-" -teams,lms,rc,cts,inv,ka/suicides +ft,tdm,tmayhem/suicides ?+rc,inv/suicides" \
-" -cts,dm,tdm,ka,ft,mayhem,tmayhem/frags" /* tdm already has this in "score" */ \
+" -teams,lms,rc,cts,surv,inv,ka/suicides +ft,tdm,tmayhem/suicides ?+rc,inv/suicides" \
+" -cts,dm,tdm,surv,ka,ft,mayhem,tmayhem/frags" /* tdm already has this in "score" */ \
" +tdm,ft,dom,ons,as,tmayhem/teamkills"\
-" -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
+" -rc,cts,surv,nb/dmg -rc,cts,surv,nb/dmgtaken" \
+" +surv/survivals +surv/hunts" \
" +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
" +lms/lives +lms/rank" \
" +kh/kckills +kh/losses +kh/caps" \
}
else if(!teamplay)
{
- int f = entcs_GetClientColors(pl.sv_entnum);
+ int f;
+ // NOTE: always adding 1024 allows saving the colormap 0 as a value != 0
+ if (playerslots[pl.sv_entnum].colormap >= 1024)
+ f = playerslots[pl.sv_entnum].colormap - 1024; // override server-side player colors
+ else
+ f = entcs_GetClientColors(pl.sv_entnum);
+
{
sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
#include <lib/csqcmodel/common.qh>
#include <lib/warpzone/common.qh>
-bool autocvar_cl_ghost_items_vehicle = true;
.vector item_glowmod;
-.bool item_simple; // probably not really needed, but better safe than sorry
+.int item_simple;
.float alpha;
.bool pushable;
-void Item_SetAlpha(entity this)
+.float anim_start_time; // reusing for bob waveform synchronisation
+.vector angles_held; // reusing for (re)storing original angles
+.float wait, delay, pointtime; // reusing for despawn effects
+
+HashMap ENT_CLIENT_ITEM_simple;
+STATIC_INIT(ENT_CLIENT_ITEM_simple)
+{
+ HM_NEW(ENT_CLIENT_ITEM_simple);
+}
+SHUTDOWN(ENT_CLIENT_ITEM_simple)
{
- bool veh_hud = (hud && autocvar_cl_ghost_items_vehicle);
+ HM_DELETE(ENT_CLIENT_ITEM_simple);
+}
- if(!veh_hud && (this.ItemStatus & ITS_AVAILABLE))
+void ItemSetModel(entity this, bool wantsimple)
+{
+ if(wantsimple)
{
- this.alpha = 1;
- this.colormod = '1 1 1';
- this.glowmod = this.item_glowmod;
+ string _fn2 = substring(this.mdl, 0 , strlen(this.mdl) -4);
+ #define extensions(x) \
+ x(iqm) \
+ x(dpm) \
+ x(md3) \
+ x(mdl) \
+ /**/
+ #define tryext(ext) { \
+ string s = strcat(_fn2, autocvar_cl_simpleitems_postfix, "." #ext); \
+ string cached = HM_gets(ENT_CLIENT_ITEM_simple, s); \
+ if (cached == "") { \
+ HM_sets(ENT_CLIENT_ITEM_simple, s, cached = fexists(s) ? "1" : "0"); \
+ } \
+ if (cached != "0") { \
+ this.model = s; \
+ this.item_simple = 1; \
+ break; \
+ } \
+ }
+ do {
+ extensions(tryext);
+ this.model = this.mdl; // fall back to 3d model
+ this.item_simple = -1; // don't retry every frame
+ LOG_TRACEF("Simple item requested for %s but no model exists for it", this.mdl);
+ } while (0);
+ #undef tryext
+ #undef extensions
}
else
{
- this.alpha = autocvar_cl_ghost_items;
- this.colormod = this.glowmod = autocvar_cl_ghost_items_color;
+ this.model = this.mdl;
+ this.item_simple = 0;
}
- if((!veh_hud) && (this.ItemStatus & ITS_STAYWEP))
- {
- this.colormod = this.glowmod = autocvar_cl_weapon_stay_color;
- this.alpha = autocvar_cl_weapon_stay_alpha;
- }
-
- this.drawmask = ((this.alpha <= 0) ? 0 : MASK_NORMAL);
+ // this.model is an engine string so it doesn't need to be zoned and can't be unzoned
+ if(this.model == "")
+ LOG_WARNF("this.model is unset for item %s", this.classname);
+ precache_model(this.model);
+ _setmodel(this, this.model);
+ setsize(this, '-16 -16 0', '16 16 48');
+ // bones_was_here TODO: network proper box size for sv_legacy_bbox_expand 0
}
void ItemDraw(entity this)
{
- if(this.gravity)
- {
- Movetype_Physics_MatchServer(this, false);
- if(IS_ONGROUND(this))
- { // For some reason avelocity gets set to '0 0 0' here ...
- this.oldorigin = this.origin;
- this.gravity = 0;
-
- if(autocvar_cl_animate_items)
- { // ... so reset it if animations are requested.
- if(this.ItemStatus & ITS_ANIMATE1)
- this.avelocity = '0 180 0';
+ bool wantsimple = autocvar_cl_simple_items && this.ItemStatus & ITS_ALLOWSI;
+ if(wantsimple != this.item_simple && this.item_simple != -1)
+ ItemSetModel(this, wantsimple);
- if(this.ItemStatus & ITS_ANIMATE2)
- this.avelocity = '0 -90 0';
- }
+ // no bobbing applied to simple items, for consistency's sake (no visual difference between ammo and weapons)
+ bool animate = autocvar_cl_items_animate & 1 && this.item_simple <= 0 && (this.ItemStatus & ITS_ANIMATE1 || this.ItemStatus & ITS_ANIMATE2);
- // delay is for blocking item's position for a while;
- // it's a workaround for dropped weapons that receive the position
- // another time right after they spawn overriding animation position
- this.onground_time = time + 0.5;
- }
+ // rotation must be set before running physics
+ if(!animate)
+ {
+ this.avelocity_y = 0;
+ this.angles = this.angles_held; // restore angles sent from server
}
- else if (autocvar_cl_animate_items && !this.item_simple) // no bobbing applied to simple items, for consistency's sake (no visual difference between ammo and weapons)
+ else if(!this.avelocity_y) // unset by MOVETYPE_TOSS or animation was disabled previously
{
if(this.ItemStatus & ITS_ANIMATE1)
- {
- this.angles += this.avelocity * frametime;
- float fade_in = bound(0, time - this.onground_time, 1);
- setorigin(this, this.oldorigin + fade_in * ('0 0 10' + '0 0 8' * sin((time - this.onground_time) * 2)));
- }
+ this.avelocity_y = 180;
+ else if(this.ItemStatus & ITS_ANIMATE2)
+ this.avelocity_y = -90;
+ }
- if(this.ItemStatus & ITS_ANIMATE2)
+ // CSQC physics OR bobbing (both would look weird)
+ float bobheight = 0; // reset bob offset if animations are disabled
+ if(this.move_movetype && (!IS_ONGROUND(this) || this.velocity != '0 0 0'))
+ {
+ // this isn't equivalent to player prediction but allows smooth motion with very low ISF_LOCATION rate
+ // which requires running this even if the item is just outside visible range (it could be moving into range)
+ if(animate)
+ bobheight = this.origin_z - this.oldorigin_z;
+ Movetype_Physics_NoMatchTicrate(this, frametime, true);
+ this.oldorigin = this.origin; // update real (SVQC equivalent) origin
+ if(animate)
{
- this.angles += this.avelocity * frametime;
- float fade_in = bound(0, time - this.onground_time, 1);
- setorigin(this, this.oldorigin + fade_in * ('0 0 8' + '0 0 4' * sin((time - this.onground_time) * 3)));
+ if(bobheight)
+ {
+ this.anim_start_time += frametime; // bobbing is paused this frame
+ this.oldorigin_z -= bobheight; // restore bob offset (CSQC physics uses the offset bbox)
+ }
+ else
+ {
+ this.anim_start_time = time; // starting our bob animation from NOW
+ if(this.ItemStatus & ITS_ANIMATE1)
+ bobheight = 10; // height of wave at 0 time
+ else if(this.ItemStatus & ITS_ANIMATE2)
+ bobheight = 8; // height of wave at 0 time
+ }
}
}
+ else if(animate)
+ {
+ this.angles += this.avelocity * frametime; // MOVETYPE_TOSS does this while it's moving
- Item_SetAlpha(this);
-}
+ if(this.ItemStatus & ITS_ANIMATE1)
+ bobheight = 10 + 8 * sin((time - this.anim_start_time) * 2);
+ else if(this.ItemStatus & ITS_ANIMATE2)
+ bobheight = 8 + 4 * sin((time - this.anim_start_time) * 3);
+ }
-void Item_PreDraw(entity this)
-{
- if(warpzone_warpzones_exist)
+ // apply new bob offset
+ if (bobheight != this.origin_z - this.oldorigin_z)
{
- setpredraw(this, func_null); // no need to keep running this
- return;
+ this.origin_z = this.oldorigin_z + bobheight;
+ this.mins_z = 0 - bobheight; // don't want the absmin and absmax to bob
+ this.maxs_z = 48 - bobheight;
+ // bones_was_here TODO: network proper box size for sv_legacy_bbox_expand 0
}
- float alph;
- vector org = getpropertyvec(VF_ORIGIN);
- //if(!checkpvs(org, this)) // this makes sense as long as we don't support recursive warpzones
- //alph = 0; // this shouldn't be needed, since items behind walls are culled anyway
- if(this.fade_start)
+
+ // set alpha based on distance
+ this.alpha = 1;
+ this.drawmask = 0;
+ if(this.fade_end && !warpzone_warpzones_exist)
{
+ vector org = getpropertyvec(VF_ORIGIN);
if(vdist(org - this.origin, >, this.fade_end))
- alph = 0; // save on some processing
- else if(vdist(org - this.origin, <, this.fade_start))
- alph = 1; // more processing saved
+ this.alpha = 0; // save on some processing
+ else if(autocvar_cl_items_fadedist > 0)
+ {
+ this.fade_start = max(500, this.fade_end - autocvar_cl_items_fadedist);
+ if(vdist(org - this.origin, >, this.fade_start))
+ this.alpha = bound(0, (this.fade_end - vlen(org - this.origin - 0.5 * (this.mins + this.maxs))) / (this.fade_end - this.fade_start), 1);
+ }
+ }
+
+ if(!this.alpha)
+ return;
+
+ // modify alpha based on availability and vehicle hud
+ if(this.ItemStatus & ITS_AVAILABLE)
+ {
+ if(hud) // apparently this means we're in a vehicle lol
+ {
+ this.alpha *= autocvar_cl_items_vehicle_alpha;
+ this.colormod = this.glowmod = autocvar_cl_items_vehicle_color;
+ }
+ else if(this.ItemStatus & ITS_STAYWEP)
+ {
+ this.alpha *= autocvar_cl_weapon_stay_alpha;
+ this.colormod = this.glowmod = autocvar_cl_weapon_stay_color;
+ }
else
- alph = bound(0, (this.fade_end - vlen(org - this.origin - 0.5 * (this.mins + this.maxs))) / (this.fade_end - this.fade_start), 1);
+ {
+ this.colormod = '1 1 1';
+ this.glowmod = this.item_glowmod;
+ }
}
else
- alph = 1;
- //printf("%v <-> %v\n", view_origin, this.origin + 0.5 * (this.mins + this.maxs));
- if(!hud && (this.ItemStatus & ITS_AVAILABLE))
- this.alpha = alph;
- if(alph <= 0)
- this.drawmask = 0;
- //else
- //this.drawmask = MASK_NORMAL; // reset by the setalpha function
+ {
+ this.alpha *= autocvar_cl_ghost_items;
+ this.colormod = this.glowmod = autocvar_cl_ghost_items_color;
+ }
+
+ if(!this.alpha)
+ return;
+
+ // loot item despawn effects
+ if(this.ItemStatus & ITS_EXPIRING)
+ {
+ if(!this.wait) // when receiving the first message with ITS_EXPIRING set
+ {
+ this.wait = time + IT_DESPAWNFX_TIME; // it will despawn then
+ this.delay = 0.25;
+ }
+
+ if(autocvar_cl_items_animate & 2)
+ this.alpha *= (this.wait - time) / IT_DESPAWNFX_TIME;
+
+ if(autocvar_cl_items_animate & 4 && time >= this.pointtime)
+ {
+ pointparticles(EFFECT_ITEM_DESPAWN, this.origin + '0 0 16', '0 0 0', 1);
+ if (this.delay > 0.0625)
+ this.delay *= 0.5;
+ this.pointtime = time + this.delay;
+ }
+ }
+
+ this.drawmask = this.alpha <= 0 ? 0 : MASK_NORMAL;
}
void ItemRemove(entity this)
{
+ if(this.alpha)
+ if(!this.wait || time < this.wait - ticrate) // despawning loot items have their own effects
+ pointparticles(EFFECT_ITEM_PICKUP, (this.absmin + this.absmax) * 0.5, '0 0 0', 1);
strfree(this.mdl);
}
-HashMap ENT_CLIENT_ITEM_simple;
-STATIC_INIT(ENT_CLIENT_ITEM_simple)
-{
- HM_NEW(ENT_CLIENT_ITEM_simple);
-}
-SHUTDOWN(ENT_CLIENT_ITEM_simple)
-{
- HM_DELETE(ENT_CLIENT_ITEM_simple);
-}
-
NET_HANDLE(ENT_CLIENT_ITEM, bool isnew)
{
int sf = ReadByte();
if(sf & ISF_LOCATION)
{
- vector org = ReadVector();
- setorigin(this, org);
- this.oldorigin = org;
+ float bobheight = this.origin_z - this.oldorigin_z;
+ this.origin = this.oldorigin = ReadVector();
+ this.origin_z += bobheight; // restore animation offset (SVQC physics is unaware of CSQC bbox offset)
+ setorigin(this, this.origin); // link
}
if(sf & ISF_ANGLES)
{
- this.angles = ReadAngleVector();
+ this.angles = this.angles_held = ReadAngleVector();
}
+ /* bones_was_here TODO: network proper box size for sv_legacy_bbox_expand 0
if(sf & ISF_SIZE)
{
setsize(this, '-16 -16 0', '16 16 48');
}
+ */
if(sf & ISF_STATUS) // need to read/write status first so model can handle simple, fb etc.
{
+ int prevItemStatus = this.ItemStatus;
this.ItemStatus = ReadByte();
- Item_SetAlpha(this);
-
if(this.ItemStatus & ITS_ALLOWFB)
this.effects |= EF_FULLBRIGHT;
else
this.effects &= ~EF_FULLBRIGHT;
- if(this.ItemStatus & ITS_GLOW)
+ if(this.ItemStatus & ITS_AVAILABLE)
{
- if(this.ItemStatus & ITS_AVAILABLE)
+ if(this.solid != SOLID_TRIGGER)
+ {
+ this.solid = SOLID_TRIGGER;
+ setorigin(this, this.origin); // link it to the area grid
+ }
+
+ if(this.ItemStatus & ITS_GLOW)
this.effects |= (EF_ADDITIVE | EF_FULLBRIGHT);
- else
+ if(!(prevItemStatus & ITS_AVAILABLE) && this.alpha && !isnew)
+ pointparticles(EFFECT_ITEM_RESPAWN, (this.absmin + this.absmax) * 0.5, '0 0 0', 1);
+ }
+ else
+ {
+ if(this.solid != SOLID_NOT)
+ {
+ this.solid = SOLID_NOT;
+ setorigin(this, this.origin); // optimisation: unlink it from the area grid
+ }
+
+ if(this.ItemStatus & ITS_GLOW)
this.effects &= ~(EF_ADDITIVE | EF_FULLBRIGHT);
+ if(prevItemStatus & ITS_AVAILABLE && this.alpha)
+ pointparticles(EFFECT_ITEM_PICKUP, (this.absmin + this.absmax) * 0.5, '0 0 0', 1);
}
}
if(sf & ISF_MODEL)
{
- this.drawmask = MASK_NORMAL;
- set_movetype(this, MOVETYPE_TOSS);
- if (isnew) IL_PUSH(g_drawables, this);
- this.draw = ItemDraw;
- this.solid = SOLID_TRIGGER;
- //this.flags |= FL_ITEM;
-
- this.fade_end = ReadShort();
- this.fade_start = ReadShort();
- if(!warpzone_warpzones_exist && this.fade_start && !autocvar_cl_items_nofade)
- setpredraw(this, Item_PreDraw);
-
- strfree(this.mdl);
-
- string _fn = ReadString();
- this.item_simple = false; // reset it!
-
- if(autocvar_cl_simple_items && (this.ItemStatus & ITS_ALLOWSI))
+ if(isnew)
{
- string _fn2 = substring(_fn, 0 , strlen(_fn) -4);
- this.item_simple = true;
-
- #define extensions(x) \
- x(md3) \
- x(dpm) \
- x(iqm) \
- x(mdl) \
- /**/
- #define tryext(ext) { \
- string s = strcat(_fn2, autocvar_cl_simpleitems_postfix, "." #ext); \
- string cached = HM_gets(ENT_CLIENT_ITEM_simple, s); \
- if (cached == "") { \
- HM_sets(ENT_CLIENT_ITEM_simple, s, cached = fexists(s) ? "1" : "0"); \
- } \
- if (cached != "0") { \
- strcpy(this.mdl, s); \
- break; \
- } \
- }
- do {
- extensions(tryext);
- this.item_simple = false;
- LOG_TRACEF("Simple item requested for %s but no model exists for it", _fn);
- } while (0);
- #undef tryext
- #undef extensions
+ IL_PUSH(g_drawables, this);
+ this.draw = ItemDraw;
+ this.flags |= FL_ITEM;
}
- if(!this.item_simple)
- strcpy(this.mdl, _fn);
-
- if(this.mdl == "")
- LOG_WARNF("this.mdl is unset for item %s", this.classname);
+ this.fade_end = ReadShort();
- precache_model(this.mdl);
- _setmodel(this, this.mdl);
+ strcpy(this.mdl, ReadString());
+ this.item_simple = -2;
this.skin = ReadByte();
-
- setsize(this, '-16 -16 0', '16 16 48');
}
if(sf & ISF_COLORMAP)
{
this.gravity = 1;
this.pushable = true;
- //this.angles = '0 0 0';
set_movetype(this, MOVETYPE_TOSS);
this.velocity = ReadVector();
- setorigin(this, this.oldorigin);
-
- if(!this.move_time)
- {
- this.move_time = time;
- this.spawntime = time;
- }
- else
- this.move_time = max(this.move_time, time);
}
-
- if(autocvar_cl_animate_items)
+ else if (this.gravity) // caution: kludge FIXME (with sv_legacy_bbox_expand)
{
- if(this.ItemStatus & ITS_ANIMATE1)
- this.avelocity = '0 180 0';
-
- if(this.ItemStatus & ITS_ANIMATE2)
- this.avelocity = '0 -90 0';
+ // workaround for prediction errors caused by bbox discrepancy between SVQC and CSQC
+ this.gravity = 0; // don't do this kludge again
+ this.pushable = false; // no fun allowed
+ set_movetype(this, MOVETYPE_NONE); // disable physics
+ this.velocity = '0 0 0'; // disable it more
+ SET_ONGROUND(this); // extra overkill
}
this.entremove = ItemRemove;
const int AMMO_COUNT = 4; // amount of ammo types to show in the ammo panel
-.float onground_time;
-
-bool autocvar_cl_items_nofade;
-float autocvar_cl_animate_items = 1;
+float autocvar_cl_items_animate = 7;
+float autocvar_cl_items_fadedist = 500;
+float autocvar_cl_items_vehicle_alpha = 0.75;
+vector autocvar_cl_items_vehicle_color = '2 0.5 0.5';
float autocvar_cl_ghost_items = 0.45;
vector autocvar_cl_ghost_items_color = '-1 -1 -1';
vector autocvar_cl_weapon_stay_color = '2 0.5 0.5';
float autocvar_cl_weapon_stay_alpha = 0.75;
float autocvar_cl_simple_items = 0;
string autocvar_cl_simpleitems_postfix = "_simple";
+
.float spawntime;
.float gravity;
.vector colormod;
-void ItemDraw(entity this);
// --------------------------------------------------------------------------
// BEGIN OPTIONAL CSQC FUNCTIONS
-
+.int survival_status;
void Ent_RemovePlayerScore(entity this)
{
if(this.owner) {
FOREACH(Scores, true, {
this.owner.(scores(it)) = 0; // clear all scores
});
+ this.owner.ready = 0;
+ this.owner.eliminated = 0;
+ this.owner.colormap = 0;
+ // TODO add a hook to reset this Survival field
+ this.owner.survival_status = 0;
}
}
campaign = ReadByte();
if (campaign)
{
- string campaign_title = ReadString();
int campaign_level = ReadByte();
- string campaign_msg = ReadString();
- string welcomedialog_args;
- welcomedialog_args = strcat("HOSTNAME \"", campaign_title, "\"");
+ // Menu can't build the whole campaign message because it lacks getcommandkey and CCR
+ // so we build part of the message here and let the menu insert the level description
+ // (that client doesn't know) by replacing the keyword _LEVEL_DESC
string key = getcommandkey(_("jump"), "+jump");
string msg = strcat(
CCR("^F1"), sprintf(_("Level %d:"), campaign_level),
- sprintf(CCR(" ^BG%s\n^3\n"), campaign_msg),
+ sprintf(CCR(" ^BG%s\n\n"), "_LEVEL_DESC"),
sprintf(CCR(_("^BGPress ^F2%s^BG to enter the game")), key));
msg = MakeConsoleSafe(strreplace("\n", "\\n", msg));
- welcomedialog_args = strcat(welcomedialog_args, " WELCOME \"", msg, "\"");
+ string welcomedialog_args = strcat("CAMPAIGN ", itos(campaign_level), " \"", msg, "\"");
+
localcmd("\nmenu_cmd directmenu Welcome ", welcomedialog_args, "\n");
return true;
}
string campaign_fraglimit[CAMPAIGN_MAX_ENTRIES];
string campaign_timelimit[CAMPAIGN_MAX_ENTRIES];
string campaign_mutators[CAMPAIGN_MAX_ENTRIES];
+#ifndef SVQC
string campaign_shortdesc[CAMPAIGN_MAX_ENTRIES];
string campaign_longdesc[CAMPAIGN_MAX_ENTRIES];
+#endif
string campaign_title; // filled upon loading
// load the campaign file, but use the given offset and limit the number of
CAMPAIGN_GETARG; campaign_fraglimit[campaign_entries] = strzone(a);
CAMPAIGN_GETARG; campaign_timelimit[campaign_entries] = strzone(a);
CAMPAIGN_GETARG; campaign_mutators[campaign_entries] = strzone(a);
+ #ifdef SVQC
+ CAMPAIGN_GETARG;
+ CAMPAIGN_GETARG;
+ #else
CAMPAIGN_GETARG; campaign_shortdesc[campaign_entries] = strzone(a);
CAMPAIGN_GETARG; campaign_longdesc[campaign_entries] = strzone(strreplace("\\n", "\n", a));
+ #endif
if(i > entlen)
error("syntax error in campaign file: line has not enough fields");
strfree(campaign_fraglimit[i]);
strfree(campaign_timelimit[i]);
strfree(campaign_mutators[i]);
+ #ifndef SVQC
strfree(campaign_shortdesc[i]);
strfree(campaign_longdesc[i]);
+ #endif
}
campaign_entries = 0;
}
EFFECT(0, ITEM_PICKUP, "item_pickup")
EFFECT(0, ITEM_RESPAWN, "item_respawn")
+EFFECT(0, ITEM_DESPAWN, "item_despawn")
EFFECT(0, ONS_GENERATOR_DAMAGED, "torch_small")
EFFECT(0, ONS_GENERATOR_GIB, "onslaught_generator_gib_explode")
MY(velocityjitter) = '0 0 256';
}
+// originally based on goldendust
+DEF(item_despawn);
+SUB(item_despawn) {
+ MY(type) = "snow";
+// MY(type) = "smoke";
+ MY(blend) = "add";
+ MY(alpha_min) = 192;
+ MY(alpha_max) = 256;
+ MY(alpha_fade) = 256;
+ MY(color_min) = "0xff9600";
+ MY(color_max) = "0xffefb8";
+ MY(count) = 32;
+ MY(originjitter) = '8 8 8';
+ MY(sizeincrease) = 1;
+ MY(size_min) = 0.5;
+ MY(size_max) = 1;
+ MY(tex_min) = 48;
+ MY(tex_max) = 55;
+ MY(velocityjitter) = '16 16 32';
+ MY(lightradius) = 48;
+ MY(lightradiusfade) 64;
+ MY(lightcolor) '1 0.75 0.36';
+ MY(lightshadow) 1;
+}
+
// always add new effects to the bottom of the list. And keep this comment in the bottom line of this file!
{ WriteByte(chan, ent.sv_solid); },
{ ent.sv_solid = ReadByte(); })
-// gamemode specific player survival status (independent of score and frags)
-ENTCS_PROP(SURVIVAL_STATUS, true, survival_status, survival_status, ENTCS_SET_NORMAL,
- { WriteShort(chan, ent.survival_status); },
- { ent.survival_status = ReadShort(); })
-
#ifdef SVQC
int ENTCS_PUBLICMASK = 0, ENTCS_PRIVATEMASK = 0;
Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
}
}
+
+ if (!warmup_stage)
+ eliminatedPlayers.SendFlags |= 1;
}
MUTATOR_HOOKFUNCTION(ca, reset_map_players)
return true;
}
+MUTATOR_HOOKFUNCTION(ft, PutClientInServer)
+{
+ eliminatedPlayers.SendFlags |= 1;
+}
+
MUTATOR_HOOKFUNCTION(ft, reset_map_players)
{
FOREACH_CLIENT(IS_PLAYER(it), {
#include <client/draw.qh>
#include <client/hud/panel/modicons.qh>
+NET_HANDLE(ENT_CLIENT_SURVIVALSTATUSES, bool isnew)
+{
+ make_pure(this);
+ int sf = 0;
+ serialize(byte, 0, sf);
+ if (sf & BIT(0)) // make all players survivors
+ {
+ for (int j = 0; j < maxclients; ++j)
+ if (playerslots[j])
+ playerslots[j].survival_status = SURV_STATUS_PREY;
+ }
+ if (sf & BIT(1)) // receive hunter statuses
+ {
+ for (int i = 1; i <= maxclients; i += 8)
+ {
+ int f = 0;
+ serialize(byte, 0, f);
+ for (int b = 0; b < 8; ++b)
+ {
+ if (!(f & BIT(b))) continue;
+ int j = i - 1 + b;
+ if (playerslots[j])
+ playerslots[j].survival_status = SURV_STATUS_HUNTER;
+ }
+ }
+ }
+
+ // color all players
+ for (int i = 1; i <= maxclients; ++i)
+ {
+ entity e = playerslots[i - 1];
+ if (!e) continue;
+
+ int plcolor = SURV_COLOR_PREY; // default color
+ if(e.survival_status == SURV_STATUS_HUNTER) // if client knows this hunter
+ plcolor = SURV_COLOR_HUNTER;
+
+ e.colormap = 1024 + plcolor; // override scoreboard and player model colors
+ }
+
+ return true;
+}
+
void HUD_Mod_Survival(vector pos, vector mySize)
{
mod_active = 1; // survival should always show the mod HUD
- int mystatus = entcs_receiver(player_localnum).survival_status;
+ int mystatus = playerslots[player_localnum].survival_status;
string player_text = "";
vector player_color = '1 1 1';
//string player_icon = "";
+
+ if(STAT(GAMESTARTTIME) > time || STAT(ROUNDSTARTTIME) > time || entcs_IsSpectating(player_localnum))
+ return;
+
if(mystatus == SURV_STATUS_HUNTER)
{
player_text = _("Hunter");
player_color = '0 1 0';
//player_icon = "player_neutral";
}
- else
- {
- // if the player has no valid status, don't draw anything
- return;
- }
drawstring_aspect(pos, player_text, vec2(mySize.x, mySize.y), player_color, panel_fg_alpha, DRAWFLAG_NORMAL);
}
return false;
entity player = M_ARGV(0, entity);
- entity e = entcs_receiver(player.entnum - 1);
- int surv_status = ((e) ? e.survival_status : 0);
- int mystatus = entcs_receiver(player_localnum).survival_status;
+ entity e = playerslots[player.entnum - 1];
+ if (e && e.colormap)
+ player.colormap = e.colormap;
+ else
+ player.colormap = 1024 + SURV_COLOR_PREY;
+ return true;
+}
- int plcolor = SURV_COLOR_PREY; // default to survivor
- if((mystatus == SURV_STATUS_HUNTER || intermission || STAT(GAME_STOPPED)) && surv_status == SURV_STATUS_HUNTER)
- plcolor = SURV_COLOR_HUNTER;
+MUTATOR_HOOKFUNCTION(cl_surv, DrawScoreboard)
+{
+ if(!ISGAMETYPE(SURVIVAL))
+ return false;
- player.colormap = 1024 + plcolor;
- return true;
+ // initialize colors of new players
+ for(entity pl = players.sort_next; pl; pl = pl.sort_next)
+ if(pl.gotscores && pl.colormap == 0)
+ pl.colormap = 1024 + SURV_COLOR_PREY;
+ return false;
}
MUTATOR_HOOKFUNCTION(cl_surv, DrawScoreboard_Force)
#include "survival.qh"
+REGISTER_NET_LINKED(ENT_CLIENT_SURVIVALSTATUSES)
void nades_Clear(entity player);
+entity survivalStatuses;
+void SurvivalStatuses_Init();
+
+void SurvivalStatuses_Send()
+{
+ // SendFlags can be set to anything != 0, SurvivalStatuses_SendEntity won't use its value
+ // Dr. Jaska: this was a lie, the flags were not reset until now
+ survivalStatuses.SendFlags = 1;
+}
+
+bool SurvivalStatuses_SendEntity(entity this, entity dest, float sendflags)
+{
+ Stream out = MSG_ENTITY;
+ WriteHeader(out, ENT_CLIENT_SURVIVALSTATUSES);
+
+ // TODO: optimize this instead of always setting it on
+ sendflags = BIT(0); // reset all flags and make all players survivors
+
+ if ((dest.survival_status == SURV_STATUS_HUNTER) || round_handler_AwaitingNextRound())
+ sendflags |= BIT(1); // send hunter statuses
+
+ serialize(byte, out, sendflags);
+ if (sendflags & BIT(1)) {
+ for (int i = 1; i <= maxclients; i += 8) {
+ int f = 0;
+ entity e = edict_num(i);
+ for (int b = 0; b < 8; ++b, e = nextent(e)) {
+ bool is_hunter = (INGAME(e) && e.survival_status == SURV_STATUS_HUNTER);
+ if (is_hunter)
+ f |= BIT(b);
+ }
+ serialize(byte, out, f);
+ }
+ }
+ //print(sprintf("sent flags %f to %s\n", sendflags, dest.netname));
+ return true;
+}
+
+void SurvivalStatuses_Init()
+{
+ if(survivalStatuses)
+ {
+ backtrace("Can't spawn survivalStatuses again!");
+ return;
+ }
+ Net_LinkEntity(survivalStatuses = new_pure(survivalStatuses), false, 0, SurvivalStatuses_SendEntity);
+}
+
void Surv_UpdateScores(bool timed_out)
{
// give players their hard-earned kills now that the round is over
allowed_to_spawn = false;
game_stopped = true;
round_handler_Init(5, autocvar_g_survival_warmup, autocvar_g_survival_round_timelimit);
+ SurvivalStatuses_Send();
return 1;
}
allowed_to_spawn = false;
game_stopped = true;
round_handler_Init(5, autocvar_g_survival_warmup, autocvar_g_survival_round_timelimit);
+ SurvivalStatuses_Send();
FOREACH_CLIENT(true,
{
total_hunters++;
it.survival_status = SURV_STATUS_HUNTER;
});
+ SurvivalStatuses_Send();
FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
{
round_handler_Spawn(Surv_CheckPlayers, Surv_CheckWinner, Surv_RoundStart);
round_handler_Init(5, autocvar_g_survival_warmup, autocvar_g_survival_round_timelimit);
EliminatedPlayers_Init(surv_isEliminated);
+ SurvivalStatuses_Init();
}
M_ARGV(5, bool) = true; // anonymous attacker
}
-MUTATOR_HOOKFUNCTION(surv, PlayerPreThink)
-{
- entity player = M_ARGV(0, entity);
-
- if(IS_PLAYER(player) || INGAME_JOINED(player))
- {
- // update the scoreboard colour display to out the real killer at the end of the round
- // running this every frame to avoid cheats
- int plcolor = SURV_COLOR_PREY;
- if(player.survival_status == SURV_STATUS_HUNTER && game_stopped)
- plcolor = SURV_COLOR_HUNTER;
- setcolor(player, plcolor);
- }
-}
-
MUTATOR_HOOKFUNCTION(surv, PlayerSpawn)
{
entity player = M_ARGV(0, entity);
Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
}
}
+
+ if (!warmup_stage)
+ eliminatedPlayers.SendFlags |= 1;
}
MUTATOR_HOOKFUNCTION(surv, reset_map_players)
}
});
bot_relinkplayerlist();
+ SurvivalStatuses_Send();
return true;
}
// killed an ally! punishment is death
if(autocvar_g_survival_punish_teamkill && frag_attacker != frag_target && IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target) && frag_attacker.survival_status == frag_target.survival_status && !ITEM_DAMAGE_NEEDKILL(frag_deathtype))
- if(!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted()) // don't autokill if the round hasn't
+ if(!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted()) // don't autokill if the round hasn't started yet
Damage(frag_attacker, frag_attacker, frag_attacker, 100000, DEATH_MIRRORDAMAGE.m_id, DMG_NOWEP, frag_attacker.origin, '0 0 0');
return true;
}
const int ITS_ALLOWFB = BIT(4);
const int ITS_ALLOWSI = BIT(5);
const int ITS_GLOW = BIT(6);
+const int ITS_EXPIRING = BIT(7);
+
+// enough to notice it's about to despawn and circle jump to grab it
+const float IT_DESPAWNFX_TIME = 1.5;
+
+// 2hz probably enough to correct a desync caused by serious lag
+// FIXME but updating faster applies the kludge in Item_Think() sooner so it's less noticeable
+const float IT_UPDATE_INTERVAL = 0.0625;
.float fade_start;
.float fade_end;
// we won't check target2 here yet, as it may not even need to exist
if(this.target == "") { LOG_INFO("^1FAIL!"); delete(this); return; }
- EXACTTRIGGER_INIT;
+ WarpZoneLib_ExactTrigger_Init(this, false);
InitializeEntity(this, viewloc_init, INITPRIO_FINDTARGET);
}
return NULL;
}
-void part_minigame(entity player )
+void part_minigame(entity player)
{
entity minig = CS(player).active_minigame;
return "Invalid player";
if ( inviter == player )
return "You can't invite yourself";
+ if (PlayerInList(player, autocvar_g_playban_list) && autocvar_g_playban_minigames) // playban
+ return "You can't invite a banned player";
if ( CS(player).active_minigame == CS(inviter).active_minigame )
return strcat(player.netname," is already playing");
return;
}
+ if (PlayerInList(caller, autocvar_g_playban_list) && autocvar_g_playban_minigames) // playban
+ {
+ Send_Notification(NOTIF_ONE_ONLY, caller, MSG_CENTER, CENTER_JOIN_PLAYBAN);
+ sprint(caller, "You aren't allowed to play minigames because you are banned from them in this server.\n");
+ return;
+ }
+
if (request == CMD_REQUEST_COMMAND )
{
string minig_cmd = argv(1);
void powerups_DropItem_Think(entity this)
{
- TakeResource(this, RES_HEALTH, 1);
-
- if(GetResource(this, RES_HEALTH) < 1) {
- RemoveItem(this);
- return;
- }
-
// Only needed to update if the timer of the powerup is running
if(!GetResource(this, RES_ARMOR))
- WaypointSprite_UpdateHealth(this.waypointsprite_attached, GetResource(this, RES_HEALTH));
-
- this.nextthink = time + 1;
+ {
+ float timeleft = floor(this.wait - time);
+ if (timeleft)
+ WaypointSprite_UpdateHealth(this.waypointsprite_attached, timeleft);
+ else
+ WaypointSprite_Kill(this.waypointsprite_attached);
+ }
}
void powerups_DropItem(entity this, StatusEffects effect, bool freezeTimer)
if (!Item_InitializeLoot(e, item.m_canonical_spawnfunc, this.origin, vel, time_to_live))
return;
- e.item_spawnshieldtime = time + 0.5;
-
if(!freezeTimer)
Item_SetExpiring(e, true);
- // Use health as time left to live
- SetResourceExplicit(e, RES_HEALTH, time_to_live);
-
// Use armor as timer freezer
if(freezeTimer)
SetResourceExplicit(e, RES_ARMOR, 1);
wp.wp_extra = item.m_id;
WaypointSprite_UpdateMaxHealth(e.waypointsprite_attached, maxtime);
WaypointSprite_UpdateHealth(e.waypointsprite_attached, timeleft);
-
- // Start dropping its time to live
- setthink(e, powerups_DropItem_Think);
- e.nextthink = time + 1;
+ // Item_Think() will call powerups_DropItem_Think() to update the waypoint
}
MUTATOR_HOOKFUNCTION(powerups, ItemTouched)
.float prevstrengthsound;
.float prevstrengthsoundattempt;
+void powerups_DropItem_Think(entity this);
+
// q3compat
.float count;
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_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 ^F3https://xonotic.org^BG!"), "")
MSG_INFO_NOTIF(WEAPON_ACCORDEON_MURDER, N_CONSOLE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weapontuba", _("^BG%s%s^K1 died of ^BG%s^K1's great playing on the @!#%%'n Accordeon%s%s"), "")
MSG_INFO_NOTIF(WEAPON_ACCORDEON_SUICIDE, N_CONSOLE, 2, 1, "s1 s2loc spree_lost", "s1", "weapontuba", _("^BG%s^K1 hurt their own ears with the @!#%%'n Accordeon%s%s"), "")
MSG_CENTER_NOTIF(SUPERWEAPON_LOST, N_ENABLE, 0, 0, "", CPID_POWERUP, "0 0", _("^F2Superweapons have been lost"), "")
MSG_CENTER_NOTIF(SUPERWEAPON_PICKUP, N_ENABLE, 0, 0, "", CPID_POWERUP, "0 0", _("^F2You now have a superweapon"), "")
- MSG_CENTER_NOTIF(SURVIVAL_HUNTER, N_ENABLE, 0, 0, "", CPID_SURVIVAL, "5 0", strcat(BOLD_OPERATOR, _("^BGYou are a ^K1hunter^BG! Eliminate the survivor(s) without raising suspicion!")), "")
+ MSG_CENTER_NOTIF(SURVIVAL_HUNTER, N_ENABLE, 0, 0, "", CPID_SURVIVAL, "5 0", BOLD(_("^BGYou are a ^K1hunter^BG! Eliminate the survivor(s) without raising suspicion!")), "")
MSG_CENTER_NOTIF(SURVIVAL_HUNTER_WIN, N_ENABLE, 0, 0, "", CPID_ROUND, "0 0", _("^K1Hunters^BG win the round"), "")
- MSG_CENTER_NOTIF(SURVIVAL_SURVIVOR, N_ENABLE, 0, 0, "", CPID_SURVIVAL, "5 0", strcat(BOLD_OPERATOR, _("^BGYou are a ^F1survivor^BG! Identify and eliminate the hunter(s)!")), "")
+ MSG_CENTER_NOTIF(SURVIVAL_SURVIVOR, N_ENABLE, 0, 0, "", CPID_SURVIVAL, "5 0", BOLD(_("^BGYou are a ^F1survivor^BG! Identify and eliminate the hunter(s)!")), "")
MSG_CENTER_NOTIF(SURVIVAL_SURVIVOR_WIN, N_ENABLE, 0, 0, "", CPID_ROUND, "0 0", _("^F1Survivors^BG win the round"), "")
MULTITEAM_CENTER(TEAMCHANGE, N_ENABLE, 0, 1, "", CPID_TEAMCHANGE, "1 f1", _("^K1Changing to ^TC^TT^K1 in ^COUNT"), "", NAME)
string language_filename(string s)
{
string fn = prvm_language;
- if (fn == "" || fn == "dump") return s;
+ if (fn == "" || fn == "en" || fn == "dump") return s;
fn = strcat(s, ".", fn);
int fh = fopen(fn, FILE_READ);
if (fh >= 0)
#include "radiobutton.qh"
#include "commandbutton.qh"
#include "slider.qh"
+#include <common/campaign_common.qh>
void welcomeDialog_resetStrings(entity me)
{
strcpy(me.serverinfo_name, bufstr_get(argsbuf, ++i));
else if(s == "WELCOME")
strcpy(me.serverinfo_MOTD, bufstr_get(argsbuf, ++i));
+ else if(s == "CAMPAIGN")
+ {
+ strcpy(me.serverinfo_name, campaign_title);
+
+ int level = stoi(bufstr_get(argsbuf, ++i)) - 1;
+ string campaign_msg;
+ if (level < 0 || level >= campaign_entries)
+ campaign_msg = strcat("^1Error: invalid level number ", itos(level + 1));
+ else
+ {
+ string desc = strcat(campaign_shortdesc[level], "\n\n", campaign_longdesc[level]);
+ desc = strreplace("\n", "\\n", desc);
+ campaign_msg = strreplace("_LEVEL_DESC", desc, bufstr_get(argsbuf, ++i));
+ }
+ strcpy(me.serverinfo_MOTD, campaign_msg);
+ break;
+ }
else if(s == "RESET")
{
welcomeDialog_resetStrings(me);
line = eY * fs.y;
string l1, l2;
l1 = sprintf(_("Update to %s now!"), _Nex_ExtResponseSystem_UpdateTo);
- l2 = "http://www.xonotic.org/";
+ l2 = "https://xonotic.org";
if(_Nex_ExtResponseSystem_UpdateToURL)
l2 = _Nex_ExtResponseSystem_UpdateToURL;
GAMETYPE(MAPINFO_TYPE_CTF) \
GAMETYPE(MAPINFO_TYPE_CA) \
GAMETYPE(MAPINFO_TYPE_FREEZETAG) \
- GAMETYPE(MAPINFO_TYPE_TEAM_MAYHEM) \
GAMETYPE(MAPINFO_TYPE_MAYHEM) \
+ GAMETYPE(MAPINFO_TYPE_TEAM_MAYHEM) \
GAMETYPE(MAPINFO_TYPE_KEEPAWAY) \
GAMETYPE(MAPINFO_TYPE_TEAM_KEEPAWAY) \
GAMETYPE(MAPINFO_TYPE_KEYHUNT) \
desc = "Resets the goal stack";
break;
case BOT_CMD_CC:
- desc = "Execute client command. Examples: cc \"say something\"; cc god; cc \"name newnickname\"; cc kill;";
+ desc = "Execute client command. Examples: cc say something; cc god; cc name newnickname; cc kill;";
break;
case BOT_CMD_IF:
desc = "Perform simple conditional execution.\n"
return 0;
}
-string Campaign_GetTitle()
-{
- return campaign_title;
-}
-
int Campaign_GetLevelNum()
{
return campaign_level + 1;
}
-string Campaign_GetMessage()
-{
- return strcat(campaign_shortdesc[0], "\n^3\n", campaign_longdesc[0]);
-}
-
void CampaignPreInit()
{
float baseskill;
// this must be included BEFORE campaign_common.h to make this a memory saving
#define CAMPAIGN_MAX_ENTRIES 2
-string Campaign_GetTitle();
int Campaign_GetLevelNum();
-string Campaign_GetMessage();
void CampaignPreInit();
void CampaignPostInit();
return false; // empty list or search, just return
// this function allows abbreviated strings!
- FOREACH_WORD(list, it == substring(tofind, 0, strlen(it)),
+ FOREACH_WORD(list, it != "" && it == substring(tofind, 0, strlen(it)),
{
return true;
});
bool PlayerInList(entity player, string list)
{
+ if (list == "")
+ return false;
return boolean(PlayerInIDList(player, list) || PlayerInIPList(player, list));
}
WriteByte(msg_type, boolean(autocvar_g_campaign));
if (boolean(autocvar_g_campaign))
{
- WriteString(msg_type, Campaign_GetTitle());
WriteByte(msg_type, Campaign_GetLevelNum());
- WriteString(msg_type, Campaign_GetMessage());
return;
}
WriteString(msg_type, autocvar_hostname);
if (PlayerInList(this, autocvar_g_playban_list))
TRANSMUTE(Observer, this);
- if (PlayerInList(this, autocvar_g_muteban_list)) // muteban
+ if (PlayerInList(this, autocvar_g_chatban_list)) // chatban
CS(this).muted = true;
MUTATOR_CALLHOOK(ClientConnect, this);
#include "banning.qh"
#include <common/command/_mod.qh>
+#include <common/minigames/sv_minigames.qh>
#include <common/state.qh>
#include <common/stats.qh>
#include <common/util.qh>
if (accepted > 0)
{
string theid = "";
- if(!PlayerInIPList(client, autocvar_g_muteban_list))
+ if(!PlayerInIPList(client, autocvar_g_chatban_list))
theid = cons(theid, client.netaddress);
- if(!PlayerInIDList(client, autocvar_g_muteban_list))
+ if(!PlayerInIDList(client, autocvar_g_chatban_list))
theid = cons(theid, client.crypto_idfp);
CS(client).muted = true;
LOG_INFO(strcat("Mute-banning player ", GetCallerName(client), " (", argv(1), ")."));
- cvar_set("g_muteban_list", cons(autocvar_g_muteban_list, theid));
+ cvar_set("g_chatban_list", cons(autocvar_g_chatban_list, theid));
return;
}
{
LOG_HELP("Usage:^3 sv_cmd mute <client>");
LOG_HELP(" <client> is the entity number or name of the player to mute.");
- LOG_HELP("See also: ^2unmute, g_muteban_list^7");
+ LOG_HELP("See also: ^2unmute, g_chatban_list^7");
return;
}
}
LOG_INFO(strcat("Play-banning player ", GetCallerName(client), " (", argv(1), ")."));
PutObserverInServer(client, true, true);
+ if (autocvar_g_playban_minigames)
+ part_minigame(client);
cvar_set("g_playban_list", cons(autocvar_g_playban_list, theid));
return;
if (accepted > 0)
{
string tmp_string = "";
- FOREACH_WORD(autocvar_g_muteban_list, it != client.netaddress,
+ FOREACH_WORD(autocvar_g_chatban_list, it != client.netaddress,
{
if(client.crypto_idfp && it == substring(client.crypto_idfp, 0, strlen(it)))
continue;
tmp_string = cons(tmp_string, it);
});
- cvar_set("g_muteban_list", tmp_string);
+ cvar_set("g_chatban_list", tmp_string);
LOG_INFO(strcat("Unmuting player ", GetCallerName(client), " (", original_arg, ")."));
CS(client).muted = false;
{
LOG_HELP("Usage:^3 sv_cmd unmute <client>");
LOG_HELP(" <client> is the entity number or name of the player to unmute.");
- LOG_HELP("See also: ^2mute, g_muteban_list^7");
+ LOG_HELP("See also: ^2mute, g_chatban_list^7");
return;
}
}
bool autocvar_g_ban_telluser = true;
string autocvar_g_banned_list;
bool autocvar_g_banned_list_idmode;
-string autocvar_g_muteban_list; // "List of banned players from chat"
-string autocvar_g_playban_list; // "List of banned players from playing (forced to spectate)"
-string autocvar_g_voteban_list; // "List of banned players from voting"
+string autocvar_g_chatban_list;
+string autocvar_g_playban_list;
+bool autocvar_g_playban_minigames;
+string autocvar_g_voteban_list;
#define GET_BAN_ARG(v, d) if (argc > reason_arg) { if ((v = stof(argv(reason_arg))) != 0) ++reason_arg; else v = d; } else { v = d; }
#define GET_BAN_REASON(v, d) if (argc > reason_arg) v = substring(command, argv_start_index(reason_arg), strlen(command) - argv_start_index(reason_arg)); else v = d;
LOG_HELP(" <client> can be either the name of the bot or a progressive number (not the entity number!)");
LOG_HELP(" can also be '*' or 'all' to allow sending the command to all the bots");
LOG_HELP(" For full list of commands, see bot_cmd help [<command>].");
- LOG_HELP("Examples: sv_cmd bot_cmd 1 cc \"say something\"");
+ LOG_HELP("Examples: sv_cmd bot_cmd 1 cc say something");
LOG_HELP(" sv_cmd bot_cmd 1 presskey jump");
LOG_HELP(" sv_cmd bot_cmd * pause");
return;
else if (destination == "spectator")
{
string pl_name = playername(client.netname, client.team, false);
- if (!IS_SPEC(client) && !IS_OBSERVER(client))
+ if (!(IS_SPEC(client) || IS_OBSERVER(client)) || INGAME(client))
{
PutObserverInServer(client, true, true);
switch (first_command) // now go through and parse the proper commands to adjust as needed.
{
+ case "movetoauto":
+ case "movetored":
+ case "movetoblue":
+ case "movetoyellow":
+ case "movetopink":
+ case "movetospec":
+ {
+ entity victim = GetIndexedEntity(argc, (startpos + 1));
+ float accepted = VerifyClientEntity(victim, true, false);
+ if (accepted > 0)
+ {
+ vote_parsed_command = vote_command;
+ vote_parsed_display = sprintf("^1%s #%d ^7%s", first_command, etof(victim), victim.netname);
+ }
+ else
+ {
+ print_to(caller, strcat("vcall: ", GetClientErrorString(accepted, argv(startpos + 1)), ".\n"));
+ return 0;
+ }
+
+ break;
+ }
+
case "kick":
case "kickban": // catch all kick/kickban commands
{
break;
}
+ case "fraglimit": // include restrictions on the maximum votable frag limit
+ {
+ float fraglimit_vote = stof(argv(startpos + 1));
+ float fraglimit_min = 0;
+ float fraglimit_max = 999999;
+ if(fraglimit_vote > fraglimit_max || fraglimit_vote < fraglimit_min)
+ {
+ print_to(caller, strcat("Invalid fraglimit vote, accepted values are between ", ftos(fraglimit_min), " and ", ftos(fraglimit_max), "."));
+ return -1;
+ }
+ vote_parsed_command = strcat("fraglimit ", ftos(fraglimit_vote));
+ vote_parsed_display = strzone(strcat("^1", vote_parsed_command));
+
+ break;
+ }
+
case "timelimit": // include restrictions on the maximum votable time limit
{
float timelimit_vote = stof(argv(startpos + 1));
print_to(caller, strcat("Invalid timelimit vote, accepted values are between ", ftos(autocvar_timelimit_min), " and ", ftos(autocvar_timelimit_max), "."));
return -1;
}
- timelimit_vote = bound(autocvar_timelimit_min, timelimit_vote, autocvar_timelimit_max);
vote_parsed_command = strcat("timelimit ", ftos(timelimit_vote));
vote_parsed_display = strzone(strcat("^1", vote_parsed_command));
if (MUTATOR_CALLHOOK(ClientObituary, inflictor, attacker, targ, deathtype, attacker.(weaponentity))) { CS(targ).killcount = 0; return; }
notif_anonymous = M_ARGV(5, bool);
+ // TODO: Replace "???" with a translatable "Anonymous player" string
+ // https://gitlab.com/xonotic/xonotic-data.pk3dir/-/issues/2839
if(notif_anonymous)
- attacker_name = "Anonymous player";
+ attacker_name = "???";
#ifdef NOTIFICATIONS_DEBUG
Debug_Notification(
if(sf & ISF_MODEL)
{
WriteShort(MSG_ENTITY, bound(0, this.fade_end, 32767));
- WriteShort(MSG_ENTITY, bound(0, this.fade_start, 32767));
if(this.mdl == "")
LOG_TRACE("^1WARNING!^7 this.mdl is unset for item ", this.classname, "expect a crash just about now");
}
else
{
- //setmodel(e, "null");
e.solid = SOLID_NOT;
- e.colormod = '0 0 0';
- //e.glowmod = e.colormod;
e.spawnshieldtime = 1;
e.ItemStatus &= ~ITS_AVAILABLE;
}
void Item_Think(entity this)
{
- this.nextthink = time;
- if(this.origin != this.oldorigin)
+ if (Item_IsLoot(this))
+ {
+ if (time < this.wait - IT_DESPAWNFX_TIME)
+ this.nextthink = min(time + IT_UPDATE_INTERVAL, this.wait - IT_DESPAWNFX_TIME); // ensuring full time for effects
+ else
+ {
+ // despawning soon, start effects
+ this.ItemStatus |= ITS_EXPIRING;
+ this.SendFlags |= ISF_STATUS;
+ if (time < this.wait - IT_UPDATE_INTERVAL)
+ this.nextthink = time + IT_UPDATE_INTERVAL;
+ else
+ {
+ setthink(this, RemoveItem);
+ this.nextthink = this.wait;
+ }
+ }
+
+ if (this.itemdef.instanceOfPowerup)
+ powerups_DropItem_Think(this);
+
+ // caution: kludge FIXME (with sv_legacy_bbox_expand)
+ // this works around prediction errors caused by bbox discrepancy between SVQC and CSQC
+ if (this.velocity == '0 0 0' && IS_ONGROUND(this))
+ this.gravity = 0; // don't send ISF_DROP anymore
+
+ // send slow updates even if the item didn't move
+ // recovers prediction desyncs where server thinks item stopped, client thinks it didn't
ItemUpdate(this);
+ }
+ else
+ {
+ // bones_was_here: TODO: predict movers, enable client prediction of items with a groundentity,
+ // and then send those less often too (and not all on the same frame)
+ this.nextthink = time;
+
+ if(this.origin != this.oldorigin)
+ ItemUpdate(this);
+ }
}
bool Item_ItemsTime_SpectatorOnly(GameItem it);
setthink(this, Item_Think);
this.nextthink = time;
-
- //Send_Effect(EFFECT_ITEM_RESPAWN, this.origin + this.mins_z * '0 0 1' + '0 0 48', '0 0 0', 1);
- Send_Effect(EFFECT_ITEM_RESPAWN, CENTER_OR_VIEWOFS(this), '0 0 0', 1);
}
void Item_RespawnCountdown(entity this)
STAT(LAST_PICKUP, toucher) = time;
- Send_Effect(EFFECT_ITEM_PICKUP, CENTER_OR_VIEWOFS(this), '0 0 0', 1);
GameItem def = this.itemdef;
int ch = ((def.instanceOfPowerup && def.m_itemid != IT_RESOURCE) ? CH_TRIGGER_SINGLE : CH_TRIGGER);
- string snd = (this.item_pickupsound ? this.item_pickupsound : Sound_fixpath(this.item_pickupsound_ent));
- _sound(toucher, ch, snd, VOL_BASE, ATTEN_NORM);
+ _sound(toucher, ch, this.item_pickupsound, VOL_BASE, ATTEN_NORM);
MUTATOR_CALLHOOK(ItemTouched, this, toucher);
if (wasfreed(this))
if(wasfreed(this) || !this) { return; }
if(this.waypointsprite_attached)
WaypointSprite_Kill(this.waypointsprite_attached);
- Send_Effect(EFFECT_ITEM_PICKUP, CENTER_OR_VIEWOFS(this), '0 0 0', 1);
delete(this);
}
void _StartItem(entity this, entity def, float defaultrespawntime, float defaultrespawntimejitter)
{
string itemname = def.m_name;
- Model itemmodel = def.m_model;
- Sound pickupsound = def.m_sound;
float(entity player, entity item) pickupevalfunc = def.m_pickupevalfunc;
float pickupbasevalue = def.m_botvalue;
- int itemflags = def.m_itemflags;
startitem_failed = false;
- this.item_model_ent = itemmodel;
- this.item_pickupsound_ent = pickupsound;
+ this.item_model_ent = def.m_model;
+ this.item_pickupsound_ent = def.m_sound;
+ if (!this.item_pickupsound)
+ this.item_pickupsound = Sound_fixpath(this.item_pickupsound_ent);
if(def.m_iteminit)
def.m_iteminit(def, this);
int weaponid = def.instanceOfWeaponPickup ? def.m_weapon.m_id : 0;
this.weapon = weaponid;
+ // bones_was_here TODO: implement sv_cullentities_dist and replace g_items_maxdist with it
if(!this.fade_end)
- {
- this.fade_start = autocvar_g_items_mindist;
this.fade_end = autocvar_g_items_maxdist;
- }
if(weaponid)
STAT(WEAPONS, this) = WepSet_FromWeapon(REGISTRY_GET(Weapons, weaponid));
- this.flags = FL_ITEM | itemflags;
+ this.flags = FL_ITEM | def.m_itemflags;
IL_PUSH(g_items, this);
if(MUTATOR_CALLHOOK(FilterItem, this)) // error means we do not want the item
return;
}
+ this.mdl = this.model ? this.model : strzone(this.item_model_ent.model_str());
+ setmodel(this, MDL_Null); // precision set below
+ // set item size before we spawn a waypoint or droptofloor or MoveOutOfSolid
+ setsize (this, this.pos1 = def.m_mins, this.pos2 = def.m_maxs);
+
if (Item_IsLoot(this))
{
this.reset = RemoveItem;
+
set_movetype(this, MOVETYPE_TOSS);
+ this.gravity = 1;
+
+ setthink(this, Item_Think);
+ this.nextthink = time + IT_UPDATE_INTERVAL;
+ this.wait = time + autocvar_g_items_dropped_lifetime;
- // Savage: remove thrown items after a certain period of time ("garbage collection")
- setthink(this, RemoveItem);
- this.nextthink = time + autocvar_g_items_dropped_lifetime;
+ this.owner = NULL; // anyone can pick this up, including the player who threw it
+ this.item_spawnshieldtime = time + 0.5; // but not straight away
this.takedamage = DAMAGE_YES;
this.event_damage = Item_Damage;
}
else
{
+ this.reset = Item_Reset;
+
// must be done after def.m_iteminit() as that may set ITEM_FLAG_MUTATORBLOCKED
if(!have_pickup_item(this))
{
if(t) this.team = crc16(false, t);
}
- this.reset = this.team ? Item_FindTeam : Item_Reset;
// it's a level item
if(this.spawnflags & 1)
this.noalign = 1;
{
// first nudge it off the floor a little bit to avoid math errors
setorigin(this, this.origin + '0 0 1');
- // set item size before we spawn a spawnfunc_waypoint
- setsize(this, def.m_mins, def.m_maxs);
- this.SendFlags |= ISF_SIZE;
// note droptofloor returns false if stuck/or would fall too far
if (!this.noalign)
droptofloor(this);
}
Item_ItemsTime_SetTime(this, 0);
+
+ this.glowmod = def.m_color;
}
this.bot_pickup = true;
this.bot_pickupevalfunc = pickupevalfunc;
this.bot_pickupbasevalue = pickupbasevalue;
- this.mdl = this.model ? this.model : strzone(this.item_model_ent.model_str());
this.netname = itemname;
settouch(this, Item_Touch);
- setmodel(this, MDL_Null); // precision set below
//this.effects |= EF_LOWPRECISION;
// support skinned models for powerups
if(!this.skin)
this.skin = def.m_skin;
- setsize (this, this.pos1 = def.m_mins, this.pos2 = def.m_maxs);
-
- this.SendFlags |= ISF_SIZE;
-
if (!(this.spawnflags & 1024)) {
if(def.instanceOfPowerup)
this.ItemStatus |= ITS_ANIMATE1;
this.ItemStatus |= ITS_ANIMATE2;
}
- if(Item_IsLoot(this))
- this.gravity = 1;
- else
- this.glowmod = def.m_color;
-
if(def.instanceOfWeaponPickup)
{
if (!Item_IsLoot(this)) // if dropped, colormap is already set up nicely
this.effects |= EF_NOGUNBOB; // marker for item team search
InitializeEntity(this, Item_FindTeam, INITPRIO_FINDTARGET);
+ this.reset = Item_FindTeam;
}
else
Item_Reset(this);
}
// we should be sure this item will spawn before loading its assets
- precache_model(this.model);
+ // CSQC handles model precaching: it may not use this model (eg simple items) and may not have connected yet
+ //precache_model(this.mdl);
precache_sound(this.item_pickupsound);
setItemGroup(this);
float autocvar_g_balance_superweapons_time;
bool autocvar_g_fullbrightitems;
-float autocvar_g_items_mindist;
float autocvar_g_items_maxdist;
float autocvar_g_items_dropped_lifetime;
int autocvar_g_pickup_items;
}
item.gravity = 1;
item.velocity = vel;
- SUB_SetFade(item, time + time_to_live, 1);
+ // StartItem sets the default .wait expiry time which is respected by Item_Think()
+ if (time_to_live)
+ item.wait = time + time_to_live;
return true;
}
/**/
MUTATOR_HOOKABLE(Item_RespawnCountdown, EV_Item_RespawnCountdown);
-/** called when a bot checks a target to attack */
+/**
+ * called when a bot checks a target to attack
+ * return false to allow the bot to attack the target (inverted logic)
+ */
#define EV_BotShouldAttack(i, o) \
/** bot */ i(entity, MUTATOR_ARGV_0_entity) \
/** target */ i(entity, MUTATOR_ARGV_1_entity) \
#include <server/weapons/weaponsystem.qh>
#include <server/world.qh>
-void thrown_wep_think(entity this)
-{
- this.nextthink = time;
- if(this.oldorigin != this.origin)
- {
- this.SendFlags |= ISF_LOCATION;
- this.oldorigin = this.origin;
- }
- this.owner = NULL;
- float timeleft = this.savenextthink - time;
- if(timeleft > 1)
- SUB_SetFade(this, this.savenextthink - 1, 1);
- else if(timeleft > 0)
- SUB_SetFade(this, time, timeleft);
- else
- SUB_VanishOrRemove(this);
-}
-
// 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)
{
weapon_defaultspawnfunc(wep, info);
if(startitem_failed)
return -1;
- setthink(wep, thrown_wep_think);
- wep.savenextthink = wep.nextthink;
- wep.nextthink = min(wep.nextthink, time + 0.5);
+
wep.pickup_anyway = true; // these are ALWAYS pickable
//wa = W_AmmoItemCode(wpn);
bool autocvar_g_weapon_throwable;
-.float savenextthink;
-void thrown_wep_think(entity this);
-
// 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);
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 (!actor.vehicle && this.state != WS_READY) return false;
}
return true;
}
BADCVAR("timeformat");
BADCVAR("timestamps");
BADCVAR("g_require_stats");
- BADCVAR("g_muteban_list");
+ BADCVAR("g_chatban_list");
BADCVAR("g_playban_list");
+ BADCVAR("g_playban_minigames");
BADCVAR("g_voteban_list");
BADPREFIX("developer_");
BADPREFIX("g_ban_");
nopicmip
{
map models/relics/sign_invisible
- blendfunc add
+ blendfunc blend
}
}
nopicmip
{
map models/relics/sign_speed
- blendfunc add
+ blendfunc blend
}
}
seta cl_spawn_point_dist_max 1200 "maximum distance from which spawnpoint particles will be visible"
freelook 1
-sensitivity 6
+sensitivity 3
v_gamma 1
viewsize 100
bgmvolume 1
// freeze camera
set cl_lockview 0 "when 1, the camera does not move any more; when 2, you can only control camera angles"
-// we now use mastervolume
-volume 1
-
// sucks less than the old one
cl_decals_newsystem 1
// enable menu syncing - must be after files that call menu_sync on startup - see alias menu_sync ""
alias menu_sync "menu_cmd sync"
-seta cl_items_nofade 0
-seta cl_animate_items 1
+seta cl_items_animate 7 "1 enables bobbing and spinning of 3d items, 2 enables fading out of despawning loot items, 4 enables glowing particles for despawning loot items; add the numbers together to enable that combination."
+seta cl_items_fadedist 500 "distance, relative to the server's g_items_maxdist, at which far away items will start to fade out; 0 disables fading effect"
+seta cl_items_vehicle_alpha 0.75 "Alpha of items seen from inside a vehicle"
+seta cl_items_vehicle_color "2 0.5 0.5" "Colour of items seen from inside a vehicle"
seta cl_ghost_items 0.45 "enable ghosted items (when between 0 and 1, overrides the alpha value)"
seta cl_ghost_items_color "-1 -1 -1" "color of ghosted items (colormod format: 0 0 0 leaves the color unchanged, negative values allowed)"
-seta cl_ghost_items_vehicle 1 "show ghosted items when inside a vehicle even when the item is available, to indicate that it can't be picked up"
seta cl_simple_items 0 "enable simple items (if server allows)"
set cl_simpleitems_postfix "_luma" "posfix to add fo model name when simple items are enabled"
set cl_weapon_stay_color "2 0.5 0.5" "Color of picked up weapons when g_weapon_stay > 0 (colormod format: 0 0 0 leaves the color unchanged, negative values allowed)"
set g_nodepthtestplayers 0 "disables depth testing on players"
set g_nodepthtestitems 0 "disables depth testing on items"
set g_casings 2 "specifies which casings (0: none, 1: only shotgun casings, 2: shotgun and machine gun casings) are sent to the client"
-set g_norecoil 0 "if set to 1 shooting weapons won't make you crosshair to move upwards (recoil)"
+set g_norecoil 1 "if set to 1 shooting weapons won't make you crosshair to move upwards (recoil)"
set g_maplist_mostrecent "" "contains the name of the maps that were most recently played"
set g_maplist_mostrecent_count 3 "number of most recent maps that are blocked from being played again"
set g_maplist_index 0 "this is used internally for saving position in maplist cycle"
set g_maplist_sizes_count_bots 1 "include the number of bots currently in the server when counting the number of players for size restrictions"
set g_maplist_sizes_specparty 0 "this fraction of people are expected to only spectate, reduces player count used to select voting GUI maps"
-set g_items_mindist 4000 "starting distance for the fading of items"
set g_items_maxdist 4500 "maximum distance at which an item can be viewed, after which it will be invisible"
set g_items_dropped_lifetime 20 "default lifetime for dropped items unless explicitly overriden (ie. flags)"
set sv_itemstime 1 "enable networking of time left until respawn for items such as mega health/armor and powerups"
// bans
-set g_ban_default_bantime 5400 "90 minutes"
+set g_ban_default_bantime 5400 "default ban time in seconds"
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_sync_uri "" "sync using this ban list provider (empty string to disable)"
-set g_ban_sync_interval 5 "sync every 5 minutes"
-set g_ban_sync_trusted_servers "" "request ban lists from these xonotic servers (do not include your own server there, or unbanning may fail)"
+set g_ban_sync_interval 5 "sync interval in minutes"
+set g_ban_sync_trusted_servers "" "request ban lists from these Xonotic servers (do not include your own server there, or unbanning may fail)"
set g_ban_sync_timeout 45 "time out in seconds for the ban sync requests"
-set g_ban_sync_trusted_servers_verify 0 "when set to 1, additional bans sent by the servers are ignored, and only bans for the requested IP are used"
-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_ban_sync_trusted_servers_verify 0 "ignore additional bans sent by the servers, and use only bans for the requested IP"
+set g_ban_telluser 1 "notify the banned player they are banned when they try to join"
+set g_banned_list "" "list of banned players; player format: 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_muteban_list "" "list of banned players from chat, format: IP playerkey ..."
-set g_playban_list "" "list of banned players from playing (forced to spectate), format: IP playerkey ..."
-set g_voteban_list "" "list of banned players from voting, format: IP playerkey ..."
+set g_chatban_list "" "list of banned players from chat; player format: IP or playerkey"
+set g_playban_list "" "list of banned players from playing (forced to spectate); player format: IP or playerkey"
+set g_playban_minigames 0 "disallow playbanned players (who are forced to spectate) from playing minigames"
+set g_voteban_list "" "list of banned players from voting; player format: IP or playerkey"
// useful vote aliases
set timelimit_increment 5 "number of minutes added to the timer when voting for extendmatchtime"