]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge remote branch 'origin/master' into mirceakitsune/damage_effects
authorSamual <samual@xonotic.org>
Fri, 20 Jan 2012 01:16:35 +0000 (20:16 -0500)
committerSamual <samual@xonotic.org>
Fri, 20 Jan 2012 01:16:35 +0000 (20:16 -0500)
22 files changed:
balance25.cfg
balanceFruitieX.cfg
balanceXPM.cfg
balanceXonotic.cfg
commands.cfg
defaultXonotic.cfg
qcsrc/client/Defs.qc
qcsrc/client/Main.qc
qcsrc/client/View.qc
qcsrc/client/hud.qc
qcsrc/csqcmodellib/cl_player.qc
qcsrc/menu/xonotic/dialog_settings_network.c
qcsrc/server/autocvars.qh
qcsrc/server/bot/scripting.qc
qcsrc/server/cl_client.qc
qcsrc/server/portals.qc
qcsrc/server/portals.qh
qcsrc/server/t_jumppads.qc
qcsrc/server/vehicles/racer.qc
qcsrc/server/vehicles/raptor.qc
qcsrc/server/vehicles/spiderbot.qc
qcsrc/server/w_porto.qc

index f65ee473fef5c9dc6beb3b5492d1c261fd8536aa..cd770d72eaae8cbfc197ba5fdb95358c762883c9 100644 (file)
@@ -627,6 +627,11 @@ set g_balance_porto_primary_refire 1.5
 set g_balance_porto_primary_animtime 0.3
 set g_balance_porto_primary_speed 2000
 set g_balance_porto_primary_lifetime 30
+set g_balance_porto_secondary 0
+set g_balance_porto_secondary_refire 1.5
+set g_balance_porto_secondary_animtime 0.3
+set g_balance_porto_secondary_speed 2000
+set g_balance_porto_secondary_lifetime 30
 set g_balance_portal_health 200 // these get recharged whenever the portal is used
 set g_balance_portal_lifetime 15 // these get recharged whenever the portal is used
 // }}}
index 9777b904d78438b66489804962e25366be936354..2218946272587e8d0eeebe1db0b632bdfb031254 100644 (file)
@@ -627,6 +627,11 @@ set g_balance_porto_primary_refire 1.5
 set g_balance_porto_primary_animtime 0.2
 set g_balance_porto_primary_speed 2000
 set g_balance_porto_primary_lifetime 5
+set g_balance_porto_secondary 0
+set g_balance_porto_secondary_refire 1.5
+set g_balance_porto_secondary_animtime 0.2
+set g_balance_porto_secondary_speed 2000
+set g_balance_porto_secondary_lifetime 5
 set g_balance_portal_health 200 // these get recharged whenever the portal is used
 set g_balance_portal_lifetime 15 // these get recharged whenever the portal is used
 // }}}
index a15f060d0f269e15068c836360fb39b888181480..001e1d2dc544a7fbe767979ad678c41474364952 100644 (file)
@@ -627,6 +627,11 @@ set g_balance_porto_primary_refire 1.5
 set g_balance_porto_primary_animtime 0.3
 set g_balance_porto_primary_speed 1000
 set g_balance_porto_primary_lifetime 5
+set g_balance_porto_secondary 0
+set g_balance_porto_secondary_refire 1.5
+set g_balance_porto_secondary_animtime 0.3
+set g_balance_porto_secondary_speed 1000
+set g_balance_porto_secondary_lifetime 5
 set g_balance_portal_health 200 // these get recharged whenever the portal is used
 set g_balance_portal_lifetime 15 // these get recharged whenever the portal is used
 // }}}
index d42284a3066230bdd0658a16e6cdc48eeb45e3a2..24adbdc31c4858bf863abfcd73cd6b8e36d0b880 100644 (file)
@@ -627,6 +627,11 @@ set g_balance_porto_primary_refire 1.5
 set g_balance_porto_primary_animtime 0.3
 set g_balance_porto_primary_speed 1000
 set g_balance_porto_primary_lifetime 5
+set g_balance_porto_secondary 1
+set g_balance_porto_secondary_refire 1.5
+set g_balance_porto_secondary_animtime 0.3
+set g_balance_porto_secondary_speed 1000
+set g_balance_porto_secondary_lifetime 5
 set g_balance_portal_health 200 // these get recharged whenever the portal is used
 set g_balance_portal_lifetime 15 // these get recharged whenever the portal is used
 // }}}
index b23a1e8474575e2f3c4a11222bac087a5e16aa5f..c4fa9a2304600be594d84898df94cd9ef64151b6 100644 (file)
@@ -221,7 +221,7 @@ alias movetoauto "moveplayer ${1 ?} auto"
 //  Aliases for settemp subsystem. Warning: Do not touch. 
 //  Usage: settemp variable value, next map resets it.
 // =======================================================
-alias settemp "qc_cmd_svcl settemp $$*"
+alias settemp "qc_cmd_svcl settemp $*"
 alias settemp_restore "qc_cmd_svcl settemp_restore"
 
 
index b76b602b4c3e42a1dfb24a2ad0c024a24f378801..8b11b01b96e6a3dfeefa7d7dc0fc913e5cd0db7c 100644 (file)
@@ -1784,7 +1784,7 @@ seta cl_forceplayercolors 0 "make everyone look like your own color (requires se
 seta cl_forcemyplayermodel "" "set to the model file name you want to show yourself as (requires server to have sv_use_csqc_players 1; does not affect how enemies look with cl_forceplayermodels)"
 seta cl_forcemyplayerskin 0 "set to the skin number you want to show yourself as (requires server to have sv_use_csqc_players 1; does not affect how enemies look with cl_forceplayermodels)"
 seta cl_forcemyplayercolors 0 "set to the color value (encoding is same as _cl_color) for your own player model (requires server to have sv_use_csqc_players 1, and is ignored in teamplay; does not affect how enemies look with cl_forceplayermodels)"
-seta cl_predictionerrorcompensation 0 "try to compensate for prediction errors and reduce preceived lag (requires server to have sv_use_csqc_players 1)"
+seta cl_movement_errorcompensation 1 "try to compensate for prediction errors and reduce preceived lag (requires server to have sv_use_csqc_players 1)"
 
 // debug cvars for keyhunt attaching
 set _angles "0 0 0"
index e4519b2b3d93fef8061208aaf78ec95c1d2b1773..9bd8e8c8a35807e0bd5cab007646136a3159edbb 100644 (file)
@@ -272,4 +272,8 @@ float serverflags;
 
 float uid2name_dialog;
 
-.float csqcmodel_isdead; // used by shownames and miscfunctions (float getplayerisdead(float) {}) to know when a player is dead
\ No newline at end of file
+.float csqcmodel_isdead; // used by shownames and miscfunctions (float getplayerisdead(float) {}) to know when a player is dead
+
+#define player_currententnum (spectatee_status > 0 ? spectatee_status : player_localnum + 1)
+
+float g_balance_porto_secondary;
index 81c1369a0e61e45b660d73dd82130eeaf33388c7..d09f5ebb24edc94fdf9158c1b371d6fe71d1c0cf 100644 (file)
@@ -881,6 +881,7 @@ void Ent_Init()
        hagar_maxrockets = ReadByte();
 
        g_trueaim_minrange = ReadCoord();
+       g_balance_porto_secondary = ReadByte();
 
        if(!postinit)
                PostInit();
index afccaf45dc6ab6f217fb0503381ed1ca2a17148b..82cff78234d316c953e7f7d947e5de8508034c79 100644 (file)
@@ -12,6 +12,8 @@ void Porto_Draw()
 
        if(activeweapon != WEP_PORTO || spectatee_status || gametype == MAPINFO_TYPE_NEXBALL)
                return;
+       if(g_balance_porto_secondary)
+               return;
        if(intermission == 1)
                return;
        if(intermission == 2)
index 1d0ad220022b8f1cf33cbb6282d01863cd303c59..02558c7ea5977809d47e7e078d09743d63cfc7e5 100644 (file)
@@ -2781,6 +2781,7 @@ void HUD_Score(void)
        string sign;
        vector distribution_color;
        entity tm, pl, me;
+
 #ifdef COMPAT_XON050_ENGINE
        me = (spectatee_status > 0) ? playerslots[spectatee_status - 1] : playerslots[player_localentnum - 1];
 #else
index 40ba3f7a9f73c4d0a283dc6578b54a396211f00e..3c75b399f7b31a310979443620a048a6c1301655 100644 (file)
@@ -20,7 +20,7 @@
  * IN THE SOFTWARE.
  */
 
-var float autocvar_cl_predictionerrorcompensation = 0;
+var float autocvar_cl_movement_errorcompensation = 0;
 
 // engine stuff
 .float pmove_flags;
@@ -54,23 +54,36 @@ vector CSQCPlayer_GetPredictionErrorV()
        return csqcplayer_predictionerrorv * (csqcplayer_predictionerrortime - time) * csqcplayer_predictionerrorfactor;
 }
 
-void CSQCPlayer_SetPredictionError(vector o, vector v)
+void CSQCPlayer_SetPredictionError(vector o, vector v, float onground_diff)
 {
-       if(!autocvar_cl_predictionerrorcompensation)
+       // error too big to compensate, we LIKELY hit a teleport or a
+       // jumppad, or it's a jump time disagreement that'll get fixed
+       // next frame
+
+       // FIXME we sometimes have disagreement in order of jump velocity. Do not act on them!
+       /*
+       // commented out as this one did not help
+       if(onground_diff)
        {
-               csqcplayer_predictionerrorfactor = 0;
+               print(sprintf("ONGROUND MISMATCH: %d x=%v v=%v\n", onground_diff, o, v));
+               return;
+       }
+       */
+       if(vlen(o) > 32 || vlen(v) > 192)
+       {
+               //print(sprintf("TOO BIG: x=%v v=%v\n", o, v));
                return;
        }
 
-       // error too big to compensate, we LIKELY hit a teleport or a
-       // jumppad, or it's a jump time disagreement that'll get fixed
-       // next frame
-       if(vlen(o) > 32 || vlen(v) > 128)
+       if(!autocvar_cl_movement_errorcompensation)
+       {
+               csqcplayer_predictionerrorfactor = 0;
                return;
+       }
 
        csqcplayer_predictionerroro = CSQCPlayer_GetPredictionErrorO() + o;
        csqcplayer_predictionerrorv = CSQCPlayer_GetPredictionErrorV() + v;
-       csqcplayer_predictionerrorfactor = autocvar_cl_predictionerrorcompensation / ticrate;
+       csqcplayer_predictionerrorfactor = autocvar_cl_movement_errorcompensation / ticrate;
        csqcplayer_predictionerrortime = time + 1.0 / csqcplayer_predictionerrorfactor;
 }
 
@@ -210,7 +223,7 @@ void CSQCPlayer_SetCamera()
                                v = v0;
                                csqcplayer_status = CSQCPLAYERSTATUS_PREDICTED;
                                CSQCPlayer_PredictTo(servercommandframe + 1, FALSE);
-                               CSQCPlayer_SetPredictionError(self.origin - o, self.velocity - v);
+                               CSQCPlayer_SetPredictionError(self.origin - o, self.velocity - v, pmove_onground - !!(self.pmove_flags & PMF_ONGROUND));
                                self.origin = o;
                                self.velocity = v;
 
index 9b6b7c33037fffef60d3f07564eb06f7405f8105..9f489a84506731fbcf2674ed47f453ef86bd6153 100644 (file)
@@ -24,6 +24,10 @@ void XonoticNetworkSettingsTab_fill(entity me)
 
        me.TR(me);
                me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "cl_movement", _("Client-side movement prediction")));
+       me.TR(me);
+               me.TDempty(me, 0.2);
+               me.TD(me, 1, 2.8, e = makeXonoticCheckBox(0, "cl_movement_errorcompensation", _("Movement error compensation")));
+                       setDependent(e, "cl_movement", 1, 1);
        me.TR(me);
                //me.TD(me, 1, 3, e = makeXonoticCheckBox(1, "cl_nolerp", _("Network update smoothing")));
        me.TR(me);
index 1111909b3ea194fcbb6df6ddc698912dcaebff24..bdf262cfea962e35341e271fed5d58e96e2b9c3b 100644 (file)
@@ -561,6 +561,11 @@ float autocvar_g_balance_porto_primary_animtime;
 float autocvar_g_balance_porto_primary_lifetime;
 float autocvar_g_balance_porto_primary_refire;
 float autocvar_g_balance_porto_primary_speed;
+float autocvar_g_balance_porto_secondary;
+float autocvar_g_balance_porto_secondary_animtime;
+float autocvar_g_balance_porto_secondary_lifetime;
+float autocvar_g_balance_porto_secondary_refire;
+float autocvar_g_balance_porto_secondary_speed;
 float autocvar_g_balance_powerup_invincible_takedamage;
 float autocvar_g_balance_powerup_invincible_time;
 float autocvar_g_balance_powerup_strength_damage;
index 2fdbdd3f4fe8c0a1a7ea237ec1725cf3d1459c72..63a6cb336d4054c90d3aa0283be057005139bed0 100644 (file)
@@ -1106,7 +1106,7 @@ float bot_cmd_debug_assert_canfire()
                if(f)
                {
                        self.colormod = '0 8 8';
-                       print("Bot wants to fire, inhibited by weaponentity state\n");
+                       print("Bot ", self.netname, " using ", self.weaponname, " wants to fire, inhibited by weaponentity state\n");
                }
        }
        else if(ATTACK_FINISHED(self) > time)
@@ -1114,7 +1114,7 @@ float bot_cmd_debug_assert_canfire()
                if(f)
                {
                        self.colormod = '8 0 8';
-                       print("Bot wants to fire, inhibited by ATTACK_FINISHED\n");
+                       print("Bot ", self.netname, " using ", self.weaponname, " wants to fire, inhibited by ATTACK_FINISHED (", ftos(ATTACK_FINISHED(self) - time), " seconds left)\n");
                }
        }
        else if(self.tuba_note)
@@ -1122,7 +1122,7 @@ float bot_cmd_debug_assert_canfire()
                if(f)
                {
                        self.colormod = '8 0 0';
-                       print("Bot wants to fire, bot still has an active tuba note\n");
+                       print("Bot ", self.netname, " using ", self.weaponname, " wants to fire, bot still has an active tuba note\n");
                }
        }
        else
@@ -1130,7 +1130,7 @@ float bot_cmd_debug_assert_canfire()
                if(!f)
                {
                        self.colormod = '8 8 0';
-                       print("Bot thinks it has fired, but apparently did not\n");
+                       print("Bot ", self.netname, " using ", self.weaponname, " thinks it has fired, but apparently did not; ATTACK_FINISHED says ", ftos(ATTACK_FINISHED(self) - time), " seconds left\n");
                }
        }
 
index ebf4abc4b4d85bc7181fd408221f03c75df988a4..ba4249744eb90862ab299b96b151ebdad0cd41da 100644 (file)
@@ -967,6 +967,7 @@ float ClientInit_SendEntity(entity to, float sf)
        WriteByte(MSG_ENTITY, autocvar_g_balance_minelayer_limit); // minelayer max mines
        WriteByte(MSG_ENTITY, autocvar_g_balance_hagar_secondary_load_max); // hagar max loadable rockets
        WriteCoord(MSG_ENTITY, autocvar_g_trueaim_minrange);
+       WriteByte(MSG_ENTITY, autocvar_g_balance_porto_secondary);
        return TRUE;
 }
 
@@ -1895,7 +1896,8 @@ void player_powerups (void)
                }
                if (self.items & IT_SUPERWEAPON)
                {
-                       self.effects = self.effects | EF_RED;
+                       //if(W_WeaponBit(self.weapon) & WEPBIT_SUPERWEAPONS)
+                       //      self.effects = self.effects | EF_RED;
                        if (!(self.weapons & WEPBIT_SUPERWEAPONS))
                        {
                                self.superweapons_finished = 0;
@@ -1917,15 +1919,22 @@ void player_powerups (void)
                                }
                        }
                }
-               else
+               else if(self.weapons & WEPBIT_SUPERWEAPONS)
                {
-                       if (time < self.superweapons_finished)
+                       if (time < self.superweapons_finished || (self.items & IT_UNLIMITED_SUPERWEAPONS))
                        {
                                self.items = self.items | IT_SUPERWEAPON;
                                sprint(self, "^3You now have a superweapon\n");
                        }
                        else
+                       {
+                               self.superweapons_finished = 0;
                                self.weapons &~= WEPBIT_SUPERWEAPONS; // just in case
+                       }
+               }
+               else
+               {
+                       self.superweapons_finished = 0;
                }
        }
        
index bf50c9d3cada867c7d4d2034244a5a75a0d8caa4..76af253dca5c0f3bd3b5785134f9c378687fdf8c 100644 (file)
@@ -645,11 +645,7 @@ float Portal_SpawnInPortalAtTrace(entity own, vector dir, float portal_id_val)
 
        portal = Portal_Spawn(own, org, ang);
        if(!portal)
-       {
-               // if(!own.portal_out || own.portal_out.portal_id == portal_id_val)
-                       Portal_ClearAll_PortalsOnly(own);
                return 0;
-       }
 
        portal.portal_id = portal_id_val;
        Portal_SetInPortal(own, portal);
@@ -669,11 +665,7 @@ float Portal_SpawnOutPortalAtTrace(entity own, vector dir, float portal_id_val)
 
        portal = Portal_Spawn(own, org, ang);
        if(!portal)
-       {
-               // if(!own.portal_in || own.portal_in.portal_id == portal_id_val)
-                       Portal_ClearAll_PortalsOnly(own);
                return 0;
-       }
 
        portal.portal_id = portal_id_val;
        Portal_SetOutPortal(own, portal);
index b2d282cf2b137e68978e0a64c08b4e0874d3ee3b..cd0bc0408097b737ee604ef1bd9aa654232ce42a 100644 (file)
@@ -8,3 +8,4 @@ float Portal_SpawnInPortalAtTrace(entity own, vector dir, float id);
 void Portal_ClearWithID(entity own, float id);
 
 vector Portal_ApplyTransformToPlayerAngle(vector transform, vector vangle);
+void Portal_ClearAll_PortalsOnly(entity own);
index bf17e3faf0ece436d0508dc64c4a9bc517103566..cce0aab32bb5f062ca66a853f2a7996dfafef5c4 100644 (file)
@@ -200,6 +200,7 @@ void trigger_push_touch()
                                other.lastteleporttime = time;
 
                        if (!other.animstate_override)
+                       if (other.deadflag == DEAD_NO)
                        {
                                if (other.crouch)
                                        setanim(other, other.anim_duckjump, FALSE, TRUE, TRUE);
index 7f5e80e4b29ae339be8003fed9e7fe29a61a053c..777820e48c261c26f18cc8c9b093b42abfa71a85 100644 (file)
@@ -521,10 +521,12 @@ void racer_exit(float eject)
            spot = vehicles_findgoodexit(spot);
            setorigin(self.owner , spot);
            self.owner.velocity = (v_up + v_forward * 0.25) * 750;
+           self.owner.oldvelocity = self.owner.velocity;
        }
        else
        {
-           self.owner.velocity = '0 0 0';
+           self.owner.velocity = normalize(self.velocity) * autocvar_sv_maxairspeed;
+           self.owner.oldvelocity = self.owner.velocity;
            spot = self.origin - v_forward * 200 + '0 0 64';
            spot = vehicles_findgoodexit(spot);
            setorigin(self.owner , spot);
index 57221afe2a169ce453d118d8a2010cdf57c0555e..ab0d388155bf7ca8d17c674d31507770656533e4 100644 (file)
@@ -244,10 +244,12 @@ void raptor_exit(float eject)
            spot = vehicles_findgoodexit(spot);
            setorigin(self.owner , spot);
            self.owner.velocity = (v_up + v_forward * 0.25) * 750;
+           self.owner.oldvelocity = self.owner.velocity;
        }
        else
        {
-           self.owner.velocity = '0 0 0';
+           self.owner.velocity = normalize(self.velocity) * autocvar_sv_maxairspeed;
+           self.owner.oldvelocity = self.owner.velocity;
            spot = self.origin - v_forward * 200 + '0 0 64';
            spot = vehicles_findgoodexit(spot);
            setorigin(self.owner , spot);
index 3d809e2425714cf3ce3b5fc74290223681d46aae..913e3316f012ecd2280611c1a2a05c6c6c4b0186 100644 (file)
@@ -450,10 +450,12 @@ void spiderbot_exit(float eject)
            spot = vehicles_findgoodexit(spot);
            setorigin(self.owner , spot);
            self.owner.velocity = (v_up + v_forward * 0.25) * 750;
+           self.owner.oldvelocity = self.owner.velocity;
        }
        else
        {
-           self.owner.velocity = '0 0 0';
+           self.owner.velocity = normalize(self.velocity) * autocvar_sv_maxairspeed;
+           self.owner.oldvelocity = self.owner.velocity;           
            spot = self.origin - v_forward * 200 + '0 0 64';
            spot = vehicles_findgoodexit(spot);
            setorigin(self.owner , spot);
index fb8e427cc141c67d64dee696496b431fa72549b1..c919c497e661f7a098eb44199efa7cdfdb6c6c26 100644 (file)
@@ -1,5 +1,5 @@
 #ifdef REGISTER_WEAPON
-REGISTER_WEAPON(PORTO, w_porto, 0, 0, WEP_TYPE_OTHER, 0, "porto" , "porto", _("Port-O-Launch"))
+REGISTER_WEAPON(PORTO, w_porto, 0, 0, WEP_TYPE_OTHER | WEP_FLAG_SUPERWEAPON, 0, "porto" , "porto", _("Port-O-Launch"))
 #else
 #ifdef SVQC
 .entity porto_current;
@@ -29,10 +29,14 @@ void W_Porto_Fail (float failhard)
        }
 
        // no portals here!
-       Portal_ClearWithID(self.realowner, self.portal_id);
+       if(self.cnt < 0)
+       {
+               Portal_ClearWithID(self.realowner, self.portal_id);
+       }
+
        self.realowner.porto_current = world;
 
-       if(!failhard && self.realowner.playerid == self.playerid && self.realowner.deadflag == DEAD_NO && !(self.realowner.weapons & WEPBIT_PORTO))
+       if(self.cnt < 0 && !failhard && self.realowner.playerid == self.playerid && self.realowner.deadflag == DEAD_NO && !(self.realowner.weapons & WEPBIT_PORTO))
        {
                setsize (self, '-16 -16 0', '16 16 32');
                setorigin(self, self.origin + trace_plane_normal);
@@ -77,6 +81,7 @@ void W_Porto_Touch (void)
        vector norm;
 
        // do not use PROJECTILE_TOUCH here
+       // FIXME but DO handle warpzones!
 
        if(other.classname == "portal")
                return; // handled by the portal
@@ -109,6 +114,42 @@ void W_Porto_Touch (void)
        {
                sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM);
                W_Porto_Fail(0);
+               if(self.cnt < 0)
+                       Portal_ClearAll_PortalsOnly(self.realowner);
+       }
+       else if(self.cnt == 0)
+       {
+               // in-portal only
+               if(Portal_SpawnInPortalAtTrace(self.realowner, self.right_vector, self.portal_id))
+               {
+                       sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTN_NORM);
+                       trace_plane_normal = norm;
+                       centerprint(self.realowner, "^1In^7-portal created.");
+                       W_Porto_Success();
+               }
+               else
+               {
+                       sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM);
+                       trace_plane_normal = norm;
+                       W_Porto_Fail(0);
+               }
+       }
+       else if(self.cnt == 1)
+       {
+               // out-portal only
+               if(Portal_SpawnOutPortalAtTrace(self.realowner, self.right_vector, self.portal_id))
+               {
+                       sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTN_NORM);
+                       trace_plane_normal = norm;
+                       centerprint(self.realowner, "^1Out^7-portal created.");
+                       W_Porto_Success();
+               }
+               else
+               {
+                       sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM);
+                       trace_plane_normal = norm;
+                       W_Porto_Fail(0);
+               }
        }
        else if(self.effects & EF_RED)
        {
@@ -126,6 +167,7 @@ void W_Porto_Touch (void)
                {
                        sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM);
                        trace_plane_normal = norm;
+                       Portal_ClearAll_PortalsOnly(self.realowner);
                        W_Porto_Fail(0);
                }
        }
@@ -143,23 +185,29 @@ void W_Porto_Touch (void)
                        else
                        {
                                sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM);
+                               Portal_ClearAll_PortalsOnly(self.realowner);
                                W_Porto_Fail(0);
                        }
                }
                else
                {
                        sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM);
+                       Portal_ClearAll_PortalsOnly(self.realowner);
                        W_Porto_Fail(0);
                }
        }
 }
 
-void W_Porto_Attack (void)
+void W_Porto_Attack (float type)
 {
        entity gren;
 
-       if not(self.items & IT_UNLIMITED_SUPERWEAPONS)
-               self.weapons = self.weapons - (self.weapons & WEPBIT_PORTO);
+       if(type == -1)
+       {
+               if not(self.items & IT_UNLIMITED_SUPERWEAPONS)
+                       self.weapons = self.weapons - (self.weapons & WEPBIT_PORTO);
+       }
+
        W_SetupShot (self, FALSE, 4, "porto/fire.wav", CH_WEAPON_A, 0);
        // always shoot from the eye
        w_shotdir = v_forward;
@@ -168,6 +216,7 @@ void W_Porto_Attack (void)
        //pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
 
        gren = spawn ();
+       gren.cnt = type;
        gren.owner = gren.realowner = self;
        gren.playerid = self.playerid;
        gren.classname = "porto";
@@ -180,13 +229,27 @@ void W_Porto_Attack (void)
        setorigin(gren, w_shotorg);
        setsize(gren, '0 0 0', '0 0 0');
 
-       gren.nextthink = time + autocvar_g_balance_porto_primary_lifetime;
+       if(type > 0)
+               gren.nextthink = time + autocvar_g_balance_porto_secondary_lifetime;
+       else
+               gren.nextthink = time + autocvar_g_balance_porto_primary_lifetime;
        gren.think = W_Porto_Think;
        gren.touch = W_Porto_Touch;
-       if(self.items & IT_STRENGTH)
-               W_SetupProjectileVelocity(gren, autocvar_g_balance_porto_primary_speed * autocvar_g_balance_powerup_strength_force, 0);
+
+       if(type > 0)
+       {
+               if(self.items & IT_STRENGTH)
+                       W_SetupProjectileVelocity(gren, autocvar_g_balance_porto_secondary_speed * autocvar_g_balance_powerup_strength_force, 0);
+               else
+                       W_SetupProjectileVelocity(gren, autocvar_g_balance_porto_secondary_speed, 0);
+       }
        else
-               W_SetupProjectileVelocity(gren, autocvar_g_balance_porto_primary_speed, 0);
+       {
+               if(self.items & IT_STRENGTH)
+                       W_SetupProjectileVelocity(gren, autocvar_g_balance_porto_primary_speed * autocvar_g_balance_powerup_strength_force, 0);
+               else
+                       W_SetupProjectileVelocity(gren, autocvar_g_balance_porto_primary_speed, 0);
+       }
 
        gren.angles = vectoangles (gren.velocity);
        gren.flags = FL_PROJECTILE;
@@ -199,7 +262,10 @@ void W_Porto_Attack (void)
 
        gren.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP;
 
-       CSQCProjectile(gren, TRUE, PROJECTILE_PORTO_RED, TRUE);
+       if(type > 0)
+               CSQCProjectile(gren, TRUE, PROJECTILE_PORTO_BLUE, TRUE);
+       else
+               CSQCProjectile(gren, TRUE, PROJECTILE_PORTO_RED, TRUE);
 
        other = gren; MUTATOR_CALLHOOK(EditProjectile);
 }
@@ -219,41 +285,65 @@ float w_porto(float req)
        {
                self.BUTTON_ATCK = FALSE;
                self.BUTTON_ATCK2 = FALSE;
-               if(bot_aim(autocvar_g_balance_porto_primary_speed, 0, autocvar_g_balance_grenadelauncher_primary_lifetime, FALSE))
-                       self.BUTTON_ATCK = TRUE;
+               if(!autocvar_g_balance_porto_secondary)
+                       if(bot_aim(autocvar_g_balance_porto_primary_speed, 0, autocvar_g_balance_grenadelauncher_primary_lifetime, FALSE))
+                               self.BUTTON_ATCK = TRUE;
        }
        else if (req == WR_THINK)
        {
-               if(self.porto_v_angle_held)
+               if(autocvar_g_balance_porto_secondary)
                {
-                       if(!self.BUTTON_ATCK2)
+                       if (self.BUTTON_ATCK)
+                       if (!self.porto_current)
+                       if (!self.porto_forbidden)
+                       if (weapon_prepareattack(0, autocvar_g_balance_porto_primary_refire))
                        {
-                               self.porto_v_angle_held = 0;
+                               W_Porto_Attack(0);
+                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_porto_primary_animtime, w_ready);
+                       }
 
-                               ClientData_Touch(self);
+                       if (self.BUTTON_ATCK2)
+                       if (!self.porto_current)
+                       if (!self.porto_forbidden)
+                       if (weapon_prepareattack(1, autocvar_g_balance_porto_secondary_refire))
+                       {
+                               W_Porto_Attack(1);
+                               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_porto_secondary_animtime, w_ready);
                        }
                }
                else
                {
-                       if(self.BUTTON_ATCK2)
+                       if(self.porto_v_angle_held)
                        {
-                               self.porto_v_angle = self.v_angle;
-                               self.porto_v_angle_held = 1;
+                               if(!self.BUTTON_ATCK2)
+                               {
+                                       self.porto_v_angle_held = 0;
 
-                               ClientData_Touch(self);
+                                       ClientData_Touch(self);
+                               }
+                       }
+                       else
+                       {
+                               if(self.BUTTON_ATCK2)
+                               {
+                                       self.porto_v_angle = self.v_angle;
+                                       self.porto_v_angle_held = 1;
+
+                                       ClientData_Touch(self);
+                               }
+                       }
+                       v_angle_save = self.v_angle;
+                       if(self.porto_v_angle_held)
+                               makevectors(self.porto_v_angle); // override the previously set angles
+
+                       if (self.BUTTON_ATCK)
+                       if (!self.porto_current)
+                       if (!self.porto_forbidden)
+                       if (weapon_prepareattack(0, autocvar_g_balance_porto_primary_refire))
+                       {
+                               W_Porto_Attack(-1);
+                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_porto_primary_animtime, w_ready);
                        }
-               }
-               v_angle_save = self.v_angle;
-               if(self.porto_v_angle_held)
-                       makevectors(self.porto_v_angle); // override the previously set angles
-
-               if (self.BUTTON_ATCK)
-               if (!self.porto_current)
-               if (!self.porto_forbidden)
-               if (weapon_prepareattack(0, autocvar_g_balance_porto_primary_refire))
-               {
-                       W_Porto_Attack();
-                       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_porto_primary_animtime, w_ready);
                }
        }
        else if (req == WR_PRECACHE)