]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge remote-tracking branch 'origin/terencehill/menu_translation_percentage'
authorRudolf Polzer <divverent@xonotic.org>
Wed, 4 Jun 2014 09:30:13 +0000 (11:30 +0200)
committerRudolf Polzer <divverent@xonotic.org>
Wed, 4 Jun 2014 09:30:13 +0000 (11:30 +0200)
* origin/terencehill/menu_translation_percentage:
  Split the Text Language listbox into two separate columns: name on the left, percentage on the right

74 files changed:
defaultXonotic.cfg
gamemodes.cfg
models/ok_player/okmale1.dpm_0.txt
models/ok_player/okmale2.dpm_0.txt
models/ok_player/okmale3.dpm_0.txt
models/ok_player/okmale4.dpm_0.txt
models/ok_player/okrobot1.dpm_0.txt
models/ok_player/okrobot2.dpm_0.txt
models/ok_player/okrobot3.dpm_0.txt
models/ok_player/okrobot4.dpm_0.txt
monsters.cfg
particles/hook_green.tga [deleted file]
particles/hook_white.tga [new file with mode: 0644]
post-config.cfg
qcsrc/Makefile
qcsrc/client/Main.qc
qcsrc/client/View.qc
qcsrc/client/autocvars.qh
qcsrc/client/hook.qc
qcsrc/client/hud.qh
qcsrc/client/miscfunctions.qc
qcsrc/client/scoreboard.qc
qcsrc/client/waypointsprites.qc
qcsrc/common/mapinfo.qc
qcsrc/common/mapinfo.qh
qcsrc/common/monsters/monster/mage.qc
qcsrc/common/monsters/monster/shambler.qc
qcsrc/common/monsters/monster/spider.qc
qcsrc/common/monsters/monster/wyvern.qc
qcsrc/common/monsters/monster/zombie.qc
qcsrc/common/monsters/monsters.qc
qcsrc/common/monsters/spawn.qc
qcsrc/common/monsters/spawn.qh
qcsrc/common/monsters/sv_monsters.qc
qcsrc/common/monsters/sv_monsters.qh
qcsrc/common/notifications.qh
qcsrc/common/util.qc
qcsrc/common/util.qh
qcsrc/menu/classes.c
qcsrc/menu/xonotic/dialog_firstrun.c
qcsrc/menu/xonotic/dialog_settings_user_languagewarning.c [new file with mode: 0644]
qcsrc/menu/xonotic/keybinder.c
qcsrc/menu/xonotic/languagelist.c
qcsrc/menu/xonotic/mainwindow.c
qcsrc/menu/xonotic/util.qc
qcsrc/server/anticheat.qc
qcsrc/server/anticheat.qh
qcsrc/server/autocvars.qh
qcsrc/server/bot/havocbot/havocbot.qc
qcsrc/server/bot/navigation.qc
qcsrc/server/bot/waypoints.qc
qcsrc/server/bot/waypoints.qh
qcsrc/server/cl_client.qc
qcsrc/server/cl_physics.qc
qcsrc/server/cl_player.qc
qcsrc/server/cl_weaponsystem.qc
qcsrc/server/command/cmd.qc
qcsrc/server/command/getreplies.qc
qcsrc/server/command/sv_cmd.qc
qcsrc/server/g_damage.qc
qcsrc/server/g_world.qc
qcsrc/server/mutators/gamemode_ctf.qc
qcsrc/server/mutators/gamemode_invasion.qc
qcsrc/server/mutators/gamemode_invasion.qh
qcsrc/server/mutators/gamemode_keepaway.qc
qcsrc/server/mutators/gamemode_nexball.qc
qcsrc/server/mutators/mutator_spawn_near_teammate.qc
qcsrc/server/playerstats.qc
qcsrc/server/sv_main.qc
qcsrc/server/t_teleporters.qc
qcsrc/server/target_music.qc
qcsrc/server/teamplay.qc
qcsrc/server/w_common.qc
qcsrc/server/w_minelayer.qc

index 747e0c4192e9a77e434719bbeaf6cc4d0268ccdf..2c048b39eac92ecebba09fbd6006e7623a6ca201 100644 (file)
@@ -56,7 +56,7 @@ _cl_playermodel models/player/erebus.iqm
 _cl_playerskin 0
 
 seta cl_reticle 1 "control for toggling whether ANY zoom reticles are shown"
-seta cl_reticle_stretch 0 "whether to stretch reticles so they fit the screen (brakes image proportions)"
+seta cl_reticle_stretch 0 "whether to stretch reticles so they fit the screen (breaks image proportions)"
 seta cl_reticle_item_nex 1 "draw aiming reticle for the nex weapon's zoom, 0 disables and values between 0 and 1 change alpha"
 seta cl_reticle_item_normal 1 "draw reticle when zooming with the zoom button, 0 disables and values between 0 and 1 change alpha"
 fov 100
@@ -224,6 +224,7 @@ seta cl_hitsound 1 "play a hit notifier sound when you have hit an enemy"
 set cl_hitsound_antispam_time 0.05 "don't play the hitsound more often than this"
 
 seta cl_eventchase_death 1 "camera goes into 3rd person mode when the player is dead"
+seta cl_eventchase_nexball 1 "camera goes into 3rd person mode when in nexball game-mode"
 seta cl_eventchase_distance 140 "final camera distance"
 seta cl_eventchase_speed 1.3 "how fast the camera slides back, 0 is instant"
 seta cl_eventchase_maxs "12 12 8" "max size of eventchase camera bbox"
index 30352bba7be4bbeef4a341674f5151d4dfb030da..dfc240536e655cc16867fbee950cb57046e0c681 100644 (file)
@@ -80,7 +80,7 @@ seta g_keyhunt_point_leadlimit -1     "Keyhunt point lead limit overriding the mapin
 seta g_race_laps_limit -1      "Race laps limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
 seta g_nexball_goallimit -1 "Nexball goal limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
 seta g_nexball_goalleadlimit -1 "Nexball goal lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
-seta g_invasion_round_limit -1 "Invasion round limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta g_invasion_point_limit -1 "Invasion point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
 
 
 // =================================
@@ -475,4 +475,7 @@ set g_invasion_round_timelimit 120 "maximum time to kill all monsters"
 set g_invasion_warmup 10 "time between waves to prepare for battle"
 set g_invasion_monster_count 10 "number of monsters on first wave (increments)"
 set g_invasion_zombies_only 0 "only spawn zombies"
-set g_invasion_spawn_delay 2 "spawn more monsters after this delay"
+set g_invasion_spawn_delay 0.25
+set g_invasion_spawnpoint_spawn_delay 0.5
+set g_invasion_teams 0 "number of teams in invasion (note: use mapinfo to set this)"
+set g_invasion_team_spawns 1 "use team spawns in teamplay invasion mode"
index da0d2a16a24a7083bfeb2aa2296ffebb4defe7b7..b9f8a15e6353e81558c29b96eb1d9574369a0d11 100644 (file)
@@ -3,5 +3,12 @@ species human
 sex Male
 weight 105
 age 26
+bone_upperbody Bip001 Neck
+bone_aim0 0.25 Bip001 Neck
+bone_aim1 0.4 Bip001 Neck
+bone_aim2 0.2 Bip001 L UpperArm
+bone_aim3 0.35 bip01 r hand
+bone_weapon bip01 r hand
+fixbone 1
 
 overkill solider
index 3e3a6cfd836a6990f21bf266bc1ccd369b16d628..96c2f5b1cab20c81176442999d9a6f106ebd7280 100644 (file)
@@ -3,5 +3,12 @@ species human
 sex Male
 weight 105
 age 26
+bone_upperbody Bip001 Neck
+bone_aim0 0.25 Bip001 Neck
+bone_aim1 0.4 Bip001 Neck
+bone_aim2 0.2 Bip001 L UpperArm
+bone_aim3 0.35 bip01 r hand
+bone_weapon bip01 r hand
+fixbone 1
 
 overkill officer
\ No newline at end of file
index 7fa399e58dc840ba3fd965b2e8e0b898cced802b..406aac21e6723d2ca5c8b7e547b3012d2aa2b40e 100644 (file)
@@ -3,5 +3,12 @@ species human
 sex Male
 weight 105
 age 28
+bone_upperbody Bip001 Neck
+bone_aim0 0.25 Bip001 Neck
+bone_aim1 0.4 Bip001 Neck
+bone_aim2 0.2 Bip001 L UpperArm
+bone_aim3 0.35 bip01 r hand
+bone_weapon bip01 r hand
+fixbone 1
 
 overkill solider
index 7fa399e58dc840ba3fd965b2e8e0b898cced802b..406aac21e6723d2ca5c8b7e547b3012d2aa2b40e 100644 (file)
@@ -3,5 +3,12 @@ species human
 sex Male
 weight 105
 age 28
+bone_upperbody Bip001 Neck
+bone_aim0 0.25 Bip001 Neck
+bone_aim1 0.4 Bip001 Neck
+bone_aim2 0.2 Bip001 L UpperArm
+bone_aim3 0.35 bip01 r hand
+bone_weapon bip01 r hand
+fixbone 1
 
 overkill solider
index 0b52a01c88718b0c62b687b5f5070692f4811dc1..844f425282d35a86c630039767ab94fada298d8f 100644 (file)
@@ -3,5 +3,12 @@ species robot_solid
 sex None
 weight 200
 age 1
+bone_upperbody Bip001 Neck
+bone_aim0 0.25 Bip001 Neck
+bone_aim1 0.4 Bip001 Neck
+bone_aim2 0.2 Bip001 L UpperArm
+bone_aim3 0.35 bip01 r hand
+bone_weapon bip01 r hand
+fixbone 1
 
 Overkill robot
index 0b52a01c88718b0c62b687b5f5070692f4811dc1..844f425282d35a86c630039767ab94fada298d8f 100644 (file)
@@ -3,5 +3,12 @@ species robot_solid
 sex None
 weight 200
 age 1
+bone_upperbody Bip001 Neck
+bone_aim0 0.25 Bip001 Neck
+bone_aim1 0.4 Bip001 Neck
+bone_aim2 0.2 Bip001 L UpperArm
+bone_aim3 0.35 bip01 r hand
+bone_weapon bip01 r hand
+fixbone 1
 
 Overkill robot
index 3157342b2638a0d2a0cdf1b7eb00ef2405524398..03dd3f2f14457682df41588aa2ddbfd278ce8a95 100644 (file)
@@ -3,5 +3,12 @@ species robot_solid
 sex None
 weight 200
 age 1
+bone_upperbody Bip001 Neck
+bone_aim0 0.25 Bip001 Neck
+bone_aim1 0.4 Bip001 Neck
+bone_aim2 0.2 Bip001 L UpperArm
+bone_aim3 0.35 bip01 r hand
+bone_weapon bip01 r hand
+fixbone 1
 
 Overkill robot
index 3157342b2638a0d2a0cdf1b7eb00ef2405524398..03dd3f2f14457682df41588aa2ddbfd278ce8a95 100644 (file)
@@ -3,5 +3,12 @@ species robot_solid
 sex None
 weight 200
 age 1
+bone_upperbody Bip001 Neck
+bone_aim0 0.25 Bip001 Neck
+bone_aim1 0.4 Bip001 Neck
+bone_aim2 0.2 Bip001 L UpperArm
+bone_aim3 0.35 bip01 r hand
+bone_weapon bip01 r hand
+fixbone 1
 
 Overkill robot
index 296d24b193acaf816ebe130cba6f91208cd9b502..3ff25a404f410256a83243c070db9bfaeb75c621 100644 (file)
@@ -90,6 +90,7 @@ set g_monsters_owners 1
 set g_monsters_teams 1
 set g_monsters_score_kill 0
 set g_monsters_score_spawned 0
+set g_monsters_sounds 1
 set g_monsters_spawnshieldtime 2
 set g_monsters_typefrag 1
 set g_monsters_target_range 2000
diff --git a/particles/hook_green.tga b/particles/hook_green.tga
deleted file mode 100644 (file)
index e01eb47..0000000
Binary files a/particles/hook_green.tga and /dev/null differ
diff --git a/particles/hook_white.tga b/particles/hook_white.tga
new file mode 100644 (file)
index 0000000..c60f070
Binary files /dev/null and b/particles/hook_white.tga differ
index 7fb4ea4af2b44490fc3c8f646f88c5b67101c564..f74727f2e8fc819566a4e3be3cc5898838a6a06b 100644 (file)
@@ -1,6 +1,6 @@
-// Mark all cvars listed in cl_forced_saved_cvars as saved. That way they'll never disappear from config.cfg.
-alias _cl_forced_saved_cvars_next "set _forced_saved_cvar_ doit; set _forced_saved_cvar_${1 ?} done; _cl_forced_saved_cvars_chck ${* ?}"
-alias _cl_forced_saved_cvars_chck "_cl_forced_saved_cvars_$_forced_saved_cvar_ ${* ?}"
-alias _cl_forced_saved_cvars_doit "seta $1 \"${$1}\"; _cl_forced_saved_cvars_next ${2- ?}"
-alias _cl_forced_saved_cvars_done ""
-_cl_forced_saved_cvars_next $cl_forced_saved_cvars
+// Mark all cvars listed in menu_forced_saved_cvars as saved. That way they'll never disappear from config.cfg.
+alias _menu_forced_saved_cvars_next "set _forced_saved_cvar_ doit; set _forced_saved_cvar_${1 ?} done; _menu_forced_saved_cvars_chck ${* ?}"
+alias _menu_forced_saved_cvars_chck "_menu_forced_saved_cvars_$_forced_saved_cvar_ ${* ?}"
+alias _menu_forced_saved_cvars_doit "seta $1 \"${$1}\"; _menu_forced_saved_cvars_next ${2- ?}"
+alias _menu_forced_saved_cvars_done ""
+_menu_forced_saved_cvars_next ${menu_forced_saved_cvars ?}
index 833787328efe69d58f57cf868a9e798e184dca32..e271ad99050377127a47fc83baa710df3da6a627 100644 (file)
@@ -22,13 +22,10 @@ QCCFLAGS ?= \
 XON_BUILDSYSTEM =
 
 all: qc
+.PHONY: all
 
 .PHONY: qc
-qc:
-       $(MAKE) qc-recursive
-
-.PHONY: qc-recursive
-qc-recursive: ../menu.dat ../progs.dat ../csprogs.dat
+qc: ../menu.dat ../progs.dat ../csprogs.dat
 
 .PHONY: clean
 clean:
index db99e507ff7b68d86586799ca7942ac99088e360..4ea750ce5dbc5c465713037d84ada9ad97b579c0 100644 (file)
@@ -214,7 +214,7 @@ float SetTeam(entity o, float Team)
                        default:
                                if(GetTeam(Team, false) == world)
                                {
-                                       printf(_("trying to switch to unsupported team %d\n"), Team);
+                                       dprintf("trying to switch to unsupported team %d\n", Team);
                                        Team = NUM_SPECTATOR;
                                }
                                break;
@@ -230,7 +230,7 @@ float SetTeam(entity o, float Team)
                        default:
                                if(GetTeam(Team, false) == world)
                                {
-                                       printf(_("trying to switch to unsupported team %d\n"), Team);
+                                       dprintf("trying to switch to unsupported team %d\n", Team);
                                        Team = NUM_SPECTATOR;
                                }
                                break;
index 85dfe547c15518ca455ff455bdd2dfe4cade9904..2a41ba13c673c601f9134052a5f4cba68425a5ee 100644 (file)
@@ -492,7 +492,8 @@ void CSQC_UpdateView(float w, float h)
        // event chase camera
        if(autocvar_chase_active <= 0) // greater than 0 means it's enabled manually, and this code is skipped
        {
-               if(((spectatee_status >= 0 && (autocvar_cl_eventchase_death && is_dead)) || intermission) && !autocvar_cl_orthoview)
+               WepSet weapons_stat = WepSet_GetFromStat();
+               if(((spectatee_status >= 0 && (autocvar_cl_eventchase_death && is_dead)) || intermission) && !autocvar_cl_orthoview || (autocvar_cl_eventchase_nexball && gametype == MAPINFO_TYPE_NEXBALL && !(weapons_stat & WepSet_FromWeapon(WEP_PORTO))))
                {
                        // make special vector since we can't use view_origin (It is one frame old as of this code, it gets set later with the results this code makes.)
                        vector current_view_origin = (csqcplayer ? csqcplayer.origin : pmove_org);
index 5ba321ae17d89456f3411c36eff91d8144fcdf65..67a03259382feba3d5e14adf53087c834eac6491 100644 (file)
@@ -410,6 +410,7 @@ float autocvar_viewsize;
 float autocvar_cl_hitsound;
 float autocvar_cl_hitsound_antispam_time;
 var float autocvar_cl_eventchase_death = 1;
+var float autocvar_cl_eventchase_nexball = 1;
 var float autocvar_cl_eventchase_distance = 140;
 var float autocvar_cl_eventchase_speed = 1.3;
 var vector autocvar_cl_eventchase_maxs = '12 12 8';
index 196730a72dde016cbe3fd28308955ed2c2de6ee1..889e75d2679dc65cb7bfd18726479fd38fd8f97f 100644 (file)
@@ -83,7 +83,7 @@ void Draw_GrapplingHook()
                        break;
        }
 
-       if((self.owner.sv_entnum == player_localentnum - 1))
+       if((self.owner.sv_entnum == player_localentnum - 1) && autocvar_chase_active <= 0)
        {
                switch(self.HookType)
                {
@@ -129,30 +129,13 @@ void Draw_GrapplingHook()
                case ENT_CLIENT_HOOK:
                        intensity = 1;
                        offset = 0;
-                       if(t == NUM_TEAM_1)
+                       switch(t)
                        {
-                               tex = "particles/hook_red";
-                               rgb = '1 .3 .3';
-                       }
-                       else if(t == NUM_TEAM_2)
-                       {
-                               tex = "particles/hook_blue";
-                               rgb = '.3 .3 1';
-                       }
-                       else if(t == NUM_TEAM_3)
-                       {
-                               tex = "particles/hook_yellow";
-                               rgb = '1 1 .3';
-                       }
-                       else if(t == NUM_TEAM_4)
-                       {
-                               tex = "particles/hook_pink";
-                               rgb = '1 .3 1';
-                       }
-                       else
-                       {
-                               tex = "particles/hook_green";
-                               rgb = '.3 1 .3';
+                               case NUM_TEAM_1: tex = "particles/hook_red"; rgb = '1 0.3 0.3'; break;
+                               case NUM_TEAM_2: tex = "particles/hook_blue"; rgb = '0.3 0.3 1'; break;
+                               case NUM_TEAM_3: tex = "particles/hook_yellow"; rgb = '1 1 0.3'; break;
+                               case NUM_TEAM_4: tex = "particles/hook_pink"; rgb = '1 0.3 1'; break;
+                               default: tex = "particles/hook_white"; rgb = getcsqcplayercolor(self.sv_entnum); break;
                        }
                        break;
                case ENT_CLIENT_LGBEAM:
@@ -232,7 +215,9 @@ void Ent_ReadHook(float bIsNew, float type)
 
        if(sf & 1)
        {
-               self.owner = playerslots[ReadByte() - 1];
+               float myowner = ReadByte();
+               self.owner = playerslots[myowner - 1];
+               self.sv_entnum = myowner;
                switch(self.HookType)
                {
                        default:
index 46bc0efbf2680c04ac678ec86a9cc41c0cd02fc9..5c062c5facb3400224105766e1628651f83c8c50 100644 (file)
@@ -118,7 +118,7 @@ float current_player;
 
 #define HUD_PANEL(NAME,draw_func,name) \
        float HUD_PANEL_##NAME; \
-       void ##draw_func(void); \
+       void draw_func(void); \
        void RegisterHUD_Panel_##NAME() \
        { \
                HUD_PANEL_LAST = HUD_PANEL_##NAME = HUD_PANEL_NUM; \
@@ -127,7 +127,7 @@ float current_player;
                hud_panelent.classname = "hud_panel"; \
                hud_panelent.panel_name = #name; \
                hud_panelent.panel_id = HUD_PANEL_##NAME; \
-               hud_panelent.panel_draw = ##draw_func; \
+               hud_panelent.panel_draw = draw_func; \
                ++HUD_PANEL_NUM; \
        } \
        ACCUMULATE_FUNCTION(RegisterHUD_Panels, RegisterHUD_Panel_##NAME);
index fbdbc45f3a3c25a0c5f4f650c7cba9cfe2349b53..8b674e7826c2ccd6529e46a703d55ee00a21a2ae 100644 (file)
@@ -589,6 +589,20 @@ float getplayeralpha(float pl)
        return 1;
 }
 
+vector getcsqcplayercolor(float pl)
+{
+       entity e;
+
+       e = CSQCModel_server2csqc(pl);
+       if(e)
+       {
+               if(e.colormap > 0)
+                       return colormapPaletteColor(((e.colormap >= 1024) ? e.colormap : stof(getplayerkeyvalue(e.colormap - 1, "colors"))) & 0x0F, TRUE);
+       }
+
+       return '1 1 1';
+}
+
 float getplayerisdead(float pl)
 {
        entity e;
index dd419ae2261ddb475fc764ddcf39695a9dfed4d7..46dcfb4c451f09c46287aa21a5a4bde008c8426e 100644 (file)
@@ -1313,7 +1313,7 @@ void HUD_DrawScoreboard()
                }
                pos = HUD_DrawScoreboardRankings(pos, playerslots[player_localnum], rgb, bg_size);
        }
-       else if(autocvar_scoreboard_accuracy && spectatee_status != -1 && !warmup_stage) {
+       else if(autocvar_scoreboard_accuracy && spectatee_status == 0 && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL) {
                if(teamplay)
                        pos = HUD_DrawScoreboardAccuracyStats(pos, Team_ColorRGB(myteam), bg_size);
                else
@@ -1349,7 +1349,7 @@ void HUD_DrawScoreboard()
 
        // Print info string
        float tl, fl, ll;
-       str = sprintf(_("playing on ^2%s^7"), shortmapname);
+       str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
        tl = getstatf(STAT_TIMELIMIT);
        fl = getstatf(STAT_FRAGLIMIT);
        ll = getstatf(STAT_LEADLIMIT);
@@ -1407,6 +1407,7 @@ void HUD_DrawScoreboard()
 
        // print information about respawn status
        float respawn_time = getstatf(STAT_RESPAWN_TIME);
+       if(!intermission)
        if(respawn_time)
        {
                if(respawn_time < 0)
index dd7ae36b91fc09897bacb967633819e7cc61db34..1367501a8a3743ab17b2c2fa5dbb02c9a7870911 100644 (file)
@@ -277,6 +277,7 @@ string spritelookuptext(string s)
                case "race-finish": return _("Finish");
                case "race-start": return _("Start");
                case "race-start-finish": return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
+               case "goal": return _("Goal");
                case "nb-ball": return _("Ball");
                case "ka-ball": return _("Ball");
                case "ka-ballcarrier": return _("Ball carrier");
index feb0e036420e49d47ce211390463ed8ccd951b9f..b8fe58b34481a868bb7883e773d887183af876d7 100644 (file)
@@ -343,6 +343,8 @@ float _MapInfo_Generate(string pFilename) // 0: failure, 1: ok ent, 2: ok bsp
                                        MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_TURRETS;
                                else if(startsWith(v, "vehicle_"))
                                        MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_VEHICLES;
+                               else if(startsWith(v, "monster_"))
+                                       MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_MONSTERS;
                                else if(v == "target_music" || v == "trigger_music")
                                        _MapInfo_Map_worldspawn_music = string_null; // don't use regular BGM
                        }
@@ -631,6 +633,7 @@ void _MapInfo_Map_ApplyGametypeEx(string s, float pWantedType, float pThisType)
                        cvar_set("g_freezetag_teams", v);
                        cvar_set("g_keyhunt_teams", v);
                        cvar_set("g_domination_default_teams", v);
+                       cvar_set("g_invasion_teams", v);
                }
                else if(k == "qualifying_timelimit")
                {
@@ -978,6 +981,7 @@ float MapInfo_Get_ByName_NoFallbacks(string pFilename, float pAllowGenerate, flo
                        if     (t == "weapons") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_WEAPONS;
                        else if(t == "turrets") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_TURRETS;
                        else if(t == "vehicles") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_VEHICLES;
+                       else if(t == "monsters") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_MONSTERS;
                        else if(t == "new_toys") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_WEAPONS;
                        else
                                dprint("Map ", pFilename, " supports unknown feature ", t, ", ignored\n");
index 7746dfe3eb1d3de6912f129527e216af9dff873f..3c0afec9827f87010a6c7fe4a7b67117dfb178b5 100644 (file)
@@ -75,12 +75,13 @@ REGISTER_GAMETYPE(_("Freeze Tag"),ft,g_freezetag,FREEZETAG,"timelimit=20 pointli
 REGISTER_GAMETYPE(_("Keepaway"),ka,g_keepaway,KEEPAWAY,"timelimit=20 pointlimit=30");
 #define g_keepaway IS_GAMETYPE(KEEPAWAY)
 
-REGISTER_GAMETYPE(_("Invasion"),inv,g_invasion,INVASION,"pointlimit=5");
+REGISTER_GAMETYPE(_("Invasion"),inv,g_invasion,INVASION,"pointlimit=50 teams=0");
 #define g_invasion IS_GAMETYPE(INVASION)
 
 const float MAPINFO_FEATURE_WEAPONS       = 1; // not defined for minstagib-only maps
 const float MAPINFO_FEATURE_VEHICLES      = 2;
 const float MAPINFO_FEATURE_TURRETS       = 4;
+const float MAPINFO_FEATURE_MONSTERS      = 8;
 
 const float MAPINFO_FLAG_HIDDEN           = 1; // not in lsmaps/menu/vcall/etc., can just be changed to manually
 const float MAPINFO_FLAG_FORBIDDEN        = 2; // don't even allow the map by a cvar setting that allows hidden maps
index 2c8ebd5479d4676d8dc85f2fb54b777826c8d549..bda48d5cb91dee7ae2fd20b5c1cb3d001c17d1b7 100644 (file)
@@ -233,7 +233,8 @@ void mage_heal()
                {
                        pointparticles(particleeffectnum("healing_fx"), head.origin, '0 0 0', 1);
                        head.health = bound(0, head.health + (autocvar_g_monster_mage_heal_allies), head.max_health);
-                       WaypointSprite_UpdateHealth(head.sprite, head.health);
+                       if(!(head.spawnflags & MONSTERFLAG_INVINCIBLE))
+                               WaypointSprite_UpdateHealth(head.sprite, head.health);
                }
        }
 
@@ -335,12 +336,7 @@ void spawnfunc_monster_mage()
 {
        self.classname = "monster_mage";
 
-       self.monster_spawnfunc = spawnfunc_monster_mage;
-
-       if(Monster_CheckAppearFlags(self))
-               return;
-
-       if(!monster_initialize(MON_MAGE, FALSE)) { remove(self); return; }
+       if(!monster_initialize(MON_MAGE)) { remove(self); return; }
 }
 
 // compatibility with old spawns
@@ -397,7 +393,7 @@ float m_mage(float req)
                }
                case MR_PRECACHE:
                {
-                       precache_model ("models/monsters/mage.dpm");
+                       precache_model("models/monsters/mage.dpm");
                        precache_sound ("weapons/grenade_impact.wav");
                        precache_sound ("weapons/tagexp1.wav");
                        return TRUE;
@@ -415,7 +411,6 @@ float m_mage(float req)
        {
                case MR_PRECACHE:
                {
-                       precache_model ("models/monsters/mage.dpm");
                        return TRUE;
                }
        }
index 866782d979771dac43d276690be865ec0a9ad1a1..0e82fe8d290e726974fd4b9d493a78fda4349096 100644 (file)
@@ -168,6 +168,7 @@ float shambler_attack(float attack_type)
                case MONSTER_ATTACK_RANGED:
                {
                        if(time >= self.shambler_lastattack) // shambler doesn't attack much
+                       if(self.flags & FL_ONGROUND)
                        if(random() <= 0.5 && vlen(self.enemy.origin - self.origin) <= 500)
                        {
                                self.frame = shambler_anim_smash;
@@ -196,12 +197,7 @@ void spawnfunc_monster_shambler()
 {
        self.classname = "monster_shambler";
 
-       self.monster_spawnfunc = spawnfunc_monster_shambler;
-
-       if(Monster_CheckAppearFlags(self))
-               return;
-
-       if(!monster_initialize(MON_SHAMBLER, FALSE)) { remove(self); return; }
+       if(!monster_initialize(MON_SHAMBLER)) { remove(self); return; }
 }
 
 float m_shambler(float req)
@@ -232,7 +228,7 @@ float m_shambler(float req)
                }
                case MR_PRECACHE:
                {
-                       precache_model ("models/monsters/shambler.mdl");
+                       precache_model("models/monsters/shambler.mdl");
                        return TRUE;
                }
        }
@@ -248,7 +244,6 @@ float m_shambler(float req)
        {
                case MR_PRECACHE:
                {
-                       precache_model ("models/monsters/shambler.mdl");
                        return TRUE;
                }
        }
index 0f46a9620e6683c5d6b8e6a1bcebd94a268d9f33..7e35b8b1626d04e56ac5ad0748cb1e2904b4f184 100644 (file)
@@ -37,7 +37,7 @@ void spider_web_explode()
                pointparticles(particleeffectnum("electro_impact"), self.origin, '0 0 0', 1);
                RadiusDamage(self, self.realowner, 0, 0, 25, world, 25, self.projectiledeathtype, world);
 
-               for(e = findradius(self.origin, 25); e; e = e.chain) if(e != self) if(e.takedamage && e.deadflag == DEAD_NO) if(e.health > 0)
+               for(e = findradius(self.origin, 25); e; e = e.chain) if(e != self) if(e.takedamage && e.deadflag == DEAD_NO) if(e.health > 0) if(e.monsterid != MON_SPIDER)
                        e.spider_slowness = time + (autocvar_g_monster_spider_attack_web_damagetime);
 
                remove(self);
@@ -119,12 +119,7 @@ void spawnfunc_monster_spider()
 {
        self.classname = "monster_spider";
 
-       self.monster_spawnfunc = spawnfunc_monster_spider;
-
-       if(Monster_CheckAppearFlags(self))
-               return;
-
-       if(!monster_initialize(MON_SPIDER, FALSE)) { remove(self); return; }
+       if(!monster_initialize(MON_SPIDER)) { remove(self); return; }
 }
 
 float m_spider(float req)
@@ -154,7 +149,7 @@ float m_spider(float req)
                }
                case MR_PRECACHE:
                {
-                       precache_model ("models/monsters/spider.dpm");
+                       precache_model("models/monsters/spider.dpm");
                        precache_sound ("weapons/electro_fire2.wav");
                        return TRUE;
                }
@@ -171,7 +166,6 @@ float m_spider(float req)
        {
                case MR_PRECACHE:
                {
-                       precache_model ("models/monsters/spider.dpm");
                        return TRUE;
                }
        }
index 2d72b4b0526e5dd84727262372a38708f9bbc011..ed4962d061ccd47934bd551d6c336189e1d103d8 100644 (file)
@@ -96,12 +96,7 @@ void spawnfunc_monster_wyvern()
 {
        self.classname = "monster_wyvern";
 
-       self.monster_spawnfunc = spawnfunc_monster_wyvern;
-
-       if(Monster_CheckAppearFlags(self))
-               return;
-
-       if(!monster_initialize(MON_WYVERN, TRUE)) { remove(self); return; }
+       if(!monster_initialize(MON_WYVERN)) { remove(self); return; }
 }
 
 // compatibility with old spawns
@@ -136,7 +131,7 @@ float m_wyvern(float req)
                }
                case MR_PRECACHE:
                {
-                       precache_model ("models/monsters/wizard.mdl");
+                       precache_model("models/monsters/wizard.mdl");
                        return TRUE;
                }
        }
@@ -152,7 +147,6 @@ float m_wyvern(float req)
        {
                case MR_PRECACHE:
                {
-                       precache_model ("models/monsters/wizard.mdl");
                        return TRUE;
                }
        }
index e5155b8aedac5e2a98194838244424eb024a5dcf..25afaf76f30b50d27c622fe6c854b5602c4df197 100644 (file)
@@ -130,14 +130,7 @@ void spawnfunc_monster_zombie()
 {
        self.classname = "monster_zombie";
 
-       self.monster_spawnfunc = spawnfunc_monster_zombie;
-
-       self.spawnflags |= MONSTER_RESPAWN_DEATHPOINT;
-
-       if(Monster_CheckAppearFlags(self))
-               return;
-
-       if(!monster_initialize(MON_ZOMBIE, FALSE)) { remove(self); return; }
+       if(!monster_initialize(MON_ZOMBIE)) { remove(self); return; }
 }
 
 float m_zombie(float req)
@@ -163,6 +156,8 @@ float m_zombie(float req)
                        if(self.spawnflags & MONSTERFLAG_NORESPAWN)
                                self.spawnflags &= ~MONSTERFLAG_NORESPAWN; // zombies always respawn
 
+                       self.spawnflags |= MONSTER_RESPAWN_DEATHPOINT;
+
                        self.monster_loot = spawnfunc_item_health_medium;
                        self.monster_attackfunc = zombie_attack;
                        self.frame = zombie_anim_spawn;
@@ -174,7 +169,7 @@ float m_zombie(float req)
                }
                case MR_PRECACHE:
                {
-                       precache_model ("models/monsters/zombie.dpm");
+                       precache_model("models/monsters/zombie.dpm");
                        return TRUE;
                }
        }
@@ -190,7 +185,6 @@ float m_zombie(float req)
        {
                case MR_PRECACHE:
                {
-                       precache_model ("models/monsters/zombie.dpm");
                        return TRUE;
                }
        }
index 70802dbbde3fafe4c90fe41002b255a206ca9be2..67e176cf26395b6001ca42c7090fbade0bf5040d 100644 (file)
@@ -18,10 +18,6 @@ void register_monster(float id, float(float) func, float monsterflags, vector mi
        e.mins = min_s;
        e.maxs = max_s;
        e.model = strzone(strcat("models/monsters/", modelname));
-
-       #ifndef MENUQC
-       func(MR_PRECACHE);
-       #endif
 }
 float m_null(float dummy) { return 0; }
 void register_monsters_done()
@@ -46,4 +42,4 @@ entity get_monsterinfo(float id)
        if(m)
                return m;
        return dummy_monster_info;
-}
\ No newline at end of file
+}
index 924a728a07678ed0101b537a35ba07a8997f8d69..be5accf5ede2d095a2d40cc398970aa406a49a74 100644 (file)
@@ -1,7 +1,7 @@
-entity spawnmonster (string monster, float monster_id, entity spawnedby, entity own, vector orig, float respwn, float moveflag)
+entity spawnmonster (string monster, float monster_id, entity spawnedby, entity own, vector orig, float respwn, float invincible, float moveflag)
 {
        // ensure spawnfunc database is initialized
-       initialize_field_db();
+       //initialize_field_db();
 
        entity e = spawn();
        float i;
@@ -11,6 +11,9 @@ entity spawnmonster (string monster, float monster_id, entity spawnedby, entity
        if(!respwn)
                e.spawnflags |= MONSTERFLAG_NORESPAWN;
 
+       if(invincible)
+               e.spawnflags |= MONSTERFLAG_INVINCIBLE;
+
        setorigin(e, orig);
 
        if(monster == "random")
@@ -32,6 +35,7 @@ entity spawnmonster (string monster, float monster_id, entity spawnedby, entity
                        if(mon.netname == monster)
                        {
                                found = TRUE;
+                               monster_id = mon.monsterid; // we have the monster, old monster id is no longer required
                                break;
                        }
                }
@@ -59,9 +63,14 @@ entity spawnmonster (string monster, float monster_id, entity spawnedby, entity
                e.angles = spawnedby.angles;
        }
 
-       monster = strcat("$ spawnfunc_monster_", monster);
+       //monster = strcat("$ spawnfunc_monster_", monster);
+       
+       entity oldself = self;
+       self = e;
+       monster_initialize(monster_id);
+       self = oldself;
 
-       target_spawn_edit_entity(e, monster, world, world, world, world, world);
+       //target_spawn_edit_entity(e, monster, world, world, world, world, world);
 
        return e;
 }
index d3d3fcb34dd79414079bac6646813ed036b27c38..02d308677c708f3d6e0fcc2211463e73be4cc7e0 100644 (file)
@@ -1 +1 @@
-entity spawnmonster (string monster, float monster_id, entity spawnedby, entity own, vector orig, float respwn, float moveflag);
+entity spawnmonster (string monster, float monster_id, entity spawnedby, entity own, vector orig, float respwn, float invincible, float moveflag);
index 927501e654b81e11171198ed9f4102a0f9ecde13..34e7ceb9901ebbb30583e88b3862ba3204cd28fb 100644 (file)
@@ -3,30 +3,13 @@
 // =========================
 
 
-void monster_item_spawn()
-{
-       if(self.monster_loot)
-               self.monster_loot();
-
-       self.gravity = 1;
-       self.reset = SUB_Remove;
-       self.noalign = TRUE;
-       self.velocity = randomvec() * 175 + '0 0 325';
-       self.classname = "droppedweapon"; // hax
-       self.item_spawnshieldtime = time + 0.7;
-
-       SUB_SetFade(self, time + autocvar_g_monsters_drop_time, 1);
-}
-
 void monster_dropitem()
 {
        if(!self.candrop || !self.monster_loot)
                return;
 
        vector org = self.origin + ((self.mins + self.maxs) * 0.5);
-       entity e = spawn();
-
-       setorigin(e, org);
+       entity e = spawn(), oldself = self;
 
        e.monster_loot = self.monster_loot;
 
@@ -34,10 +17,20 @@ void monster_dropitem()
        MUTATOR_CALLHOOK(MonsterDropItem);
        e = other;
 
-       if(e)
+       if(e && e.monster_loot)
        {
-               e.think = monster_item_spawn;
-               e.nextthink = time + 0.3;
+               self = e;
+               e.noalign = TRUE;
+               e.monster_loot();
+               e.gravity = 1;
+               e.movetype = MOVETYPE_TOSS;
+               e.reset = SUB_Remove;
+               setorigin(e, org);
+               e.velocity = randomvec() * 175 + '0 0 325';
+               e.item_spawnshieldtime = time + 0.7;
+               e.classname = "droppedweapon"; // use weapon handling to remove it on touch
+               SUB_SetFade(e, time + autocvar_g_monsters_drop_time, 1);
+               self = oldself;
        }
 }
 
@@ -56,10 +49,10 @@ float monster_isvalidtarget (entity targ, entity ent)
        if(targ == ent)
                return FALSE; // don't attack ourselves
 
-       traceline(ent.origin, targ.origin, MOVE_NORMAL, ent);
+       //traceline(ent.origin, targ.origin, MOVE_NORMAL, ent);
 
-       if(trace_ent != targ)
-               return FALSE;
+       //if(trace_ent != targ)
+               //return FALSE;
 
        if(targ.vehicle_flags & VHF_ISVEHICLE)
        if(!((get_monsterinfo(ent.monsterid)).spawnflags & MON_FLAG_RANGED))
@@ -68,9 +61,6 @@ float monster_isvalidtarget (entity targ, entity ent)
        if(time < game_starttime)
                return FALSE; // monsters do nothing before the match has started
 
-       if(vlen(targ.origin - ent.origin) >= ent.target_range)
-               return FALSE; // enemy is too far away
-
        if(targ.takedamage == DAMAGE_NO)
                return FALSE; // enemy can't be damaged
 
@@ -107,7 +97,7 @@ float monster_isvalidtarget (entity targ, entity ent)
        if (targ.freezetag_frozen)
                return FALSE; // ignore frozen
 
-       if(autocvar_g_monsters_target_infront || ent.spawnflags & MONSTERFLAG_INFRONT)
+       if(autocvar_g_monsters_target_infront || (ent.spawnflags & MONSTERFLAG_INFRONT))
        if(ent.enemy != targ)
        {
                float dot;
@@ -128,6 +118,7 @@ entity FindTarget (entity ent)
 
        entity head, closest_target = world;
        head = findradius(ent.origin, ent.target_range);
+       //head = WarpZone_FindRadius(ent.origin, ent.target_range, TRUE);
 
        while(head) // find the closest acceptable target to pass to
        {
@@ -136,12 +127,16 @@ entity FindTarget (entity ent)
                {
                        // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
                        vector head_center = CENTER_OR_VIEWOFS(head);
+                       //vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
                        vector ent_center = CENTER_OR_VIEWOFS(ent);
 
-                       //if(ctf_CheckPassDirection(head_center, ent_center, ent.v_angle, head.WarpZone_findradius_nearest))
+                       traceline(ent_center, head_center, MOVE_NORMAL, ent);
+
+                       if(trace_ent == head)
                        if(closest_target)
                        {
                                vector closest_target_center = CENTER_OR_VIEWOFS(closest_target);
+                               //vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
                                if(vlen(ent_center - head_center) < vlen(ent_center - closest_target_center))
                                        { closest_target = head; }
                        }
@@ -279,7 +274,10 @@ void UpdateMonsterSounds()
 
 void MonsterSound(.string samplefield, float sound_delay, float delaytoo, float chan)
 {
-       if(delaytoo && time < self.msound_delay)
+       if(!autocvar_g_monsters_sounds) { return; }
+
+       if(delaytoo)
+       if(time < self.msound_delay)
                return; // too early
        GlobalSound(self.samplefield, chan, VOICETYPE_PLAYERSOUND);
 
@@ -357,14 +355,21 @@ float Monster_CanRespawn(entity ent)
        return TRUE;
 }
 
+float monster_initialize(float mon_id);
+void monster_respawn()
+{
+       // is this function really needed?
+       monster_initialize(self.monsterid);
+}
+
 void Monster_Fade ()
 {
        if(Monster_CanRespawn(self))
        {
                self.spawnflags |= MONSTERFLAG_RESPAWNED;
-               self.think = self.monster_spawnfunc;
+               self.think = monster_respawn;
                self.nextthink = time + self.respawntime;
-               self.ltime = 0;
+               self.monster_lifetime = 0;
                self.deadflag = DEAD_RESPAWNING;
                if(self.spawnflags & MONSTER_RESPAWN_DEATHPOINT)
                {
@@ -383,9 +388,6 @@ void Monster_Fade ()
                // number of monsters spawned with mobspawn command
                totalspawned -= 1;
 
-               if(IS_CLIENT(self.realowner))
-                       self.realowner.monstercount -= 1;
-
                SUB_SetFade(self, time + 3, 1);
        }
 }
@@ -472,10 +474,40 @@ vector monster_pickmovetarget(entity targ)
        // enemy is always preferred target
        if(self.enemy)
        {
-               makevectors(self.angles);
+               vector targ_origin = ((self.enemy.absmin + self.enemy.absmax) * 0.5);
+               targ_origin = WarpZone_RefSys_TransformOrigin(self.enemy, self, targ_origin); // origin of target as seen by the monster (us)
+               WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
+               
+               if((self.enemy == world)
+                       || (self.enemy.deadflag != DEAD_NO || self.enemy.health < 1)
+                       || (self.enemy.freezetag_frozen)
+                       || (self.enemy.flags & FL_NOTARGET)
+                       || (self.enemy.alpha < 0.5)
+                       || (self.enemy.takedamage == DAMAGE_NO)
+                       || (vlen(self.origin - targ_origin) > self.target_range)
+                       || ((trace_fraction < 1) && (trace_ent != self.enemy)))
+                       //|| (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit)) // TODO: chase timelimit?
+               {
+                       self.enemy = world;
+                       self.pass_distance = 0;
+               }
+               
+               if(self.enemy)
+               {
+                       /*WarpZone_TrailParticles(world, particleeffectnum("red_pass"), self.origin, targ_origin);
+                       print("Trace origin: ", vtos(targ_origin), "\n");
+                       print("Target origin: ", vtos(self.enemy.origin), "\n");
+                       print("My origin: ", vtos(self.origin), "\n"); */
+                       
+                       self.monster_movestate = MONSTER_MOVE_ENEMY;
+                       self.last_trace = time + 1.2;
+                       return targ_origin;
+               }
+       
+               /*makevectors(self.angles);
                self.monster_movestate = MONSTER_MOVE_ENEMY;
                self.last_trace = time + 1.2;
-               return self.enemy.origin;
+               return self.enemy.origin; */
        }
 
        switch(self.monster_moveflags)
@@ -503,34 +535,70 @@ vector monster_pickmovetarget(entity targ)
                {
                        vector pos;
                        self.monster_movestate = MONSTER_MOVE_WANDER;
-                       self.last_trace = time + 2;
-
-                       self.angles_y = rint(random() * 500);
-                       makevectors(self.angles);
-                       pos = self.origin + v_forward * 600;
-
-                       if(self.flags & FL_FLY || self.flags & FL_SWIM)
-                       if(self.spawnflags & MONSTERFLAG_FLY_VERTICAL)
-                       {
-                               pos_z = random() * 200;
-                               if(random() >= 0.5)
-                                       pos_z *= -1;
-                       }
 
                        if(targ)
                        {
                                self.last_trace = time + 0.5;
                                pos = targ.origin;
                        }
+                       else
+                       {
+                               self.last_trace = time + self.wander_delay;
+
+                               self.angles_y = rint(random() * 500);
+                               makevectors(self.angles);
+                               pos = self.origin + v_forward * self.wander_distance;
+
+                               if(((self.flags & FL_FLY) && (self.spawnflags & MONSTERFLAG_FLY_VERTICAL)) || (self.flags & FL_SWIM))
+                               {
+                                       pos_z = random() * 200;
+                                       if(random() >= 0.5)
+                                               pos_z *= -1;
+                               }
+                       }
 
                        return pos;
                }
        }
 }
 
+void monster_CalculateVelocity(entity mon, vector to, vector from, float turnrate, float movespeed)
+{
+       float current_distance = vlen((('1 0 0' * to_x) + ('0 1 0' * to_y)) - (('1 0 0' * from_x) + ('0 1 0' * from_y))); // for the sake of this check, exclude Z axis
+       float initial_height = 0; //min(50, (targ_distance * tanh(20)));
+       float current_height = (initial_height * min(1, (current_distance / self.pass_distance)));
+       //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
+
+       vector targpos;
+       if(current_height) // make sure we can actually do this arcing path
+       {
+               targpos = (to + ('0 0 1' * current_height));
+               WarpZone_TraceLine(mon.origin, targpos, MOVE_NOMONSTERS, mon);
+               if(trace_fraction < 1)
+               {
+                       //print("normal arc line failed, trying to find new pos...");
+                       WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, mon);
+                       targpos = (trace_endpos + '0 0 -10');
+                       WarpZone_TraceLine(mon.origin, targpos, MOVE_NOMONSTERS, mon);
+                       if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
+                       /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
+               }
+       }
+       else { targpos = to; }
+
+       //mon.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
+
+       vector desired_direction = normalize(targpos - from);
+       if(turnrate) { mon.velocity = (normalize(normalize(mon.velocity) + (desired_direction * 50)) * movespeed); }
+       else { mon.velocity = (desired_direction * movespeed); }
+
+       //mon.steerto = steerlib_attract2(targpos, 0.5, 500, 0.95);
+       //mon.angles = vectoangles(mon.velocity);
+}
+
 void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_run, float manim_walk, float manim_idle)
 {
-       fixedmakevectors(self.angles);
+       //fixedmakevectors(self.angles);
 
        if(self.target2)
                self.goalentity = find(world, targetname, self.target2);
@@ -581,7 +649,7 @@ void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_
        monster_speed_run = runspeed;
        monster_speed_walk = walkspeed;
 
-       if(MUTATOR_CALLHOOK(MonsterMove) || gameover || (round_handler_IsActive() && !round_handler_IsRoundStarted()) || time < game_starttime || (autocvar_g_campaign && !campaign_bots_may_start) || time < self.spawn_time)
+       if(MUTATOR_CALLHOOK(MonsterMove) || gameover || self.draggedby != world || (round_handler_IsActive() && !round_handler_IsRoundStarted()) || time < game_starttime || (autocvar_g_campaign && !campaign_bots_may_start) || time < self.spawn_time)
        {
                runspeed = walkspeed = 0;
                if(time >= self.spawn_time)
@@ -605,22 +673,23 @@ void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_
        if(DIFF_TEAM(self.monster_owner, self))
                self.monster_owner = world;
 
-       if(self.enemy && self.enemy.health < 1)
-               self.enemy = world; // enough!
-
        if(time >= self.last_enemycheck)
        {
-               if(!monster_isvalidtarget(self.enemy, self))
-                       self.enemy = world;
-
                if(!self.enemy)
                {
                        self.enemy = FindTarget(self);
                        if(self.enemy)
+                       {
+                               WarpZone_RefSys_Copy(self.enemy, self);
+                               WarpZone_RefSys_AddInverse(self.enemy, self); // wz1^-1 ... wzn^-1 receiver
+                               self.moveto = WarpZone_RefSys_TransformOrigin(self.enemy, self, (0.5 * (self.enemy.absmin + self.enemy.absmax)));
+                               
+                               self.pass_distance = vlen((('1 0 0' * self.enemy.origin_x) + ('0 1 0' * self.enemy.origin_y)) - (('1 0 0' *  self.origin_x) + ('0 1 0' *  self.origin_y)));
                                MonsterSound(monstersound_sight, 0, FALSE, CH_VOICE);
+                       }
                }
 
-               self.last_enemycheck = time + 0.5;
+               self.last_enemycheck = time + 1; // check for enemies every second
        }
 
        if(self.state == MONSTER_STATE_ATTACK_MELEE && time >= self.attack_finished_single)
@@ -633,49 +702,32 @@ void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_
        if(!self.enemy)
                MonsterSound(monstersound_idle, 7, TRUE, CH_VOICE);
 
-       if(self.state != MONSTER_STATE_ATTACK_LEAP && self.state != MONSTER_STATE_ATTACK_MELEE)
-               self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
-
        if(self.state == MONSTER_STATE_ATTACK_LEAP && (self.flags & FL_ONGROUND))
        {
                self.state = 0;
                self.touch = MonsterTouch;
        }
 
-       //self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
-
-       float turny = 0;
-       vector real_angle = vectoangles(self.steerto) - self.angles;
-
-       if(self.state != MONSTER_STATE_ATTACK_LEAP && self.state != MONSTER_STATE_ATTACK_MELEE)
-               turny = 20;
-
-       if(self.flags & FL_SWIM)
-               turny = vlen(self.angles - self.moveto);
-
-       if(turny)
-       {
-               turny = bound(turny * -1, shortangle_f(real_angle_y, self.angles_y), turny);
-               self.angles_y += turny;
-       }
-
        if(self.state == MONSTER_STATE_ATTACK_MELEE)
                self.moveto = self.origin;
 
        if(self.enemy && self.enemy.vehicle)
                runspeed = 0;
 
-       if(((self.flags & FL_FLY) || (self.flags & FL_SWIM)) && self.spawnflags & MONSTERFLAG_FLY_VERTICAL)
-               v_forward = normalize(self.moveto - self.origin);
-       else
+       if(!(((self.flags & FL_FLY) && (self.spawnflags & MONSTERFLAG_FLY_VERTICAL)) || (self.flags & FL_SWIM)))
+               //v_forward = normalize(self.moveto - self.origin);
+       //else
                self.moveto_z = self.origin_z;
 
        if(vlen(self.origin - self.moveto) > 64)
        {
-               if(self.flags & FL_FLY || self.flags & FL_SWIM)
+               if((self.flags & FL_ONGROUND) || ((self.flags & FL_FLY) || (self.flags & FL_SWIM)))
+                       monster_CalculateVelocity(self, self.moveto, self.origin, TRUE, ((self.enemy) ? runspeed : walkspeed));
+               
+               /*&if(self.flags & FL_FLY || self.flags & FL_SWIM)
                        movelib_move_simple(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6);
                else
-                       movelib_move_simple_gravity(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6);
+                       movelib_move_simple_gravity(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6); */
 
                if(time > self.pain_finished)
                if(time > self.attack_finished_single)
@@ -698,6 +750,18 @@ void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_
                if (vlen(self.velocity) <= 30)
                        self.frame = manim_idle;
        }
+       
+       self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
+       
+       vector real_angle = vectoangles(self.steerto) - self.angles;
+       float turny = 25;
+       if(self.state == MONSTER_STATE_ATTACK_MELEE)
+               turny = 0;
+       if(turny)
+       {
+               turny = bound(turny * -1, shortangle_f(real_angle_y, self.angles_y), turny);
+               self.angles_y += turny;
+       }
 
        monster_checkattack(self, self.enemy);
 }
@@ -723,8 +787,8 @@ void monster_dead_think()
 
        CSQCMODEL_AUTOUPDATE();
 
-       if(self.ltime != 0)
-       if(time >= self.ltime)
+       if(self.monster_lifetime != 0)
+       if(time >= self.monster_lifetime)
        {
                Monster_Fade();
                return;
@@ -740,16 +804,17 @@ void monsters_setstatus()
 void Monster_Appear()
 {
        self.enemy = activator;
-       self.spawnflags &= ~MONSTERFLAG_APPEAR;
-       self.monster_spawnfunc();
+       self.spawnflags &= ~MONSTERFLAG_APPEAR; // otherwise, we get an endless loop
+       monster_initialize(self.monsterid);
 }
 
-float Monster_CheckAppearFlags(entity ent)
+float Monster_CheckAppearFlags(entity ent, float monster_id)
 {
        if(!(ent.spawnflags & MONSTERFLAG_APPEAR))
                return FALSE;
 
        ent.think = func_null;
+       ent.monsterid = monster_id; // set so this monster is properly registered (otherwise, normal initialization is used)
        ent.nextthink = 0;
        ent.use = Monster_Appear;
        ent.flags = FL_MONSTER; // set so this monster can get butchered
@@ -783,11 +848,9 @@ void monsters_corpse_damage (entity inflictor, entity attacker, float damage, fl
                // number of monsters spawned with mobspawn command
                totalspawned -= 1;
 
-               if(IS_CLIENT(self.realowner))
-                       self.realowner.monstercount -= 1;
-
                self.think = SUB_Remove;
                self.nextthink = time + 0.1;
+               self.event_damage = func_null;
        }
 }
 
@@ -795,7 +858,7 @@ void monster_die(entity attacker, float gibbed)
 {
        self.think = monster_dead_think;
        self.nextthink = time;
-       self.ltime = time + 5;
+       self.monster_lifetime = time + 5;
 
        monster_dropitem();
 
@@ -812,15 +875,12 @@ void monster_die(entity attacker, float gibbed)
        {
                // number of monsters spawned with mobspawn command
                totalspawned -= 1;
-
-               if(IS_CLIENT(self.realowner))
-                       self.realowner.monstercount -= 1;
        }
 
        if(self.candrop && self.weapon)
                W_ThrowNewWeapon(self, self.weapon, 0, self.origin, randomvec() * 150 + '0 0 325');
 
-       self.event_damage       = monsters_corpse_damage;
+       self.event_damage       = ((gibbed) ? func_null : monsters_corpse_damage);
        self.solid                      = SOLID_CORPSE;
        self.takedamage         = DAMAGE_AIM;
        self.deadflag           = DEAD_DEAD;
@@ -832,7 +892,7 @@ void monster_die(entity attacker, float gibbed)
        self.state                      = 0;
        self.attack_finished_single = 0;
 
-       if(!(self.flags & FL_FLY))
+       if(!((self.flags & FL_FLY) || (self.flags & FL_SWIM)))
                self.velocity = '0 0 0';
 
        MON_ACTION(self.monsterid, MR_DEATH);
@@ -840,12 +900,18 @@ void monster_die(entity attacker, float gibbed)
 
 void monsters_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
 {
+       if((self.spawnflags & MONSTERFLAG_INVINCIBLE) && deathtype != DEATH_KILL)
+               return;
+
        if(time < self.pain_finished && deathtype != DEATH_KILL)
                return;
 
        if(time < self.spawnshieldtime && deathtype != DEATH_KILL)
                return;
 
+       if(deathtype == DEATH_FALL && self.draggedby != world)
+               return;
+
        vector v;
        float take, save;
 
@@ -902,26 +968,43 @@ void monsters_damage (entity inflictor, entity attacker, float damage, float dea
        }
 }
 
-void monster_setupcolors()
+void monster_setupcolors(entity mon)
 {
-       if(IS_PLAYER(self.monster_owner))
-               self.colormap = self.monster_owner.colormap;
-       else if(teamplay && self.team)
-               self.colormap = 1024 + (self.team - 1) * 17;
+       if(IS_PLAYER(mon.monster_owner))
+               mon.colormap = mon.monster_owner.colormap;
+       else if(teamplay && mon.team)
+               mon.colormap = 1024 + (mon.team - 1) * 17;
        else
        {
-               if(self.monster_skill <= MONSTER_SKILL_EASY)
-                       self.colormap = 1029;
-               else if(self.monster_skill <= MONSTER_SKILL_MEDIUM)
-                       self.colormap = 1027;
-               else if(self.monster_skill <= MONSTER_SKILL_HARD)
-                       self.colormap = 1038;
-               else if(self.monster_skill <= MONSTER_SKILL_INSANE)
-                       self.colormap = 1028;
-               else if(self.monster_skill <= MONSTER_SKILL_NIGHTMARE)
-                       self.colormap = 1032;
+               if(mon.monster_skill <= MONSTER_SKILL_EASY)
+                       mon.colormap = 1029;
+               else if(mon.monster_skill <= MONSTER_SKILL_MEDIUM)
+                       mon.colormap = 1027;
+               else if(mon.monster_skill <= MONSTER_SKILL_HARD)
+                       mon.colormap = 1038;
+               else if(mon.monster_skill <= MONSTER_SKILL_INSANE)
+                       mon.colormap = 1028;
+               else if(mon.monster_skill <= MONSTER_SKILL_NIGHTMARE)
+                       mon.colormap = 1032;
                else
-                       self.colormap = 1024;
+                       mon.colormap = 1024;
+       }
+}
+
+void monster_changeteam(entity ent, float newteam)
+{
+       if(!teamplay) { return; }
+       
+       ent.team = newteam;
+       ent.monster_attack = TRUE; // new team, activate attacking
+       monster_setupcolors(ent);
+       
+       if(ent.sprite)
+       {
+               WaypointSprite_UpdateTeamRadar(ent.sprite, RADARICON_DANGER, ((newteam) ? Team_ColorRGB(newteam) : '1 0 0'));
+
+               ent.sprite.team = newteam;
+               ent.sprite.SendFlags |= 1;
        }
 }
 
@@ -930,8 +1013,8 @@ void monster_think()
        self.think = monster_think;
        self.nextthink = self.ticrate;
 
-       if(self.ltime)
-       if(time >= self.ltime)
+       if(self.monster_lifetime)
+       if(time >= self.monster_lifetime)
        {
                Damage(self, self, self, self.health + self.max_health, DEATH_KILL, self.origin, self.origin);
                return;
@@ -965,6 +1048,9 @@ float monster_spawn()
        if(!self.attack_range)
                self.attack_range = autocvar_g_monsters_attack_range;
 
+       if(!self.wander_delay) { self.wander_delay = 2; }
+       if(!self.wander_distance) { self.wander_distance = 600; }
+
        precache_monstersounds();
        UpdateMonsterSounds();
 
@@ -974,8 +1060,11 @@ float monster_spawn()
        MonsterSound(monstersound_spawn, 0, FALSE, CH_VOICE);
 
        WaypointSprite_Spawn(M_NAME(self.monsterid), 0, 1024, self, '0 0 1' * (self.maxs_z + 15), world, self.team, self, sprite, TRUE, RADARICON_DANGER, ((self.team) ? Team_ColorRGB(self.team) : '1 0 0'));
-       WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
-       WaypointSprite_UpdateHealth(self.sprite, self.health);
+       if(!(self.spawnflags & MONSTERFLAG_INVINCIBLE))
+       {
+               WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
+               WaypointSprite_UpdateHealth(self.sprite, self.health);
+       }
 
        self.think = monster_think;
        self.nextthink = time + self.ticrate;
@@ -986,10 +1075,11 @@ float monster_spawn()
        return TRUE;
 }
 
-float monster_initialize(float mon_id, float nodrop)
+float monster_initialize(float mon_id)
 {
-       if(!autocvar_g_monsters)
-               return FALSE;
+       if(!autocvar_g_monsters) { return FALSE; }
+       if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) { MON_ACTION(mon_id, MR_PRECACHE); }
+       if(Monster_CheckAppearFlags(self, mon_id)) { return TRUE; } // return true so the monster isn't removed
 
        entity mon = get_monsterinfo(mon_id);
 
@@ -1005,11 +1095,11 @@ float monster_initialize(float mon_id, float nodrop)
                self.team = 0;
 
        if(!(self.spawnflags & MONSTERFLAG_SPAWNED)) // naturally spawned monster
-       if(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
+       if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) // don't count re-spawning monsters either
                monsters_total += 1;
 
        setmodel(self, mon.model);
-       setsize(self, mon.mins, mon.maxs);
+       //setsize(self, mon.mins, mon.maxs);
        self.flags                              = FL_MONSTER;
        self.takedamage                 = DAMAGE_AIM;
        self.bot_attack                 = TRUE;
@@ -1035,14 +1125,20 @@ float monster_initialize(float mon_id, float nodrop)
        self.candrop                    = TRUE;
        self.view_ofs                   = '0 0 1' * (self.maxs_z * 0.5);
        self.oldtarget2                 = self.target2;
+       self.pass_distance              = 0;
        self.deadflag                   = DEAD_NO;
-       self.scale                              = 1;
-       self.noalign                    = nodrop;
+       self.noalign                    = ((mon.spawnflags & MONSTER_TYPE_FLY) || (mon.spawnflags & MONSTER_TYPE_SWIM));
        self.spawn_time                 = time;
        self.spider_slowness    = 0;
        self.gravity                    = 1;
        self.dphitcontentsmask  = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
 
+       if(!self.scale)
+               self.scale = 1;
+
+       if(autocvar_g_monsters_edit)
+               self.grab = 1; // owner may carry their monster
+
        if(autocvar_g_fullbrightplayers)
                self.effects |= EF_FULLBRIGHT;
 
@@ -1059,7 +1155,10 @@ float monster_initialize(float mon_id, float nodrop)
        }
 
        if(mon.spawnflags & MONSTER_SIZE_BROKEN)
-               self.scale = 1.3;
+       if(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
+               self.scale *= 1.3;
+               
+       setsize(self, mon.mins * self.scale, mon.maxs * self.scale);
 
        if(!self.ticrate)
                self.ticrate = autocvar_g_monsters_think_delay;
@@ -1089,7 +1188,7 @@ float monster_initialize(float mon_id, float nodrop)
                return FALSE;
 
        if(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
-               monster_setupcolors();
+               monster_setupcolors(self);
 
        CSQCMODEL_AUTOINIT();
 
index 65331207279af26037e6a9084253e4cb7d8ebc45..239db02bd225cb3928453f7ca4bef8f4133209e7 100644 (file)
@@ -2,7 +2,6 @@
 .float monster_attack;
 
 .entity monster_owner; // new monster owner entity, fixes non-solid monsters
-.float monstercount; // per player monster count
 
 .float stat_monsters_killed; // stats
 .float stat_monsters_total;
@@ -11,6 +10,11 @@ float monsters_killed;
 void monsters_setstatus(); // monsters.qc
 .float monster_moveflags; // checks where to move when not attacking
 
+.float wander_delay;
+.float wander_distance;
+
+.float monster_lifetime;
+
 .float spider_slowness; // special spider timer
 
 void monster_remove(entity mon); // removes a monster
@@ -69,11 +73,10 @@ const float MONSTERFLAG_NORESPAWN = 4;
 const float MONSTERFLAG_FLY_VERTICAL = 8; // fly/swim vertically
 const float MONSTERFLAG_INFRONT = 32; // only check for enemies infront of us
 const float MONSTERFLAG_MINIBOSS = 64; // monster spawns as mini-boss (also has a chance of naturally becoming one)
+const float MONSTERFLAG_INVINCIBLE = 128; // monster doesn't take damage (may be used for map objects & temporary monsters)
 const float MONSTERFLAG_SPAWNED = 16384; // flag for spawned monsters
 const float MONSTERFLAG_RESPAWNED = 32768; // flag for re-spawned monsters
 
-.void() monster_spawnfunc;
-
 .float monster_movestate; // used to tell what the monster is currently doing
 const float MONSTER_MOVE_OWNER = 1; // monster will move to owner if in range, or stand still
 const float MONSTER_MOVE_WANDER = 2; // monster will ignore owner & wander around
index 003c0fcc1d64fc4fa0e112576e4342f5933b5d36..117965f7bb016aacb21fbb0ce5733197db86e231 100644 (file)
@@ -637,6 +637,7 @@ void Send_Notification_WOCOVA(
        MSG_CENTER_NOTIF(1, CENTER_JOIN_PREVENT,                0, 0, "",              CPID_PREVENT_JOIN,     "0 0", _("^K1You may not join the game at this time.\nThe player limit reached maximum capacity."), "") \
        MSG_CENTER_NOTIF(1, CENTER_KEEPAWAY_DROPPED,            1, 0, "s1",            CPID_KEEPAWAY,         "0 0", _("^BG%s^BG has dropped the ball!"), "") \
        MSG_CENTER_NOTIF(1, CENTER_KEEPAWAY_PICKUP,             1, 0, "s1",            CPID_KEEPAWAY,         "0 0", _("^BG%s^BG has picked up the ball!"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_KEEPAWAY_PICKUP_SELF,        0, 0, "",              CPID_KEEPAWAY,         "0 0", _("^BGYou picked up the ball"), "") \
        MSG_CENTER_NOTIF(1, CENTER_KEEPAWAY_WARN,               0, 0, "",              CPID_KEEPAWAY_WARN,    "0 0", _("^BGKilling people while you don't have the ball gives no points!"), "") \
        MSG_CENTER_NOTIF(1, CENTER_KEYHUNT_HELP,                0, 0, "",              CPID_KEYHUNT,          "0 0", _("^BGAll keys are in your team's hands!\nHelp the key carriers to meet!"), "") \
        MULTITEAM_CENTER(1, CENTER_KEYHUNT_INTERFERE_, 4,       0, 0, "",              CPID_KEYHUNT,          "0 0", _("^BGAll keys are in ^TC^TT team^BG's hands!\nInterfere ^F4NOW^BG!"), "") \
index bdf80e5d1cb92f698d3944743f7d4a5375258570..c50a3ba5350d3b1fd1bdb394bc0b3ca5852026dd 100644 (file)
@@ -2789,3 +2789,18 @@ float Mod_Q1BSP_NativeContentsFromSuperContents(float supercontents)
        return CONTENT_EMPTY;
 }
 #endif
+
+vector bezier_quadratic_getpoint(vector a, vector b, vector c, float t)
+{
+       return
+               (c - 2 * b + a) * (t * t) +
+               (b - a) * (2 * t) +
+               a;
+}
+
+vector bezier_quadratic_getderivative(vector a, vector b, vector c, float t)
+{
+       return
+               (c - 2 * b + a) * (2 * t) +
+               (b - a) * 2;
+}
index 820f4f5db3fe626400502f4dd78656c402a4aa42..503aa2d2a0f9452fa81232493caa94e95383477b 100644 (file)
@@ -441,3 +441,7 @@ float Announcer_PickNumber(float type, float num);
 float Mod_Q1BSP_SuperContentsFromNativeContents(float nativecontents);
 float Mod_Q1BSP_NativeContentsFromSuperContents(float supercontents);
 #endif
+
+// Quadratic splines (bezier)
+vector bezier_quadratic_getpoint(vector a, vector p, vector b, float t);
+vector bezier_quadratic_getderivative(vector a, vector p, vector b, float t);
index 1de86b6474fe51e529d809a0eeeb69bd9738e572..ff1210c74780fe2d6a512854a75723748c60ba13 100644 (file)
@@ -35,6 +35,7 @@
 #include "xonotic/dialog_settings_effects.c"
 #include "xonotic/dialog_settings_audio.c"
 #include "xonotic/dialog_settings_user.c"
+#include "xonotic/dialog_settings_user_languagewarning.c"
 #include "xonotic/dialog_settings_misc.c"
 #include "xonotic/dialog_multiplayer.c"
 #include "xonotic/dialog_multiplayer_playersetup.c"
index 01ce7ba4347edba58ea9fc77a97f5d4d11dd2c86..1b975da2b188c8fd27098897d5553fc1d80b3245 100644 (file)
@@ -29,6 +29,12 @@ float CheckFirstRunButton(entity me)
        return 0;
 }
 
+void firstRun_setLanguage(entity me)
+{
+       if(prvm_language != cvar_string("_menu_prvm_language"))
+               localcmd("\nprvm_language \"$_menu_prvm_language\"; saveconfig; menu_restart\n");
+}
+
 void XonoticFirstRunDialog_fill(entity me)
 {
        entity e;
@@ -69,7 +75,7 @@ void XonoticFirstRunDialog_fill(entity me)
        me.TR(me);
                me.TD(me, 6, 2, e = makeXonoticLanguageList());
                        e.name = "languageselector_firstrun";
-                       e.doubleClickCommand = "prvm_language \"$_menu_prvm_language\"; saveconfig; menu_restart";
+                       e.setLanguage = firstRun_setLanguage;
        me.TR(me);
        me.TR(me);
 
diff --git a/qcsrc/menu/xonotic/dialog_settings_user_languagewarning.c b/qcsrc/menu/xonotic/dialog_settings_user_languagewarning.c
new file mode 100644 (file)
index 0000000..c830b55
--- /dev/null
@@ -0,0 +1,26 @@
+#ifdef INTERFACE
+CLASS(XonoticLanguageWarningDialog) EXTENDS(XonoticDialog)
+       METHOD(XonoticLanguageWarningDialog, fill, void(entity)) // to be overridden by user to fill the dialog with controls
+       ATTRIB(XonoticLanguageWarningDialog, title, string, _("Warning"))
+       ATTRIB(XonoticLanguageWarningDialog, color, vector, SKINCOLOR_DIALOG_HUDCONFIRM)
+       ATTRIB(XonoticLanguageWarningDialog, intendedWidth, float, 0.6)
+       ATTRIB(XonoticLanguageWarningDialog, rows, float, 5)
+       ATTRIB(XonoticLanguageWarningDialog, columns, float, 4)
+ENDCLASS(XonoticLanguageWarningDialog)
+#endif
+
+#ifdef IMPLEMENTATION
+void XonoticLanguageWarningDialog_fill(entity me)
+{
+       entity e;
+       me.TR(me);
+               me.TD(me, 1, 4, e = makeXonoticTextLabel(0, _("While connected language changes will be applied only to the menu,")));
+       me.TR(me);
+               me.TD(me, 1, 4, e = makeXonoticTextLabel(0, _("full language changes will take effect starting from the next game")));
+       me.TR(me);
+       me.TR(me);
+               // reconnect command doesn't work properly, otherwise it would replace disconnect
+               me.TD(me, 1, 2, e = makeXonoticCommandButton(_("Disconnect now"), '0 0 0', "disconnect", 0));
+               me.TD(me, 1, 2, e = makeXonoticCommandButton(_("Switch language"), '0 0 0', "prvm_language \"$_menu_prvm_language\"; menu_restart; menu_cmd languageselect", 0));
+}
+#endif
index d16e8bb26ede658e06ded51df4cf0fe5ddce36d5..66c3486a3a91c22747a646153d64d181f4f14302 100644 (file)
@@ -143,6 +143,13 @@ void XonoticKeyBinder_keyGrabbed(entity me, float key, float ascii)
        if(key == K_ESCAPE)
                return;
 
+       // forbid these keys from being bound in the menu
+       if(key == K_CAPSLOCK || key == K_NUMLOCK)
+       {
+               KeyBinder_Bind_Change(me, me);
+               return;
+       }
+
        func = Xonotic_KeyBinds_Functions[me.selectedItem];
        if(func == "")
                return;
index b8b31d9e1a475aefe568f0ca9f2f6a1b3464f241..b91610a30ed01e452ccf1eb607f07e53c14596f3 100644 (file)
@@ -28,8 +28,6 @@ CLASS(XonoticLanguageList) EXTENDS(XonoticListBox)
        METHOD(XonoticLanguageList, languageParameter, string(entity, float, float))
 
        ATTRIB(XonoticLanguageList, name, string, "languageselector") // change this to make it noninteractive (for first run dialog)
-
-       ATTRIB(XonoticLanguageList, doubleClickCommand, string, "prvm_language \"$_menu_prvm_language\"\nmenu_restart\nmenu_cmd languageselect")
 ENDCLASS(XonoticLanguageList)
 
 entity makeXonoticLanguageList();
@@ -194,7 +192,13 @@ void XonoticLanguageList_getLanguages(entity me)
 
 void XonoticLanguageList_setLanguage(entity me)
 {
-       localcmd(sprintf("\n%s\n", me.doubleClickCommand));
+       if(prvm_language != cvar_string("_menu_prvm_language"))
+       {
+               if(!(gamestatus & GAME_CONNECTED))
+                       localcmd("\nprvm_language \"$_menu_prvm_language\"; menu_restart; menu_cmd languageselect\n");
+               else
+                       DialogOpenButton_Click(me, main.languageWarningDialog);
+       }
 }
 
 string XonoticLanguageList_languageParameter(entity me, float i, float key)
index 72cecea19cfce5ce4e9627e2adcad594eb79fa93..debe6bd975c0f5dcfe3fde58b185c595363358fe 100644 (file)
@@ -16,6 +16,7 @@ CLASS(MainWindow) EXTENDS(ModalController)
        ATTRIB(MainWindow, crosshairDialog, entity, NULL)
        ATTRIB(MainWindow, hudDialog, entity, NULL)
        ATTRIB(MainWindow, hudconfirmDialog, entity, NULL)
+       ATTRIB(MainWindow, languageWarningDialog, entity, NULL)
        ATTRIB(MainWindow, mainNexposee, entity, NULL)
        ATTRIB(MainWindow, fadedAlpha, float, SKINALPHA_BEHIND)
        ATTRIB(MainWindow, dialogToShow, entity, NULL)
@@ -135,6 +136,10 @@ void MainWindow_configureMainWindow(entity me)
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
 
+       me.languageWarningDialog = i = spawnXonoticLanguageWarningDialog();
+       i.configureDialog(i);
+       me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
+
 
        // dialog used by singleplayer
        me.winnerDialog = i = spawnXonoticWinnerDialog();
index f06866ccffdd64ebd7a2b84ea4efc993824010fc..74b60d30aef98a01243a99b34117b8a19e00ae37 100644 (file)
@@ -656,7 +656,7 @@ float updateCompression()
        GAMETYPE(MAPINFO_TYPE_LMS) \
        GAMETYPE(MAPINFO_TYPE_NEXBALL) \
        GAMETYPE(MAPINFO_TYPE_ONSLAUGHT) \
-       GAMETYPE(MAPINFO_TYPE_RACE) \
+       if (cvar("developer")) GAMETYPE(MAPINFO_TYPE_RACE) \
        GAMETYPE(MAPINFO_TYPE_CTS) \
        GAMETYPE(MAPINFO_TYPE_TEAM_DEATHMATCH) \
        //GAMETYPE(MAPINFO_TYPE_INVASION) \
@@ -667,7 +667,7 @@ float GameType_GetID(float cnt)
        float i;
        i = 0;
 
-       #define GAMETYPE(id) if(i++ == cnt) return id;
+       #define GAMETYPE(id) { if(i++ == cnt) return id; }
        GAMETYPES
        #undef GAMETYPE
 
index d00c60b09d8b47134f9f0a18c94ac7d007bfa08d..9a1872c89fadfb68d651babe38a85097dd1b9091 100644 (file)
@@ -25,6 +25,8 @@ float mean_evaluate(entity e, .float a, .float c, float mean)
 #define MEAN_EVALUATE(prefix) mean_evaluate(self,prefix##_accumulator,prefix##_count,prefix##_mean)
 #define MEAN_DECLARE(prefix,m) float prefix##_mean = m; .float prefix##_count, prefix##_accumulator
 
+.float anticheat_fixangle_endtime;
+
 float anticheat_div0_evade_evasion_delta;
 .float anticheat_div0_evade_offset;
 .vector anticheat_div0_evade_v_angle;
@@ -37,10 +39,32 @@ MEAN_DECLARE(anticheat_div0_strafebot_old, 5);
 .vector anticheat_div0_strafebot_forward_prev;
 MEAN_DECLARE(anticheat_div0_strafebot_new, 5);
 
+// Snap-aim detection: we track the average angular speed of aiming over time, in "radians per second".
+// Signal: a high-power mean. Cheaters will have high "signal" here.
+// Noise: a low-power mean. Active/shivery players will have high "noise" here.
+// Note one can always artificially add noise - so very high values of both signal and noise need to be checked too.
+MEAN_DECLARE(anticheat_idle_snapaim_signal, 5);
+MEAN_DECLARE(anticheat_idle_snapaim_noise, 1);
+
+// TEMP DEBUG STUFF.
+MEAN_DECLARE(anticheat_idle_snapaim_m2, 2);
+MEAN_DECLARE(anticheat_idle_snapaim_m3, 3);
+MEAN_DECLARE(anticheat_idle_snapaim_m4, 4);
+MEAN_DECLARE(anticheat_idle_snapaim_m7, 7);
+MEAN_DECLARE(anticheat_idle_snapaim_m10, 10);
+
 .float anticheat_speedhack_offset;
 .float anticheat_speedhack_movetime, anticheat_speedhack_movetime_count, anticheat_speedhack_movetime_frac;
 MEAN_DECLARE(anticheat_speedhack, 5);
 
+.float anticheat_speedhack_accu;
+.float anticheat_speedhack_lasttime;
+MEAN_DECLARE(anticheat_speedhack_m1, 1);
+MEAN_DECLARE(anticheat_speedhack_m2, 2);
+MEAN_DECLARE(anticheat_speedhack_m3, 3);
+MEAN_DECLARE(anticheat_speedhack_m4, 4);
+MEAN_DECLARE(anticheat_speedhack_m5, 5);
+
 float movement_oddity(vector m0, vector m1)
 {
        float cosangle = normalize(m0) * normalize(m1);
@@ -60,7 +84,7 @@ void anticheat_physics()
        if(self.anticheat_div0_evade_offset == 0)
        {
                f = fabs(anticheat_div0_evade_evasion_delta - floor(anticheat_div0_evade_evasion_delta) - 0.5) * 2; // triangle function
-               self.anticheat_div0_evade_offset = time + sys_frametime * (3 * f - 1);
+               self.anticheat_div0_evade_offset = servertime + sys_frametime * (3 * f - 1);
                self.anticheat_div0_evade_v_angle = self.v_angle;
                self.anticheat_div0_evade_forward_initial = v_forward;
                MEAN_ACCUMULATE(anticheat_div0_evade, 0, 1);
@@ -75,8 +99,32 @@ void anticheat_physics()
        MEAN_ACCUMULATE(anticheat_div0_strafebot_old, movement_oddity(self.movement, self.anticheat_div0_strafebot_movement_prev), 1);
        self.anticheat_div0_strafebot_movement_prev = self.movement;
 
-       if(vlen(self.anticheat_div0_strafebot_forward_prev))
-               MEAN_ACCUMULATE(anticheat_div0_strafebot_new, 0.5 - 0.5 * (self.anticheat_div0_strafebot_forward_prev * v_forward), 1);
+       // Note: this actually tries to detect snap-aim.
+       if(vlen(self.anticheat_div0_strafebot_forward_prev) && time > self.anticheat_fixangle_endtime) {
+               float cosangle = self.anticheat_div0_strafebot_forward_prev * v_forward;
+               float angle = cosangle < -1 ? M_PI : cosangle > 1 ? 0 : acos(cosangle);
+               /*
+               if (angle >= 10 * M_PI / 180)
+                       printf("SNAP %s: %f for %f, %f since fixangle\n", self.netname, angle * 180 / M_PI, cosangle, time - self.anticheat_fixangle_endtime);
+               */
+               MEAN_ACCUMULATE(anticheat_div0_strafebot_new, angle / M_PI, 1);
+
+               if (autocvar_slowmo > 0) {
+                       // Technically this is a NOP, as the engine should be ensuring
+                       // this in the first place. Let's guard against dividing by
+                       // zero anyway.
+                       float dt = max(0.001, frametime) / autocvar_slowmo;
+
+                       float anglespeed = angle / dt;
+                       MEAN_ACCUMULATE(anticheat_idle_snapaim_signal, anglespeed, dt);
+                       MEAN_ACCUMULATE(anticheat_idle_snapaim_noise, anglespeed, dt);
+                       MEAN_ACCUMULATE(anticheat_idle_snapaim_m2, anglespeed, dt);
+                       MEAN_ACCUMULATE(anticheat_idle_snapaim_m3, anglespeed, dt);
+                       MEAN_ACCUMULATE(anticheat_idle_snapaim_m4, anglespeed, dt);
+                       MEAN_ACCUMULATE(anticheat_idle_snapaim_m7, anglespeed, dt);
+                       MEAN_ACCUMULATE(anticheat_idle_snapaim_m10, anglespeed, dt);
+               }
+       }
        self.anticheat_div0_strafebot_forward_prev = v_forward;
 
        // generic speedhack detection: correlate anticheat_speedhack_movetime (UPDATED BEFORE THIS) and server time
@@ -94,6 +142,25 @@ void anticheat_physics()
                self.anticheat_speedhack_offset += (f - self.anticheat_speedhack_offset) * frametime * 0.1;
        }
 
+       // new generic speedhack detection
+       if (self.anticheat_speedhack_lasttime > 0) {
+               float dt = servertime - self.anticheat_speedhack_lasttime;
+               const float falloff = 0.2;
+               self.anticheat_speedhack_accu *= exp(-dt * falloff);
+               self.anticheat_speedhack_accu += frametime * falloff;
+               // NOTE: at cl_netfps x, this actually averages not to 1, but to 1/x * falloff / (1 - exp(-1/x * falloff))
+               // For 15 netfps (absolute minimum bearable), and 0.2 falloff, this is: 1.0067
+               self.anticheat_speedhack_lasttime = servertime;
+               MEAN_ACCUMULATE(anticheat_speedhack_m1, self.anticheat_speedhack_accu, frametime);
+               MEAN_ACCUMULATE(anticheat_speedhack_m2, self.anticheat_speedhack_accu, frametime);
+               MEAN_ACCUMULATE(anticheat_speedhack_m3, self.anticheat_speedhack_accu, frametime);
+               MEAN_ACCUMULATE(anticheat_speedhack_m4, self.anticheat_speedhack_accu, frametime);
+               MEAN_ACCUMULATE(anticheat_speedhack_m5, self.anticheat_speedhack_accu, frametime);
+       } else {
+               self.anticheat_speedhack_accu = 1;
+               self.anticheat_speedhack_lasttime = servertime;
+       }
+
        // race/CTS: force kbd movement for fairness
        if(g_race || g_cts)
        {
@@ -168,15 +235,69 @@ void anticheat_report()
 {
        if(!autocvar_sv_eventlog)
                return;
+       // TODO(divVerent): Use xonstat to acquire good thresholds.
        GameLogEcho(strcat(":anticheat:_time:", ftos(self.playerid), ":", ftos(servertime - self.anticheat_jointime)));
        GameLogEcho(strcat(":anticheat:speedhack:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_speedhack), 240, 0.1, 0.15)));
+       GameLogEcho(strcat(":anticheat:speedhack_m1:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_speedhack_m1), 240, 0.1, 0.15)));
+       GameLogEcho(strcat(":anticheat:speedhack_m2:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_speedhack_m2), 240, 0.1, 0.15)));
+       GameLogEcho(strcat(":anticheat:speedhack_m3:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_speedhack_m3), 240, 0.1, 0.15)));
+       GameLogEcho(strcat(":anticheat:speedhack_m4:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_speedhack_m4), 240, 0.1, 0.15)));
+       GameLogEcho(strcat(":anticheat:speedhack_m5:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_speedhack_m5), 240, 0.1, 0.15)));
        GameLogEcho(strcat(":anticheat:div0_strafebot_old:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_div0_strafebot_old), 120, 0.3, 0.4)));
        GameLogEcho(strcat(":anticheat:div0_strafebot_new:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_div0_strafebot_new), 120, 0.3, 0.4)));
        GameLogEcho(strcat(":anticheat:div0_evade:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_div0_evade), 120, 0.1, 0.2)));
+       GameLogEcho(strcat(":anticheat:idle_snapaim:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_idle_snapaim_signal) - MEAN_EVALUATE(anticheat_idle_snapaim_noise), 120, 0.1, 0.2)));
+       GameLogEcho(strcat(":anticheat:idle_snapaim_signal:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_idle_snapaim_signal), 120, 0.1, 0.2)));
+       GameLogEcho(strcat(":anticheat:idle_snapaim_noise:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_idle_snapaim_noise), 120, 0.1, 0.2)));
+       GameLogEcho(strcat(":anticheat:idle_snapaim_m2:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_idle_snapaim_m2), 120, 0.1, 0.2)));
+       GameLogEcho(strcat(":anticheat:idle_snapaim_m3:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_idle_snapaim_m3), 120, 0.1, 0.2)));
+       GameLogEcho(strcat(":anticheat:idle_snapaim_m4:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_idle_snapaim_m4), 120, 0.1, 0.2)));
+       GameLogEcho(strcat(":anticheat:idle_snapaim_m7:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_idle_snapaim_m7), 120, 0.1, 0.2)));
+       GameLogEcho(strcat(":anticheat:idle_snapaim_m10:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_idle_snapaim_m10), 120, 0.1, 0.2)));
+}
+
+float anticheat_getvalue(string id)
+{
+       switch(id) {
+               case "_time": return servertime - self.anticheat_jointime;
+               case "speedhack": return MEAN_EVALUATE(anticheat_speedhack);
+               case "speedhack_m1": return MEAN_EVALUATE(anticheat_speedhack_m1);
+               case "speedhack_m2": return MEAN_EVALUATE(anticheat_speedhack_m2);
+               case "speedhack_m3": return MEAN_EVALUATE(anticheat_speedhack_m3);
+               case "speedhack_m4": return MEAN_EVALUATE(anticheat_speedhack_m4);
+               case "speedhack_m5": return MEAN_EVALUATE(anticheat_speedhack_m5);
+               case "div0_strafebot_old": return MEAN_EVALUATE(anticheat_div0_strafebot_old);
+               case "div0_strafebot_new": return MEAN_EVALUATE(anticheat_div0_strafebot_new);
+               case "div0_evade": return MEAN_EVALUATE(anticheat_div0_evade);
+               case "idle_snapaim": return MEAN_EVALUATE(anticheat_idle_snapaim_signal) - MEAN_EVALUATE(anticheat_idle_snapaim_noise);
+               case "idle_snapaim_signal": return MEAN_EVALUATE(anticheat_idle_snapaim_signal);
+               case "idle_snapaim_noise": return MEAN_EVALUATE(anticheat_idle_snapaim_noise);
+               case "idle_snapaim_m2": return MEAN_EVALUATE(anticheat_idle_snapaim_m2);
+               case "idle_snapaim_m3": return MEAN_EVALUATE(anticheat_idle_snapaim_m3);
+               case "idle_snapaim_m4": return MEAN_EVALUATE(anticheat_idle_snapaim_m4);
+               case "idle_snapaim_m7": return MEAN_EVALUATE(anticheat_idle_snapaim_m7);
+               case "idle_snapaim_m10": return MEAN_EVALUATE(anticheat_idle_snapaim_m10);
+       }
+       return -1;
+}
+
+void anticheat_startframe()
+{
+       anticheat_div0_evade_evasion_delta += frametime * (0.5 + random());
+}
+
+void anticheat_fixangle()
+{
+       self.anticheat_fixangle_endtime = servertime + ANTILAG_LATENCY(self) + 0.2;
 }
 
-void anticheat_serverframe()
+void anticheat_endframe()
 {
+       entity oldself = self;
+       FOR_EACH_CLIENT(self)
+               if (self.fixangle)
+                       anticheat_fixangle();
+       self = oldself;
        anticheat_div0_evade_evasion_delta += frametime * (0.5 + random());
 }
 
index 80c7636a0b6852a86d1b35d36e84ff87e2e15f5f..e46dcce7b2051a8af3e8e847975d76e41b5079f3 100644 (file)
@@ -6,4 +6,9 @@ void anticheat_physics();
 void anticheat_spectatecopy(entity spectatee);
 void anticheat_prethink();
 
-void anticheat_serverframe();
+float anticheat_getvalue(string name);
+
+void anticheat_startframe();
+void anticheat_endframe();
+
+void anticheat_fixangle();
index 674c95b14ea735e2b67e0cb063f21702e2a1c754..ee7d24d8b95ca0155dd3a7612c60a2740edd506d 100644 (file)
@@ -1221,6 +1221,7 @@ float autocvar_g_physical_items_damageforcescale;
 float autocvar_g_physical_items_reset;
 float autocvar_g_monsters;
 float autocvar_g_monsters_edit;
+float autocvar_g_monsters_sounds;
 float autocvar_g_monsters_think_delay;
 float autocvar_g_monsters_max;
 float autocvar_g_monsters_max_perplayer;
@@ -1244,7 +1245,10 @@ float autocvar_g_touchexplode_damage;
 float autocvar_g_touchexplode_edgedamage;
 float autocvar_g_touchexplode_force;
 float autocvar_g_invasion_round_timelimit;
-#define autocvar_g_invasion_round_limit cvar("g_invasion_round_limit")
+float autocvar_g_invasion_teams;
+float autocvar_g_invasion_team_spawns;
+float autocvar_g_invasion_spawnpoint_spawn_delay;
+#define autocvar_g_invasion_point_limit cvar("g_invasion_point_limit")
 float autocvar_g_invasion_warmup;
 float autocvar_g_invasion_monster_count;
 float autocvar_g_invasion_zombies_only;
index a0e6bfffd42926004503c8e9f63b8e9eaaa07836..2e57eecb3b1c6cf3e2b7e0e7bbc4e15e6b2a6867 100644 (file)
@@ -722,10 +722,10 @@ void havocbot_movetogoal()
                        vector dst_ahead, dst_down;
                        makevectors(self.v_angle_y * '0 1 0');
                        dst_ahead = self.origin + self.view_ofs + (self.velocity * 0.4) + (v_forward * 32 * 3);
-                       dst_down = dst_ahead + '0 0 -1500';
+                       dst_down = dst_ahead - '0 0 1500';
 
                        // Look ahead
-                       traceline(self.origin + self.view_ofs , dst_ahead, TRUE, world);
+                       traceline(self.origin + self.view_ofs, dst_ahead, TRUE, world);
 
                        // Check head-banging against walls
                        if(vlen(self.origin + self.view_ofs - trace_endpos) < 25 && !(self.aistatus & AI_STATUS_OUT_WATER))
index 691ce3e0a929ab44e3b0138c1a91a2fa760b82b0..0facbf037d881265dc55c10fb1205dedbfd9c391 100644 (file)
@@ -366,7 +366,7 @@ float navigation_waypoint_will_link(vector v, vector org, entity ent, float walk
 }
 
 // find the spawnfunc_waypoint near a dynamic goal such as a dropped weapon
-entity navigation_findnearestwaypoint_withdist(entity ent, float walkfromwp, float bestdist)
+entity navigation_findnearestwaypoint_withdist_except(entity ent, float walkfromwp, float bestdist, entity except)
 {
        entity waylist, w, best;
        vector v, org, pm1, pm2;
@@ -380,7 +380,7 @@ entity navigation_findnearestwaypoint_withdist(entity ent, float walkfromwp, flo
        while (w)
        {
                // if object is touching spawnfunc_waypoint
-               if(w != ent)
+               if(w != ent && w != except)
                        if (boxesoverlap(pm1, pm2, w.absmin, w.absmax))
                                return w;
                w = w.chain;
@@ -426,7 +426,14 @@ entity navigation_findnearestwaypoint_withdist(entity ent, float walkfromwp, flo
 }
 entity navigation_findnearestwaypoint(entity ent, float walkfromwp)
 {
-       return navigation_findnearestwaypoint_withdist(ent, walkfromwp, 1050);
+       entity wp = navigation_findnearestwaypoint_withdist_except(ent, walkfromwp, 1050, world);
+       if (autocvar_g_waypointeditor_auto)
+       {
+               entity wp2 = navigation_findnearestwaypoint_withdist_except(ent, walkfromwp, 1050, wp);
+               if (!wp2)
+                       wp.wpflags |= WAYPOINTFLAG_PROTECTED;
+       }
+       return wp;
 }
 
 // finds the waypoints near the bot initiating a navigation query
index 78bf0f264693a819949f357a94a9e16eb5a3d6c3..da0b3508941ecaec1fdf0205e0b464be0c971978 100644 (file)
@@ -543,6 +543,46 @@ void waypoint_load_links_hardwired()
        dprint(".waypoints.hardwired\n");
 }
 
+entity waypoint_get_link(entity w, float i)
+{
+       switch(i)
+       {
+               case  0:return w.wp00;
+               case  1:return w.wp01;
+               case  2:return w.wp02;
+               case  3:return w.wp03;
+               case  4:return w.wp04;
+               case  5:return w.wp05;
+               case  6:return w.wp06;
+               case  7:return w.wp07;
+               case  8:return w.wp08;
+               case  9:return w.wp09;
+               case 10:return w.wp10;
+               case 11:return w.wp11;
+               case 12:return w.wp12;
+               case 13:return w.wp13;
+               case 14:return w.wp14;
+               case 15:return w.wp15;
+               case 16:return w.wp16;
+               case 17:return w.wp17;
+               case 18:return w.wp18;
+               case 19:return w.wp19;
+               case 20:return w.wp20;
+               case 21:return w.wp21;
+               case 22:return w.wp22;
+               case 23:return w.wp23;
+               case 24:return w.wp24;
+               case 25:return w.wp25;
+               case 26:return w.wp26;
+               case 27:return w.wp27;
+               case 28:return w.wp28;
+               case 29:return w.wp29;
+               case 30:return w.wp30;
+               case 31:return w.wp31;
+               default:return world;
+       }
+}
+
 // Save all waypoint links to a file
 void waypoint_save_links()
 {
@@ -565,44 +605,7 @@ void waypoint_save_links()
                for(i=0;i<32;++i)
                {
                        // :S
-                       link = world;
-                       switch(i)
-                       {
-                               //      for i in $(seq -w 0 31); do echo "case $i:link = w.wp$i; break;"; done;
-                               case  0:link = w.wp00; break;
-                               case  1:link = w.wp01; break;
-                               case  2:link = w.wp02; break;
-                               case  3:link = w.wp03; break;
-                               case  4:link = w.wp04; break;
-                               case  5:link = w.wp05; break;
-                               case  6:link = w.wp06; break;
-                               case  7:link = w.wp07; break;
-                               case  8:link = w.wp08; break;
-                               case  9:link = w.wp09; break;
-                               case 10:link = w.wp10; break;
-                               case 11:link = w.wp11; break;
-                               case 12:link = w.wp12; break;
-                               case 13:link = w.wp13; break;
-                               case 14:link = w.wp14; break;
-                               case 15:link = w.wp15; break;
-                               case 16:link = w.wp16; break;
-                               case 17:link = w.wp17; break;
-                               case 18:link = w.wp18; break;
-                               case 19:link = w.wp19; break;
-                               case 20:link = w.wp20; break;
-                               case 21:link = w.wp21; break;
-                               case 22:link = w.wp22; break;
-                               case 23:link = w.wp23; break;
-                               case 24:link = w.wp24; break;
-                               case 25:link = w.wp25; break;
-                               case 26:link = w.wp26; break;
-                               case 27:link = w.wp27; break;
-                               case 28:link = w.wp28; break;
-                               case 29:link = w.wp29; break;
-                               case 30:link = w.wp30; break;
-                               case 31:link = w.wp31; break;
-                       }
-
+                       link = waypoint_get_link(w, i);
                        if(link==world)
                                continue;
 
@@ -876,7 +879,7 @@ float botframe_autowaypoints_fixdown(vector v)
        return 1;
 }
 
-float botframe_autowaypoints_createwp(vector v, entity p, .entity fld)
+float botframe_autowaypoints_createwp(vector v, entity p, .entity fld, float f)
 {
        entity w;
 
@@ -890,7 +893,7 @@ float botframe_autowaypoints_createwp(vector v, entity p, .entity fld)
                w = find(w, classname, "waypoint");
        }
 
-       waypoint_schedulerelink(p.fld = waypoint_spawn(v, v, 0));
+       waypoint_schedulerelink(p.fld = waypoint_spawn(v, v, f));
        return 1;
 }
 
@@ -916,7 +919,7 @@ float botframe_autowaypoints_fix_from(entity p, float walkfromwp, entity wp, .en
 
        if(wp)
        {
-               // if any WP w fulfills wp -> w -> porg, then switch from wp to w
+               // if any WP w fulfills wp -> w -> porg and w is closer than wp, then switch from wp to w
 
                // if wp -> porg, then OK
                float maxdist;
@@ -993,7 +996,7 @@ float botframe_autowaypoints_fix_from(entity p, float walkfromwp, entity wp, .en
                t = (tmin + tmax) * 0.5;
                o = antilag_takebackorigin(p, time - t);
                if(!botframe_autowaypoints_fixdown(o))
-                       return -1;
+                       return -2;
                o = trace_endpos;
 
                if(wp)
@@ -1032,7 +1035,7 @@ float botframe_autowaypoints_fix_from(entity p, float walkfromwp, entity wp, .en
        }
 
        print("spawning a waypoint for connecting to ", etos(wp), "\n");
-       botframe_autowaypoints_createwp(o, p, fld);
+       botframe_autowaypoints_createwp(o, p, fld, 0);
        return 1;
 }
 
@@ -1051,7 +1054,78 @@ void botframe_autowaypoints_fix(entity p, float walkfromwp, .entity fld)
        print("emergency: got no good nearby WP to build a link from, starting a new chain\n");
        if(!botframe_autowaypoints_fixdown(p.origin))
                return; // shouldn't happen, caught above
-       botframe_autowaypoints_createwp(trace_endpos, p, fld);
+       botframe_autowaypoints_createwp(trace_endpos, p, fld, WAYPOINTFLAG_PROTECTED);
+}
+
+void botframe_deleteuselesswaypoints()
+{
+       entity w, w1, w2;
+       float i, j, k;
+       for (w = world; (w = findfloat(w, bot_pickup, TRUE)); )
+       {
+               // NOTE: this protects waypoints if they're the ONLY nearest
+               // waypoint. That's the intention.
+               navigation_findnearestwaypoint(w, FALSE);  // Walk TO item.
+               navigation_findnearestwaypoint(w, TRUE);  // Walk FROM item.
+       }
+       for (w = world; (w = find(w, classname, "waypoint")); )
+       {
+               w.wpflags &= ~WAYPOINTFLAG_USEFUL;
+               // WP is useful if:
+               if (w.wpflags & WAYPOINTFLAG_ITEM)
+                       w.wpflags |= WAYPOINTFLAG_USEFUL;
+               if (w.wpflags & WAYPOINTFLAG_TELEPORT)
+                       w.wpflags |= WAYPOINTFLAG_USEFUL;
+               if (w.wpflags & WAYPOINTFLAG_PROTECTED)
+                       w.wpflags |= WAYPOINTFLAG_USEFUL;
+               // b) WP is closest WP for an item/spawnpoint/other entity
+               //    This has been done above by protecting these WPs.
+       }
+       // c) There are w1, w, w2 so that w1 -> w, w -> w2 and not w1 -> w2.
+       for (w1 = world; (w1 = find(w1, classname, "waypoint")); )
+       {
+               if (w1.wpflags & WAYPOINTFLAG_PERSONAL)
+                       continue;
+               for (i = 0; i < 32; ++i)
+               {
+                       w = waypoint_get_link(w1, i);
+                       if (!w)
+                               break;
+                       if (w.wpflags & WAYPOINTFLAG_PERSONAL)
+                               continue;
+                       if (w.wpflags & WAYPOINTFLAG_USEFUL)
+                               continue;
+                       for (j = 0; j < 32; ++j)
+                       {
+                               w2 = waypoint_get_link(w1, i);
+                               if (!w2)
+                                       break;
+                               if (w2.wpflags & WAYPOINTFLAG_PERSONAL)
+                                       continue;
+                               for (k = 0; k < 32; ++k)
+                               {
+                                       if (waypoint_get_link(w1, k) == w2)
+                                               continue;
+                                       // IF WE GET HERE, w is proven useful
+                                       // to get from w1 to w2!
+                                       w.wpflags |= WAYPOINTFLAG_USEFUL;
+                                       continue;
+                               }
+                       }
+               }
+       }
+       for (w = world; (w = find(w, classname, "waypoint")); )
+       {
+               if (!(w.wpflags & WAYPOINTFLAG_USEFUL))
+               {
+                       printf("Removed a waypoint at %v. Try again for more!\n", w.origin);
+                       te_explosion(w.origin);
+                       waypoint_remove(w);
+                       break;
+               }
+       }
+       for (w = world; (w = find(w, classname, "waypoint")); )
+               w.wpflags &= ~WAYPOINTFLAG_USEFUL; // temp flag
 }
 
 void botframe_autowaypoints()
@@ -1066,5 +1140,7 @@ void botframe_autowaypoints()
                botframe_autowaypoints_fix(p, TRUE, botframe_autowaypoints_lastwp1);
                //te_explosion(p.botframe_autowaypoints_lastwp0.origin);
        }
+
+       botframe_deleteuselesswaypoints();
 }
 
index 5a75551d2452652383a62728383141633b412c97..9ac92405d9ee928f9bf03d653e9f56ee6a1f3002 100644 (file)
@@ -7,6 +7,8 @@ const float WAYPOINTFLAG_ITEM = 4194304;
 const float WAYPOINTFLAG_TELEPORT = 2097152;
 const float WAYPOINTFLAG_NORELINK = 1048576;
 const float WAYPOINTFLAG_PERSONAL = 524288;
+const float WAYPOINTFLAG_PROTECTED = 262144;  // Useless WP detection never kills these.
+const float WAYPOINTFLAG_USEFUL = 131072;  // Useless WP detection temporary flag.
 
 // fields you can query using prvm_global server to get some statistics about waypoint linking culling
 float relink_total, relink_walkculled, relink_pvsculled, relink_lengthculled;
index 691c4930e995978bc50135e3185260b659fd6979..4ab1836e8c0e242828aa2b0e7cf470b1897ea71b 100644 (file)
@@ -283,7 +283,7 @@ void FixPlayermodel()
                if(teamplay)
                {
                        string s;
-                       s = Team_ColorName_Lower(self.team);
+                       s = Static_Team_ColorName_Lower(self.team);
                        if(s != "neutral")
                        {
                                defaultmodel = cvar_string(strcat("sv_defaultplayermodel_", s));
@@ -1425,7 +1425,7 @@ void player_powerups (void)
        // add a way to see what the items were BEFORE all of these checks for the mutator hook
        olditems = self.items;
 
-       if((self.items & IT_USING_JETPACK) && !self.deadflag)
+       if((self.items & IT_USING_JETPACK) && !self.deadflag && !gameover)
                self.modelflags |= MF_ROCKET;
        else
                self.modelflags &= ~MF_ROCKET;
index 0098561373abb930bb7d869d71ac88f0bffeac33..43c7be517958e718b864f25bbf46e699b741c90d 100644 (file)
@@ -19,6 +19,9 @@ When you press the jump key
 */
 void PlayerJump (void)
 {
+       if(self.player_blocked)
+               return; // no jumping while blocked
+
        float doublejump = FALSE;
 
        player_multijump = doublejump;
@@ -636,9 +639,7 @@ void SV_PlayerPhysics()
 
        maxspd_mod = 1;
        if(self.ballcarried)
-               if(g_nexball)
-                       maxspd_mod *= autocvar_g_nexball_basketball_carrier_highspeed;
-               else if(g_keepaway)
+               if(g_keepaway)
                        maxspd_mod *= autocvar_g_keepaway_ballcarrier_highspeed;
 
        maxspd_mod *= autocvar_g_movement_highspeed;
index 67738c4c6c0d28fb4c59a960203330356dd1567a..0fe71f1448c7dd72fbfad0ab3e23066e1f9f166f 100644 (file)
@@ -673,15 +673,7 @@ void PlayerDamage (entity inflictor, entity attacker, float damage, float deatht
 
                Portal_ClearAllLater(self);
 
-               if(IS_REAL_CLIENT(self))
-               {
-                       self.fixangle = TRUE;
-                       //msg_entity = self;
-                       //WriteByte (MSG_ONE, SVC_SETANGLE);
-                       //WriteAngle (MSG_ONE, self.v_angle_x);
-                       //WriteAngle (MSG_ONE, self.v_angle_y);
-                       //WriteAngle (MSG_ONE, 80);
-               }
+               self.fixangle = TRUE;
 
                if(defer_ClientKill_Now_TeamChange)
                        ClientKill_Now_TeamChange(); // can turn player into spectator
@@ -758,7 +750,7 @@ float Say(entity source, float teamsay, entity privatesay, string msgin, float f
 //   0 = reject
 //  -1 = fake accept
 {
-       string msgstr, colorstr, cmsgstr, namestr, fullmsgstr, sourcemsgstr, fullcmsgstr, sourcecmsgstr;
+       string msgstr, colorstr, cmsgstr, namestr, fullmsgstr, sourcemsgstr, fullcmsgstr, sourcecmsgstr, colorprefix;
        float flood;
        var .float flood_field;
        entity head;
@@ -803,14 +795,19 @@ float Say(entity source, float teamsay, entity privatesay, string msgin, float f
        else
                namestr = source.netname;
 
+       if(strdecolorize(namestr) == namestr)
+               colorprefix = "^3";
+       else
+               colorprefix = "^7";
+
        if(msgin != "")
        {
                if(privatesay)
                {
-                       msgstr = strcat("\{1}\{13}* ^3", namestr, "^3 tells you: ^7");
+                       msgstr = strcat("\{1}\{13}* ", colorprefix, namestr, "^3 tells you: ^7");
                        privatemsgprefixlen = strlen(msgstr);
                        msgstr = strcat(msgstr, msgin);
-                       cmsgstr = strcat(colorstr, "^3", namestr, "^3 tells you:\n^7", msgin);
+                       cmsgstr = strcat(colorstr, colorprefix, namestr, "^3 tells you:\n^7", msgin);
                        if(autocvar_g_chat_teamcolors)
                                privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", playername(privatesay), ": ^7");
                        else
@@ -818,12 +815,12 @@ float Say(entity source, float teamsay, entity privatesay, string msgin, float f
                }
                else if(teamsay)
                {
-                       msgstr = strcat("\{1}\{13}", colorstr, "(^3", namestr, colorstr, ") ^7", msgin);
-                       cmsgstr = strcat(colorstr, "(^3", namestr, colorstr, ")\n^7", msgin);
+                       msgstr = strcat("\{1}\{13}", colorstr, "(", colorprefix, namestr, colorstr, ") ^7", msgin);
+                       cmsgstr = strcat(colorstr, "(", colorprefix, namestr, colorstr, ")\n^7", msgin);
                }
                else
                {
-                       msgstr = strcat("\{1}", namestr, "^7: ", msgin);
+                       msgstr = strcat("\{1}", colorprefix, namestr, "^7: ", msgin);
                        cmsgstr = "";
                }
                msgstr = strcat(strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint
index f3d675f35e71a3000ef814d410c43930cb959b59..c36033a2818886ff62ba943a24ca1aa49874f625 100644 (file)
@@ -98,7 +98,7 @@ void W_HitPlotAnalysis(entity player, vector screenforward, vector screenright,
 
                org = player.origin + player.view_ofs;
                traceline_antilag_force(player, org, org + screenforward * MAX_SHOT_DISTANCE, MOVE_NORMAL, player, lag);
-               if(IS_CLIENT(trace_ent))
+               if(IS_CLIENT(trace_ent) || (trace_ent.flags & FL_MONSTER))
                {
                        antilag_takeback(trace_ent, time - lag);
                        hitplot = W_HitPlotNormalizedUntransform(org, trace_ent, screenforward, screenright, screenup, trace_endpos);
index c8c87e09687f49a6df6e356acd6e58eaf9cd2255..78424484aa34f4ff826c794a5c893383414c600f 100644 (file)
@@ -267,7 +267,7 @@ void ClientCommand_mobspawn(float request, float argc)
                {
                        entity e;
                        string tospawn;
-                       float moveflag;
+                       float moveflag, monstercount = 0;
                        
                        moveflag = (argv(2) ? stof(argv(2)) : 1); // follow owner if not defined
                        tospawn = strtolower(argv(1));
@@ -278,14 +278,21 @@ void ClientCommand_mobspawn(float request, float argc)
                                return;
                        }
                        
+                       FOR_EACH_MONSTER(e)
+                       {
+                               if(e.realowner == self)
+                                       ++monstercount;
+                       }
+                       
                        if(autocvar_g_monsters_max <= 0 || autocvar_g_monsters_max_perplayer <= 0) { sprint(self, "Monster spawning is disabled.\n"); return; }
                        else if(!IS_PLAYER(self)) { sprint(self, "You can't spawn monsters while spectating.\n"); return; }
                        else if(MUTATOR_CALLHOOK(AllowMobSpawning)) { sprint(self, "Monster spawning is currently disabled by a mutator.\n"); return; }
                        else if(!autocvar_g_monsters) { Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_MONSTERS_DISABLED); return; }
                        else if(self.vehicle) { sprint(self, "You can't spawn monsters while driving a vehicle.\n"); return; }
+                       else if(self.freezetag_frozen) { sprint(self, "You can't spawn monsters while frozen.\n"); return; }
                        else if(autocvar_g_campaign) { sprint(self, "You can't spawn monsters in campaign mode.\n"); return; }
                        else if(self.deadflag != DEAD_NO) { sprint(self, "You can't spawn monsters while dead.\n"); return; }
-                       else if(self.monstercount >= autocvar_g_monsters_max_perplayer) { sprint(self, "You have spawned too many monsters, kill some before trying to spawn any more.\n"); return; }
+                       else if(monstercount >= autocvar_g_monsters_max_perplayer) { sprint(self, "You have spawned too many monsters, kill some before trying to spawn any more.\n"); return; }
                        else if(totalspawned >= autocvar_g_monsters_max) { sprint(self, "The global maximum monster count has been reached, kill some before trying to spawn any more.\n"); return; }
                        else if(tospawn != "")
                        {
@@ -302,16 +309,15 @@ void ClientCommand_mobspawn(float request, float argc)
                                        }
                                }
 
-                               if(found)
+                               if(found || tospawn == "random")
                                {
-                                       self.monstercount += 1;
                                        totalspawned += 1;
                                
                                        makevectors(self.v_angle);
                                        WarpZone_TraceBox (CENTER_OR_VIEWOFS(self), PL_MIN, PL_MAX, CENTER_OR_VIEWOFS(self) + v_forward * 150, TRUE, self);
                                        //WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 150, MOVE_NORMAL, self);
                                
-                                       e = spawnmonster(tospawn, 0, self, self, trace_endpos, FALSE, moveflag);
+                                       e = spawnmonster(tospawn, 0, self, self, trace_endpos, FALSE, FALSE, moveflag);
                                        
                                        sprint(self, strcat("Spawned ", e.monster_name, "\n"));
                                        
@@ -456,8 +462,9 @@ void ClientCommand_selectteam(float request, float argc)
                                                                        {
                                                                                if(autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance)
                                                                                {
+                                                                                       CheckAllowedTeams(self);
                                                                                        GetTeamCounts(self);
-                                                                                       if(!TeamSmallerEqThanTeam(selection, self.team, self))
+                                                                                       if(!TeamSmallerEqThanTeam(Team_TeamToNumber(selection), Team_TeamToNumber(self.team), self))
                                                                                        {
                                                                                                sprint(self, "Cannot change to a larger/better/shinier team\n");
                                                                                                return;
index b8979155d9d3f0a139a38a9d88bab925d001bf3d..29e817ea6249ef9c1b5293c313f6e52e53d25401 100644 (file)
@@ -338,7 +338,7 @@ string getlsmaps()
        }
 
        MapInfo_ClearTemps();
-       return sprintf("^7Maps available%s: %s\n", (newmaps ? " (New maps have asterisks marked in blue)" : ""), lsmaps);
+       return sprintf("^7Maps available (%d)%s: %s\n", tokenize_console(lsmaps), (newmaps ? " (New maps have asterisks marked in blue)" : ""), lsmaps);
 }
 
 string getmonsterlist()
index 45871b7aeb60ad51101651ca3c019fdcd1c5a4d8..c0da029c51248b0fbafb4eaee9053a4f898acfde 100644 (file)
@@ -157,9 +157,6 @@ void GameCommand_mobbutcher(float request)
                                ++removed_count;
                        }
 
-                       FOR_EACH_PLAYER(head)
-                               head.monstercount = 0;
-
                        monsters_total = 0; // reset stats?
                        monsters_killed = 0;
 
@@ -1516,7 +1513,9 @@ void GameCommand_trace(float request, float argc)
                        {
                                case "debug":
                                {
+                                       float hitcount = 0;
                                        print("TEST CASE. If this returns the runaway loop counter error, possibly everything is oaky.\n");
+                                       float worst_endpos_bug = 0;
                                        for(;;)
                                        {
                                                org = world.mins;
@@ -1534,49 +1533,49 @@ void GameCommand_trace(float request, float argc)
                                                end = stov(vtos(end));
 
                                                tracebox(start, PL_MIN, PL_MAX, end, MOVE_NOMONSTERS, world);
-                                               if(!trace_startsolid)
+                                               if(!trace_startsolid && trace_fraction < 1)
                                                {
                                                        p = trace_endpos;
                                                        tracebox(p, PL_MIN, PL_MAX, p, MOVE_NOMONSTERS, world);
-                                                       if(trace_startsolid || trace_fraction == 1)
+                                                       if(trace_startsolid)
                                                        {
                                                                rint(42); // do an engine breakpoint on VM_rint so you can get the trace that errnoeously returns startsolid
                                                                tracebox(start, PL_MIN, PL_MAX, end, MOVE_NOMONSTERS, world);
 
-                                                               if(trace_startsolid)
+                                                               // how much do we need to back off?
+                                                               safe = 1;
+                                                               unsafe = 0;
+                                                               for(;;)
                                                                {
-                                                                       // how much do we need to back off?
-                                                                       safe = 1;
-                                                                       unsafe = 0;
-                                                                       for(;;)
+                                                                       pos = p * (1 - (safe + unsafe) * 0.5) + start * ((safe + unsafe) * 0.5);
+                                                                       tracebox(pos, PL_MIN, PL_MAX, pos, MOVE_NOMONSTERS, world);
+                                                                       if(trace_startsolid)
                                                                        {
-                                                                               pos = p * (1 - (safe + unsafe) * 0.5) + start * ((safe + unsafe) * 0.5);
-                                                                               tracebox(pos, PL_MIN, PL_MAX, pos, MOVE_NOMONSTERS, world);
-                                                                               if(trace_startsolid)
-                                                                               {
-                                                                                       if((safe + unsafe) * 0.5 == unsafe)
-                                                                                               break;
-                                                                                       unsafe = (safe + unsafe) * 0.5;
-                                                                               }
-                                                                               else
-                                                                               {
-                                                                                       if((safe + unsafe) * 0.5 == safe)
-                                                                                               break;
-                                                                                       safe = (safe + unsafe) * 0.5;
-                                                                               }
+                                                                               if((safe + unsafe) * 0.5 == unsafe)
+                                                                                       break;
+                                                                               unsafe = (safe + unsafe) * 0.5;
                                                                        }
-
-                                                                       print("safe distance to back off: ", ftos(safe * vlen(p - start)), "qu\n");
-                                                                       print("unsafe distance to back off: ", ftos(unsafe * vlen(p - start)), "qu\n");
-
-                                                                       tracebox(p, PL_MIN + '0.1 0.1 0.1', PL_MAX - '0.1 0.1 0.1', p, MOVE_NOMONSTERS, world);
-                                                                       if(trace_startsolid)
-                                                                               print("trace_endpos much in solid when tracing from ", vtos(start), " to ", vtos(end), " endpos ", vtos(p), "\n");
                                                                        else
-                                                                               print("trace_endpos just in solid when tracing from ", vtos(start), " to ", vtos(end), " endpos ", vtos(p), "\n");
-                                                                       break;
+                                                                       {
+                                                                               if((safe + unsafe) * 0.5 == safe)
+                                                                                       break;
+                                                                               safe = (safe + unsafe) * 0.5;
+                                                                       }
                                                                }
 
+                                                               print("safe distance to back off: ", ftos(safe * vlen(p - start)), "qu\n");
+                                                               print("unsafe distance to back off: ", ftos(unsafe * vlen(p - start)), "qu\n");
+
+                                                               tracebox(p, PL_MIN + '0.1 0.1 0.1', PL_MAX - '0.1 0.1 0.1', p, MOVE_NOMONSTERS, world);
+                                                               if(trace_startsolid)
+                                                                       print("trace_endpos much in solid when tracing from ", vtos(start), " to ", vtos(end), " endpos ", vtos(p), "\n");
+                                                               else
+                                                                       print("trace_endpos just in solid when tracing from ", vtos(start), " to ", vtos(end), " endpos ", vtos(p), "\n");
+                                                               if (++hitcount >= 10)
+                                                                       break;
+                                                       }
+                                                       else
+                                                       {
                                                                q0 = p;
                                                                dq = 0;
                                                                dqf = 1;
@@ -1593,11 +1592,13 @@ void GameCommand_trace(float request, float argc)
                                                                        dqf *= 0.5;
                                                                        q0 = q;
                                                                }
-                                                               if(dq > 0)
+                                                               if(dq > worst_endpos_bug)
                                                                {
+                                                                       worst_endpos_bug = dq;
                                                                        print("trace_endpos still before solid when tracing from ", vtos(start), " to ", vtos(end), " endpos ", vtos(p), "\n");
                                                                        print("could go ", ftos(dq), " units further to ", vtos(q), "\n");
-                                                                       break;
+                                                                       if (++hitcount >= 10)
+                                                                               break;
                                                                }
                                                        }
                                                }
index 45319ca0748e6f19a6ae95722967249421ea724f..42dee6a98fc479c89fb260bfaa593e5e7c831859 100644 (file)
@@ -1217,6 +1217,7 @@ void Fire_ApplyDamage(entity e)
        e.fire_hitsound = TRUE;
 
        if (!IS_INDEPENDENT_PLAYER(e))
+       if(!e.freezetag_frozen)
        FOR_EACH_PLAYER(other) if(e != other)
        {
                if(IS_PLAYER(other))
index 875a0c3e6a4a2404354747de17734d43cbebc408..f635bff2a2f6399e209588a9487536201c340646 100644 (file)
@@ -1576,7 +1576,7 @@ float InitiateSuddenDeath()
        // - for this timelimit_overtime needs to be >0 of course
        // - also check the winning condition calculated in the previous frame and only add normal overtime
        //   again, if at the point at which timelimit would be extended again, still no winner was found
-       if (!autocvar_g_campaign && (checkrules_overtimesadded >= 0) && (checkrules_overtimesadded < autocvar_timelimit_overtimes) && autocvar_timelimit_overtime && !(g_race && !g_race_qualifying))
+       if (!autocvar_g_campaign && (checkrules_overtimesadded >= 0) && (checkrules_overtimesadded < autocvar_timelimit_overtimes || autocvar_timelimit_overtimes < 0) && autocvar_timelimit_overtime && !(g_race && !g_race_qualifying))
        {
                return 1; // need to call InitiateOvertime later
        }
@@ -2730,6 +2730,8 @@ string GotoMap(string m)
 
 void EndFrame()
 {
+       anticheat_endframe();
+
        float altime;
        FOR_EACH_REALCLIENT(self)
        {
index 66c7b8715f900707381039f7913cf10faddcbc08..b8419624ffbafaf3ad6ea33a068f1808ea0fd27e 100644 (file)
@@ -656,7 +656,7 @@ void ctf_FlagThink()
        if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
                dprint("wtf the flag got squashed?\n");
                tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
-               if(!trace_startsolid) // can we resize it without getting stuck?
+               if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
                        setsize(self, FLAG_MIN, FLAG_MAX); }
 
        switch(self.ctf_status) // reset flag angles in case warpzones adjust it
@@ -793,7 +793,7 @@ void ctf_FlagTouch()
        // special touch behaviors
        if(toucher.vehicle_flags & VHF_ISVEHICLE)
        {
-               if(autocvar_g_ctf_allow_vehicle_touch)
+               if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
                        toucher = toucher.owner; // the player is actually the vehicle owner, not other
                else
                        return; // do nothing
index 5acc70388804c45c22d8002d695bb8453d05cd7f..8448a215e93bdbc989c01bfcfadbcf6d4702ab8c 100644 (file)
@@ -1,8 +1,12 @@
-void invasion_spawnpoint()
+void spawnfunc_invasion_spawnpoint()
 {
        if(!g_invasion) { remove(self); return; }
 
        self.classname = "invasion_spawnpoint";
+
+       if(autocvar_g_invasion_zombies_only) // precache only if it hasn't been already
+       if(self.monsterid)
+               MON_ACTION(self.monsterid, MR_PRECACHE);
 }
 
 float invasion_PickMonster(float supermonster_count)
@@ -21,7 +25,7 @@ float invasion_PickMonster(float supermonster_count)
                if((mon.spawnflags & MONSTER_TYPE_FLY) || (mon.spawnflags & MONSTER_TYPE_SWIM) || ((mon.spawnflags & MON_FLAG_SUPERMONSTER) && supermonster_count >= 1))
                        continue; // flying/swimming monsters not yet supported
 
-               RandomSelection_Add(world, i, "", 1, 1);
+               RandomSelection_Add(world, i, string_null, 1, 1);
        }
 
        return RandomSelection_chosen_float;
@@ -34,7 +38,10 @@ entity invasion_PickSpawn()
        RandomSelection_Init();
 
        for(e = world;(e = find(e, classname, "invasion_spawnpoint")); )
-               RandomSelection_Add(e, 0, string_null, 1, 1);
+       {
+               RandomSelection_Add(e, 0, string_null, 1, ((time >= e.spawnshieldtime) ? 0.2 : 1)); // give recently used spawnpoints a very low rating
+               e.spawnshieldtime = time + autocvar_g_invasion_spawnpoint_spawn_delay;
+       }
 
        return RandomSelection_chosen_ent;
 }
@@ -47,11 +54,52 @@ void invasion_SpawnChosenMonster(float mon)
 
        if(spawn_point == world)
        {
-               dprint("Warning: couldn't find any invasion_spawnpoint spawnpoints, no monsters will spawn!\n");
-               return;
+               dprint("Warning: couldn't find any invasion_spawnpoint spawnpoints, attempting to spawn monsters in random locations\n");
+               entity e = spawn();
+               setsize(e, (get_monsterinfo(mon)).mins, (get_monsterinfo(mon)).maxs);
+
+               if(MoveToRandomMapLocation(e, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
+                       monster = spawnmonster("", mon, world, world, e.origin, FALSE, FALSE, 2);
+               else return;
+
+               e.think = SUB_Remove;
+               e.nextthink = time + 0.1;
+       }
+       else
+               monster = spawnmonster("", ((spawn_point.monsterid) ? spawn_point.monsterid : mon), spawn_point, spawn_point, spawn_point.origin, FALSE, FALSE, 2);
+       
+       if(spawn_point) monster.target2 = spawn_point.target2;
+       monster.spawnshieldtime = time;
+       if(spawn_point && spawn_point.target_range) monster.target_range = spawn_point.target_range;
+       
+       if(teamplay)
+       if(spawn_point && spawn_point.team && inv_monsters_perteam[spawn_point.team] > 0)
+               monster.team = spawn_point.team;
+       else
+       {
+               RandomSelection_Init();
+               if(inv_monsters_perteam[NUM_TEAM_1] > 0) RandomSelection_Add(world, NUM_TEAM_1, string_null, 1, 1);
+               if(inv_monsters_perteam[NUM_TEAM_2] > 0) RandomSelection_Add(world, NUM_TEAM_2, string_null, 1, 1);
+               if(invasion_teams >= 3) if(inv_monsters_perteam[NUM_TEAM_3] > 0) { RandomSelection_Add(world, NUM_TEAM_3, string_null, 1, 1); }
+               if(invasion_teams >= 4) if(inv_monsters_perteam[NUM_TEAM_4] > 0) { RandomSelection_Add(world, NUM_TEAM_4, string_null, 1, 1); }
+               
+               monster.team = RandomSelection_chosen_float;
        }
+       
+       if(teamplay)
+       {
+               monster_setupcolors(monster);
+       
+               if(monster.sprite)
+               {
+                       WaypointSprite_UpdateTeamRadar(monster.sprite, RADARICON_DANGER, ((monster.team) ? Team_ColorRGB(monster.team) : '1 0 0'));
 
-       monster = spawnmonster("", mon, spawn_point, spawn_point, spawn_point.origin, FALSE, 2);
+                       monster.sprite.team = 0;
+                       monster.sprite.SendFlags |= 1;
+               }
+       }
+       
+       monster.monster_attack = FALSE; // it's the player's job to kill all the monsters
 
        if(inv_roundcnt >= inv_maxrounds)
                monster.spawnflags |= MONSTERFLAG_MINIBOSS; // last round spawns minibosses
@@ -72,28 +120,31 @@ float Invasion_CheckWinner()
                FOR_EACH_MONSTER(head)
                        monster_remove(head);
 
-               if(inv_roundcnt >= inv_maxrounds)
-               {
-                       NextLevel();
-                       return 1;
-               }
-
                Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
                Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
                round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
                return 1;
        }
 
-       float total_alive_monsters = 0, supermonster_count = 0;
+       float total_alive_monsters = 0, supermonster_count = 0, red_alive = 0, blue_alive = 0, yellow_alive = 0, pink_alive = 0;;
 
        FOR_EACH_MONSTER(head) if(head.health > 0)
        {
                if((get_monsterinfo(head.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
                        ++supermonster_count;
                ++total_alive_monsters;
+
+               if(teamplay)
+               switch(head.team)
+               {
+                       case NUM_TEAM_1: ++red_alive; break;
+                       case NUM_TEAM_2: ++blue_alive; break;
+                       case NUM_TEAM_3: ++yellow_alive; break;
+                       case NUM_TEAM_4: ++pink_alive; break;
+               }
        }
 
-       if((total_alive_monsters + inv_numkilled) < inv_maxspawned && inv_maxcurrent < 10) // 10 at a time should be plenty
+       if((total_alive_monsters + inv_numkilled) < inv_maxspawned && inv_maxcurrent < inv_maxspawned)
        {
                if(time >= inv_lastcheck)
                {
@@ -104,18 +155,35 @@ float Invasion_CheckWinner()
                return 0;
        }
 
-       if(inv_numspawned < 1 || inv_numkilled < inv_maxspawned)
-               return 0; // nothing has spawned yet, or there are still alive monsters
-
-       if(inv_roundcnt >= inv_maxrounds)
+       if(inv_numspawned < 1)
+               return 0; // nothing has spawned yet
+               
+       if(teamplay)
        {
-               NextLevel();
-               return 1;
+               if(((red_alive > 0) + (blue_alive > 0) + (yellow_alive > 0) + (pink_alive > 0)) > 1)
+                       return 0;
        }
+       else if(inv_numkilled < inv_maxspawned)
+               return 0;
 
        entity winner = world;
-       float winning_score = 0;
+       float winning_score = 0, winner_team = 0;
+
 
+       if(teamplay)
+       {
+               if(red_alive > 0) { winner_team = NUM_TEAM_1; }
+               if(blue_alive > 0)
+               if(winner_team) { winner_team = 0; }
+               else { winner_team = NUM_TEAM_2; }
+               if(yellow_alive > 0)
+               if(winner_team) { winner_team = 0; }
+               else { winner_team = NUM_TEAM_3; }
+               if(pink_alive > 0)
+               if(winner_team) { winner_team = 0; }
+               else { winner_team = NUM_TEAM_4; }
+       }       
+       else
        FOR_EACH_PLAYER(head)
        {
                float cs = PlayerScore_Add(head, SP_KILLS, 0);
@@ -129,7 +197,15 @@ float Invasion_CheckWinner()
        FOR_EACH_MONSTER(head)
                monster_remove(head);
 
-       if(winner)
+       if(teamplay)
+       {
+               if(winner_team)
+               {
+                       Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
+                       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
+               }
+       }
+       else if(winner)
        {
                Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, winner.netname);
                Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_PLAYER_WIN, winner.netname);
@@ -155,15 +231,25 @@ void Invasion_RoundStart()
                ++numplayers;
        }
 
-       inv_roundcnt += 1;
+       if(inv_roundcnt < inv_maxrounds)
+               inv_roundcnt += 1; // a limiter to stop crazy counts
 
-       inv_monsterskill = inv_roundcnt + (numplayers * 0.3);
+       inv_monsterskill = inv_roundcnt + max(1, numplayers * 0.3);
 
        inv_maxcurrent = 0;
        inv_numspawned = 0;
        inv_numkilled = 0;
 
-       inv_maxspawned = rint(min(autocvar_g_invasion_monster_count, autocvar_g_invasion_monster_count * (inv_roundcnt * 0.5)));
+       inv_maxspawned = rint(max(autocvar_g_invasion_monster_count, autocvar_g_invasion_monster_count * (inv_roundcnt * 0.5)));
+
+       if(teamplay)
+       {
+               DistributeEvenly_Init(inv_maxspawned, invasion_teams);
+               inv_monsters_perteam[NUM_TEAM_1] = DistributeEvenly_Get(1);
+               inv_monsters_perteam[NUM_TEAM_2] = DistributeEvenly_Get(1);
+               if(invasion_teams >= 3) inv_monsters_perteam[NUM_TEAM_3] = DistributeEvenly_Get(1);
+               if(invasion_teams >= 4) inv_monsters_perteam[NUM_TEAM_4] = DistributeEvenly_Get(1);
+       }
 }
 
 MUTATOR_HOOKFUNCTION(invasion_MonsterDies)
@@ -172,9 +258,17 @@ MUTATOR_HOOKFUNCTION(invasion_MonsterDies)
        {
                inv_numkilled += 1;
                inv_maxcurrent -= 1;
+               if(teamplay) { inv_monsters_perteam[self.team] -= 1; }
 
                if(IS_PLAYER(frag_attacker))
+               if(SAME_TEAM(frag_attacker, self)) // in non-teamplay modes, same team = same player, so this works
+                       PlayerScore_Add(frag_attacker, SP_KILLS, -1);
+               else
+               {
                        PlayerScore_Add(frag_attacker, SP_KILLS, +1);
+                       if(teamplay)
+                               TeamScore_AddToTeam(frag_attacker.team, ST_INV_KILLS, +1);
+               }
        }
 
        return FALSE;
@@ -183,10 +277,7 @@ MUTATOR_HOOKFUNCTION(invasion_MonsterDies)
 MUTATOR_HOOKFUNCTION(invasion_MonsterSpawn)
 {
        if(!(self.spawnflags & MONSTERFLAG_SPAWNED))
-       {
-               monster_remove(self);
-               return FALSE;
-       }
+               return TRUE;
 
        if(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
        {
@@ -204,7 +295,16 @@ MUTATOR_HOOKFUNCTION(invasion_MonsterSpawn)
        return FALSE;
 }
 
-MUTATOR_HOOKFUNCTION(invasion_PlayerThink)
+MUTATOR_HOOKFUNCTION(invasion_OnEntityPreSpawn)
+{
+       if(startsWith(self.classname, "monster_"))
+       if(!(self.spawnflags & MONSTERFLAG_SPAWNED))
+               return TRUE;
+       
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(invasion_StartFrame)
 {
        monsters_total = inv_maxspawned; // TODO: make sure numspawned never exceeds maxspawned
        monsters_killed = inv_numkilled;
@@ -256,6 +356,14 @@ MUTATOR_HOOKFUNCTION(invasion_PlayerCommand)
        return FALSE;
 }
 
+MUTATOR_HOOKFUNCTION(invasion_BotShouldAttack)
+{
+       if(!(checkentity.flags & FL_MONSTER))
+               return TRUE;
+       
+       return FALSE;
+}
+
 MUTATOR_HOOKFUNCTION(invasion_SetStartItems)
 {
        start_health = 200;
@@ -277,18 +385,31 @@ MUTATOR_HOOKFUNCTION(invasion_AllowMobSpawning)
        return TRUE;
 }
 
-void invasion_ScoreRules()
+MUTATOR_HOOKFUNCTION(invasion_GetTeamCount)
 {
-       ScoreRules_basics(0, 0, 0, FALSE);
-       ScoreInfo_SetLabel_PlayerScore(SP_KILLS, "frags", SFL_SORT_PRIO_PRIMARY);
+       ret_float = invasion_teams;
+       return FALSE;
+}
+
+void invasion_ScoreRules(float inv_teams)
+{
+       if(inv_teams) { CheckAllowedTeams(world); }
+       ScoreRules_basics(inv_teams, 0, 0, FALSE);
+       if(inv_teams) ScoreInfo_SetLabel_TeamScore(ST_INV_KILLS, "frags", SFL_SORT_PRIO_PRIMARY);
+       ScoreInfo_SetLabel_PlayerScore(SP_KILLS, "frags", ((inv_teams) ? SFL_SORT_PRIO_SECONDARY : SFL_SORT_PRIO_PRIMARY));
        ScoreRules_basics_end();
 }
 
-void invasion_Initialize()
+void invasion_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
 {
+       if(autocvar_g_invasion_teams)
+               invasion_teams = bound(2, autocvar_g_invasion_teams, 4);
+       else
+               invasion_teams = 0;
+       
        independent_players = 1; // to disable extra useless scores
 
-       invasion_ScoreRules();
+       invasion_ScoreRules(invasion_teams);
 
        independent_players = 0;
 
@@ -298,20 +419,45 @@ void invasion_Initialize()
        allowed_to_spawn = TRUE;
 
        inv_roundcnt = 0;
+       inv_maxrounds = 15; // 15?
+}
+
+void invasion_Initialize()
+{
+       if(autocvar_g_invasion_zombies_only)
+               MON_ACTION(MON_ZOMBIE, MR_PRECACHE);
+       else
+       {
+               float i;
+               entity mon;
+               for(i = MON_FIRST; i <= MON_LAST; ++i)
+               {
+                       mon = get_monsterinfo(i);
+                       if((mon.spawnflags & MONSTER_TYPE_FLY) || (mon.spawnflags & MONSTER_TYPE_SWIM))
+                               continue; // flying/swimming monsters not yet supported
+
+                       MON_ACTION(i, MR_PRECACHE);
+               }
+       }
+       
+       InitializeEntity(world, invasion_DelayedInit, INITPRIO_GAMETYPE);
 }
 
 MUTATOR_DEFINITION(gamemode_invasion)
 {
        MUTATOR_HOOK(MonsterDies, invasion_MonsterDies, CBC_ORDER_ANY);
        MUTATOR_HOOK(MonsterSpawn, invasion_MonsterSpawn, CBC_ORDER_ANY);
-       MUTATOR_HOOK(PlayerPreThink, invasion_PlayerThink, CBC_ORDER_ANY);
+       MUTATOR_HOOK(OnEntityPreSpawn, invasion_OnEntityPreSpawn, CBC_ORDER_ANY);
+       MUTATOR_HOOK(SV_StartFrame, invasion_StartFrame, CBC_ORDER_ANY);
        MUTATOR_HOOK(PlayerRegen, invasion_PlayerRegen, CBC_ORDER_ANY);
        MUTATOR_HOOK(PlayerSpawn, invasion_PlayerSpawn, CBC_ORDER_ANY);
        MUTATOR_HOOK(PlayerDamage_Calculate, invasion_PlayerDamage, CBC_ORDER_ANY);
        MUTATOR_HOOK(SV_ParseClientCommand, invasion_PlayerCommand, CBC_ORDER_ANY);
+       MUTATOR_HOOK(BotShouldAttack, invasion_BotShouldAttack, CBC_ORDER_ANY);
        MUTATOR_HOOK(SetStartItems, invasion_SetStartItems, CBC_ORDER_ANY);
        MUTATOR_HOOK(AccuracyTargetValid, invasion_AccuracyTargetValid, CBC_ORDER_ANY);
        MUTATOR_HOOK(AllowMobSpawning, invasion_AllowMobSpawning, CBC_ORDER_ANY);
+       MUTATOR_HOOK(GetTeamCount, invasion_GetTeamCount, CBC_ORDER_ANY);
 
        MUTATOR_ONADD
        {
index 3438f472413b73901bce8028124a07f62a399515..04492d4a345824652998afe8d413af6ea9e42082 100644 (file)
@@ -6,4 +6,9 @@ float inv_numkilled;
 float inv_lastcheck;
 float inv_maxcurrent;
 
+float invasion_teams;
+float inv_monsters_perteam[17];
+
 float inv_monsterskill;
+
+#define ST_INV_KILLS 1
index ec6ee8cd4c6d83b60ef19dec2e9d575439fb9127..942682c16ff060435497cf6ea7a5bd526bd03ea3 100644 (file)
@@ -25,28 +25,28 @@ void ka_RespawnBall() // runs whenever the ball needs to be relocated
        if(gameover) { return; }
        vector oldballorigin = self.origin;
 
-       if(MoveToRandomMapLocation(self, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
+       if(!MoveToRandomMapLocation(self, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
        {
-               makevectors(self.angles);
-               self.movetype = MOVETYPE_BOUNCE;
-               self.velocity = '0 0 200';
-               self.angles = '0 0 0';
-               self.effects = autocvar_g_keepawayball_effects;
-               self.think = ka_RespawnBall;
-               self.nextthink = time + autocvar_g_keepawayball_respawntime;
+               entity spot = SelectSpawnPoint(TRUE);
+               setorigin(self, spot.origin);
+               self.angles = spot.angles;
+       }
 
-               pointparticles(particleeffectnum("electro_combo"), oldballorigin, '0 0 0', 1);
-               pointparticles(particleeffectnum("electro_combo"), self.origin, '0 0 0', 1);
+       makevectors(self.angles);
+       self.movetype = MOVETYPE_BOUNCE;
+       self.velocity = '0 0 200';
+       self.angles = '0 0 0';
+       self.effects = autocvar_g_keepawayball_effects;
+       self.think = ka_RespawnBall;
+       self.nextthink = time + autocvar_g_keepawayball_respawntime;
 
-               WaypointSprite_Spawn("ka-ball", 0, 0, self, '0 0 64', world, self.team, self, waypointsprite_attachedforcarrier, FALSE, RADARICON_FLAGCARRIER, '0 1 1');
-               WaypointSprite_Ping(self.waypointsprite_attachedforcarrier);
+       pointparticles(particleeffectnum("electro_combo"), oldballorigin, '0 0 0', 1);
+       pointparticles(particleeffectnum("electro_combo"), self.origin, '0 0 0', 1);
 
-               sound(self, CH_TRIGGER, "keepaway/respawn.wav", VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
-       }
-       else
-       {
-               ka_RespawnBall(); // finding a location failed, retry
-       }
+       WaypointSprite_Spawn("ka-ball", 0, 0, self, '0 0 64', world, self.team, self, waypointsprite_attachedforcarrier, FALSE, RADARICON_FLAGCARRIER, '0 1 1');
+       WaypointSprite_Ping(self.waypointsprite_attachedforcarrier);
+
+       sound(self, CH_TRIGGER, "keepaway/respawn.wav", VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
 }
 
 void ka_TimeScoring()
@@ -102,7 +102,8 @@ void ka_TouchEvent() // runs any time that the ball comes in contact with someth
        // messages and sounds
        ka_EventLog("pickup", other);
        Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_KEEPAWAY_PICKUP, other.netname);
-       Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_KEEPAWAY_PICKUP, other.netname);
+       Send_Notification(NOTIF_ALL_EXCEPT, other, MSG_CENTER, CENTER_KEEPAWAY_PICKUP, other.netname);
+       Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_KEEPAWAY_PICKUP_SELF);
        sound(self.owner, CH_TRIGGER, "keepaway/pickedup.wav", VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
 
        // scoring
index 2d2c857e90dbff166c673e53f05488bb6970e878..014d37ec20169fd6497b6f3b50764b51deb1d91c 100644 (file)
@@ -457,11 +457,21 @@ void nb_spawnteams(void)
        }
 }
 
+// scoreboard setup
+void nb_ScoreRules(float teams)
+{
+       ScoreRules_basics(teams, 0, 0, TRUE);
+       ScoreInfo_SetLabel_TeamScore(   ST_NEXBALL_GOALS,  "goals", SFL_SORT_PRIO_PRIMARY);
+       ScoreInfo_SetLabel_PlayerScore( SP_NEXBALL_GOALS,  "goals", SFL_SORT_PRIO_PRIMARY);
+       ScoreInfo_SetLabel_PlayerScore(SP_NEXBALL_FAULTS, "faults", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER);
+       ScoreRules_basics_end();
+}
+
 void nb_delayedinit(void)
 {
        if(find(world, classname, "nexball_team") == world)
                nb_spawnteams();
-       ScoreRules_nexball(nb_teams);
+       nb_ScoreRules(nb_teams);
 }
 
 
@@ -471,11 +481,7 @@ void nb_delayedinit(void)
 
 void SpawnBall(void)
 {
-       if(!g_nexball)
-       {
-               remove(self);
-               return;
-       }
+       if(!g_nexball) { remove(self); return; }
 
 //     balls += 4; // using the remaining bits to count balls will leave more than the max edict count, so it's fine
 
@@ -559,14 +565,28 @@ void spawnfunc_nexball_football(void)
        SpawnBall();
 }
 
+float nb_Goal_Customize()
+{
+       entity e, wp_owner;
+       e = WaypointSprite_getviewentity(other);
+       wp_owner = self.owner;
+       if(SAME_TEAM(e, wp_owner)) { return FALSE; }
+
+       return TRUE;
+}
+
 void SpawnGoal(void)
 {
-       if(!g_nexball)
+       if(!g_nexball) { remove(self); return; }
+
+       EXACTTRIGGER_INIT;
+
+       if(self.team != GOAL_OUT && Team_TeamToNumber(self.team) != -1)
        {
-               remove(self);
-               return;
+               WaypointSprite_SpawnFixed("goal", (self.absmin + self.absmax) * 0.5, self, sprite, RADARICON_NONE, ((self.team) ? Team_ColorRGB(self.team) : '1 0.5 0'));
+               self.sprite.customizeentityforclient = nb_Goal_Customize;
        }
-       EXACTTRIGGER_INIT;
+
        self.classname = "nexball_goal";
        if(self.noise == "")
                self.noise = "ctf/respawn.wav";
@@ -674,7 +694,7 @@ void W_Nexball_Touch(void)
 
        PROJECTILE_TOUCH;
        if(attacker.team != other.team || autocvar_g_nexball_basketball_teamsteal)
-               if((ball = other.ballcarried) && (IS_PLAYER(attacker)))
+               if((ball = other.ballcarried) && !other.deadflag && (IS_PLAYER(attacker)))
                {
                        other.velocity = other.velocity + normalize(self.velocity) * other.damageforcescale * autocvar_g_balance_nexball_secondary_force;
                        other.flags &= ~FL_ONGROUND;
@@ -683,7 +703,7 @@ void W_Nexball_Touch(void)
                                LogNB("stole", attacker);
                                sound(other, CH_TRIGGER, ball.noise2, VOL_BASE, ATTEN_NORM);
 
-                               if(attacker.team == other.team && time > attacker.teamkill_complain)
+                               if(SAME_TEAM(attacker, other) && time > attacker.teamkill_complain)
                                {
                                        attacker.teamkill_complain = time + 5;
                                        attacker.teamkill_soundtime = time + 0.4;
@@ -760,7 +780,7 @@ void W_Nexball_Attack2(void)
        missile.movetype = MOVETYPE_FLY;
        PROJECTILE_MAKETRIGGER(missile);
 
-       setmodel(missile, "models/elaser.mdl");  // precision set below
+       //setmodel(missile, "models/elaser.mdl");  // precision set below
        setsize(missile, '0 0 0', '0 0 0');
        setorigin(missile, w_shotorg);
 
@@ -772,6 +792,8 @@ void W_Nexball_Attack2(void)
 
        missile.effects = EF_BRIGHTFIELD | EF_LOWPRECISION;
        missile.flags = FL_PROJECTILE;
+
+       CSQCProjectile(missile, TRUE, PROJECTILE_ELECTRO, TRUE);
 }
 
 float ball_customize()
@@ -859,18 +881,6 @@ MUTATOR_HOOKFUNCTION(nexball_BallDrop)
        return 0;
 }
 
-MUTATOR_HOOKFUNCTION(nexball_BuildMutatorsString)
-{
-       ret_string = strcat(ret_string, ":NB");
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(nexball_BuildMutatorsPrettyString)
-{
-       ret_string = strcat(ret_string, ", NexBall");
-       return 0;
-}
-
 MUTATOR_HOOKFUNCTION(nexball_PlayerPreThink)
 {
        makevectors(self.v_angle);
@@ -947,6 +957,16 @@ MUTATOR_HOOKFUNCTION(nexball_PlayerSpawn)
        return FALSE;
 }
 
+MUTATOR_HOOKFUNCTION(nexball_PlayerPhysics)
+{
+       if(self.ballcarried)
+       {
+               self.stat_sv_airspeedlimit_nonqw *= autocvar_g_nexball_basketball_carrier_highspeed;
+               self.stat_sv_maxspeed *= autocvar_g_nexball_basketball_carrier_highspeed;
+       }
+       return FALSE;
+}
+
 MUTATOR_HOOKFUNCTION(nexball_SetStartItems)
 {
        start_items |= IT_UNLIMITED_SUPERWEAPONS; // FIXME BAD BAD BAD BAD HACK, NEXBALL SHOULDN'T ABUSE PORTO'S WEAPON SLOT
@@ -954,16 +974,34 @@ MUTATOR_HOOKFUNCTION(nexball_SetStartItems)
        return FALSE;
 }
 
+MUTATOR_HOOKFUNCTION(nexball_ForbidThrowing)
+{
+       if(self.weapon == WEP_GRENADE_LAUNCHER)
+               return TRUE;
+
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(nexball_FilterItem)
+{
+       if(self.classname == "droppedweapon")
+       if(self.weapon == WEP_GRENADE_LAUNCHER)
+               return TRUE;
+
+       return FALSE;
+}
+
 MUTATOR_DEFINITION(gamemode_nexball)
 {
        MUTATOR_HOOK(PlayerDies, nexball_BallDrop, CBC_ORDER_ANY);
        MUTATOR_HOOK(MakePlayerObserver, nexball_BallDrop, CBC_ORDER_ANY);
        MUTATOR_HOOK(ClientDisconnect, nexball_BallDrop, CBC_ORDER_ANY);
-       MUTATOR_HOOK(BuildMutatorsPrettyString, nexball_BuildMutatorsPrettyString, CBC_ORDER_ANY);
-       MUTATOR_HOOK(BuildMutatorsString, nexball_BuildMutatorsString, CBC_ORDER_ANY);
        MUTATOR_HOOK(PlayerSpawn, nexball_PlayerSpawn, CBC_ORDER_ANY);
        MUTATOR_HOOK(PlayerPreThink, nexball_PlayerPreThink, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerPhysics, nexball_PlayerPhysics, CBC_ORDER_ANY);
        MUTATOR_HOOK(SetStartItems, nexball_SetStartItems, CBC_ORDER_ANY);
+       MUTATOR_HOOK(ForbidThrowCurrentWeapon, nexball_ForbidThrowing, CBC_ORDER_ANY);
+       MUTATOR_HOOK(FilterItem, nexball_FilterItem, CBC_ORDER_ANY);
 
        MUTATOR_ONADD
        {
@@ -973,6 +1011,8 @@ MUTATOR_DEFINITION(gamemode_nexball)
                g_nexball_meter_period = rint(g_nexball_meter_period * 32) / 32; //Round to 1/32ths to send as a byte multiplied by 32
                addstat(STAT_NB_METERSTART, AS_FLOAT, metertime);
 
+               w_porto(WR_PRECACHE); // abuse
+
                // General settings
                /*
                CVTOV(g_nexball_football_boost_forward);   //100
index 54df3a97cdcaa22f101fedaf9c8c80bcde358438..e53c80f848cca975344b6e2025076bdecfec6c33 100644 (file)
@@ -41,6 +41,7 @@ MUTATOR_HOOKFUNCTION(msnt_Spawn_Score)
 
 MUTATOR_HOOKFUNCTION(msnt_PlayerSpawn)
 {
+       // Note: when entering this, fixangle is already set.
        if(autocvar_g_spawn_near_teammate_ignore_spawnpoint)
        {
                if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death)
@@ -112,7 +113,6 @@ MUTATOR_HOOKFUNCTION(msnt_PlayerSpawn)
                                                                                setorigin(self, trace_endpos);
                                                                                self.angles = team_mate.angles;
                                                                                self.angles_z = 0; // never spawn tilted even if the spot says to
-                                                                               self.fixangle = TRUE; // turn this way immediately
                                                                                team_mate.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay;
                                                                                return 0;
                                                                        }
@@ -130,7 +130,6 @@ MUTATOR_HOOKFUNCTION(msnt_PlayerSpawn)
                        setorigin(self, best_spot);
                        self.angles = best_mate.angles;
                        self.angles_z = 0; // never spawn tilted even if the spot says to
-                       self.fixangle = TRUE; // turn this way immediately
                        best_mate.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay;
                }
        }
@@ -139,7 +138,6 @@ MUTATOR_HOOKFUNCTION(msnt_PlayerSpawn)
                self.angles = vectoangles(spawn_spot.msnt_lookat.origin - self.origin);
                self.angles_x = -self.angles_x;
                self.angles_z = 0; // never spawn tilted even if the spot says to
-               self.fixangle = TRUE; // turn this way immediately
                /*
                sprint(self, "You should be looking at ", spawn_spot.msnt_lookat.netname, "^7.\n");
                sprint(self, "distance: ", vtos(spawn_spot.msnt_lookat.origin - self.origin), "\n");
index 2c6e941a6a4dfd7585986ea74c969702e8609b81..3186b1aa3b432f9060ac130554895e419e13c29e 100644 (file)
@@ -5,6 +5,26 @@ string events_last;
 .float playerstats_addedglobalinfo;
 .string playerstats_id;
 
+#define ALL_ANTICHEATS \
+       ANTICHEAT("_time"); \
+       ANTICHEAT("speedhack"); \
+       ANTICHEAT("speedhack_m1"); \
+       ANTICHEAT("speedhack_m2"); \
+       ANTICHEAT("speedhack_m3"); \
+       ANTICHEAT("speedhack_m4"); \
+       ANTICHEAT("speedhack_m5"); \
+       ANTICHEAT("div0_strafebot_old"); \
+       ANTICHEAT("div0_strafebot_new"); \
+       ANTICHEAT("div0_evade"); \
+       ANTICHEAT("idle_snapaim"); \
+       ANTICHEAT("idle_snapaim_signal"); \
+       ANTICHEAT("idle_snapaim_noise"); \
+       ANTICHEAT("idle_snapaim_m2"); \
+       ANTICHEAT("idle_snapaim_m3"); \
+       ANTICHEAT("idle_snapaim_m4"); \
+       ANTICHEAT("idle_snapaim_m7"); \
+       ANTICHEAT("idle_snapaim_m10");
+
 void PlayerStats_Init() // initiated before InitGameplayMode so that scores are added properly
 {
        string uri;
@@ -44,6 +64,11 @@ void PlayerStats_Init() // initiated before InitGameplayMode so that scores are
         PlayerStats_AddEvent(strcat("acc-", w.netname, "-frags"));
     }
 
+#define ANTICHEAT(name) \
+       PlayerStats_AddEvent("anticheat-" name)
+       ALL_ANTICHEATS
+#undef ANTICHEAT
+
        PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_3);
        PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_5);
        PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_10);
@@ -356,6 +381,18 @@ void PlayerStats_Accuracy(entity p)
     //backtrace(strcat("adding player stat accuracy for ", p.netname, ".\n"));
 }
 
+void PlayerStats_Anticheat(entity p)
+{
+       entity oldself = self;
+       self = p;
+
+#define ANTICHEAT(name) \
+       PlayerStats_Event(p, "anticheat-" name, anticheat_getvalue(name))
+       ALL_ANTICHEATS
+#undef ANTICHEAT
+       self = oldself;
+}
+
 void PlayerStats_AddGlobalInfo(entity p)
 {
        if(playerstats_db < 0)
@@ -384,6 +421,8 @@ void PlayerStats_AddGlobalInfo(entity p)
 
        PlayerStats_Accuracy(p);
 
+       PlayerStats_Anticheat(p);
+
        if(IS_REAL_CLIENT(p))
        {
                if(p.latency_cnt)
@@ -430,3 +469,5 @@ void PlayerStats_EndMatch(float finished)
                }
        }
 }
+
+#undef ALL_ANTICHEATS
index 920f738aeebbeb8a321dd3456e33e8af4bb1a6c9..73e733be8f97d927c521c62a48751cfa0ca88c48 100644 (file)
@@ -238,6 +238,8 @@ void StartFrame (void)
        FOR_EACH_PLAYER(self)
                self.porto_forbidden = max(0, self.porto_forbidden - 1);
 
+       anticheat_startframe();
+
        MUTATOR_CALLHOOK(SV_StartFrame);
 }
 
index 8f15a4f820c428c976ca17d5871ce178d99b9b6e..6e7c35b6a662f7f75ac5d6a5578d7da56517f103 100644 (file)
@@ -338,6 +338,12 @@ void spawnfunc_trigger_teleport (void)
 void WarpZone_PostTeleportPlayer_Callback(entity pl)
 {
        UpdateCSQCProjectileAfterTeleport(pl);
+       {
+               entity oldself = self;
+               self = pl;
+               anticheat_fixangle();
+               self = oldself;
+       }
        // "disown" projectiles after teleport
        if(pl.owner)
        if(pl.owner == pl.realowner)
index 769416c467e534441279a52222fd802e283d26ac..600af861ac956c55cba7a867ee567aa03b59bc5a 100644 (file)
@@ -28,8 +28,12 @@ void target_music_use()
 {
        if(!activator)
                return;
+       if(!IS_REAL_CLIENT(activator))
+               return;
        msg_entity = activator;
        target_music_sendto(MSG_ONE, 1);
+       entity head;
+       FOR_EACH_SPEC(head) if(head.enemy == activator) { msg_entity = head; target_music_sendto(MSG_ONE, 1); }
 }
 void spawnfunc_target_music()
 {
index e332e55ed80de06b3b4ce73e6fa2f063d1f4d8d0..a0c80dbd2132b144f2ce0929249aca17d12050d5 100644 (file)
@@ -197,8 +197,13 @@ void InitGameplayMode()
 
        if(g_invasion)
        {
-               timelimit_override = 0; // no timelimit in invasion, round based
-               fraglimit_override = autocvar_g_invasion_round_limit;
+               fraglimit_override = autocvar_g_invasion_point_limit;
+               if(autocvar_g_invasion_teams >= 2)
+               {
+                       ActivateTeamplay();
+                       if(autocvar_g_invasion_team_spawns)
+                               have_team_spawns = -1; // request team spawns
+               }
                MUTATOR_ADD(gamemode_invasion);
        }
 
@@ -241,12 +246,6 @@ void InitGameplayMode()
                else
                        g_race_qualifying = 0;
        }
-       
-       if(g_invasion)
-       {
-               inv_maxrounds = cvar("fraglimit");
-               cvar_set("fraglimit", "0");
-       }
 
        if(g_race || g_cts)
        {
index f51db6dd5dc48708f8c7477804fea7179b30d811..a7ae4d3266f0edd2df798039406002121ec46fe3 100644 (file)
@@ -200,9 +200,13 @@ void fireBullet(vector start, vector dir, float spread, float max_solid_penetrat
        if(autocvar_g_antilag == 0 || self.cvar_cl_noantilag)
                lag = 0; // only do hitscan, but no antilag
        if(lag)
+       {
                FOR_EACH_PLAYER(pl)
                        if(pl != self)
                                antilag_takeback(pl, time - lag);
+               FOR_EACH_MONSTER(pl)
+                       antilag_takeback(pl, time - lag);
+       }
 
        WarpZone_trace_forent = self;
 
@@ -303,9 +307,13 @@ void fireBullet(vector start, vector dir, float spread, float max_solid_penetrat
        }
 
        if(lag)
+       {
                FOR_EACH_PLAYER(pl)
                        if(pl != self)
                                antilag_restore(pl);
+               FOR_EACH_MONSTER(pl)
+                       antilag_restore(pl);
+       }
 }
 
 float W_CheckProjectileDamage(entity inflictor, entity projowner, float deathtype, float exception)
index 5d88df8b254ac08be41bcec0422d0af8a464a34c..ec36f5d32541cea2fbca1c8fa29572b0e1994d06 100644 (file)
@@ -195,7 +195,7 @@ void W_Mine_Think (void)
 
        // a player's mines shall explode if he disconnects or dies
        // TODO: Do this on team change too -- Samual: But isn't a player killed when they switch teams?
-       if(!IS_PLAYER(self.realowner) || self.realowner.deadflag != DEAD_NO)
+       if(!IS_PLAYER(self.realowner) || self.realowner.deadflag != DEAD_NO || self.realowner.freezetag_frozen)
        {
                other = world;
                self.projectiledeathtype |= HITTYPE_BOUNCE;
@@ -207,7 +207,7 @@ void W_Mine_Think (void)
        head = findradius(self.origin, autocvar_g_balance_minelayer_proximityradius);
        while(head)
        {
-               if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
+               if(IS_PLAYER(head) && head.deadflag == DEAD_NO && !head.freezetag_frozen)
                if(head != self.realowner && DIFF_TEAM(head, self.realowner)) // don't trigger for team mates
                if(!self.mine_time)
                {