1 /*
2 	cl_ents.c
3 
4 	entity parsing and management
5 
6 	Copyright (C) 1996-1997  Id Software, Inc.
7 
8 	This program is free software; you can redistribute it and/or
9 	modify it under the terms of the GNU General Public License
10 	as published by the Free Software Foundation; either version 2
11 	of the License, or (at your option) any later version.
12 
13 	This program is distributed in the hope that it will be useful,
14 	but WITHOUT ANY WARRANTY; without even the implied warranty of
15 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 
17 	See the GNU General Public License for more details.
18 
19 	You should have received a copy of the GNU General Public License
20 	along with this program; if not, write to:
21 
22 		Free Software Foundation, Inc.
23 		59 Temple Place - Suite 330
24 		Boston, MA  02111-1307, USA
25 
26 */
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30 
31 #ifdef HAVE_STRING_H
32 # include <string.h>
33 #endif
34 #ifdef HAVE_STRINGS_H
35 # include <strings.h>
36 #endif
37 
38 #include "QF/cvar.h"
39 #include "QF/locs.h"
40 #include "QF/msg.h"
41 #include "QF/render.h"
42 #include "QF/skin.h"
43 #include "QF/sys.h"
44 
45 #include "qw/msg_ucmd.h"
46 
47 #include "qw/bothdefs.h"
48 #include "chase.h"
49 #include "cl_cam.h"
50 #include "cl_ents.h"
51 #include "cl_main.h"
52 #include "cl_parse.h"
53 #include "cl_pred.h"
54 #include "cl_tent.h"
55 #include "compat.h"
56 #include "d_iface.h"
57 #include "host.h"
58 #include "qw/pmove.h"
59 #include "clview.h"
60 
61 entity_t    cl_player_ents[MAX_CLIENTS];
62 entity_t    cl_flag_ents[MAX_CLIENTS];
63 entity_t    cl_entities[512];	// FIXME: magic number
64 byte        cl_entity_valid[2][512];
65 
66 void
CL_ClearEnts(void)67 CL_ClearEnts (void)
68 {
69 	size_t      i;
70 
71 	i = qw_entstates.num_frames * qw_entstates.num_entities;
72 	memset (qw_entstates.frame[0], 0, i * sizeof (entity_state_t));
73 	memset (cl_entity_valid, 0, sizeof (cl_entity_valid));
74 	for (i = 0; i < sizeof (cl_entities) / sizeof (cl_entities[0]); i++)
75 		CL_Init_Entity (&cl_entities[i]);
76 	for (i = 0; i < sizeof (cl_flag_ents) / sizeof (cl_flag_ents[0]); i++)
77 		CL_Init_Entity (&cl_flag_ents[i]);
78 	for (i = 0; i < sizeof (cl_player_ents) / sizeof (cl_player_ents[0]); i++)
79 		CL_Init_Entity (&cl_player_ents[i]);
80 }
81 
82 static void
CL_NewDlight(int key,vec3_t org,int effects,byte glow_size,byte glow_color)83 CL_NewDlight (int key, vec3_t org, int effects, byte glow_size,
84 			  byte glow_color)
85 {
86 	float       radius;
87 	dlight_t   *dl;
88 	static quat_t normal = {0.4, 0.2, 0.05, 0.7};
89 	static quat_t red = {0.5, 0.05, 0.05, 0.7};
90 	static quat_t blue = {0.05, 0.05, 0.5, 0.7};
91 	static quat_t purple = {0.5, 0.05, 0.5, 0.7};
92 
93 	effects &= EF_BLUE | EF_RED | EF_BRIGHTLIGHT | EF_DIMLIGHT;
94 	if (!effects) {
95 		if (!glow_size)
96 			return;
97 	}
98 
99 	dl = r_funcs->R_AllocDlight (key);
100 	if (!dl)
101 		return;
102 	VectorCopy (org, dl->origin);
103 
104 	if (effects & (EF_BLUE | EF_RED | EF_BRIGHTLIGHT | EF_DIMLIGHT)) {
105 		radius = 200 + (rand () & 31);
106 		if (effects & EF_BRIGHTLIGHT) {
107 			radius += 200;
108 			dl->origin[2] += 16;
109 		}
110 		if (effects & EF_DIMLIGHT)
111 			if (effects & ~EF_DIMLIGHT)
112 				radius -= 100;
113 		dl->radius = radius;
114 		dl->die = cl.time + 0.1;
115 
116 		switch (effects & (EF_RED | EF_BLUE)) {
117 			case EF_RED | EF_BLUE:
118 				QuatCopy (purple, dl->color);
119 				break;
120 			case EF_RED:
121 				QuatCopy (red, dl->color);
122 				break;
123 			case EF_BLUE:
124 				QuatCopy (blue, dl->color);
125 				break;
126 			default:
127 				QuatCopy (normal, dl->color);
128 				break;
129 		}
130 	}
131 
132 	if (glow_size) {
133 		dl->radius += glow_size < 128 ? glow_size * 8.0 :
134 			(glow_size - 256) * 8.0;
135 		dl->die = cl.time + 0.1;
136 		if (glow_color) {
137 			if (glow_color == 255) {
138 				dl->color[0] = dl->color[1] = dl->color[2] = 1.0;
139 			} else {
140 				byte        *tempcolor;
141 
142 				tempcolor = (byte *) &d_8to24table[glow_color];
143 				VectorScale (tempcolor, 1 / 255.0, dl->color);
144 			}
145 		}
146 	}
147 }
148 
149 // Hack hack hack
150 static inline int
is_dead_body(entity_state_t * s1)151 is_dead_body (entity_state_t *s1)
152 {
153 	int         i = s1->frame;
154 
155 	if (s1->modelindex == cl_playerindex
156 		&& (i == 49 || i == 60 || i == 69 || i == 84 || i == 93 || i == 102))
157 		return 1;
158 	return 0;
159 }
160 
161 // Hack hack hack
162 static inline int
is_gib(entity_state_t * s1)163 is_gib (entity_state_t *s1)
164 {
165 	if (s1->modelindex == cl_h_playerindex || s1->modelindex == cl_gib1index
166 		|| s1->modelindex == cl_gib2index || s1->modelindex == cl_gib3index)
167 		return 1;
168 	return 0;
169 }
170 
171 void
CL_TransformEntity(entity_t * ent,const vec3_t angles,qboolean force)172 CL_TransformEntity (entity_t *ent, const vec3_t angles, qboolean force)
173 {
174 	vec3_t      ang;
175 	vec_t      *forward, *left, *up;
176 
177 	if (VectorIsZero (angles)) {
178 		VectorSet (1, 0, 0, ent->transform + 0);
179 		VectorSet (0, 1, 0, ent->transform + 4);
180 		VectorSet (0, 0, 1, ent->transform + 8);
181 	} else if (force || !VectorCompare (angles, ent->angles)) {
182 		forward = ent->transform + 0;
183 		left = ent->transform + 4;
184 		up = ent->transform + 8;
185 		VectorCopy (angles, ang);
186 		if (ent->model && ent->model->type == mod_alias) {
187 			// stupid quake bug
188 			// why, oh, why, do alias models pitch in the opposite direction
189 			// to everything else?
190 			ang[PITCH] = -ang[PITCH];
191 		}
192 		AngleVectors (ang, forward, left, up);
193 		VectorNegate (left, left);  // AngleVectors is right-handed
194 	}
195 	VectorCopy (angles, ent->angles);
196 	ent->transform[3] = 0;
197 	ent->transform[7] = 0;
198 	ent->transform[11] = 0;
199 	VectorCopy (ent->origin, ent->transform + 12);
200 	ent->transform[15] = 1;
201 }
202 
203 static void
CL_ModelEffects(entity_t * ent,int num,int glow_color)204 CL_ModelEffects (entity_t *ent, int num, int glow_color)
205 {
206 	dlight_t   *dl;
207 	model_t    *model = ent->model;
208 
209 	// add automatic particle trails
210 	if (model->flags & EF_ROCKET) {
211 		dl = r_funcs->R_AllocDlight (num);
212 		if (dl) {
213 			VectorCopy (ent->origin, dl->origin);
214 			dl->radius = 200.0;
215 			dl->die = cl.time + 0.1;
216 			//FIXME VectorCopy (r_firecolor->vec, dl->color);
217 			VectorSet (0.9, 0.7, 0.0, dl->color);
218 			dl->color[3] = 0.7;
219 		}
220 		r_funcs->particles->R_RocketTrail (ent);
221 	} else if (model->flags & EF_GRENADE)
222 		r_funcs->particles->R_GrenadeTrail (ent);
223 	else if (model->flags & EF_GIB)
224 		r_funcs->particles->R_BloodTrail (ent);
225 	else if (model->flags & EF_ZOMGIB)
226 		r_funcs->particles->R_SlightBloodTrail (ent);
227 	else if (model->flags & EF_TRACER)
228 		r_funcs->particles->R_WizTrail (ent);
229 	else if (model->flags & EF_TRACER2)
230 		r_funcs->particles->R_FlameTrail (ent);
231 	else if (model->flags & EF_TRACER3)
232 		r_funcs->particles->R_VoorTrail (ent);
233 	else if (model->flags & EF_GLOWTRAIL)
234 		if (r_funcs->particles->R_GlowTrail)
235 			r_funcs->particles->R_GlowTrail (ent, glow_color);
236 }
237 
238 static void
set_entity_model(entity_t * ent,int modelindex)239 set_entity_model (entity_t *ent, int modelindex)
240 {
241 	ent->model = cl.model_precache[modelindex];
242 	// automatic animation (torches, etc) can be either all together
243 	// or randomized
244 	if (ent->model) {
245 		if (ent->model->synctype == ST_RAND)
246 			ent->syncbase = (float) (rand () & 0x7fff) / 0x7fff;
247 		else
248 			ent->syncbase = 0.0;
249 	}
250 }
251 
252 static void
CL_LinkPacketEntities(void)253 CL_LinkPacketEntities (void)
254 {
255 	int         i, j, forcelink;
256 	float       frac, f;
257 	entity_t   *ent;
258 	entity_state_t *new, *old;
259 	vec3_t      delta;
260 
261 	frac = 1;
262 	for (i = 0; i < 512; i++) {
263 		new = &qw_entstates.frame[cl.link_sequence & UPDATE_MASK][i];
264 		old = &qw_entstates.frame[cl.prev_sequence & UPDATE_MASK][i];
265 		ent = &cl_entities[i];
266 		forcelink = cl_entity_valid[0][i] != cl_entity_valid[1][i];
267 		cl_entity_valid[1][i] = cl_entity_valid[0][i];
268 		// if the object wasn't included in the last packet, remove it
269 		if (!cl_entity_valid[0][i]) {
270 			ent->model = NULL;
271 			ent->pose1 = ent->pose2 = -1;
272 			if (ent->efrag)
273 				r_funcs->R_RemoveEfrags (ent);	// just became empty
274 			continue;
275 		}
276 
277 		// spawn light flashes, even ones coming from invisible objects
278 		CL_NewDlight (i, new->origin, new->effects, new->glow_size,
279 					  new->glow_color);
280 
281 		// if set to invisible, skip
282 		if (!new->modelindex
283 			|| (cl_deadbodyfilter->int_val && is_dead_body (new))
284 			|| (cl_gibfilter->int_val && is_gib (new))) {
285 			if (ent->efrag)
286 				r_funcs->R_RemoveEfrags (ent);
287 			continue;
288 		}
289 
290 		if (forcelink)
291 			*old = *new;
292 
293 		if (forcelink || new->modelindex != old->modelindex) {
294 			old->modelindex = new->modelindex;
295 			set_entity_model (ent, new->modelindex);
296 		}
297 		ent->frame = new->frame;
298 		if (forcelink || new->colormap != old->colormap
299 			|| new->skinnum != old->skinnum) {
300 			old->skinnum = new->skinnum;
301 			ent->skinnum = new->skinnum;
302 			old->colormap = new->colormap;
303 			if (new->colormap && (new->colormap <= MAX_CLIENTS)
304 				&& cl.players[new->colormap - 1].name
305 				&& cl.players[new->colormap - 1].name->value[0]) {
306 				player_info_t *player = &cl.players[new->colormap - 1];
307 				ent->skin = mod_funcs->Skin_SetSkin (ent->skin, new->colormap,
308 													 player->skinname->value);
309 				ent->skin = mod_funcs->Skin_SetColormap (ent->skin,
310 														 new->colormap);
311 			} else {
312 				ent->skin = mod_funcs->Skin_SetColormap (ent->skin, 0);
313 			}
314 		}
315 		ent->scale = new->scale / 16.0;
316 
317 		VectorCopy (ent_colormod[new->colormod], ent->colormod);
318 		ent->colormod[3] = new->alpha / 255.0;
319 
320 		ent->min_light = 0;
321 		ent->fullbright = 0;
322 		if (new->modelindex == cl_playerindex) {
323 			ent->min_light = min (cl.fbskins, cl_fb_players->value);
324 			if (ent->min_light >= 1.0)
325 				ent->fullbright = 1;
326 		}
327 
328 		if (forcelink) {
329 			ent->pose1 = ent->pose2 = -1;
330 			VectorCopy (new->origin, ent->origin);
331 			if (!(ent->model->flags & EF_ROTATE))
332 				CL_TransformEntity (ent, new->angles, true);
333 			if (i != cl.viewentity || chase_active->int_val) {
334 				if (ent->efrag)
335 					r_funcs->R_RemoveEfrags (ent);
336 				r_funcs->R_AddEfrags (ent);
337 			}
338 			VectorCopy (ent->origin, ent->old_origin);
339 		} else {
340 			f = frac;
341 			VectorCopy (ent->origin, ent->old_origin);
342 			VectorSubtract (new->origin, old->origin, delta);
343 			// If the delta is large, assume a teleport and don't lerp
344 			if (fabs (delta[0]) > 100 || fabs (delta[1] > 100)
345 				|| fabs (delta[2]) > 100) {
346 				// assume a teleportation, not a motion
347 				VectorCopy (new->origin, ent->origin);
348 				if (!(ent->model->flags & EF_ROTATE))
349 					CL_TransformEntity (ent, new->angles, true);
350 				ent->pose1 = ent->pose2 = -1;
351 			} else {
352 				vec3_t      angles, d;
353 				// interpolate the origin and angles
354 				VectorMultAdd (old->origin, f, delta, ent->origin);
355 				if (!(ent->model->flags & EF_ROTATE)) {
356 					VectorSubtract (new->angles, old->angles, d);
357 					for (j = 0; j < 3; j++) {
358 						if (d[j] > 180)
359 							d[j] -= 360;
360 						else if (d[j] < -180)
361 							d[j] += 360;
362 					}
363 					VectorMultAdd (old->angles, f, d, angles);
364 					CL_TransformEntity (ent, angles, false);
365 				}
366 			}
367 			if (i != cl.viewentity || chase_active->int_val) {
368 				if (ent->efrag) {
369 					if (!VectorCompare (ent->origin, ent->old_origin)) {
370 						r_funcs->R_RemoveEfrags (ent);
371 						r_funcs->R_AddEfrags (ent);
372 					}
373 				} else {
374 					r_funcs->R_AddEfrags (ent);
375 				}
376 			}
377 		}
378 		if (!ent->efrag)
379 			r_funcs->R_AddEfrags (ent);
380 
381 		// rotate binary objects locally
382 		if (ent->model->flags & EF_ROTATE) {
383 			vec3_t      angles;
384 			angles[PITCH] = 0;
385 			angles[YAW] = anglemod (100 * cl.time);
386 			angles[ROLL] = 0;
387 			CL_TransformEntity (ent, angles, false);
388 		}
389 		//CL_EntityEffects (i, ent, new);
390 		//CL_NewDlight (i, ent->origin, new->effects, 0, 0);
391 		if (VectorDistance_fast (old->origin, ent->origin) > (256 * 256))
392 			VectorCopy (ent->origin, old->origin);
393 		if (ent->model->flags & ~EF_ROTATE)
394 			CL_ModelEffects (ent, -new->number, new->glow_color);
395 	}
396 }
397 
398 /*
399 	CL_AddFlagModels
400 
401 	Called when the CTF flags are set. Flags are effectively temp entities.
402 
403 	NOTE: this must be called /after/ the entity has been transformed as it
404 	uses the entity's transform matrix to get the frame vectors
405 */
406 static void
CL_AddFlagModels(entity_t * ent,int team,int key)407 CL_AddFlagModels (entity_t *ent, int team, int key)
408 {
409 	static float flag_offsets[] = {
410 		16.0, 22.0, 26.0, 25.0, 24.0, 18.0,					// 29-34 axpain
411 		16.0, 24.0, 24.0, 22.0, 18.0, 16.0,					// 35-40 pain
412 	};
413 	float       f;
414 	entity_t   *fent;
415 	vec_t      *v_forward, *v_left;
416 	vec3_t      ang;
417 
418 	if (cl_flagindex == -1)
419 		return;
420 
421 	f = 14.0;
422 	if (ent->frame >= 29 && ent->frame <= 40) {
423 		f = flag_offsets[ent->frame - 29];
424 	} else if (ent->frame >= 103 && ent->frame <= 118) {
425 		if (ent->frame <= 106)							// 103-104 nailattack
426 			f = 20.0;									// 105-106 light
427 		else											// 107-112 rocketattack
428 			f = 21.0;									// 112-118 shotattack
429 	}
430 
431 	fent = &cl_flag_ents[key];
432 	fent->model = cl.model_precache[cl_flagindex];
433 	fent->skinnum = team;
434 
435 	v_forward = ent->transform + 0;
436 	v_left = ent->transform + 4;
437 
438 	VectorMultAdd (ent->origin, -f, v_forward, fent->origin);
439 	VectorMultAdd (fent->origin, -22, v_left, fent->origin);
440 	fent->origin[2] -= 16.0;
441 
442 	VectorCopy (ent->angles, ang);
443 	ang[2] -= 45.0;
444 	CL_TransformEntity (fent, ang, false);
445 
446 	r_funcs->R_EnqueueEntity (fent);//FIXME should use efrag (needs smarter
447 									// handling //in the player code)
448 }
449 
450 /*
451 	CL_LinkPlayers
452 
453 	Create visible entities in the correct position
454 	for all current players
455 */
456 static void
CL_LinkPlayers(void)457 CL_LinkPlayers (void)
458 {
459 	double			playertime;
460 	int				msec, oldphysent, i, j;
461 	entity_t	   *ent;
462 	frame_t		   *frame;
463 	player_info_t  *info;
464 	player_state_t	exact;
465 	player_state_t *state;
466 	qboolean		clientplayer;
467 	vec3_t			org, ang = {0, 0, 0};
468 
469 	playertime = realtime - cls.latency + 0.02;
470 	if (playertime > realtime)
471 		playertime = realtime;
472 
473 	frame = &cl.frames[cl.parsecount & UPDATE_MASK];
474 
475 	for (j = 0, info = cl.players, state = frame->playerstate; j < MAX_CLIENTS;
476 		 j++, info++, state++) {
477 		ent = &cl_player_ents[j];
478 		if (ent->efrag)
479 			r_funcs->R_RemoveEfrags (ent);
480 		if (state->messagenum != cl.parsecount)
481 			continue;							// not present this frame
482 
483 		if (!info->name || !info->name->value[0])
484 			continue;
485 
486 		// spawn light flashes, even ones coming from invisible objects
487 		if (j == cl.playernum) {
488 			VectorCopy (cl.simorg, org);
489 			r_data->player_entity = &cl_player_ents[j];
490 			clientplayer = true;
491 		} else {
492 			VectorCopy (state->pls.origin, org);
493 			clientplayer = false;
494 		}
495 		if (info->chat && info->chat->value[0] != '0') {
496 			dlight_t   *dl = r_funcs->R_AllocDlight (j + 1);
497 			VectorCopy (org, dl->origin);
498 			dl->radius = 100;
499 			dl->die = cl.time + 0.1;
500 			QuatSet (0.0, 1.0, 0.0, 1.0, dl->color);
501 		} else {
502 			CL_NewDlight (j + 1, org, state->pls.effects, state->pls.glow_size,
503 						  state->pls.glow_color);
504 		}
505 
506 		// Draw player?
507 		if (!Cam_DrawPlayer (j))
508 			continue;
509 
510 		if (!state->pls.modelindex)
511 			continue;
512 
513 		// Hack hack hack
514 		if (cl_deadbodyfilter->int_val
515 			&& state->pls.modelindex == cl_playerindex
516 			&& ((i = state->pls.frame) == 49 || i == 60 || i == 69 || i == 84
517 				|| i == 93 || i == 102))
518 			continue;
519 
520 		// predict only half the move to minimize overruns
521 		msec = 500 * (playertime - state->state_time);
522 		if (msec <= 0 || (!cl_predict_players->int_val) || cls.demoplayback2) {
523 			VectorCopy (state->pls.origin, ent->origin);
524 		} else {									// predict players movement
525 			state->pls.cmd.msec = msec = min (msec, 255);
526 
527 			oldphysent = pmove.numphysent;
528 			CL_SetSolidPlayers (j);
529 			CL_PredictUsercmd (state, &exact, &state->pls.cmd, clientplayer);
530 			pmove.numphysent = oldphysent;
531 			VectorCopy (exact.pls.origin, ent->origin);
532 		}
533 
534 		// angles
535 		if (j == cl.playernum)
536 		{
537 			ang[PITCH] = -cl.viewangles[PITCH] / 3.0;
538 			ang[YAW] = cl.viewangles[YAW];
539 		} else {
540 			ang[PITCH] = -state->viewangles[PITCH] / 3.0;
541 			ang[YAW] = state->viewangles[YAW];
542 		}
543 		ang[ROLL] = V_CalcRoll (ang, state->pls.velocity) * 4.0;
544 
545 		ent->model = cl.model_precache[state->pls.modelindex];
546 		ent->frame = state->pls.frame;
547 		ent->skinnum = state->pls.skinnum;
548 
549 		CL_TransformEntity (ent, ang, false);
550 
551 		ent->min_light = 0;
552 		ent->fullbright = 0;
553 
554 		if (state->pls.modelindex == cl_playerindex) { //XXX
555 			// use custom skin
556 			ent->skin = info->skin;
557 
558 			ent->min_light = min (cl.fbskins, cl_fb_players->value);
559 
560 			if (ent->min_light >= 1.0)
561 				ent->fullbright = 1;
562 		} else {
563 			// FIXME no team colors on nonstandard player models
564 			ent->skin = 0;
565 		}
566 
567 		// stuff entity in map
568 		r_funcs->R_AddEfrags (ent);
569 
570 		if (state->pls.effects & EF_FLAG1)
571 			CL_AddFlagModels (ent, 0, j);
572 		else if (state->pls.effects & EF_FLAG2)
573 			CL_AddFlagModels (ent, 1, j);
574 	}
575 }
576 
577 /*
578 	CL_EmitEntities
579 
580 	Builds the visedicts array for cl.time
581 
582 	Made up of: clients, packet_entities, nails, and tents
583 */
584 void
CL_EmitEntities(void)585 CL_EmitEntities (void)
586 {
587 	if (cls.state != ca_active)
588 		return;
589 	if (!cl.validsequence)
590 		return;
591 
592 	CL_LinkPlayers ();
593 	CL_LinkPacketEntities ();
594 	CL_UpdateTEnts ();
595 	if (cl_draw_locs->int_val) {
596 		//FIXME custom ent rendering code would be nice
597 		dlight_t   *dl;
598 		location_t *nearloc;
599 		vec3_t      trueloc;
600 		int         i;
601 
602 		nearloc = locs_find (cl.simorg);
603 		if (nearloc) {
604 			dl = r_funcs->R_AllocDlight (4096);
605 			if (dl) {
606 				VectorCopy (nearloc->loc, dl->origin);
607 				dl->radius = 200;
608 				dl->die = r_data->realtime + 0.1;
609 				dl->color[0] = 0;
610 				dl->color[1] = 1;
611 				dl->color[2] = 0;
612 				dl->color[3] = 0.7;
613 			}
614 			VectorCopy (nearloc->loc, trueloc);
615 			r_funcs->particles->R_Particle_New (pt_smokecloud, part_tex_smoke,
616 					trueloc, 2.0,
617 					vec3_origin, r_data->realtime + 9.0, 254,
618 					0.25 + qfrandom (0.125), 0.0);
619 			for (i = 0; i < 15; i++)
620 				r_funcs->particles->R_Particle_NewRandom (pt_fallfade,
621 						part_tex_dot, trueloc, 12,
622 						0.7, 96, r_data->realtime + 5.0,
623 						104 + (rand () & 7), 1.0, 0.0);
624 		}
625 	}
626 }
627 
628 void
CL_Ents_Init(void)629 CL_Ents_Init (void)
630 {
631 	r_data->view_model = &cl.viewent;
632 }
633