Merge branch 'terencehill/bot_waypoints' into terencehill/bot_ai
authorterencehill <piuntn@gmail.com>
Mon, 15 Jan 2018 16:57:12 +0000 (17:57 +0100)
committerterencehill <piuntn@gmail.com>
Mon, 15 Jan 2018 16:57:12 +0000 (17:57 +0100)
# Conflicts:
# qcsrc/server/mutators/mutator/gamemode_race.qc

27 files changed:
defaultServer.cfg
defaultXonotic.cfg
mutators.cfg
qcsrc/client/view.qc
qcsrc/client/view.qh
qcsrc/common/mutators/mutator/instagib/items.qh
qcsrc/common/mutators/mutator/instagib/sv_instagib.qc
qcsrc/common/t_items.qc
qcsrc/common/t_items.qh
qcsrc/common/triggers/trigger/jumppads.qc
qcsrc/common/triggers/trigger/viewloc.qc
qcsrc/common/triggers/trigger/viewloc.qh
qcsrc/common/viewloc.qc
qcsrc/common/weapons/weapon/electro.qc
qcsrc/common/weapons/weapon/hook.qc
qcsrc/common/weapons/weapon/mortar.qh
qcsrc/common/weapons/weapon/rifle.qh
qcsrc/server/bot/api.qh
qcsrc/server/bot/default/bot.qc
qcsrc/server/bot/default/navigation.qc
qcsrc/server/bot/default/navigation.qh
qcsrc/server/bot/default/waypoints.qc
qcsrc/server/impulse.qc
qcsrc/server/miscfunctions.qc
qcsrc/server/mutators/mutator/gamemode_cts.qc
qcsrc/server/mutators/mutator/gamemode_race.qc
qcsrc/server/weapons/tracing.qc

index 61aab3a..7397462 100644 (file)
@@ -203,7 +203,6 @@ set g_casings 2 "specifies which casings (0: none, 1: only shotgun casings, 2: s
 set g_norecoil 0 "if set to 1 shooting weapons won't make you crosshair to move upwards (recoil)"
 set g_maplist_mostrecent "" "contains the name of the maps that were most recently played"
 set g_maplist_mostrecent_count 3       "number of most recent maps that are blocked from being played again"
-set g_maplist "" "the list of maps to be cycled among (is autogenerated if empty)"
 set g_maplist_index 0  "this is used internally for saving position in maplist cycle"
 set g_maplist_selectrandom 0   "if 1, a random map will be chosen as next map - DEPRECATED in favor of g_maplist_shuffle"
 set g_maplist_shuffle 1        "new randomization method: like selectrandom, but avoid playing the same maps in short succession. This works by taking out the first element and inserting it into g_maplist with a bias to the end of the list"
index eb6c72a..bac826e 100644 (file)
@@ -52,6 +52,9 @@ set _campaign_index ""
 set _campaign_name ""
 set _campaign_testrun 0 "To verify the campaign file, set this to 1, then start the first campaign level from the menu. If you end up in the menu again, it's good, if you get a QC crash, it's bad."
 
+// used by both server and menu to maintain the available list of maps
+seta g_maplist "" "the list of maps to be cycled among (is autogenerated if empty)"
+
 // we must change its default from 1.0 to 1 to be consistent with menuqc
 set slowmo 1
 
index 79ac417..af0fa9e 100644 (file)
@@ -40,7 +40,7 @@ set g_instagib_ammo_convert_bullets 0 "convert bullet ammo packs to insta cell a
 set g_instagib_ammo_convert_cells 0 "convert normal cell ammo packs to insta cell ammo packs"
 set g_instagib_ammo_convert_rockets 0 "convert rocket ammo packs to insta cell ammo packs"
 set g_instagib_ammo_convert_shells 0 "convert shell ammo packs to insta cell ammo packs"
-set g_instagib_invisibility_time 30 "Time of ivisibility powerup in seconds."
+set g_instagib_invisibility_time 30 "Time of invisibility powerup in seconds."
 set g_instagib_invis_alpha 0.15
 set g_instagib_speed_time 30 "Time of speed powerup in seconds."
 set g_instagib_speed_highspeed 1.5 "speed-multiplier that applies while you carry the invincibility powerup"
index 4f2b43e..95a437e 100644 (file)
@@ -31,6 +31,7 @@
 #include <common/vehicles/all.qh>
 #include <common/weapons/_all.qh>
 #include <common/viewloc.qh>
+#include <common/triggers/trigger/viewloc.qh>
 #include <common/minigames/cl_minigames.qh>
 #include <common/minigames/cl_minigames_hud.qh>
 
@@ -1479,6 +1480,19 @@ void HUD_Draw(entity this)
        HitSound();
 }
 
+void ViewLocation_Mouse()
+{
+       if(spectatee_status)
+               return; // don't draw it as spectator!
+
+       viewloc_mousepos += getmousepos() * autocvar_menu_mouse_speed;
+       viewloc_mousepos.x = bound(0, viewloc_mousepos.x, vid_conwidth);
+       viewloc_mousepos.y = bound(0, viewloc_mousepos.y, vid_conheight);
+
+       float cursor_alpha = 1 - autocvar__menu_alpha;
+       draw_cursor(viewloc_mousepos, '0.5 0.5 0', "/cursor_move", '1 1 1', cursor_alpha);
+}
+
 bool ov_enabled;
 float oldr_nearclip;
 float oldr_farclip_base;
@@ -1576,6 +1590,11 @@ void CSQC_UpdateView(entity this, float w, float h)
                button_zoom = false;
        }
 
+       // abused multiple places below
+       entity local_player = ((csqcplayer) ? csqcplayer : CSQCModel_server2csqc(player_localentnum - 1));
+       if(!local_player)
+               local_player = this; // fall back!
+
        // event chase camera
        if(autocvar_chase_active <= 0) // greater than 0 means it's enabled manually, and this code is skipped
        {
@@ -1627,10 +1646,6 @@ void CSQC_UpdateView(entity this, float w, float h)
                        }
                        eventchase_running = true;
 
-                       entity local_player = ((csqcplayer) ? csqcplayer : CSQCModel_server2csqc(player_localentnum - 1));
-                       if(!local_player)
-                               local_player = this; // fall back!
-
                        // make special vector since we can't use view_origin (It is one frame old as of this code, it gets set later with the results this code makes.)
                        vector current_view_origin = (csqcplayer ? csqcplayer.origin : pmove_org);
                        if (custom_eventchase)
@@ -2062,7 +2077,7 @@ void CSQC_UpdateView(entity this, float w, float h)
                // reticle_type is changed to the item we are zooming / aiming with, to decide which reticle to use
                // It must be a persisted float for fading out to work properly (you let go of the zoom button for
                // the view to go back to normal, so reticle_type would become 0 as we fade out)
-               if(spectatee_status || is_dead || hud != HUD_NORMAL)
+               if(spectatee_status || is_dead || hud != HUD_NORMAL || local_player.viewloc)
                {
                        // no zoom reticle while dead
                        reticle_type = 0;
@@ -2375,6 +2390,8 @@ void CSQC_UpdateView(entity this, float w, float h)
                HUD_Minigame_Mouse();
        else if(QuickMenu_IsOpened())
                QuickMenu_Mouse();
+       else if(local_player.viewloc && (local_player.viewloc.spawnflags & VIEWLOC_FREEAIM))
+               ViewLocation_Mouse(); // NOTE: doesn't use cursormode
        else
                HUD_Radar_Mouse();
 
index ac916a0..0a2c5c0 100644 (file)
@@ -5,3 +5,5 @@
 vector crosshair_getcolor(entity this, float health_stat);
 
 entity viewmodels[MAX_WEAPONSLOTS];
+
+vector viewloc_mousepos;
index ab6843e..fe0070a 100644 (file)
@@ -82,7 +82,7 @@ void powerup_invisibility_init(entity item);
 REGISTER_ITEM(Invisibility, Powerup) {
     this.m_canonical_spawnfunc = "item_invisibility";
 #ifdef GAMEQC
-       this.spawnflags = ITEM_FLAG_INSTAGIB;
+       this.spawnflags = ITEM_FLAG_INSTAGIB | ITEM_FLAG_MUTATORBLOCKED;
     this.m_model            =   MDL_Invisibility_ITEM;
     this.m_sound            =   SND_Invisibility;
     this.m_glow             =   true;
@@ -117,7 +117,7 @@ void powerup_speed_init(entity item);
 REGISTER_ITEM(Speed, Powerup) {
     this.m_canonical_spawnfunc = "item_speed";
 #ifdef GAMEQC
-       this.spawnflags = ITEM_FLAG_INSTAGIB;
+       this.spawnflags = ITEM_FLAG_INSTAGIB | ITEM_FLAG_MUTATORBLOCKED;
     this.m_model            =   MDL_Speed_ITEM;
     this.m_sound            =   SND_Speed;
     this.m_glow             =   true;
index c21623f..473e36e 100644 (file)
@@ -19,19 +19,23 @@ float autocvar_g_instagib_speed_highspeed;
 
 REGISTER_MUTATOR(mutator_instagib, autocvar_g_instagib && !g_nexball)
 {
-    MUTATOR_ONADD
-    {
-        ITEM_VaporizerCells.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED;
-    }
-    MUTATOR_ONROLLBACK_OR_REMOVE
-    {
-        ITEM_VaporizerCells.spawnflags |= ITEM_FLAG_MUTATORBLOCKED;
-    }
+       MUTATOR_ONADD
+       {
+               ITEM_VaporizerCells.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED;
+               ITEM_Invisibility.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED;
+               ITEM_Speed.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED;
+       }
+       MUTATOR_ONROLLBACK_OR_REMOVE
+       {
+               ITEM_VaporizerCells.spawnflags |= ITEM_FLAG_MUTATORBLOCKED;
+               ITEM_Invisibility.spawnflags |= ITEM_FLAG_MUTATORBLOCKED;
+               ITEM_Speed.spawnflags |= ITEM_FLAG_MUTATORBLOCKED;
+       }
 }
 
 void instagib_invisibility(entity this)
 {
-       this.strength_finished = autocvar_g_balance_powerup_strength_time;
+       this.strength_finished = autocvar_g_instagib_invisibility_time;
        StartItem(this, ITEM_Invisibility);
 }
 
@@ -42,7 +46,7 @@ void instagib_extralife(entity this)
 
 void instagib_speed(entity this)
 {
-       this.invincible_finished = autocvar_g_balance_powerup_invincible_time;
+       this.invincible_finished = autocvar_g_instagib_speed_time;
        StartItem(this, ITEM_Speed);
 }
 
index 7409a66..feefd10 100644 (file)
@@ -533,7 +533,7 @@ void Item_Respawn (entity this)
 
 void Item_RespawnCountdown (entity this)
 {
-       if(this.count >= ITEM_RESPAWN_TICKS)
+       if(this.item_respawncounter >= ITEM_RESPAWN_TICKS)
        {
                if(this.waypointsprite_attached)
                        WaypointSprite_Kill(this.waypointsprite_attached);
@@ -542,8 +542,8 @@ void Item_RespawnCountdown (entity this)
        else
        {
                this.nextthink = time + 1;
-               this.count += 1;
-               if(this.count == 1)
+               this.item_respawncounter += 1;
+               if(this.item_respawncounter == 1)
                {
                        do {
                                {
@@ -584,7 +584,7 @@ void Item_RespawnCountdown (entity this)
                        });
 
                        WaypointSprite_Ping(this.waypointsprite_attached);
-                       //WaypointSprite_UpdateHealth(this.waypointsprite_attached, this.count);
+                       //WaypointSprite_UpdateHealth(this.waypointsprite_attached, this.item_respawncounter);
                }
        }
 }
@@ -607,7 +607,7 @@ void Item_ScheduleRespawnIn(entity e, float t)
                setthink(e, Item_RespawnCountdown);
                e.nextthink = time + max(0, t - ITEM_RESPAWN_TICKS);
                e.scheduledrespawntime = e.nextthink + ITEM_RESPAWN_TICKS;
-               e.count = 0;
+               e.item_respawncounter = 0;
                if(Item_ItemsTime_Allow(e.itemdef) || (e.weapons & WEPSET_SUPERWEAPONS))
                {
                        t = Item_ItemsTime_UpdateTime(e, e.scheduledrespawntime);
index fd1c9d2..5d815ce 100644 (file)
@@ -64,6 +64,8 @@ const float ITEM_RESPAWN_TICKS = 10;
 .float max_armorvalue;
 .float pickup_anyway;
 
+.float item_respawncounter;
+
 void Item_Show (entity e, float mode);
 
 void Item_Respawn (entity this);
index 5bb6d39..9e40cfd 100644 (file)
@@ -207,7 +207,10 @@ bool jumppad_push(entity this, entity targ)
                                        centerprint(targ, this.message);
                        }
                        else
+                       {
                                targ.lastteleporttime = time;
+                               targ.lastteleport_origin = targ.origin;
+                       }
 
                        if (!IS_DEAD(targ))
                                animdecide_setaction(targ, ANIMACTION_JUMP, true);
@@ -278,23 +281,27 @@ bool trigger_push_testorigin(entity tracetest_ent, entity targ, entity jp, vecto
        if(trace_startsolid)
                return false;
 
-       if(!jp.height)
+       if (!jp.height)
        {
                // since tracetoss starting from jumppad's origin often fails when target
                // is very close to real destination, start it directly from target's
                // origin instead
+               vector ofs = '0 0 0';
+               if (vdist(vec2(tracetest_ent.velocity), <, autocvar_sv_maxspeed))
+                       ofs = stepheightvec;
+
                tracetest_ent.velocity.z = 0;
-               setorigin(tracetest_ent, targ.origin + stepheightvec);
+               setorigin(tracetest_ent, targ.origin + ofs);
                tracetoss(tracetest_ent, tracetest_ent);
-               if(trace_startsolid)
+               if (trace_startsolid && ofs.z)
                {
-                       setorigin(tracetest_ent, targ.origin + stepheightvec / 2);
+                       setorigin(tracetest_ent, targ.origin + ofs / 2);
                        tracetoss(tracetest_ent, tracetest_ent);
-                       if(trace_startsolid)
+                       if (trace_startsolid && ofs.z)
                        {
                                setorigin(tracetest_ent, targ.origin);
                                tracetoss(tracetest_ent, tracetest_ent);
-                               if(trace_startsolid)
+                               if (trace_startsolid)
                                        return false;
                        }
                }
@@ -310,7 +317,7 @@ bool trigger_push_test(entity this, entity item)
 {
        // first calculate a typical start point for the jump
        vector org = (this.absmin + this.absmax) * 0.5;
-       org.z = this.absmax.z - PL_MIN_CONST.z;
+       org.z = this.absmax.z - PL_MIN_CONST.z - 10;
 
        if (this.target)
        {
index ffc04a0..41394ac 100644 (file)
@@ -13,13 +13,37 @@ REGISTER_NET_LINKED(ENT_CLIENT_VIEWLOC_TRIGGER)
 
 void viewloc_think(entity this)
 {
-       entity e;
-
        // we abuse this method, rather than using normal .touch, because touch isn't reliable with multiple clients inside the same trigger, and can't "untouch" entities
 
        // set myself as current viewloc where possible
+#if 1
+       FOREACH_CLIENT(it.viewloc == this,
+       {
+               it.viewloc = NULL;
+       });
+#else
+       entity e;
        for(e = NULL; (e = findentity(e, viewloc, this)); )
                e.viewloc = NULL;
+#endif
+
+#if 1
+       FOREACH_CLIENT(!it.viewloc && IS_PLAYER(it),
+       {
+               vector emin = it.absmin;
+               vector emax = it.absmax;
+               if(this.solid == SOLID_BSP)
+               {
+                       emin -= '1 1 1';
+                       emax += '1 1 1';
+               }
+               if(boxesoverlap(emin, emax, this.absmin, this.absmax)) // quick
+               {
+                       if(WarpZoneLib_BoxTouchesBrush(emin, emax, this, it)) // accurate
+                               it.viewloc = this;
+               }
+       });
+#else
 
                for(e = findradius((this.absmin + this.absmax) * 0.5, vlen(this.absmax - this.absmin) * 0.5 + 1); e; e = e.chain)
                        if(!e.viewloc)
@@ -37,6 +61,7 @@ void viewloc_think(entity this)
                                                if(WarpZoneLib_BoxTouchesBrush(emin, emax, this, e)) // accurate
                                                        e.viewloc = this;
                                }
+#endif
 
        this.nextthink = time;
 }
@@ -46,6 +71,8 @@ bool trigger_viewloc_send(entity this, entity to, int sf)
        // CSQC doesn't need to know our origin (yet), as we're only available for referencing
        WriteHeader(MSG_ENTITY, ENT_CLIENT_VIEWLOC_TRIGGER);
 
+       WriteByte(MSG_ENTITY, this.spawnflags);
+
        WriteEntity(MSG_ENTITY, this.enemy);
        WriteEntity(MSG_ENTITY, this.goalentity);
 
@@ -143,6 +170,8 @@ void trigger_viewloc_updatelink(entity this)
 
 NET_HANDLE(ENT_CLIENT_VIEWLOC_TRIGGER, bool isnew)
 {
+       this.spawnflags = ReadByte();
+
        float point1 = ReadShort();
        float point2 = ReadShort();
 
index 167fc21..3181e67 100644 (file)
@@ -2,6 +2,9 @@
 
 .entity viewloc;
 
+const int VIEWLOC_NOSIDESCROLL = BIT(0); // NOTE: currently unimplemented
+const int VIEWLOC_FREEAIM = BIT(1);
+
 #ifdef CSQC
 .entity goalentity;
 .entity enemy;
index d6d7d9f..11dab52 100644 (file)
@@ -21,31 +21,27 @@ void viewloc_PlayerPhysics(entity this)
                PHYS_CS(this).movement_x = old_movement_y;
                PHYS_CS(this).movement_y = 0;
 
-               if(PHYS_CS(this).movement_x < 0)
-                       PHYS_CS(this).movement_x = -PHYS_CS(this).movement_x;
-
                vector level_start, level_end;
                level_start = this.viewloc.enemy.origin;
                level_end = this.viewloc.goalentity.origin;
-               vector forward, backward;
-               forward = vectoangles(normalize(level_end - level_start));
-               backward = vectoangles(normalize(level_start - level_end));
+               vector forward = vectoangles(normalize(level_end - level_start));
+               vector backward = vectoangles(normalize(level_start - level_end));
 
-               if(PHYS_CS(this).movement_x < 0) // left
-                       this.angles_y = backward_y;
-               if(PHYS_CS(this).movement_x > 0) // right
-                       this.angles_y = forward_y;
+               if(this.viewloc.spawnflags & VIEWLOC_FREEAIM)
+               {
+                       if(this.angles_y > 0)
+                               PHYS_CS(this).movement_x = -PHYS_CS(this).movement_x;
+               }
+               else
+               {
+                       if(PHYS_CS(this).movement_x < 0)
+                               PHYS_CS(this).movement_x = -PHYS_CS(this).movement_x;
 
-               if(old_movement_x > 0)
-#ifdef CSQC
-                       input_angles_x =
-#endif
-                       this.v_angle_x = this.angles_x = -50;
-               else if(old_movement_x < 0)
-#ifdef CSQC
-                       input_angles_x =
-#endif
-                       this.v_angle_x = this.angles_x = 50;
+                       if(PHYS_CS(this).movement_x < 0) // left
+                               this.angles_y = backward.y;
+                       if(PHYS_CS(this).movement_x > 0) // right
+                               this.angles_y = forward.y;
+               }
 
                //if(!PHYS_INPUT_BUTTON_CROUCH(this) && !IS_DUCKED(this))
 #ifdef SVQC
@@ -77,6 +73,14 @@ void viewloc_SetTags(entity this)
                this.viewloc = findfloat(NULL, entnum, this.tag_networkviewloc);
 }
 
+vector CursorToWorldCoord(vector mpos)
+{
+       vector wnear = cs_unproject(vec2(mpos)); // determine the world coordinate for the mouse cursor upon the near clip plane
+       vector wfar = cs_unproject(vec3(mpos.x, mpos.y, max_shot_distance)); // determine the world coordinate for the mouse cursor upon the far clip plane, with an outrageously large value as a workaround for dp.
+       traceline(wnear, wfar, MOVE_NOMONSTERS, NULL);
+       return trace_endpos;
+}
+
 vector old_camera_angle = '0 0 0';
 bool autocvar_cam_snap_close;
 bool autocvar_cam_track;
@@ -90,22 +94,14 @@ void viewloc_SetViewLocation()
        if(view.viewloc && !wasfreed(view.viewloc) && view.viewloc.enemy && view.viewloc.goalentity)
        {
                bool have_sidescroll = (view.viewloc.enemy != view.viewloc.goalentity);
-               vector position_a, position_b, camera_position, camera_angle = '0 0 0', forward, backward;
-               //vector scratch;
-
-               position_a = view.viewloc.enemy.origin;
-               position_b = view.viewloc.goalentity.origin;
+               vector position_a = view.viewloc.enemy.origin;
+               vector position_b = view.viewloc.goalentity.origin;
+               vector camera_angle = '0 0 0';
+               vector camera_position;
 
-#if 0
                /*TODO: have the camera only move when a player moves too much from the center of the camera
-                * basically the player can move around in a "box" in the center of th screen with out changing the camera position or angles
-               */
-               if (cvar("cam_box")) {
-                       camera_position = vec_bounds_in(view.origin, position_a, position_b);
-               }
-               else
-#endif
-                       camera_position = vec_bounds_in(view.origin, position_a, position_b);
+                * basically the player would move around in a small "box" in the center of the screen with out changing the camera position or angles */
+               camera_position = vec_bounds_in(view.origin, position_a, position_b);
 
 
                // a tracking camera follows the player when it leaves the world box
@@ -114,13 +110,12 @@ void viewloc_SetViewLocation()
                }
 
                // hard snap changes the angle as soon as it crosses over the nearest 90 degree mark
-               if (autocvar_cam_snap_hard){
+               if (autocvar_cam_snap_hard) {
                        camera_angle = angle_snap_vec(aim_vec(camera_position, view.origin), 90);
                }
 
                // tries to avoid snapping unless it *really* needs to
-               if (autocvar_cam_snap_close){
-
+               if (autocvar_cam_snap_close) {
                        // like hard snap, but don't snap angles yet.
                        camera_angle = aim_vec(camera_position, view.origin);
 
@@ -128,18 +123,19 @@ void viewloc_SetViewLocation()
                         * NOTE: bug/feature: this will use non-snaped angles for one frame.
                         * doing this resualts in less code, faster code, and a smoother transisition between angles.
                         */
-                       float camera_angle_diff = max(camera_angle_y, old_camera_angle_y) - min(camera_angle_y, old_camera_angle_y);
+                       float camera_angle_diff = max(camera_angle.y, old_camera_angle.y) - min(camera_angle.y, old_camera_angle.y);
 
-                       if ( camera_angle_diff >= 60)
-                               old_camera_angle_y = angle_snap_f(camera_angle_y, 90);
-                       else
-                               camera_angle_y = old_camera_angle_y;
+                       if (60 <= camera_angle_diff) { // use new angles
+                               old_camera_angle.y = angle_snap_f(camera_angle.y, 90);
+                       } else { // use old angles
+                               camera_angle.y = old_camera_angle.y;
+                       }
                }
 
                //unlocking this allows the camera to look up and down. this also allows a top-down view.
                if (!autocvar_cam_snap_unlock) {
-                       camera_angle_x = 0;
-                       camera_angle_z = 0;
+                       camera_angle.x = 0;
+                       camera_angle.z = 0;
                }
 
 #if 0
@@ -153,17 +149,36 @@ void viewloc_SetViewLocation()
                setproperty(VF_ORIGIN, camera_position);
                setproperty(VF_ANGLES, camera_angle);
 
-               if(have_sidescroll)
-               {
-                       forward = vectoangles(normalize(vec_to_min(position_b, position_a) - vec_to_max(position_b, position_a)));
-                       backward = vectoangles(normalize(vec_to_max(position_b, position_a) - vec_to_min(position_b, position_a)));
-
-                       if(input_movevalues_y < 0) // left
-                               view.angles_y = backward.y;
-                       if(input_movevalues_y > 0) // favour right
-                               view.angles_y = forward.y;
-
-                       setproperty(VF_CL_VIEWANGLES, view.angles);
+               if(spectatee_status)
+                       return; // if spectating, don't replace angles or inputs!
+
+               if (have_sidescroll) {
+                       vector view_angle = view.angles;
+                       if (!(view.viewloc.spawnflags & VIEWLOC_FREEAIM)) {
+                               vector avatar_facing_dir;
+                               // get the player's forward-facing direction, based on positions a and b
+                               if (0 == input_movevalues.y) {
+                                       avatar_facing_dir = view_angle; // default to the previous values
+                               } else if (0 > input_movevalues.y) { // left is forward
+                                       avatar_facing_dir = vectoangles(normalize(vec_to_max(position_b, position_a) - vec_to_min(position_b, position_a)));
+                               } else { // right is forward
+                                       avatar_facing_dir = vectoangles(normalize(vec_to_min(position_b, position_a) - vec_to_max(position_b, position_a)));
+                               }
+                               view_angle.y = avatar_facing_dir.y; // snap avatar to look on along the correct axis
+
+                               // if (0 == input_movevalues.x) look straight ahead
+                               if (0 > input_movevalues.x) { // look up
+                                       view_angle.x = 50;
+                               } else if (0 < input_movevalues.x) { // look down
+                                       view_angle.x = -50;
+                               }
+                       } else {
+                               vector mpos = CursorToWorldCoord(viewloc_mousepos);
+                               mpos.x = view.origin.x; // replace the cursor's x position with the player's
+                               view_angle = aim_vec(view.origin, mpos); // get new angles
+                       }
+                       view.angles_y = view_angle.y;
+                       setproperty(VF_CL_VIEWANGLES, view_angle);
                }
        }
 }
index 5aec7fe..05f3061 100644 (file)
@@ -281,7 +281,7 @@ void W_Electro_Orb_Touch(entity this, entity toucher)
        PROJECTILE_TOUCH(this, toucher);
        if(toucher.takedamage == DAMAGE_AIM)
                { if(WEP_CVAR_SEC(electro, touchexplode)) { W_Electro_Explode(this, toucher); } }
-       else
+       else if(toucher.owner != this.owner && toucher.classname != this.classname) // don't stick to player's other projectiles!
        {
                //UpdateCSQCProjectile(this);
                spamsound(this, CH_SHOTS, SND_ELECTRO_BOUNCE, VOL_BASE, ATTEN_NORM);
index 4a21a51..13b5661 100644 (file)
@@ -318,8 +318,8 @@ void Draw_GrapplingHook(entity this)
                {
                        default:
                        case NET_ENT_CLIENT_HOOK:
-                               if(autocvar_chase_active > 0)
-                                       a = csqcplayer.origin;
+                               if(autocvar_chase_active)
+                                       a = csqcplayer.origin + csqcplayer.view_ofs;
                                else
                                        a = view_origin + view_forward * vs.x + view_right * -vs.y + view_up * vs.z;
                                b = this.origin;
index affec0d..2161d46 100644 (file)
@@ -4,7 +4,7 @@ CLASS(Mortar, Weapon)
 /* spawnfunc */ ATTRIB(Mortar, m_canonical_spawnfunc, string, "weapon_mortar");
 /* ammotype  */ ATTRIB(Mortar, ammo_type, int, RESOURCE_ROCKETS);
 /* impulse   */ ATTRIB(Mortar, impulse, int, 4);
-/* flags     */ ATTRIB(Mortar, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH);
+/* flags     */ ATTRIB(Mortar, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH | WEP_FLAG_NODUAL);
 /* rating    */ ATTRIB(Mortar, bot_pickupbasevalue, float, 7000);
 /* color     */ ATTRIB(Mortar, wpcolor, vector, '1 0 0');
 /* modelname */ ATTRIB(Mortar, mdl, string, "gl");
index 560354c..87bc4d7 100644 (file)
@@ -4,7 +4,7 @@ CLASS(Rifle, Weapon)
 /* spawnfunc */ ATTRIB(Rifle, m_canonical_spawnfunc, string, "weapon_rifle");
 /* ammotype  */ ATTRIB(Rifle, ammo_type, int, RESOURCE_BULLETS);
 /* impulse   */ ATTRIB(Rifle, impulse, int, 7);
-/* flags     */ ATTRIB(Rifle, spawnflags, int, WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_PENETRATEWALLS);
+/* flags     */ ATTRIB(Rifle, spawnflags, int, WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_PENETRATEWALLS | WEP_FLAG_NODUAL);
 /* rating    */ ATTRIB(Rifle, bot_pickupbasevalue, float, 7000);
 /* color     */ ATTRIB(Rifle, wpcolor, vector, '0.5 1 0');
 /* modelname */ ATTRIB(Rifle, mdl, string, "campingrifle");
index 3dab2c2..51ac148 100644 (file)
@@ -37,6 +37,7 @@ float skill;
 .void(entity this) havocbot_previous_role;
 .float isbot; // true if this client is actually a bot
 .float lastteleporttime;
+.vector lastteleport_origin;
 .float navigation_hasgoals;
 .float nearestwaypointtimeout;
 .entity nearestwaypoint;
index 276d5b5..af23802 100644 (file)
@@ -759,8 +759,7 @@ void bot_serverframe()
        {
                botframe_spawnedwaypoints = true;
                waypoint_loadall();
-               if(!waypoint_load_links())
-                       waypoint_schedulerelinkall(); // link all the autogenerated waypoints (teleporters)
+               waypoint_load_links();
        }
 
        if (bot_list)
index c7c3c55..3754897 100644 (file)
@@ -1595,6 +1595,12 @@ int navigation_poptouchedgoals(entity this)
                if(this.lastteleporttime > 0
                        && time - this.lastteleporttime < ((this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL) ? 2 : 0.15))
                {
+                       if (this.jumppadcount && !boxesoverlap(this.goalcurrent.absmin, this.goalcurrent.absmax,
+                               this.lastteleport_origin + STAT(PL_MIN, this), this.lastteleport_origin + STAT(PL_MAX, this)))
+                       {
+                               return;
+                       }
+
                        if(this.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
                        if(this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL && this.goalcurrent.owner==this)
                        {
index c6d172c..75713e8 100644 (file)
@@ -57,6 +57,7 @@ float tracewalk_dest_height;
 .entity wp_goal_prev1;
 
 .float lastteleporttime;
+.vector lastteleport_origin;
 
 .float blacklisted;
 
index fed491b..fa82d92 100644 (file)
@@ -179,19 +179,12 @@ void waypoint_setupmodel(entity wp)
                wp.model = "";
 }
 
-// create a new spawnfunc_waypoint and automatically link it to other waypoints, and link
-// them back to it as well
-// (suitable for spawnfunc_waypoint editor)
 entity waypoint_spawn(vector m1, vector m2, float f)
 {
-       if(!(f & WAYPOINTFLAG_PERSONAL))
+       if(!(f & (WAYPOINTFLAG_PERSONAL | WAYPOINTFLAG_GENERATED)) && m1 == m2)
        {
-               vector em1 = m1, em2 = m2;
-               if (!(f & WAYPOINTFLAG_GENERATED) && m1 == m2)
-               {
-                       em1 = m1 - '8 8 8';
-                       em2 = m2 + '8 8 8';
-               }
+               vector em1 = m1 - '8 8 8';
+               vector em2 = m2 + '8 8 8';
                IL_EACH(g_waypoints, boxesoverlap(em1, em2, it.absmin, it.absmax),
                {
                        return it;
@@ -686,22 +679,32 @@ void waypoint_schedulerelinkall()
        waypoint_load_links_hardwired();
 }
 
+#define GET_GAMETYPE_EXTENSION() ((g_race) ? ".race" : "")
+
 // Load waypoint links from file
 bool waypoint_load_links()
 {
-       string filename, s;
+       string s;
        float file, tokens, c = 0, found;
        entity wp_from = NULL, wp_to;
        vector wp_to_pos, wp_from_pos;
-       filename = strcat("maps/", mapname);
-       filename = strcat(filename, ".waypoints.cache");
+
+       string gt_ext = GET_GAMETYPE_EXTENSION();
+
+       string filename = sprintf("maps/%s.waypoints.cache", strcat(mapname, gt_ext));
        file = fopen(filename, FILE_READ);
 
+       if (gt_ext != "" && file < 0)
+       {
+               // if race waypoint file doesn't exist load the default one
+               filename = sprintf("maps/%s.waypoints.cache", mapname);
+               file = fopen(filename, FILE_READ);
+       }
+
        if (file < 0)
        {
-               LOG_TRACE("waypoint links load from ");
-               LOG_TRACE(filename);
-               LOG_TRACE(" failed");
+               LOG_TRACE("waypoint links load from ", filename, " failed");
+               waypoint_schedulerelinkall();
                return false;
        }
 
@@ -721,7 +724,20 @@ bool waypoint_load_links()
                        else
                        {
                                if(ver < WAYPOINT_VERSION)
-                                       return false;
+                               {
+                                       LOG_TRACE("waypoint links for this map are outdated.");
+                                       if (g_assault)
+                                       {
+                                               LOG_TRACE("Assault waypoint links need to be manually updated in the editor");
+                                       }
+                                       else
+                                       {
+                                               LOG_TRACE("automatically updating...");
+                                               waypoint_schedulerelinkall();
+                                               fclose(file);
+                                               return false;
+                                       }
+                               }
                                parse_comments = false;
                        }
                }
@@ -732,6 +748,7 @@ bool waypoint_load_links()
                {
                        // bad file format
                        fclose(file);
+                       waypoint_schedulerelinkall(); // link all the autogenerated waypoints (teleporters)
                        return false;
                }
 
@@ -759,7 +776,6 @@ bool waypoint_load_links()
                                LOG_TRACE("waypoint_load_links: couldn't find 'from' waypoint at ", vtos(wp_from_pos));
                                continue;
                        }
-
                }
 
                // Search "to" waypoint
@@ -788,7 +804,19 @@ bool waypoint_load_links()
 
        fclose(file);
 
-       LOG_TRACE("loaded ", ftos(c), " waypoint links from maps/", mapname, ".waypoints.cache");
+       LOG_TRACE("loaded ", ftos(c), " waypoint links from ", filename);
+
+       bool scheduled = false;
+       IL_EACH(g_waypoints, it.wpflags & WAYPOINTFLAG_ITEM,
+       {
+               if (!it.wp00)
+               {
+                       waypoint_schedulerelink(it);
+                       scheduled = true;
+               }
+       });
+       if (scheduled)
+               return false;
 
        botframe_cachedwaypointlinks = true;
        return true;
@@ -796,14 +824,23 @@ bool waypoint_load_links()
 
 void waypoint_load_or_remove_links_hardwired(bool removal_mode)
 {
-       string filename, s;
+       string s;
        float file, tokens, c = 0, found;
        entity wp_from = NULL, wp_to;
        vector wp_to_pos, wp_from_pos;
-       filename = strcat("maps/", mapname);
-       filename = strcat(filename, ".waypoints.hardwired");
+
+       string gt_ext = GET_GAMETYPE_EXTENSION();
+
+       string filename = sprintf("maps/%s.waypoints.hardwired", strcat(mapname, gt_ext));
        file = fopen(filename, FILE_READ);
 
+       if (gt_ext != "" && file < 0)
+       {
+               // if race waypoint file doesn't exist load the default one
+               filename = sprintf("maps/%s.waypoints.hardwired", mapname);
+               file = fopen(filename, FILE_READ);
+       }
+
        botframe_loadedforcedlinks = true;
 
        if (file < 0)
@@ -890,8 +927,8 @@ void waypoint_load_or_remove_links_hardwired(bool removal_mode)
 
        fclose(file);
 
-       if(!removal_mode)
-               LOG_TRACE("loaded ", ftos(c), " waypoint links from maps/", mapname, ".waypoints.hardwired");
+       LOG_TRACE(((removal_mode) ? "unloaded " : "loaded "),
+               ftos(c), " waypoint links from maps/", mapname, ".waypoints.hardwired");
 }
 
 entity waypoint_get_link(entity w, float i)
@@ -940,7 +977,9 @@ void waypoint_save_links()
        // temporarily remove hardwired links so they don't get saved among normal links
        waypoint_remove_links_hardwired();
 
-       string filename = sprintf("maps/%s.waypoints.cache", mapname);
+       string gt_ext = GET_GAMETYPE_EXTENSION();
+
+       string filename = sprintf("maps/%s.waypoints.cache", strcat(mapname, gt_ext));
        int file = fopen(filename, FILE_WRITE);
        if (file < 0)
        {
@@ -965,9 +1004,10 @@ void waypoint_save_links()
                }
        });
        fclose(file);
+
        botframe_cachedwaypointlinks = true;
 
-       LOG_INFOF("saved %d waypoint links to maps/%s.waypoints.cache", c, mapname);
+       LOG_INFOF("saved %d waypoint links to %s", c, filename);
 
        waypoint_load_links_hardwired();
 }
@@ -975,7 +1015,9 @@ void waypoint_save_links()
 // save waypoints to gamedir/data/maps/mapname.waypoints
 void waypoint_saveall()
 {
-       string filename = sprintf("maps/%s.waypoints", mapname);
+       string gt_ext = GET_GAMETYPE_EXTENSION();
+
+       string filename = sprintf("maps/%s.waypoints", strcat(mapname, gt_ext));
        int file = fopen(filename, FILE_WRITE);
        if (file < 0)
        {
@@ -1011,23 +1053,29 @@ void waypoint_saveall()
        waypoint_save_links();
        botframe_loadedforcedlinks = false;
 
-       LOG_INFOF("saved %d waypoints to maps/%s.waypoints", c, mapname);
+       LOG_INFOF("saved %d waypoints to %s", c, filename);
 }
 
 // load waypoints from file
 float waypoint_loadall()
 {
-       string filename, s;
+       string s;
        float file, cwp, cwb, fl;
        vector m1, m2;
        cwp = 0;
        cwb = 0;
-       filename = strcat("maps/", mapname);
-       filename = strcat(filename, ".waypoints");
+
+       string gt_ext = GET_GAMETYPE_EXTENSION();
+
+       string filename = sprintf("maps/%s.waypoints", strcat(mapname, gt_ext));
        file = fopen(filename, FILE_READ);
 
-       bool parse_comments = true;
-       float ver = 0;
+       if (gt_ext != "" && file < 0)
+       {
+               // if race waypoint file doesn't exist load the default one
+               filename = sprintf("maps/%s.waypoints", mapname);
+               file = fopen(filename, FILE_READ);
+       }
 
        if (file < 0)
        {
@@ -1035,6 +1083,9 @@ float waypoint_loadall()
                return 0;
        }
 
+       bool parse_comments = true;
+       float ver = 0;
+
        while ((s = fgets(file)))
        {
                if(parse_comments)
@@ -1048,7 +1099,10 @@ float waypoint_loadall()
                        else
                        {
                                if(floor(ver) < floor(WAYPOINT_VERSION))
+                               {
                                        LOG_TRACE("waypoints for this map are outdated");
+                                       LOG_TRACE("please update them in the editor");
+                               }
                                parse_comments = false;
                        }
                }
index 588b794..5010d28 100644 (file)
@@ -343,8 +343,9 @@ IMPULSE(weapon_reload)
                Weapon w = this.(weaponentity).m_weapon;
                w.wr_reload(w, actor, weaponentity);
 
-               if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
-                       break;
+               // allow reloading all active slots?
+               //if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
+                       //break;
        }
 }
 
index ef69905..8b4204b 100644 (file)
@@ -1244,6 +1244,7 @@ float MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundma
     });
        if(!sp)
        {
+               int items_checked = 0;
                IL_EACH(g_items, checkpvs(mstart, it),
                {
                        if((traceline(mstart, it.origin + (it.mins + it.maxs) * 0.5, MOVE_NORMAL, e), trace_fraction) >= 1)
@@ -1251,6 +1252,10 @@ float MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundma
                                sp = it;
                                break;
                        }
+
+                       ++items_checked;
+                       if(items_checked >= attempts)
+                               break; // sanity
                });
 
                if(!sp)
index ba2405b..daaf8a9 100644 (file)
@@ -17,12 +17,25 @@ void havocbot_role_cts(entity this)
        {
                navigation_goalrating_start(this);
 
+               bool raw_touch_check = true;
+               int cp = this.race_checkpoint;
+
+               LABEL(search_racecheckpoints)
                IL_EACH(g_racecheckpoints, true,
                {
-                       if(it.cnt == this.race_checkpoint)
-                               navigation_routerating(this, it, 1000000, 5000);
-                       else if(this.race_checkpoint == -1)
+                       if(it.cnt == cp || cp == -1)
+                       {
+                               // redirect bot to next goal if it touched the waypoint of an untouchable checkpoint
+                               // e.g. checkpoint in front of Stormkeep's warpzone
+                               // the same workaround is applied in Race game mode
+                               if (raw_touch_check && vdist(this.origin - it.nearestwaypoint.origin, <, 30))
+                               {
+                                       cp = race_NextCheckpoint(cp);
+                                       raw_touch_check = false;
+                                       goto search_racecheckpoints;
+                               }
                                navigation_routerating(this, it, 1000000, 5000);
+                       }
                });
 
                navigation_goalrating_end(this);
index c4ba8e9..690d23c 100644 (file)
@@ -18,11 +18,23 @@ void havocbot_role_race(entity this)
        {
                navigation_goalrating_start(this);
 
+               bool raw_touch_check = true;
+               int cp = this.race_checkpoint;
+
+               LABEL(search_racecheckpoints)
                IL_EACH(g_racecheckpoints, true,
                {
-                       if(it.cnt == this.race_checkpoint)
-                               navigation_routerating(this, it, 1000000, 5000);
-                       else if(this.race_checkpoint == -1)
+                       if(it.cnt == cp || cp == -1)
+                       {
+                               // redirect bot to next goal if it touched the waypoint of an untouchable checkpoint
+                               // e.g. checkpoint in front of Stormkeep's warpzone
+                               // the same workaround is applied in CTS game mode
+                               if (raw_touch_check && vdist(this.origin - it.nearestwaypoint.origin, <, 30))
+                               {
+                                       cp = race_NextCheckpoint(cp);
+                                       raw_touch_check = false;
+                                       goto search_racecheckpoints;
+                               }
                                navigation_routerating(this, it, 1000000, 5000);
                });
 
index 157adb5..486ae18 100644 (file)
@@ -16,6 +16,7 @@
 #include <common/util.qh>
 
 #include <common/weapons/_all.qh>
+#include <common/wepent.qh>
 #include <common/state.qh>
 
 #include <lib/warpzone/common.qh>
@@ -134,7 +135,14 @@ void W_SetupShot_Dir_ProjectileSize_Range(entity ent, .entity weaponentity, vect
                ent.punchangle_x = recoil * -1;
 
        if (snd != SND_Null) {
-               sound (ent, chan, snd, VOL_BASE, ATTN_NORM);
+               int held_weapons = 0; // HACK: muffle weapon sounds slightly while dual wielding!
+               for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+               {
+                       .entity wep_ent = weaponentities[slot];
+                       if(ent.(wep_ent) && ent.(wep_ent).m_switchweapon != WEP_Null)
+                               ++held_weapons;
+               }
+               sound (ent, chan, snd, ((held_weapons > 1) ? VOL_BASE * 0.7 : VOL_BASE), ATTN_NORM);
                W_PlayStrengthSound(ent);
        }