]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge remote branch 'origin/master' into samual/hud_updates
authorSamual <samual@xonotic.org>
Mon, 24 Oct 2011 20:30:16 +0000 (16:30 -0400)
committerSamual <samual@xonotic.org>
Mon, 24 Oct 2011 20:30:16 +0000 (16:30 -0400)
defaultXonotic.cfg
models/keys/key.md3 [new file with mode: 0644]
models/keys/key.tga [new file with mode: 0644]
qcsrc/server/cl_client.qc
qcsrc/server/clientcommands.qc
qcsrc/server/defs.qh
qcsrc/server/item_key.qc [new file with mode: 0644]
qcsrc/server/item_key.qh [new file with mode: 0644]
qcsrc/server/mutators/base.qh
qcsrc/server/progs.src
qcsrc/server/t_plats.qc

index a13177b020d2fec74e2cbf23520164deb001ff4c..23866b0e7134c79b497a30a2c742d68e85e5aef8 100644 (file)
@@ -1591,6 +1591,7 @@ set capturelimit 0
 prvm_leaktest_ignore_classnames "ctf_team dom_team tdm_team"
 
 sv_allowdownloads_inarchive 1 // for csprogs.dat
+sv_allowdownloads 0 // download protocol is evil
 
 set g_jump_grunt 0     "Do you make a grunting noise every time you jump? Is it the same grunting noise every time?"
 
diff --git a/models/keys/key.md3 b/models/keys/key.md3
new file mode 100644 (file)
index 0000000..097c90c
Binary files /dev/null and b/models/keys/key.md3 differ
diff --git a/models/keys/key.tga b/models/keys/key.tga
new file mode 100644 (file)
index 0000000..5e70fa3
Binary files /dev/null and b/models/keys/key.tga differ
index 299df0d082ef84ee9d06d001f4d3b7f29fc77046..de4c7431d5aafad3db68fe68f1da5e7ad5208070 100644 (file)
@@ -861,6 +861,9 @@ void PutClientInServer (void)
                WriteByte(MSG_ONE, SVC_SETVIEW);
                WriteEntity(MSG_ONE, self);
        }
+       
+       // reset player keys
+       self.itemkeys = 0;
 
        // player is dead and becomes observer
        // FIXME fix LMS scoring for new system
index 2ffa4afd9007578ba97f864e7f41621d02f328db..cfbc5ff18427f68e033e26bb70e5fc53afa447ad 100644 (file)
@@ -145,33 +145,34 @@ float cmd_floodcheck()
 
 .float checkfail;
 void SV_ParseClientCommand(string s) {
-       string cmd;
-       float tokens;
        float i;
        entity e;
 
-       tokens = tokenize_console(s);
-
-       cmd = strtolower(argv(0));
-       if(cmd != "reportcvar")
-       if(cmd != "sentcvar")
-       if(cmd != "pause")
-       if(cmd != "prespawn")
-       if(cmd != "spawn")
-       if(cmd != "begin")
+       cmd_argc = tokenize_console(s);
+       cmd_string = s;
+       cmd_name = strtolower(argv(0));
+       if(cmd_name != "reportcvar")
+       if(cmd_name != "sentcvar")
+       if(cmd_name != "pause")
+       if(cmd_name != "prespawn")
+       if(cmd_name != "spawn")
+       if(cmd_name != "begin")
        {
                if(cmd_floodcheck())
                        return;
        }
 
+       if(MUTATOR_CALLHOOK(SV_ParseClientCommand))
+               return; // already handled
+       
        if(GameCommand_Vote(s, self)) {
                return;
        } else if(GameCommand_MapVote(argv(0))) {
                return;
-       } else if(cmd == "checkfail") {
+       } else if(cmd_name == "checkfail") {
                print(sprintf("CHECKFAIL: %s (%s) epically failed check %s\n", self.netname, self.netaddress, substring(s, argv_start_index(1), argv_end_index(-1) - argv_start_index(1))));
                self.checkfail = 1;
-       } else if(cmd == "autoswitch") {
+       } else if(cmd_name == "autoswitch") {
                // be backwards compatible with older clients (enabled)
                self.autoswitch = ("0" != argv(1));
                string autoswitchmsg;
@@ -181,7 +182,7 @@ void SV_ParseClientCommand(string s) {
                        autoswitchmsg = "off";
                }
                sprint(self, strcat("^1autoswitch turned ", autoswitchmsg, "\n"));
-       } else if(cmd == "clientversion") {
+       } else if(cmd_name == "clientversion") {
                if not(self.flags & FL_CLIENT)
                        return;
                if (argv(1) == "$gameversion") {
@@ -201,21 +202,21 @@ void SV_ParseClientCommand(string s) {
                        self.classname = "observer";
                        stuffcmd(self,"menu_showteamselect\n");
                }
-       } else if(cmd == "reportcvar") { // old system
+       } else if(cmd_name == "reportcvar") { // old system
                if(substring(argv(2), 0, 1) == "$") // undefined cvar: use the default value on the server then
                {
                        s = strcat(substring(s, argv_start_index(0), argv_end_index(1) - argv_start_index(0)), " \"", cvar_defstring(argv(1)), "\"");
-                       tokens = tokenize_console(s);
+                       cmd_argc = tokenize_console(s);
                }
                GetCvars(1);
-       } else if(cmd == "sentcvar") { // new system
-               if(tokens == 2) // undefined cvar: use the default value on the server then
+       } else if(cmd_name == "sentcvar") { // new system
+               if(cmd_argc == 2) // undefined cvar: use the default value on the server then
                {
                        s = strcat(substring(s, argv_start_index(0), argv_end_index(1) - argv_start_index(0)), " \"", cvar_defstring(argv(1)), "\"");
-                       tokens = tokenize_console(s);
+                       cmd_argc = tokenize_console(s);
                }
                GetCvars(1);
-       } else if(cmd == "spectate") {
+       } else if(cmd_name == "spectate") {
                if(cmd_floodcheck())
                        return;
                if not(self.flags & FL_CLIENT)
@@ -244,7 +245,7 @@ void SV_ParseClientCommand(string s) {
                        sprint(self, "WARNING: you will spectate in the next round.\n");
                        self.caplayer = 0;
                }
-       } else if(cmd == "join") {
+       } else if(cmd_name == "join") {
                if not(self.flags & FL_CLIENT)
                        return;
                if(!g_arena)
@@ -265,7 +266,7 @@ void SV_ParseClientCommand(string s) {
                                centerprint(self, PREVENT_JOIN_TEXT);
                        }
                }
-       } else if( cmd == "selectteam" ) {
+       } else if( cmd_name == "selectteam" ) {
                if not(self.flags & FL_CLIENT)
                        return;
                if( !teamplay ) {
@@ -301,7 +302,7 @@ void SV_ParseClientCommand(string s) {
                } else {
                        sprint( self, strcat( "selectteam none/red/blue/yellow/pink/auto - \"", argv(1), "\" not recognised\n" ) );
                }
-       } else if(cmd == "ready") {
+       } else if(cmd_name == "ready") {
                if not(self.flags & FL_CLIENT)
                        return;
 
@@ -328,54 +329,54 @@ void SV_ParseClientCommand(string s) {
                                sprint(self, "^1Game has already been restarted\n");
                        }
                }
-       } else if(cmd == "maplist") {
+       } else if(cmd_name == "maplist") {
                sprint(self, maplist_reply);
-       } else if(cmd == "lsmaps") {
+       } else if(cmd_name == "lsmaps") {
                sprint(self, lsmaps_reply);
-       } else if(cmd == "lsnewmaps") {
+       } else if(cmd_name == "lsnewmaps") {
                sprint(self, lsnewmaps_reply);
-       } else if(cmd == "records") {
+       } else if(cmd_name == "records") {
                for(i = 0; i < 10; ++i)
                        sprint(self, records_reply[i]);
-       } else if(cmd == "ladder") {
+       } else if(cmd_name == "ladder") {
                sprint(self, ladder_reply);
-       } else if(cmd == "rankings") {
+       } else if(cmd_name == "rankings") {
                sprint(self, rankings_reply);
-       } else if(cmd == "voice") {
-               if(tokens >= 3)
+       } else if(cmd_name == "voice") {
+               if(cmd_argc >= 3)
                        VoiceMessage(argv(1), substring(s, argv_start_index(2), argv_end_index(-1) - argv_start_index(2)));
                else
                        VoiceMessage(argv(1), "");
-       } else if(cmd == "say") {
-               if(tokens >= 2)
+       } else if(cmd_name == "say") {
+               if(cmd_argc >= 2)
                        Say(self, FALSE, world, substring(s, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1);
                //clientcommand(self, formatmessage(s));
-       } else if(cmd == "say_team") {
-               if(tokens >= 2)
+       } else if(cmd_name == "say_team") {
+               if(cmd_argc >= 2)
                        Say(self, TRUE, world, substring(s, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1);
                //clientcommand(self, formatmessage(s));
-       } else if(cmd == "tell") {
-               e = GetCommandPlayerSlotTargetFromTokenizedCommand(tokens, 1);
-               if(e && tokens > ParseCommandPlayerSlotTarget_firsttoken)
+       } else if(cmd_name == "tell") {
+               e = GetCommandPlayerSlotTargetFromTokenizedCommand(cmd_argc, 1);
+               if(e && cmd_argc > ParseCommandPlayerSlotTarget_firsttoken)
                {
                        Say(self, FALSE, e, substring(s, argv_start_index(ParseCommandPlayerSlotTarget_firsttoken), argv_end_index(-1) - argv_start_index(ParseCommandPlayerSlotTarget_firsttoken)), TRUE);
                }
                else
                {
-                       if(tokens > ParseCommandPlayerSlotTarget_firsttoken)
+                       if(cmd_argc > ParseCommandPlayerSlotTarget_firsttoken)
                                trigger_magicear_processmessage_forallears(self, -1, world, substring(s, argv_start_index(ParseCommandPlayerSlotTarget_firsttoken), argv_end_index(-1) - argv_start_index(ParseCommandPlayerSlotTarget_firsttoken)));
                        sprint(self, "ERROR: usage: tell # playerid text...\n");
                }
                //clientcommand(self, formatmessage(s));
-       } else if(cmd == "info") {
-               cmd = cvar_string_builtin(strcat("sv_info_", argv(1))); // This needed fixed for the cvar check
-               if(cmd == "")
+       } else if(cmd_name == "info") {
+               cmd_name = cvar_string_builtin(strcat("sv_info_", argv(1))); // This needed fixed for the cvar check
+               if(cmd_name == "")
                        sprint(self, "ERROR: unsupported info command\n");
                else
-                       wordwrap_sprint(cmd, 1111);
-       } else if(cmd == "suggestmap") {
+                       wordwrap_sprint(cmd_name, 1111);
+       } else if(cmd_name == "suggestmap") {
                sprint(self, strcat(MapVote_Suggest(argv(1)), "\n"));
-       } else if(cmd == "timeout") {
+       } else if(cmd_name == "timeout") {
                if not(self.flags & FL_CLIENT)
                        return;
                if(autocvar_sv_timeout) {
@@ -388,42 +389,42 @@ void SV_ParseClientCommand(string s) {
                        else
                                sprint(self, "^7Error: only players can call a timeout!\n");
                }
-       } else if(cmd == "timein") {
+       } else if(cmd_name == "timein") {
                if not(self.flags & FL_CLIENT)
                        return;
                if(autocvar_sv_timeout) {
                        evaluateTimein();
                }
-       } else if(cmd == "teamstatus") {
+       } else if(cmd_name == "teamstatus") {
                Score_NicePrint(self);
-       } else if(cmd == "cvar_changes") {
+       } else if(cmd_name == "cvar_changes") {
                sprint(self, cvar_changes);
-       } else if(cmd == "cvar_purechanges") {
+       } else if(cmd_name == "cvar_purechanges") {
                sprint(self, cvar_purechanges);
-       } else if(CheatCommand(tokens)) {
+       } else if(CheatCommand(cmd_argc)) {
        } else {
 #if 0
                //if(ctf_clientcommand())
                //      return;
                // grep for Cmd_AddCommand_WithClientCommand to find them all
-               if(cmd != "status")
-               //if(cmd != "say") // handled above
-               //if(cmd != "say_team") // handled above
-               if(cmd != "kill")
-               if(cmd != "pause")
-               if(cmd != "ping")
-               if(cmd != "name")
-               if(cmd != "color")
-               if(cmd != "rate")
-               if(cmd != "pmodel")
-               if(cmd != "playermodel")
-               if(cmd != "playerskin")
-               if(cmd != "prespawn")
-               if(cmd != "spawn")
-               if(cmd != "begin")
-               if(cmd != "pings")
-               if(cmd != "sv_startdownload")
-               if(cmd != "download")
+               if(cmd_name != "status")
+               //if(cmd_name != "say") // handled above
+               //if(cmd_name != "say_team") // handled above
+               if(cmd_name != "kill")
+               if(cmd_name != "pause")
+               if(cmd_name != "ping")
+               if(cmd_name != "name")
+               if(cmd_name != "color")
+               if(cmd_name != "rate")
+               if(cmd_name != "pmodel")
+               if(cmd_name != "playermodel")
+               if(cmd_name != "playerskin")
+               if(cmd_name != "prespawn")
+               if(cmd_name != "spawn")
+               if(cmd_name != "begin")
+               if(cmd_name != "pings")
+               if(cmd_name != "sv_startdownload")
+               if(cmd_name != "download")
                {
                        print("WARNING: Invalid clientcommand by ", self.netname, ": ", s, "\n");
                        return;
@@ -431,7 +432,7 @@ void SV_ParseClientCommand(string s) {
 #endif
 
                if(self.jointime > 0 && time > self.jointime + 10 && time > self.nickspamtime) // allow any changes in the first 10 seconds since joining
-               if(cmd == "name" || cmd == "playermodel") // TODO also playerskin and color?
+               if(cmd_name == "name" || cmd_name == "playermodel") // TODO also playerskin and color?
                {
                        if(self.nickspamtime == 0 || time > self.nickspamtime + autocvar_g_nick_flood_timeout)
                                // good, no serious flood
index 5e5c8c7ba6f089720458b5526292b18f0ed5c9b9..b84d03164d312d8c2b3b410bb8c86a602bb1fca0 100644 (file)
@@ -246,6 +246,12 @@ float alreadychangedlevel;
 
 .float runes;
 
+// Keys player is holding
+.float itemkeys;
+// message delay for func_door locked by keys and key locks
+// this field is used on player entities
+.float key_door_messagetime;
+
 
 .float version;
 
@@ -663,3 +669,4 @@ float serverflags;
 .float misc_bulletcounter;     // replaces uzi & hlac bullet counter.
 
 void PlayerUseKey();
+
diff --git a/qcsrc/server/item_key.qc b/qcsrc/server/item_key.qc
new file mode 100644 (file)
index 0000000..e39f281
--- /dev/null
@@ -0,0 +1,423 @@
+/*
+TODO:
+- add an unlock sound (here to trigger_keylock and to func_door)
+- display available keys on the HUD
+- make more tests
+- think about adding NOT_EASY/NOT_NORMAL/NOT_HARD for Q1 compatibility
+- should keys have a trigger?
+*/
+
+float item_keys_usekey(entity l, entity p) {
+       float valid = l.itemkeys & p.itemkeys;
+       
+       if not(valid) {
+               // other has none of the needed keys
+               return FALSE;
+       } else if (l.itemkeys == valid) {
+               // ALL needed keys were given
+               l.itemkeys = 0;
+               return TRUE;
+       } else {
+               // only some of the needed keys were given
+               l.itemkeys &~= valid;
+               return TRUE;
+       }
+}
+
+string item_keys_keylist(float keylist) {
+       float base, l;
+       string n;
+       
+       // no keys
+       if not(keylist)
+               return "";
+       
+       // one key
+       if ((keylist & (keylist-1)) != 0)
+               return strcat("the ", item_keys_names[lowestbit(keylist)]);
+       
+       while (keylist) {
+               l = lowestbit(keylist);
+               if (n)
+                       n = strcat(n, ", the ", item_keys_names[base + l]);
+               else
+                       n = strcat("the ", item_keys_names[base + l]);
+               
+               keylist = bitshift(keylist,  -(l + 1));
+               base+= l + 1;
+       }
+       
+       return n;
+}
+
+
+/*
+================================
+item_key
+================================
+*/
+
+/**
+ * Key touch handler.
+ */
+void item_key_touch(void) {
+       if (other.classname != "player")
+               return;
+               
+       // player already picked up this key
+       if (other.itemkeys & self.itemkeys)
+               return;
+       
+       other.itemkeys |= self.itemkeys;
+       play2(other, self.noise);
+       
+       centerprint(other, self.message);
+};
+
+/**
+ * Spawn a key with given model, key code and color.
+ */
+void spawn_item_key() {
+       precache_model(self.model);
+       
+       if (self.spawnflags & 1) // FLOATING
+               self.noalign = 1;
+       
+       if (self.noalign)
+               self.movetype = MOVETYPE_NONE;
+       else
+               self.movetype = MOVETYPE_TOSS;
+               
+       precache_sound(self.noise);
+               
+       self.mdl = self.model;
+       self.effects = EF_LOWPRECISION;
+       setmodel(self, self.model);
+       //setsize(self, '-16 -16 -24', '16 16 32');
+       setorigin(self, self.origin + '0 0 32');
+       setsize(self, '-16 -16 -56', '16 16 0');
+       self.modelflags |= MF_ROTATE;
+       self.solid = SOLID_TRIGGER;
+       
+       if (!self.noalign)
+       {
+               // first nudge it off the floor a little bit to avoid math errors
+               setorigin(self, self.origin + '0 0 1');
+               // note droptofloor returns FALSE if stuck/or would fall too far
+               droptofloor();
+       }
+
+       self.touch = item_key_touch;
+};
+
+
+/*QUAKED item_key (0 .5 .8) (-16 -16 -24) (16 16 32) FLOATING
+A key entity.
+The itemkeys should contain one of the following key IDs:
+1 - GOLD key - 
+2 - SILVER key
+4 - BRONZE key
+8 - RED keycard
+16 - BLUE keycard
+32 - GREEN keycard
+Custom keys:
+... - last key is 1<<23
+Keys with bigger Id than 32 don't have a default netname and model, if you use one of them, you MUST provide those.
+-----------KEYS------------
+colormod: color of the key (default: '.9 .9 .9').
+itemkeys: a key Id.
+message: message to print when player picks up this key.
+model: custom key model to use.
+netname: the display name of the key.
+noise: custom sound to play when player picks up the key.
+-------- SPAWNFLAGS --------
+FLOATING: the item will float in air, instead of aligning to the floor by falling
+---------NOTES----------
+This is the only correct way to put keys on the map!
+
+itemkeys MUST always have exactly one bit set.
+*/
+void spawnfunc_item_key() {
+       local string _model, _netname;
+       local vector _colormod;
+       
+       // reject this entity if more than one key was set!
+       if (self.itemkeys>0 && (self.itemkeys & (self.itemkeys-1)) != 0) {
+               objerror("item_key.itemkeys must contain only 1 bit set specifying the key it represents!");
+               remove(self);
+               return;
+       }
+
+       // find default netname and colormod
+       switch(self.itemkeys) {
+       case 1:
+               _netname = "GOLD key";
+               _colormod = '1 .9 0';
+               break;
+               
+       case 2:
+               _netname = "SILVER key";
+               _colormod = '.9 .9 .9';
+               break;
+               
+       case 4:
+               _netname = "BRONZE key";
+               _colormod = '.6 .25 0';
+               break;
+               
+       case 8:
+               _netname = "RED keycard";
+               _colormod = '.9 0 0';
+               break;
+               
+       case 16:
+               _netname = "BLUE keycard";
+               _colormod = '0 0 .9';
+               break;
+               
+       case 32:
+               _netname = "GREEN keycard";
+               _colormod = '0 .9 0';
+               break;
+       
+       default:
+               if (!self.netname) {
+                       objerror("item_key doesn't have a default name for this key and a custom one was not specified!");
+                       remove(self);
+                       return;
+               } else if (!self.colormod) {
+                       _colormod = '1 1 1';
+               }
+               break;
+               
+       }
+       
+       // find default model
+       if (self.itemkeys <= ITEM_KEY_BIT(2)) {
+               _model = "models/keys/key.md3";
+       } else if (self.itemkeys >= ITEM_KEY_BIT(3) && self.itemkeys <= ITEM_KEY_BIT(5)) {
+               _model = "models/keys/key.md3"; // FIXME: replace it by a keycard model!
+       } else if (!self.model) {
+               objerror("item_key doesn't have a default model for this key and a custom one was not specified!");
+               remove(self);
+               return;
+       }
+       
+       // set defailt netname
+       if (!self.netname)
+               self.netname = _netname;
+       
+       // set default colormod
+       if (!self.colormod)
+               self.colormod = _colormod;
+       
+       // set default model
+       if (!self.model)
+               self.model = _model;
+       
+       // set default pickup message
+       if (!self.message)
+               self.message = strzone(strcat("You've picked up the ", self.netname, "!"));
+
+       if (!self.noise)
+               self.noise = "misc/itempickup.wav";
+       
+       // save the name for later
+       item_keys_names[lowestbit(self.itemkeys)] = self.netname;
+
+       // put the key on the map       
+       spawn_item_key();
+}
+
+/*QUAKED item_key1 (0 .5 .8) (-16 -16 -24) (16 16 32) FLOATING
+SILVER key.
+-----------KEYS------------
+colormod: color of the key (default: '.9 .9 .9').
+message: message to print when player picks up this key.
+model: custom model to use.
+noise: custom sound to play when player picks up the key.
+-------- SPAWNFLAGS --------
+FLOATING: the item will float in air, instead of aligning to the floor by falling
+---------NOTES----------
+Don't use this entity on new maps! Use item_key instead.
+*/
+void spawnfunc_item_key1(void) {
+       self.classname = "item_key";
+       self.itemkeys = ITEM_KEY_BIT(1);
+       spawnfunc_item_key();
+};
+
+/*QUAKED item_key2 (0 .5 .8) (-16 -16 -24) (16 16 32) FLOATING
+GOLD key.
+-----------KEYS------------
+colormod: color of the key (default: '1 .9 0').
+message: message to print when player picks up this key.
+model: custom model to use.
+noise: custom sound to play when player picks up the key.
+-------- SPAWNFLAGS --------
+FLOATING: the item will float in air, instead of aligning to the floor by falling
+---------NOTES----------
+Don't use this entity on new maps! Use item_key instead.
+*/
+void spawnfunc_item_key2(void) {
+       self.classname = "item_key";
+       self.itemkeys = ITEM_KEY_BIT(0);
+       spawnfunc_item_key();
+};
+
+
+/*
+================================
+trigger_keylock
+================================
+*/
+
+/**
+ * trigger givent targets
+ */
+void trigger_keylock_trigger(string s) {
+       local entity t, stemp, otemp, atemp;
+       
+       stemp = self;
+       otemp = other;
+       atemp = activator;
+       
+       
+       for(t = world; (t = find(t, targetname, s)); )
+               if (t.use) {
+                       self = t;
+                       other = stemp;
+                       activator = atemp;
+                       self.use();
+               }
+       
+       self = stemp;
+       other = otemp;
+       activator = atemp;
+};
+
+/**
+ * kill killtarget of trigger keylock.
+ */
+void trigger_keylock_kill(string s) {
+       local entity t;
+       for(t = world; (t = find(t, targetname, s)); )
+               remove(t);
+};
+
+void trigger_keylock_touch(void) {
+       local float key_used, started_delay;
+       
+       key_used = FALSE;
+       started_delay = FALSE;
+       
+       // only player may trigger the lock
+       if (other.classname != "player")
+               return;
+       
+       
+       // check silver key
+       if (self.itemkeys)
+               key_used = item_keys_usekey(self, other);
+       
+       activator = other;
+       
+       if (self.itemkeys) {
+               // at least one of the keys is missing
+               if (key_used) {
+                       // one or more keys were given, but others are still missing!
+                       play2(other, self.noise1);
+                       centerprint(other, strcat("You also need ", item_keys_keylist(self.itemkeys), "!"));
+                       other.key_door_messagetime = time + 2;
+               } else if (other.key_door_messagetime <= time) {
+                       // no keys were given
+                       play2(other, self.noise2);
+                       centerprint(other, strcat("You need ", item_keys_keylist(self.itemkeys), "!"));
+                       other.key_door_messagetime = time + 2;
+               }
+               
+               // trigger target2
+               if (self.delay <= time || started_delay == TRUE)
+               if (self.target2) {
+                       trigger_keylock_trigger(self.target2);
+                       started_delay = TRUE;
+                       self.delay = time + self.wait;
+               }
+       } else {
+               // all keys were given!
+               play2(other, self.noise);
+               centerprint(other, self.message);
+               
+               if (self.target)
+                       trigger_keylock_trigger(self.target);
+                       
+               if (self.killtarget)
+                       trigger_keylock_kill(self.killtarget);
+               
+               remove(self);
+       }
+       
+};
+
+/*QUAKED trigger_keylock (.0 .5 .8) ?
+Keylock trigger.  Must target other entities.
+This trigger will trigger target entities when all required keys are provided.
+-------- KEYS --------
+itemkeys: A bit field with key IDs that are needed to open this lock.
+sounds: 1 to play misc/secret.wav, 2 to play misc/talk.wav, 3 to play misc/trigger1.wav (3 is default)
+target: trigger all entities with this targetname when triggered and all keys have been given to it, then remove this trigger
+target2: trigger all entities with this targetname when triggered without giving it all the required keys.
+killtarget: remove all entities with this targetname when triggered with all the needed keys.
+message: print this message to the player who activated the trigger when all needed keys have been given.
+message2: print this message to the player who activated the trigger when not all of the needed keys have been given.
+noise: sound to play when lock gets unlocked (default: see sounds)
+noise1: sound to play when only some of the needed key were used but not all (default: misc/decreasevalue.wav)
+noise2: sound to play when a key is missing (default: misc/talk.wav)
+wait: prevent triggering again for this amount of time (default: 5) - applies to target2, target3, target4.
+---------NOTES----------
+If spawned without any key specified in itemkeys, this trigger will display an error and remove itself.
+message2 and noise2 will be resent to the player every 2 seconds while he is in the trigger zone.
+*/
+void spawnfunc_trigger_keylock(void) {
+       if (!self.itemkeys) {
+               remove(self);
+               return;
+       }
+
+       // set unlocked message 
+       if (!self.message)
+               self.message = "Unlocked!";
+       
+       // set default unlock noise
+       if (!self.noise) {
+               if (self.sounds == 1)
+                       self.noise = "misc/secret.wav";
+               else if (self.sounds == 2)
+                       self.noise = "misc/talk.wav";
+               else //if (self.sounds == 3) {
+                       self.noise = "misc/trigger1.wav";
+       }
+       
+       // set default use key sound
+       if (!self.noise1)
+               self.noise1 = "misc/decreasevalue.wav";
+       
+       // set closed sourd
+       if (!self.noise2)
+               self.noise2 = "misc/talk.wav";
+       
+       // delay between triggering message2 and trigger2
+       if (!self.wait)
+               self.wait = 5;
+       
+       // precache sounds
+       precache_sound(self.noise);
+       precache_sound(self.noise1);
+       precache_sound(self.noise2);
+       
+       EXACTTRIGGER_INIT;
+       
+       self.touch = trigger_keylock_touch;
+};
+
+
diff --git a/qcsrc/server/item_key.qh b/qcsrc/server/item_key.qh
new file mode 100644 (file)
index 0000000..24ef1e9
--- /dev/null
@@ -0,0 +1,23 @@
+/**
+ * Returns the bit ID of a key
+ */
+#define ITEM_KEY_BIT(n)        ( bitshift(1, n) )
+
+#define ITEM_KEY_MAX   24
+
+/**
+ * list of key names.
+ */
+string item_keys_names[ITEM_KEY_MAX];
+
+/**
+ * Use keys from p on l.
+ * Returns TRUE if any new keys were given, FALSE otherwise.
+ */
+float item_keys_usekey(entity l, entity p);
+
+/**
+ * Returns a string with a comma separated list of key names, as specified in keylist.
+ */
+string item_keys_keylist(float keylist);
+
index e26280e61a695cf0fedcff92ff3fd487c803a830..73b23bbd3f224201ecffa53725fe544a3b225573 100644 (file)
@@ -159,3 +159,32 @@ MUTATOR_HOOKABLE(PlayerUseKey);
        // called when the use key is pressed
        // if MUTATOR_RETURNVALUE is 1, don't do anything
        // return 1 if the use key actually did something
+
+MUTATOR_HOOKABLE(SV_ParseClientCommand);
+       // called when a client command is parsed
+       // NOTE: hooks MUST start with if(MUTATOR_RETURNVALUE) return 0;
+       // NOTE: return 1 if you handled the command, return 0 to continue handling
+       // NOTE: THESE HOOKS MUST NEVER EVER CALL tokenize()
+       // INPUT
+       string cmd_name; // command name
+       float cmd_argc; // also, argv() can be used
+       string cmd_string; // whole command, use only if you really have to
+       /*
+               // example:
+               MUTATOR_HOOKFUNCTION(foo_SV_ParseClientCommand)
+               {
+                       if(MUTATOR_RETURNVALUE) // command was already handled?
+                               return 0;
+                       if(cmd_name == "echocvar" && cmd_argc >= 2)
+                       {
+                               print(cvar_string(argv(1)), "\n");
+                               return 1;
+                       }
+                       if(cmd_name == "echostring" && cmd_argc >= 2)
+                       {
+                               print(substring(cmd_string, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), "\n");
+                               return 1;
+                       }
+                       return 0;
+               }
+       */
index 3d1e3970a1f34a31fdafda89a5367a8d803c832c..b5307d42893dda6cbb1a8715ac92c6209a20c50e 100644 (file)
@@ -64,6 +64,8 @@ vote.qh
 
 playerdemo.qh
 
+item_key.qh
+
 scores_rules.qc
 
 miscfunctions.qc
@@ -110,6 +112,7 @@ w_all.qc
 t_items.qc
 cl_weapons.qc
 cl_impulse.qc
+item_key.qc
 
 ent_cs.qc
 
index 9ac9df289243bd36ba3028cec515d4f5394117cb..f44dcb7b9476be220390010163dd175108282428 100644 (file)
@@ -885,6 +885,7 @@ void door_go_up()
 }
 
 
+
 /*
 =============================================================================
 
@@ -893,6 +894,50 @@ ACTIVATION FUNCTIONS
 =============================================================================
 */
 
+float door_check_keys(void) {
+       local entity door;
+       
+       
+       if (self.owner)
+               door = self.owner;
+       else
+               door = self;
+       
+       // no key needed
+       if not(door.itemkeys)
+               return TRUE;
+
+       // this door require a key
+       // only a player can have a key
+       if (other.classname != "player")
+               return FALSE;
+       
+       if (item_keys_usekey(door, other)) {
+               // some keys were used
+               if (other.key_door_messagetime <= time) {
+                       play2(other, "misc/talk.wav");
+                       centerprint(other, strcat("You also need ", item_keys_keylist(door.itemkeys), "!"));
+                       other.key_door_messagetime = time + 2;
+               }
+       } else {
+               // no keys were used
+               if (other.key_door_messagetime <= time) {
+                       play2(other, "misc/talk.wav");
+                       centerprint(other, strcat("You need ", item_keys_keylist(door.itemkeys), "!"));
+                       other.key_door_messagetime = time + 2;
+               }
+       }
+
+       if (door.itemkeys) {
+               // door is now unlocked
+               play2(other, "misc/talk.wav");
+               centerprint(other, "Door unlocked!");
+               return TRUE;
+       } else
+               return FALSE;
+}
+
+
 void door_fire()
 {
        entity  oself;
@@ -958,6 +1003,7 @@ void door_use()
        entity oself;
 
        //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
+       
        if (self.owner)
        {
                oself = self;
@@ -971,11 +1017,16 @@ void door_use()
 void door_trigger_touch()
 {
        if (other.health < 1)
-       if not(other.iscreature && other.deadflag == DEAD_NO)
-               return;
+               if not(other.iscreature && other.deadflag == DEAD_NO)
+                       return;
 
        if (time < self.attack_finished_single)
                return;
+       
+       // check if door is locked
+       if (!door_check_keys())
+               return;
+       
        self.attack_finished_single = time + 1;
 
        activator = other;
@@ -992,6 +1043,12 @@ void door_damage(entity inflictor, entity attacker, float damage, float deathtyp
                if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
                        return;
        self.health = self.health - damage;
+       
+       if (self.itemkeys) {
+               // don't allow opening doors through damage if keys are required
+               return;
+       }
+       
        if (self.health <= 0)
        {
                oself = self;
@@ -1271,13 +1328,17 @@ void LinkDoors()
 }
 
 
-/*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE
+/*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
 if two doors touch, they are assumed to be connected and operate as a unit.
 
 TOGGLE causes the door to wait in both the start and end states for a trigger event.
 
 START_OPEN causes the door to move to its destination when spawned, and operate in reverse.  It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors).
 
+GOLD_KEY causes the door to open only if the activator holds a gold key.
+
+SILVER_KEY causes the door to open only if the activator holds a silver key.
+
 "message"      is printed when the door is touched if it is a trigger door and it hasn't been fired yet
 "angle"                determines the opening direction
 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
@@ -1311,8 +1372,17 @@ void door_reset()
        self.think = SUB_Null;
 }
 
+// spawnflags require key (for now only func_door)
+#define SPAWNFLAGS_GOLD_KEY 8
+#define SPAWNFLAGS_SILVER_KEY 16
 void spawnfunc_func_door()
 {
+       // Quake 1 keys compatibility
+       if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
+               self.itemkeys |= ITEM_KEY_BIT(0);
+       if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
+               self.itemkeys |= ITEM_KEY_BIT(1);
+               
        //if (!self.deathtype) // map makers can override this
        //      self.deathtype = " got in the way";
        SetMovedir ();
@@ -1326,8 +1396,9 @@ void spawnfunc_func_door()
        self.blocked = door_blocked;
        self.use = door_use;
 
-    if(self.spawnflags & 8)
-        self.dmg = 10000;
+       // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
+       // if(self.spawnflags & 8)
+       //      self.dmg = 10000;
 
     if(self.dmg && (!self.message))
                self.message = "was squished";