Merge branch 'master' into Lyberta/StandaloneOverkillWeapons
authorLyberta <lyberta@lyberta.net>
Mon, 8 Jan 2018 16:07:28 +0000 (19:07 +0300)
committerLyberta <lyberta@lyberta.net>
Mon, 8 Jan 2018 16:07:28 +0000 (19:07 +0300)
36 files changed:
defaultServer.cfg
defaultXonotic.cfg
minigames.cfg
mutators.cfg
qcsrc/client/hud/panel/scoreboard.qc
qcsrc/client/view.qc
qcsrc/client/view.qh
qcsrc/common/gamemodes/gamemode/nexball/sv_weapon.qc
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/teams.qh
qcsrc/common/triggers/func/door.qc
qcsrc/common/triggers/func/door_rotating.qc
qcsrc/common/triggers/func/door_secret.qc
qcsrc/common/triggers/trigger/jumppads.qc
qcsrc/common/triggers/trigger/jumppads.qh
qcsrc/common/triggers/trigger/multi.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/porto.qc
qcsrc/common/weapons/weapon/rifle.qh
qcsrc/server/bot/default/bot.qc
qcsrc/server/client.qc
qcsrc/server/defs.qh
qcsrc/server/g_damage.qc
qcsrc/server/impulse.qc
qcsrc/server/miscfunctions.qc
qcsrc/server/teamplay.qc
qcsrc/server/teamplay.qh
qcsrc/server/weapons/tracing.qc

index 155bb3c..decd229 100644 (file)
@@ -202,7 +202,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 754eb0e..0cdd7a4 100644 (file)
@@ -14,13 +14,5 @@ set sv_minigames_pong_ai_thinkspeed 0.1     "Seconds between AI actions"
 set sv_minigames_pong_ai_tolerance  0.33    "Distance of the ball relative to the paddle size"
 
 
-// Snake? Snake! SNAAAAKE!!
-set sv_minigames_snake_wrap 0 "Wrap around the edges of the screen instead of dying on touch"
-set sv_minigames_snake_delay_initial 0.7 "Initial delay between snake movement"
-set sv_minigames_snake_delay_multiplier 50 "Multiplier of incremental of movement speed (player_score / cvar)"
-set sv_minigames_snake_delay_min 0.1 "Minimum delay between snake movement (at fastest rate)"
-set sv_minigames_snake_lives 3
-
-
 // Bulldozer
 set sv_minigames_bulldozer_startlevel "level1"
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 063cb88..0360eb0 100644 (file)
@@ -362,16 +362,18 @@ void Cmd_Scoreboard_Help()
 "ping pl name |" \
 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
 " -teams,lms/deaths +ft,tdm/deaths" \
+" +tdm/sum" \
 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
-" +teams/teamkills"\
 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
+" +tdm,ft,dom,ons,as/teamkills"\
 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
-" +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
+" +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
 " +lms/lives +lms/rank" \
-" +kh/caps +kh/pushes +kh/destroyed" \
+" +kh/kckills +kh/losses +kh/caps" \
 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
 " +as/objectives +nb/faults +nb/goals" \
 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
+" +dom/ticks +dom/takes" \
 " -lms,rc,cts,inv,nb/score"
 
 void Cmd_Scoreboard_SetFields(int argc)
index d3c1314..7808ebc 100644 (file)
@@ -32,6 +32,7 @@
 #include <common/weapons/_all.qh>
 #include <common/mutators/mutator/overkill/okvortex.qh>
 #include <common/viewloc.qh>
+#include <common/triggers/trigger/viewloc.qh>
 #include <common/minigames/cl_minigames.qh>
 #include <common/minigames/cl_minigames_hud.qh>
 
@@ -1505,6 +1506,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;
@@ -1602,6 +1616,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
        {
@@ -1653,10 +1672,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)
@@ -2088,7 +2103,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;
@@ -2401,6 +2416,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 705ac6d..3568f22 100644 (file)
@@ -2,7 +2,7 @@
 
 void W_Nexball_Attack(entity actor, .entity weaponentity, float t);
 void W_Nexball_Attack2(entity actor, .entity weaponentity);
-vector trigger_push_calculatevelocity(vector org, entity tgt, float ht);
+vector trigger_push_calculatevelocity(vector org, entity tgt, float ht, entity pushed_entity);
 
 METHOD(BallStealer, wr_think, void(BallStealer thiswep, entity actor, .entity weaponentity, int fire))
 {
@@ -147,7 +147,7 @@ void W_Nexball_Attack2(entity actor, .entity weaponentity)
        {
                entity _ball = actor.ballcarried;
                W_SetupShot(actor, weaponentity, false, 4, SND_NB_SHOOT1, CH_WEAPON_A, 0);
-               DropBall(_ball, w_shotorg, trigger_push_calculatevelocity(_ball.origin, _ball.enemy, 32));
+               DropBall(_ball, w_shotorg, trigger_push_calculatevelocity(_ball.origin, _ball.enemy, 32, _ball));
                setthink(_ball, W_Nexball_Think);
                _ball.nextthink = time;
                return;
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 f4431ee..fa6d0f6 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 5ecbe54..3740c9a 100644 (file)
@@ -62,6 +62,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 1f8a603..57d644c 100644 (file)
@@ -125,6 +125,42 @@ float Team_ColorToTeam(string team_color)
        return -1;
 }
 
+/// \brief Returns whether team is valid.
+/// \param[in] team_ Team to check.
+/// \return True if team is valid, false otherwise.
+bool Team_IsValidTeam(int team_)
+{
+       switch (team_)
+       {
+               case NUM_TEAM_1:
+               case NUM_TEAM_2:
+               case NUM_TEAM_3:
+               case NUM_TEAM_4:
+               {
+                       return true;
+               }
+       }
+       return false;
+}
+
+/// \brief Returns whether team number is valid.
+/// \param[in] number Team number to check.
+/// \return True if team number is valid, false otherwise.
+bool Team_IsValidNumber(int number)
+{
+       switch (number)
+       {
+               case 1:
+               case 2:
+               case 3:
+               case 4:
+               {
+                       return true;
+               }
+       }
+       return false;
+}
+
 float Team_NumberToTeam(float number)
 {
        switch(number)
index 392ab3e..bb15503 100644 (file)
@@ -765,6 +765,7 @@ spawnfunc(func_door)
 
        if (this.health)
        {
+               //this.canteamdamage = true; // TODO
                this.takedamage = DAMAGE_YES;
                this.event_damage = door_damage;
        }
index 2c72dc9..c61a026 100644 (file)
@@ -109,6 +109,7 @@ spawnfunc(func_door_rotating)
 
        if (this.health)
        {
+               //this.canteamdamage = true; // TODO
                this.takedamage = DAMAGE_YES;
                this.event_damage = door_damage;
        }
index 0bad196..6f2d101 100644 (file)
@@ -228,6 +228,7 @@ spawnfunc(func_door_secret)
 
        if (this.spawnflags & SECRET_YES_SHOOT)
        {
+               //this.canteamdamage = true; // TODO
                this.health = 10000;
                this.takedamage = DAMAGE_YES;
                this.event_damage = fd_secret_damage;
index 822411c..4316a0e 100644 (file)
@@ -25,22 +25,23 @@ REGISTER_NET_LINKED(ENT_CLIENT_TARGET_PUSH)
          tgt - target entity (can be either a point or a model entity; if it is
                the latter, its midpoint is used)
          ht  - jump height, measured from the higher one of org and tgt's midpoint
+         pushed_entity - object that is to be pushed
 
        Returns: velocity for the jump
        the global trigger_push_calculatevelocity_flighttime is set to the total
        jump time
  */
 
-vector trigger_push_calculatevelocity(vector org, entity tgt, float ht)
+vector trigger_push_calculatevelocity(vector org, entity tgt, float ht, entity pushed_entity)
 {
        float grav, sdist, zdist, vs, vz, jumpheight;
        vector sdir, torg;
 
        torg = tgt.origin + (tgt.mins + tgt.maxs) * 0.5;
 
-       grav = PHYS_GRAVITY(tgt);
-       if(PHYS_ENTGRAVITY(tgt))
-               grav *= PHYS_ENTGRAVITY(tgt);
+       grav = PHYS_GRAVITY(NULL);
+       if(pushed_entity && PHYS_ENTGRAVITY(pushed_entity))
+               grav *= PHYS_ENTGRAVITY(pushed_entity);
 
        zdist = torg.z - org.z;
        sdist = vlen(torg - org - zdist * '0 0 1');
@@ -137,7 +138,7 @@ bool jumppad_push(entity this, entity targ)
 
        if(this.enemy)
        {
-               targ.velocity = trigger_push_calculatevelocity(targ.origin, this.enemy, this.height);
+               targ.velocity = trigger_push_calculatevelocity(targ.origin, this.enemy, this.height, targ);
        }
        else if(this.target && this.target != "")
        {
@@ -150,7 +151,7 @@ bool jumppad_push(entity this, entity targ)
                        else
                                RandomSelection_AddEnt(e, 1, 1);
                }
-               targ.velocity = trigger_push_calculatevelocity(targ.origin, RandomSelection_chosen_ent, this.height);
+               targ.velocity = trigger_push_calculatevelocity(targ.origin, RandomSelection_chosen_ent, this.height, targ);
        }
        else
        {
@@ -289,7 +290,7 @@ void trigger_push_findtarget(entity this)
                        entity e = spawn();
                        setorigin(e, org);
                        setsize(e, PL_MIN_CONST, PL_MAX_CONST);
-                       e.velocity = trigger_push_calculatevelocity(org, t, this.height);
+                       e.velocity = trigger_push_calculatevelocity(org, t, this.height, e);
                        tracetoss(e, e);
                        if(e.move_movetype == MOVETYPE_NONE)
                                waypoint_spawnforteleporter(this, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity));
index 76d244d..8615bc6 100644 (file)
@@ -26,13 +26,14 @@ void trigger_push_use(entity this, entity actor, entity trigger);
          tgt - target entity (can be either a point or a model entity; if it is
                the latter, its midpoint is used)
          ht  - jump height, measured from the higher one of org and tgt's midpoint
+         pushed_entity - object that is to be pushed
 
        Returns: velocity for the jump
        the global trigger_push_calculatevelocity_flighttime is set to the total
        jump time
  */
 
-vector trigger_push_calculatevelocity(vector org, entity tgt, float ht);
+vector trigger_push_calculatevelocity(vector org, entity tgt, float ht, entity pushed_entity);
 
 void trigger_push_touch(entity this, entity toucher);
 
index 5e8c641..2f32e50 100644 (file)
@@ -104,6 +104,9 @@ void multi_eventdamage(entity this, entity inflictor, entity attacker, float dam
        if(this.spawnflags & DOOR_NOSPLASH)
                if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
                        return;
+       if(this.team)
+               if(((this.spawnflags & 4) == 0) == (this.team != attacker.team))
+                       return;
        this.health = this.health - damage;
        if (this.health <= 0)
        {
@@ -169,6 +172,7 @@ spawnfunc(trigger_multiple)
        {
                if (this.spawnflags & SPAWNFLAG_NOTOUCH)
                        objerror (this, "health and notouch don't make sense\n");
+               this.canteamdamage = true;
                this.max_health = this.health;
                this.event_damage = multi_eventdamage;
                this.takedamage = DAMAGE_YES;
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 9738914..a134c03 100644 (file)
@@ -45,7 +45,7 @@ void W_Porto_Fail(entity this, float failhard)
                {
                        this.flags = FL_ITEM;
                        IL_PUSH(g_items, this);
-                       this.velocity = trigger_push_calculatevelocity(this.origin, this.realowner, 128);
+                       this.velocity = trigger_push_calculatevelocity(this.origin, this.realowner, 128, this);
                        tracetoss(this, this);
                        if(vdist(trace_endpos - this.realowner.origin, <, 128))
                        {
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 f8ddd0c..6ea375c 100644 (file)
@@ -438,7 +438,7 @@ void bot_clientconnect(entity this)
        else if(this.bot_forced_team==4)
                this.team = NUM_TEAM_4;
        else
-               JoinBestTeam(this, false, true);
+               JoinBestTeam(this, true);
 
        havocbot_setupbot(this);
 }
index 856783d..399d11d 100644 (file)
@@ -509,7 +509,7 @@ void PutPlayerInServer(entity this)
        accuracy_resend(this);
 
        if (this.team < 0)
-               JoinBestTeam(this, false, true);
+               JoinBestTeam(this, true);
 
        entity spot = SelectSpawnPoint(this, false);
        if (!spot) {
@@ -879,7 +879,7 @@ void ClientKill_Now_TeamChange(entity this)
 {
        if(this.killindicator_teamchange == -1)
        {
-               JoinBestTeam( this, false, true );
+               JoinBestTeam( this, true );
        }
        else if(this.killindicator_teamchange == -2)
        {
@@ -1191,7 +1191,7 @@ void ClientConnect(entity this)
     {
         int id = this.playerid;
         this.playerid = 0; // silent
-           JoinBestTeam(this, false, false); // if the team number is valid, keep it
+           JoinBestTeam(this, false); // if the team number is valid, keep it
            this.playerid = id;
     }
 
@@ -2006,7 +2006,7 @@ void Join(entity this)
 
        if(!this.team_selected)
        if(autocvar_g_campaign || autocvar_g_balance_teams)
-               JoinBestTeam(this, false, true);
+               JoinBestTeam(this, true);
 
        if(autocvar_g_campaign)
                campaign_bots_may_start = true;
index 3034961..7ae23dc 100644 (file)
@@ -222,6 +222,8 @@ int have_team_spawns_forteams; // if Xth bit is 1 then team X has spawns else it
 // set when showing a kill countdown
 .entity killindicator;
 
+.bool canteamdamage;
+
 void Damage (entity targ, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force);
 
 float lockteams;
index c0ff6b3..029660c 100644 (file)
@@ -698,7 +698,7 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, int d
                                                                        force = '0 0 0';
                                                        }
                                                }
-                                               else
+                                               else if(!targ.canteamdamage)
                                                        damage = 0;
                                        }
                                }
index 56ea478..5b426c1 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 fd73969..33ad8f8 100644 (file)
@@ -545,6 +545,14 @@ void GetTeamCounts(entity ignore)
 bool IsTeamSmallerThanTeam(int team_a, int team_b, entity player,
        bool use_score)
 {
+       if (!Team_IsValidNumber(team_a))
+       {
+               LOG_FATALF("IsTeamSmallerThanTeam: team_a is invalid: %f", team_a);
+       }
+       if (!Team_IsValidNumber(team_b))
+       {
+               LOG_FATALF("IsTeamSmallerThanTeam: team_b is invalid: %f", team_b);
+       }
        if (team_a == team_b)
        {
                return false;
@@ -642,6 +650,14 @@ bool IsTeamSmallerThanTeam(int team_a, int team_b, entity player,
 
 bool IsTeamEqualToTeam(int team_a, int team_b, entity player, bool use_score)
 {
+       if (!Team_IsValidNumber(team_a))
+       {
+               LOG_FATALF("IsTeamEqualToTeam: team_a is invalid: %f", team_a);
+       }
+       if (!Team_IsValidNumber(team_b))
+       {
+               LOG_FATALF("IsTeamEqualToTeam: team_b is invalid: %f", team_b);
+       }
        if (team_a == team_b)
        {
                return true;
@@ -837,53 +853,56 @@ int FindSmallestTeam(entity player, float ignore_player)
        return RandomSelection_chosen_float;
 }
 
-int JoinBestTeam(entity this, bool only_return_best, bool force_best_team)
+void JoinBestTeam(entity this, bool force_best_team)
 {
        // don't join a team if we're not playing a team game
        if (!teamplay)
        {
-               return 0;
+               return;
        }
 
        // find out what teams are available
        CheckAllowedTeams(this);
 
-       // if we don't care what team he ends up on, put him on whatever team he entered as.
-       // if he's not on a valid team, then let other code put him on the smallest team
+       // if we don't care what team they end up on, put them on whatever team they entered as.
+       // if they're not on a valid team, then let other code put them on the smallest team
        if (!force_best_team)
        {
                int selected_team;
-               if(     c1 >= 0 && this.team == NUM_TEAM_1)
+               if ((c1 >= 0) && (this.team == NUM_TEAM_1))
+               {
                        selected_team = this.team;
-               else if(c2 >= 0 && this.team == NUM_TEAM_2)
+               }
+               else if ((c2 >= 0) && (this.team == NUM_TEAM_2))
+               {
                        selected_team = this.team;
-               else if(c3 >= 0 && this.team == NUM_TEAM_3)
+               }
+               else if ((c3 >= 0) && (this.team == NUM_TEAM_3))
+               {
                        selected_team = this.team;
-               else if(c4 >= 0 && this.team == NUM_TEAM_4)
+               }
+               else if ((c4 >= 0) && (this.team == NUM_TEAM_4))
+               {
                        selected_team = this.team;
+               }
                else
+               {
                        selected_team = -1;
+               }
 
                if (selected_team > 0)
                {
-                       if (!only_return_best)
-                       {
-                               SetPlayerTeamSimple(this, selected_team);
-
-                               // when JoinBestTeam is called by client.qc/ClientKill_Now_TeamChange the players team is -1 and thus skipped
-                               // when JoinBestTeam is called by client.qc/ClientConnect the player_id is 0 the log attempt is rejected
-                               LogTeamchange(this.playerid, this.team, 99);
-                       }
-                       return selected_team;
+                       SetPlayerTeamSimple(this, selected_team);
+                       LogTeamchange(this.playerid, this.team, 99);
+                       return;
                }
-               // otherwise end up on the smallest team (handled below)
        }
-
-       int best_team = FindSmallestTeam(this, true);
-       if (only_return_best || this.bot_forced_team)
+       // otherwise end up on the smallest team (handled below)
+       if (this.bot_forced_team)
        {
-               return best_team;
+               return;
        }
+       int best_team = FindSmallestTeam(this, true);
        best_team = Team_NumberToTeam(best_team);
        if (best_team == -1)
        {
@@ -893,12 +912,11 @@ int JoinBestTeam(entity this, bool only_return_best, bool force_best_team)
        TeamchangeFrags(this);
        SetPlayerTeamSimple(this, best_team);
        LogTeamchange(this.playerid, this.team, 2); // log auto join
-       if (!IS_BOT_CLIENT(this))
+       if ((old_team != -1) && !IS_BOT_CLIENT(this))
        {
                AutoBalanceBots(old_team, Team_TeamToNumber(best_team));
        }
        KillPlayerForTeamChange(this);
-       return best_team;
 }
 
 void SV_ChangeTeam(entity this, float _color)
@@ -978,8 +996,15 @@ void SV_ChangeTeam(entity this, float _color)
 
 void AutoBalanceBots(int source_team, int destination_team)
 {
-       if ((source_team == -1) || (destination_team == -1))
+       if (!Team_IsValidNumber(source_team))
+       {
+               LOG_WARNF("AutoBalanceBots: Source team is invalid: %f", source_team);
+               return;
+       }
+       if (!Team_IsValidNumber(destination_team))
        {
+               LOG_WARNF("AutoBalanceBots: Destination team is invalid: %f",
+                       destination_team);
                return;
        }
        if (!autocvar_g_balance_teams ||
@@ -1013,6 +1038,10 @@ void AutoBalanceBots(int source_team, int destination_team)
                        break;
                }
        }
+       if (num_players_source_team < 0)
+       {
+               return;
+       }
        switch (destination_team)
        {
                case 1:
index 1813db0..7c4ebe7 100644 (file)
@@ -107,7 +107,7 @@ int FindBestTeams(entity player, bool use_score);
 // NOTE: Assumes CheckAllowedTeams has already been called!
 int FindSmallestTeam(entity player, float ignore_player);
 
-int JoinBestTeam(entity this, bool only_return_best, bool force_best_team);
+void JoinBestTeam(entity this, bool force_best_team);
 
 /// \brief Auto balances bots in teams after the player has changed team.
 /// \param[in] source_team Previous team of the player (1, 2, 3, 4).
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);
        }