1#include "item_key.qh"
2
3#include "../common/triggers/subs.qh"
4#include "../common/monsters/_mod.qh"
5#include "../common/notifications/all.qh"
6#include "../common/util.qh"
7#include "../lib/warpzone/util_server.qh"
8
9/*
10TODO:
11- add an unlock sound (here to trigger_keylock and to func_door)
12- display available keys on the HUD
13- make more tests
14- think about adding NOT_EASY/NOT_NORMAL/NOT_HARD for Q1 compatibility
15- should keys have a trigger?
16*/
17
18bool item_keys_usekey(entity l, entity p)
19{
20	float valid = l.itemkeys & p.itemkeys;
21
22	if (!valid) {
23		// other has none of the needed keys
24		return false;
25	} else if (l.itemkeys == valid) {
26		// ALL needed keys were given
27		l.itemkeys = 0;
28		return true;
29	} else {
30		// only some of the needed keys were given
31		l.itemkeys &= ~valid;
32		return true;
33	}
34}
35
36string item_keys_keylist(float keylist) {
37	// no keys
38	if (!keylist)
39		return "";
40
41	// one key
42	if ((keylist & (keylist-1)) == 0)
43		return strcat("the ", item_keys_names[lowestbit(keylist)]);
44
45	string n = "";
46	int base = 0;
47	while (keylist) {
48		int l = lowestbit(keylist);
49		if (n)
50			n = strcat(n, ", the ", item_keys_names[base + l]);
51		else
52			n = strcat("the ", item_keys_names[base + l]);
53
54		keylist = bitshift(keylist,  -(l + 1));
55		base+= l + 1;
56	}
57
58	return n;
59}
60
61
62/*
63================================
64item_key
65================================
66*/
67
68/**
69 * Key touch handler.
70 */
71void item_key_touch(entity this, entity toucher)
72{
73	if (!IS_PLAYER(toucher))
74		return;
75
76	// player already picked up this key
77	if (toucher.itemkeys & this.itemkeys)
78		return;
79
80	toucher.itemkeys |= this.itemkeys;
81	play2(toucher, this.noise);
82
83	centerprint(toucher, this.message);
84
85	string oldmsg = this.message;
86	this.message = "";
87	SUB_UseTargets(this, toucher, toucher); // TODO: should we be using toucher for the trigger here?
88	this.message = oldmsg;
89};
90
91/**
92 * Spawn a key with given model, key code and color.
93 */
94void spawn_item_key(entity this)
95{
96	precache_model(this.model);
97
98	if (this.spawnflags & 1) // FLOATING
99		this.noalign = 1;
100
101	if (this.noalign)
102		set_movetype(this, MOVETYPE_NONE);
103	else
104		set_movetype(this, MOVETYPE_TOSS);
105
106	precache_sound(this.noise);
107
108	this.mdl = this.model;
109	this.effects = EF_LOWPRECISION;
110	_setmodel(this, this.model);
111	//setsize(this, '-16 -16 -24', '16 16 32');
112	setorigin(this, this.origin + '0 0 32');
113	setsize(this, '-16 -16 -56', '16 16 0');
114	this.modelflags |= MF_ROTATE;
115	this.solid = SOLID_TRIGGER;
116
117	if (!this.noalign)
118	{
119		// first nudge it off the floor a little bit to avoid math errors
120		setorigin(this, this.origin + '0 0 1');
121		// note droptofloor returns false if stuck/or would fall too far
122		droptofloor(this);
123	}
124
125	settouch(this, item_key_touch);
126};
127
128
129/*QUAKED item_key (0 .5 .8) (-16 -16 -24) (16 16 32) FLOATING
130A key entity.
131The itemkeys should contain one of the following key IDs:
1321 - GOLD key -
1332 - SILVER key
1344 - BRONZE key
1358 - RED keycard
13616 - BLUE keycard
13732 - GREEN keycard
138Custom keys:
139... - last key is 1<<23
140Keys with bigger Id than 32 don't have a default netname and model, if you use one of them, you MUST provide those.
141-----------KEYS------------
142colormod: color of the key (default: '.9 .9 .9').
143itemkeys: a key Id.
144message: message to print when player picks up this key.
145model: custom key model to use.
146netname: the display name of the key.
147noise: custom sound to play when player picks up the key.
148-------- SPAWNFLAGS --------
149FLOATING: the item will float in air, instead of aligning to the floor by falling
150---------NOTES----------
151This is the only correct way to put keys on the map!
152
153itemkeys MUST always have exactly one bit set.
154*/
155spawnfunc(item_key)
156{
157	string _netname;
158	vector _colormod;
159
160	// reject this entity if more than one key was set!
161	if (this.itemkeys>0 && (this.itemkeys & (this.itemkeys-1)) != 0) {
162		objerror(this, "item_key.itemkeys must contain only 1 bit set specifying the key it represents!");
163		delete(this);
164		return;
165	}
166
167	// find default netname and colormod
168	switch(this.itemkeys) {
169	case BIT(0):
170		_netname = "GOLD key";
171		_colormod = '1 .9 0';
172		break;
173
174	case BIT(1):
175		_netname = "SILVER key";
176		_colormod = '.9 .9 .9';
177		break;
178
179	case BIT(2):
180		_netname = "BRONZE key";
181		_colormod = '.6 .25 0';
182		break;
183
184	case BIT(3):
185		_netname = "RED keycard";
186		_colormod = '.9 0 0';
187		break;
188
189	case BIT(4):
190		_netname = "BLUE keycard";
191		_colormod = '0 0 .9';
192		break;
193
194	case BIT(5):
195		_netname = "GREEN keycard";
196		_colormod = '0 .9 0';
197		break;
198
199	default:
200		_netname = "FLUFFY PINK keycard";
201		_colormod = '1 1 1';
202
203		if (this.netname == "") {
204			objerror(this, "item_key doesn't have a default name for this key and a custom one was not specified!");
205			delete(this);
206			return;
207		}
208		break;
209
210	}
211
212	// find default model
213	string _model = string_null;
214	if (this.itemkeys <= ITEM_KEY_BIT(2)) {
215		_model = "models/keys/key.md3";
216	} else if (this.itemkeys >= ITEM_KEY_BIT(3) && this.itemkeys <= ITEM_KEY_BIT(5)) {
217		_model = "models/keys/key.md3"; // FIXME: replace it by a keycard model!
218	} else if (this.model == "") {
219		objerror(this, "item_key doesn't have a default model for this key and a custom one was not specified!");
220		delete(this);
221		return;
222	}
223
224	// set defailt netname
225	if (this.netname == "")
226		this.netname = _netname;
227
228	// set default colormod
229	if (!this.colormod)
230		this.colormod = _colormod;
231
232	// set default model
233	if (this.model == "")
234		this.model = _model;
235
236	// set default pickup message
237	if (this.message == "")
238		this.message = strzone(strcat("You've picked up the ", this.netname, "!"));
239
240	if (this.noise == "")
241		this.noise = strzone(SND(ITEMPICKUP));
242
243	// save the name for later
244	item_keys_names[lowestbit(this.itemkeys)] = this.netname;
245
246	// put the key on the map
247	spawn_item_key(this);
248}
249
250/*QUAKED item_key1 (0 .5 .8) (-16 -16 -24) (16 16 32) FLOATING
251SILVER key.
252-----------KEYS------------
253colormod: color of the key (default: '.9 .9 .9').
254message: message to print when player picks up this key.
255model: custom model to use.
256noise: custom sound to play when player picks up the key.
257-------- SPAWNFLAGS --------
258FLOATING: the item will float in air, instead of aligning to the floor by falling
259---------NOTES----------
260Don't use this entity on new maps! Use item_key instead.
261*/
262spawnfunc(item_key1)
263{
264	this.classname = "item_key";
265	this.itemkeys = ITEM_KEY_BIT(1);
266	spawnfunc_item_key(this);
267};
268
269/*QUAKED item_key2 (0 .5 .8) (-16 -16 -24) (16 16 32) FLOATING
270GOLD key.
271-----------KEYS------------
272colormod: color of the key (default: '1 .9 0').
273message: message to print when player picks up this key.
274model: custom model to use.
275noise: custom sound to play when player picks up the key.
276-------- SPAWNFLAGS --------
277FLOATING: the item will float in air, instead of aligning to the floor by falling
278---------NOTES----------
279Don't use this entity on new maps! Use item_key instead.
280*/
281spawnfunc(item_key2)
282{
283	this.classname = "item_key";
284	this.itemkeys = ITEM_KEY_BIT(0);
285	spawnfunc_item_key(this);
286};
287