]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge remote-tracking branch 'origin/Juhu/scoreboard-strafe' into morosophos/server...
authorNick S <nick@teichisma.info>
Thu, 22 Jun 2023 21:06:06 +0000 (00:06 +0300)
committerNick S <nick@teichisma.info>
Thu, 22 Jun 2023 21:06:06 +0000 (00:06 +0300)
60 files changed:
.gitlab-ci.yml
.tx/merge-base
commands.cfg
common.es.po
common.ja_JP.po
common.la.po
common.zh_CN.po
common.zh_HK.po
common.zh_TW.po
demos/the-big-keybench.dem
effectinfo.txt
languages.txt
maps/campaignxonoticbeta.txt.zh_CN [new file with mode: 0644]
maps/campaignxonoticbeta.txt.zh_HK [new file with mode: 0644]
maps/campaignxonoticbeta.txt.zh_TW [new file with mode: 0644]
models/relics/sign_invisible.tga
models/relics/sign_speed.tga
qcsrc/client/hud/panel/scoreboard.qc
qcsrc/client/items/items.qc
qcsrc/client/items/items.qh
qcsrc/client/main.qc
qcsrc/common/campaign_common.qh
qcsrc/common/campaign_file.qc
qcsrc/common/effects/all.inc
qcsrc/common/effects/effectinfo.inc
qcsrc/common/ent_cs.qc
qcsrc/common/gamemodes/gamemode/clanarena/sv_clanarena.qc
qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qc
qcsrc/common/gamemodes/gamemode/survival/cl_survival.qc
qcsrc/common/gamemodes/gamemode/survival/survival.qc
qcsrc/common/gamemodes/gamemode/survival/sv_survival.qc
qcsrc/common/items/item.qh
qcsrc/common/mapobjects/trigger/viewloc.qc
qcsrc/common/minigames/sv_minigames.qc
qcsrc/common/mutators/mutator/powerups/sv_powerups.qc
qcsrc/common/mutators/mutator/powerups/sv_powerups.qh
qcsrc/common/notifications/all.inc
qcsrc/lib/i18n.qh
qcsrc/menu/xonotic/dialog_welcome.qc
qcsrc/menu/xonotic/util.qc
qcsrc/server/bot/default/scripting.qc
qcsrc/server/campaign.qc
qcsrc/server/campaign.qh
qcsrc/server/client.qc
qcsrc/server/command/banning.qc
qcsrc/server/command/banning.qh
qcsrc/server/command/sv_cmd.qc
qcsrc/server/command/vote.qc
qcsrc/server/damage.qc
qcsrc/server/items/items.qc
qcsrc/server/items/items.qh
qcsrc/server/items/spawning.qc
qcsrc/server/mutators/events.qh
qcsrc/server/weapons/throwing.qc
qcsrc/server/weapons/throwing.qh
qcsrc/server/weapons/weaponsystem.qc
qcsrc/server/world.qc
scripts/relics.shader
xonotic-client.cfg
xonotic-server.cfg

index fc4ce4ffff5d36693fedc43f255f7f70816102ca..5af7d56d147d1570d3be94d65115d859ea1e3522 100644 (file)
@@ -75,7 +75,7 @@ test_sv_game:
     - 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 '^:'
index 398912c6b5e4886a1da6879749ae8c262765ce5a..e32679df7de430374511b209107159fd37261a0d 100644 (file)
@@ -1 +1 @@
-Sat 03 Jun 2023 07:22:57 AM CEST
+Sun 18 Jun 2023 07:22:56 AM CEST
index ea4a41396fcfad5a4619661232e016c036350490..308cd0b8e281dbb3245c5c21c830d11bc69d6d6f 100644 (file)
@@ -282,6 +282,8 @@ alias unmute               "qc_cmd_sv     unmute               ${* ?}" // Unmute
 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  ${* ?}"
 
 
 
@@ -297,7 +299,8 @@ alias voteban                 "qc_cmd_sv     voteban              ${* ?}" // Ban
 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 ${* ?}"
 
index 1bd1298f68a6e2204bb435f220bf113f3365fb09..f9d9beabdfd5a18480703bc77c957275585543c6 100644 (file)
@@ -33,7 +33,7 @@ msgstr ""
 "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"
@@ -708,7 +708,7 @@ msgstr "Número de asesinatos menos suicidios"
 
 #: 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"
@@ -1530,7 +1530,7 @@ msgstr "Combate a muerte"
 
 #: 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"
@@ -1609,8 +1609,8 @@ msgstr "Vidas:"
 #: 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"
@@ -1683,16 +1683,17 @@ msgstr "Superviviente"
 #: 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"
@@ -1701,17 +1702,18 @@ msgstr "Combate a muerte por equipos"
 #: 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
@@ -1797,11 +1799,11 @@ msgstr "Lanzamiento de la @!#%'n Tuba"
 #: 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"
@@ -4535,7 +4537,7 @@ msgstr "^BGHas recogido la pelota"
 
 #: 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 ""
@@ -4912,7 +4914,7 @@ msgstr "tirar granada"
 #: 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
@@ -4921,7 +4923,7 @@ msgstr "¡%s^K1 ha hecho TRIPLE ACIERTO! %s^BG"
 
 #: qcsrc/common/notifications/all.qh:467
 msgid "TRIPLE FRAG! "
-msgstr "¡TRIPLE ELIMINACIÓN! "
+msgstr "¡TRIPLE BAJA! "
 
 #: qcsrc/common/notifications/all.qh:468
 #, c-format
@@ -5043,7 +5045,7 @@ msgstr "¡%d aciertos seguidos! "
 #: 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! "
@@ -5064,7 +5066,7 @@ msgstr "¡Primera víctima! "
 #: 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
@@ -5084,7 +5086,7 @@ msgstr "¡%s^K1 fue el primero en acertar! %s^BG"
 #: 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
@@ -5094,7 +5096,7 @@ msgstr ", finalizando sus %d aciertos seguidos"
 #: 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
@@ -8420,7 +8422,7 @@ msgstr "Burlas automáticas:"
 
 #: 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"
@@ -9059,7 +9061,7 @@ msgstr "¿Deseas iniciar un juego local para configurar el HUD?"
 
 #: 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"
@@ -9104,14 +9106,13 @@ msgstr "Imprimir en línea separada"
 #: 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"
index 1496dc9ebded7d94563a4d78017b329821c647d5..fa1c3aec4c0bbd9bec211bfcab977089a332917b 100644 (file)
@@ -17,7 +17,7 @@ msgstr ""
 "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"
@@ -1648,11 +1648,11 @@ msgstr "サバイバー"
 
 #: 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"
@@ -1664,11 +1664,11 @@ msgstr "チームデスマッチ (TDM)"
 
 #: 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 ""
index 2d4217afcb666985f3dbfd1b3c23fdb35b3831f6..b8925fbdf2a1e8041750aea9f0943f5924dfc848 100644 (file)
@@ -695,11 +695,11 @@ msgstr "SCO^pilport"
 
 #: 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"
@@ -847,11 +847,11 @@ msgstr "SCO^quot vicit"
 
 #: 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"
@@ -879,11 +879,11 @@ msgstr "summa"
 
 #: 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)"
@@ -1639,19 +1639,21 @@ msgstr "Circumitus:"
 
 #: 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"
@@ -1663,11 +1665,11 @@ msgstr "Manuum interfectio"
 
 #: 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 ""
@@ -2452,7 +2454,7 @@ msgstr "^1Moderatri notae:"
 
 #: 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"
@@ -2461,15 +2463,15 @@ msgstr ""
 
 #: 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
@@ -3354,7 +3356,7 @@ msgstr "^BG%s^F3 expulsus est quia manus collegas nimis interficiebat"
 #: 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
@@ -3437,12 +3439,12 @@ msgstr "^BG%s^K1 Perarma tenet"
 #: 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"
@@ -3717,7 +3719,7 @@ msgstr ""
 #, 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
@@ -3782,7 +3784,7 @@ msgstr ""
 #: 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
@@ -4414,6 +4416,8 @@ msgid ""
 "^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"
@@ -4421,7 +4425,7 @@ msgstr "^BGPilam cepisti"
 
 #: 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 ""
@@ -4655,7 +4659,7 @@ msgstr "^F2Invisibilitas evanuit"
 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!"
@@ -4690,11 +4694,11 @@ msgstr "^F2Perarma tenes"
 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"
@@ -4754,11 +4758,11 @@ msgstr "^F2Irrumpens visus, scuta detrahuntur!"
 #: 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
@@ -6067,7 +6071,7 @@ msgstr "Sinica (Sina)"
 
 #: qcsrc/menu/xonotic/credits.qc:180
 msgid "Chinese (Hong Kong)"
-msgstr ""
+msgstr "Sinica (Hong Cong)"
 
 #: qcsrc/menu/xonotic/credits.qc:193
 msgid "Chinese (Taiwan)"
@@ -6107,7 +6111,7 @@ msgstr "Hungarica"
 
 #: qcsrc/menu/xonotic/credits.qc:275
 msgid "Indonesian"
-msgstr ""
+msgstr "Indonesica"
 
 #: qcsrc/menu/xonotic/credits.qc:280
 msgid "Irish"
@@ -6131,7 +6135,7 @@ msgstr "Coreana"
 
 #: qcsrc/menu/xonotic/credits.qc:307
 msgid "Latin"
-msgstr ""
+msgstr "Latina"
 
 #: qcsrc/menu/xonotic/credits.qc:310
 msgid "Polish"
index 85d3c5f1f0fbc7217c7203611ca009a317ebfabe..9e9606387134b2237e06a272444c2ef6171dab81 100644 (file)
@@ -712,11 +712,11 @@ msgstr "进球"
 
 #: 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"
@@ -896,7 +896,7 @@ msgstr "总和"
 
 #: 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"
index d954d65063beac2efa21e550e4ffa674717ade27..43f1734b5d2615bd0122d00ec783dd371babe39c 100644 (file)
@@ -706,11 +706,11 @@ msgstr "進球"
 
 #: 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"
@@ -890,7 +890,7 @@ msgstr "總和"
 
 #: 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"
index 5d076b141d2c982723717ab4f808bc560cbc195e..7aff493c4fc3cea642a1af7c70598aae20a238bc 100644 (file)
@@ -714,11 +714,11 @@ msgstr "進球"
 
 #: 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"
@@ -898,7 +898,7 @@ msgstr "總和"
 
 #: 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"
index 52e6b396beaa340dd2953f92b46ac77551e9bd39..84e11a81adb7b3b9c2cfb3a8560bee8e5d71b5ae 100644 (file)
Binary files a/demos/the-big-keybench.dem and b/demos/the-big-keybench.dem differ
index 7fc732b7da5037d59c87a0762f85c71f6be3a813..81b71a57900173ad2ba3bb0878d480f206c22111 100644 (file)
@@ -8390,3 +8390,19 @@ effect respawn_ghost
        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
index 6d62da3576f11f6ee565e5a6a5170129e8975745..793ae991ef592132a9556a4797622ab5011ee72c 100644 (file)
@@ -4,11 +4,11 @@ de    "German" "Deutsch" 100%
 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%
@@ -28,5 +28,5 @@ uk    "Ukrainian" "Українська" 48%
 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%
diff --git a/maps/campaignxonoticbeta.txt.zh_CN b/maps/campaignxonoticbeta.txt.zh_CN
new file mode 100644 (file)
index 0000000..91e89ef
--- /dev/null
@@ -0,0 +1,44 @@
+//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
diff --git a/maps/campaignxonoticbeta.txt.zh_HK b/maps/campaignxonoticbeta.txt.zh_HK
new file mode 100644 (file)
index 0000000..04e203b
--- /dev/null
@@ -0,0 +1,44 @@
+//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
diff --git a/maps/campaignxonoticbeta.txt.zh_TW b/maps/campaignxonoticbeta.txt.zh_TW
new file mode 100644 (file)
index 0000000..3e9c63f
--- /dev/null
@@ -0,0 +1,44 @@
+//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
index de1745b09d168a21ab641ce439e51fc0b7ee419b..28b31a0b2eb386f3638fe0fb3325155ef64b9d8a 100644 (file)
Binary files a/models/relics/sign_invisible.tga and b/models/relics/sign_invisible.tga differ
index bca09682511e360c5e72fdb1827e577fb006326c..4deadbe34907b8c35d3318fa1ef7c7e9aeb9f761 100644 (file)
Binary files a/models/relics/sign_speed.tga and b/models/relics/sign_speed.tga differ
index 9e21ccfda0555bce3dddaf28791fb383f63bdc2f..f3b1091bde9a1721dff195148af74fbdf09ca94c 100644 (file)
@@ -123,60 +123,66 @@ string Label_getInfo(string label, int mode)
        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;
@@ -739,13 +745,14 @@ void Cmd_Scoreboard_Help()
 // 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" \
@@ -976,7 +983,13 @@ string Scoreboard_GetName(entity pl)
        }
        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";
index f4abe998999df6f273de160cb0ce37195ef8b388..755f0f9cd40d0a52aff03018e70a485ef9574d17 100644 (file)
 #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)
@@ -246,27 +312,17 @@ NET_HANDLE(ENT_CLIENT_ITEM, bool isnew)
        {
                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;
index 78e109db46d6368b076f399957a5aefbdf65ca37..0a8fd62b414408fad601022eb3204993eaa5cb79 100644 (file)
@@ -2,18 +2,18 @@
 
 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);
index ce7f51e1d9356619f63a09ca14282f72e23fdea2..58d497aab77b176a9ef60b951762e60d7eecac95 100644 (file)
@@ -541,7 +541,7 @@ float CSQC_InputEvent(int bInputType, float nPrimary, float nSecondary)
 
 // --------------------------------------------------------------------------
 // BEGIN OPTIONAL CSQC FUNCTIONS
-
+.int survival_status;
 void Ent_RemovePlayerScore(entity this)
 {
        if(this.owner) {
@@ -550,6 +550,11 @@ void Ent_RemovePlayerScore(entity this)
                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;
        }
 }
 
@@ -1397,18 +1402,18 @@ bool net_handle_ServerWelcome()
        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;
        }
index 088aefea09a0479c2061e483491923090653df60..8f70c51cfaf5e960b6c7c8982332926070f88242 100644 (file)
@@ -14,8 +14,10 @@ float campaign_botskill[CAMPAIGN_MAX_ENTRIES];
 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
index 65e1917fabdddc6a5b5c4ba274a3c6dd58a87133..0fef7816b34720448f34b36185ef14e6b40e6e45 100644 (file)
@@ -62,8 +62,13 @@ float CampaignFile_Load(int offset, float n)
                                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");
@@ -95,8 +100,10 @@ void CampaignFile_Unload()
                        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;
        }
index 179d96224ed5109a764cef8570e8217fab7a4f24..d7654f06ad6370ba864b9568fafca49552e450f9 100644 (file)
@@ -231,6 +231,7 @@ entity EFFECT_CAP(int teamid)
 
 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")
index 090d2b111902b66247a52978b1c404598a5b4c1a..e6909e4cdbcd60a1860f8124578b3c0d416a854e 100644 (file)
@@ -9107,4 +9107,29 @@ SUB(respawn_ghost) {
        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!
index b84a98ca6d3c9d1f723eec988d7cb89bb2de9918..85119de08825c52ab9c6692d87d4d9a3eb9fd83b 100644 (file)
@@ -157,11 +157,6 @@ ENTCS_PROP(SOLID, true, sv_solid, solid, ENTCS_SET_NORMAL,
        { 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;
index 7a16141fc8ebbe89409b947cdfd9b979da571233..f0a793afd50f4d39aa2d28fe1e3bfc313e438e43 100644 (file)
@@ -270,6 +270,9 @@ MUTATOR_HOOKFUNCTION(ca, PutClientInServer)
                        Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
                }
        }
+
+       if (!warmup_stage)
+               eliminatedPlayers.SendFlags |= 1;
 }
 
 MUTATOR_HOOKFUNCTION(ca, reset_map_players)
index f1e6b1ce2ec0f5b7fb3f361f9674806459d525e6..77fbfc231633cda534ad78aeca6e3484cb2cfdbf 100644 (file)
@@ -429,6 +429,11 @@ MUTATOR_HOOKFUNCTION(ft, PlayerSpawn)
        return true;
 }
 
+MUTATOR_HOOKFUNCTION(ft, PutClientInServer)
+{
+       eliminatedPlayers.SendFlags |= 1;
+}
+
 MUTATOR_HOOKFUNCTION(ft, reset_map_players)
 {
        FOREACH_CLIENT(IS_PLAYER(it), {
index 02d0907abccf0a40b28a6c388455fd962cd8f2ad..0f57de68111debd0a796b5eb1b5fb6b4cde86dd2 100644 (file)
@@ -3,14 +3,61 @@
 #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");
@@ -23,11 +70,6 @@ void HUD_Mod_Survival(vector pos, vector mySize)
                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);
 }
@@ -40,16 +82,24 @@ MUTATOR_HOOKFUNCTION(cl_surv, ForcePlayercolors_Skip, CBC_ORDER_LAST)
                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)
index 1f2d1440224a593013f901868d688cd10424e5e3..a8a67ad44efe5e76395c1587fe1b13c4ea2ff201 100644 (file)
@@ -1 +1,2 @@
 #include "survival.qh"
+REGISTER_NET_LINKED(ENT_CLIENT_SURVIVALSTATUSES)
index 6414686e0bcc67638503f6aaeda15ecb9e67754b..cfdc948c04748576a07bc9a345cf5ffd12145882 100644 (file)
@@ -8,6 +8,54 @@ bool autocvar_g_survival_reward_survival = true;
 
 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
@@ -48,6 +96,7 @@ float Surv_CheckWinner()
                allowed_to_spawn = false;
                game_stopped = true;
                round_handler_Init(5, autocvar_g_survival_warmup, autocvar_g_survival_round_timelimit);
+               SurvivalStatuses_Send();
                return 1;
        }
 
@@ -85,6 +134,7 @@ float Surv_CheckWinner()
        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,
        {
@@ -119,6 +169,7 @@ void Surv_RoundStart()
                total_hunters++;
                it.survival_status = SURV_STATUS_HUNTER;
        });
+       SurvivalStatuses_Send();
 
        FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
        {
@@ -181,6 +232,7 @@ void surv_Initialize() // run at the start of a match, initiates game mode
        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();
 }
 
 
@@ -207,21 +259,6 @@ MUTATOR_HOOKFUNCTION(surv, ClientObituary)
                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);
@@ -258,6 +295,9 @@ MUTATOR_HOOKFUNCTION(surv, PutClientInServer)
                        Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
                }
        }
+
+       if (!warmup_stage)
+               eliminatedPlayers.SendFlags |= 1;
 }
 
 MUTATOR_HOOKFUNCTION(surv, reset_map_players)
@@ -273,6 +313,7 @@ MUTATOR_HOOKFUNCTION(surv, reset_map_players)
                }
        });
        bot_relinkplayerlist();
+       SurvivalStatuses_Send();
        return true;
 }
 
@@ -330,7 +371,7 @@ MUTATOR_HOOKFUNCTION(surv, PlayerDies)
 
        // 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;
 }
index 350421465459bdd57312511b2cd935c565327a1d..038566784725b962e5403755f2e3cae674a13323 100644 (file)
@@ -57,6 +57,14 @@ const int ITS_AVAILABLE         = BIT(3);
 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;
index 4679e75f7f0f521c4248676365578de1e10b6851..eb3ae7f4b67e6ba3a07ee3b1d1c9d6dc271e7c8b 100644 (file)
@@ -115,7 +115,7 @@ spawnfunc(trigger_viewlocation)
        // 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);
 }
 
index 57f6f46b6153570fe0b4c276875d07664191b539..46f98959272627c1b52aeead334f03dff83277f9 100644 (file)
@@ -213,7 +213,7 @@ entity join_minigame(entity player, string game_id )
        return NULL;
 }
 
-void part_minigame(entity player )
+void part_minigame(entity player)
 {
        entity minig = CS(player).active_minigame;
 
@@ -268,6 +268,8 @@ string invite_minigame(entity inviter, entity player)
                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");
 
@@ -312,6 +314,13 @@ void ClientCommand_minigame(entity caller, int request, int argc, string command
                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);
index d655ae19a29458e83bd7b52ab75becd9eb6ff7fe..4fb9882c97ea94e195f21a0d115c80bbc1b6d8ef 100644 (file)
@@ -79,18 +79,15 @@ MUTATOR_HOOKFUNCTION(powerups, MonsterValidTarget)
 
 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)
@@ -125,14 +122,9 @@ 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);
@@ -142,10 +134,7 @@ void powerups_DropItem(entity this, StatusEffects effect, bool freezeTimer)
        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)
index 06098c95ebf1095c3297ab23f57d32608948b652..005cbcba42da09be81afa3cbb37e4765892b1166 100644 (file)
@@ -14,5 +14,7 @@ REGISTER_MUTATOR(powerups, true);
 .float prevstrengthsound;
 .float prevstrengthsoundattempt;
 
+void powerups_DropItem_Think(entity this);
+
 // q3compat
 .float count;
index effed26d98f90084dd53d1afb8da23e5db5664a5..57877daccd6338f6e3463003cb0870dccb4f2901 100644 (file)
@@ -455,7 +455,7 @@ string multiteam_info_sprintf(string input, string teamname) { return ((input !=
 
     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"), "")
@@ -785,9 +785,9 @@ string multiteam_info_sprintf(string input, string teamname) { return ((input !=
     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)
index 3069f519dc33454a5310a68a52e9ba16d2570fd8..657cadac61b4ffa7754ee0e5f148110812a8ec5e 100644 (file)
@@ -14,7 +14,7 @@ ERASEABLE
 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)
index bf45084d73bc47b305d62ed831e09536e42f14b9..1651af5e6d4ca725e0ff29635227bbe78881d449 100644 (file)
@@ -6,6 +6,7 @@
 #include "radiobutton.qh"
 #include "commandbutton.qh"
 #include "slider.qh"
+#include <common/campaign_common.qh>
 
 void welcomeDialog_resetStrings(entity me)
 {
@@ -56,6 +57,23 @@ void XonoticWelcomeDialog_readInputArgs(entity me, int argsbuf)
                        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);
index c038d52347e999cb61198efdfdf5d9fb92c523af..ac3c798a9f3f424e8fbe338b8459fc9ada444f27 100644 (file)
@@ -551,7 +551,7 @@ void preMenuDraw()
                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;
 
@@ -662,8 +662,8 @@ float updateCompression()
        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) \
index 5a13330652f576268b6ce42a2939716d4517a66f..4ad6246e611a797a821a0fb63ae7d78900cac2f0 100644 (file)
@@ -390,7 +390,7 @@ void bot_cmdhelp(string scmd)
                                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"
index bdb704b985d5beba3e3f707f71c4615a480cc08a..6ebce75b8e3e90a74fc8f1073cbb61ace801988f 100644 (file)
@@ -41,21 +41,11 @@ float Campaign_Invalid()
        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;
index 10e06a240f836f6328cf7834c6a66989529e6a1a..a9c790e3647a9cb0d8622652c63ee85de49956e2 100644 (file)
@@ -10,9 +10,7 @@ int autocvar_g_campaign_skill;
 // 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();
index e55fc78eefce53b1856e1dbfeeb01bf1281343f9..00063bba214fae3ba064a144ec18bf2983db2eae 100644 (file)
@@ -982,7 +982,7 @@ bool findinlist_abbrev(string tofind, string list)
                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;
        });
@@ -1010,6 +1010,8 @@ bool PlayerInIDList(entity p, string idlist)
 
 bool PlayerInList(entity player, string list)
 {
+       if (list == "")
+               return false;
        return boolean(PlayerInIDList(player, list) || PlayerInIPList(player, list));
 }
 
@@ -1042,9 +1044,7 @@ void SendWelcomeMessage(entity this, int msg_type)
        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);
@@ -1192,7 +1192,7 @@ void ClientConnect(entity this)
        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);
index 933eb3c83d8e85fc59ebb825136aa0e641d968cf..b4f0bb3082a4980bb4ed6e9c31f1b958a88b1246 100644 (file)
@@ -1,6 +1,7 @@
 #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>
@@ -133,13 +134,13 @@ void BanCommand_mute(int request, int argc, string command)
                                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;
                                }
@@ -156,7 +157,7 @@ void BanCommand_mute(int request, int argc, string command)
                {
                        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;
                }
        }
@@ -183,6 +184,8 @@ void BanCommand_playban(int request, int argc, string command)
 
                                        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;
@@ -267,14 +270,14 @@ void BanCommand_unmute(int request, int argc)
                                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;
 
@@ -293,7 +296,7 @@ void BanCommand_unmute(int request, int argc)
                {
                        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;
                }
        }
index 6aa70c6a8028a7dcca41d5ea229c5e51f3d71424..4c373b52e2d946fd0950a975af6567a483190ec1 100644 (file)
@@ -10,9 +10,10 @@ string autocvar_g_ban_sync_uri;
 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;
index a52b5252aa31fe0a837474b231be03df21fe11ca..4d61a3962268b62df4e64fa3d108d7a9ee174b34 100644 (file)
@@ -466,7 +466,7 @@ void GameCommand_bot_cmd(int request, int argc, string command)
                        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;
@@ -1070,7 +1070,7 @@ void GameCommand_moveplayer(int request, int argc)
                                        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);
 
index 118b852a632c0476b14a9efd2974b9e21eeffcc3..a3d146c75f7529cbe8a9e9689e717a983ceefe43 100644 (file)
@@ -754,6 +754,29 @@ int VoteCommand_parse(entity caller, string vote_command, string vote_list, floa
 
        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
                {
@@ -806,6 +829,22 @@ int VoteCommand_parse(entity caller, string vote_command, string vote_list, floa
                        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));
@@ -814,7 +853,6 @@ int VoteCommand_parse(entity caller, string vote_command, string vote_list, floa
                                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));
 
index cf0dedd97e08e2ef8ada6de35c2e3ff6beb6e7ed..74eef6a7c403ea8644c9fa8156905376502c0b94 100644 (file)
@@ -245,8 +245,10 @@ void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .en
        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(
index 57720ef9aff2e29fe8912ba276d50bb379e58600..d0353866ff9007e0fe5ac16c30fe0f9e60cbde8c 100644 (file)
@@ -59,7 +59,6 @@ bool ItemSend(entity this, entity to, int sf)
        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");
@@ -158,10 +157,7 @@ void Item_Show(entity e, int mode)
                }
                else
                {
-                       //setmodel(e, "null");
                        e.solid = SOLID_NOT;
-                       e.colormod = '0 0 0';
-                       //e.glowmod = e.colormod;
                        e.spawnshieldtime = 1;
                        e.ItemStatus &= ~ITS_AVAILABLE;
                }
@@ -188,9 +184,45 @@ void Item_Show(entity e, int mode)
 
 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);
@@ -213,9 +245,6 @@ void Item_Respawn(entity this)
 
        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)
@@ -701,11 +730,9 @@ LABEL(pickup)
 
        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))
@@ -820,7 +847,6 @@ void RemoveItem(entity 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);
 }
 
@@ -940,16 +966,15 @@ void item_use(entity this, entity actor, entity trigger)
 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);
@@ -962,16 +987,14 @@ void _StartItem(entity this, entity def, float defaultrespawntime, float default
        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
@@ -981,14 +1004,24 @@ void _StartItem(entity this, entity def, float defaultrespawntime, float default
                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;
@@ -1013,6 +1046,8 @@ void _StartItem(entity this, entity def, float defaultrespawntime, float default
        }
        else
        {
+               this.reset = Item_Reset;
+
                // must be done after def.m_iteminit() as that may set ITEM_FLAG_MUTATORBLOCKED
                if(!have_pickup_item(this))
                {
@@ -1038,7 +1073,6 @@ void _StartItem(entity this, entity def, float defaultrespawntime, float default
                        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;
@@ -1051,9 +1085,6 @@ void _StartItem(entity this, entity def, float defaultrespawntime, float default
                {
                        // 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);
@@ -1102,25 +1133,21 @@ void _StartItem(entity this, entity def, float defaultrespawntime, float default
                }
 
                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;
@@ -1129,11 +1156,6 @@ void _StartItem(entity this, entity def, float defaultrespawntime, float default
                        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
@@ -1151,6 +1173,7 @@ void _StartItem(entity this, entity def, float defaultrespawntime, float default
 
                this.effects |= EF_NOGUNBOB; // marker for item team search
                InitializeEntity(this, Item_FindTeam, INITPRIO_FINDTARGET);
+               this.reset = Item_FindTeam;
        }
        else
                Item_Reset(this);
@@ -1166,7 +1189,8 @@ void _StartItem(entity this, entity def, float defaultrespawntime, float default
        }
 
        // 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);
index 8af67c8deea9a203ff6b759abb19ed24b3921f32..bd26ce4b675e359e6df576d5befc5ce5acd4f9b5 100644 (file)
@@ -5,7 +5,6 @@
 
 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;
index 561a5673a08011856e26d68cd28ecf8d9786add7..2f066834417387fb16769a8641cd677b0f4c4a19 100644 (file)
@@ -115,7 +115,9 @@ bool Item_InitializeLoot(entity item, string class_name, vector position,
        }
        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;
 }
 
index ce9f958266efbfc203192a3fb07b01b65dc78680..46fb4ede17e4cc4fdb67d82ae8149fd4eee31bd9 100644 (file)
@@ -621,7 +621,10 @@ MUTATOR_HOOKABLE(SetWeaponreplace, EV_SetWeaponreplace);
     /**/
 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) \
index 01c6ac0d200634347c27d88ba2631322b8fb7c66..a979b7b0c65f9689e33be33270414fca26d37ba7 100644 (file)
 #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)
 {
@@ -91,9 +73,7 @@ float W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector
        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);
index b3c7df645e26c247e6332b23987b7b09ae683fd4..afbc51851632648b89cf01c0ff3a3b1c9d2a63ec 100644 (file)
@@ -5,9 +5,6 @@
 
 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);
 
index df6490128aa993ac84239930a0a477336d078fc0..20e713c2a7db4c6b287f5f63792f02ed054d9310 100644 (file)
@@ -291,7 +291,7 @@ bool weapon_prepareattack_check(Weapon thiswep, entity actor, .entity weaponenti
                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;
 }
index 8b38b06fe2fe51af63cbf251d0504e4fb1827ac6..977f235715a90a90064d4a8f332f3dbd36595af1 100644 (file)
@@ -236,8 +236,9 @@ void cvar_changes_init()
                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_");
index e070ea2b6c8bd673cb316aa5650370394264cdc1..a25c990f6c1013c29691bc325ec21d90089621fd 100644 (file)
@@ -89,7 +89,7 @@ models/relics/sign_invisible
        nopicmip
        {
                map models/relics/sign_invisible
-               blendfunc add
+               blendfunc blend
        }
 }
 
@@ -144,7 +144,7 @@ models/relics/sign_speed
        nopicmip
        {
                map models/relics/sign_speed
-               blendfunc add
+               blendfunc blend
        }
 }
 
index 4aab3265ad7af951c8f04c9343bdf9cc7c48cf71..f260bcd26013469f9adf407e6080a36206d77e3b 100644 (file)
@@ -76,7 +76,7 @@ seta cl_spawn_point_particles 1 "pointparticles effect at all spawn points" // m
 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
@@ -810,9 +810,6 @@ seta cl_autodemo_delete_keeprecords 0 "when 1, records with a newly made race/ct
 // 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
 
@@ -904,11 +901,12 @@ exec hud_luma.cfg
 // 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)"
index 576cf801d4478b3a64160cf31798c5a553eb977b..e869b1b311f5b3bab048a4c6d3a2bb4db20da08c 100644 (file)
@@ -221,7 +221,7 @@ set g_fullbrightitems 0 "disables lighting effects on items, making them appear
 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"
@@ -233,7 +233,6 @@ set g_maplist_sizes_count_maxplayers 1 "check the player limit when getting the
 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)"
 
@@ -396,19 +395,20 @@ set sv_waypointsprite_limitedrange 5120 "default maximum viewing distance of way
 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"