]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/portals.qc
Merge branch 'master' into Mario/vehicles
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / portals.qc
1 #if defined(CSQC)
2 #elif defined(MENUQC)
3 #elif defined(SVQC)
4         #include "../dpdefs/progsdefs.qh"
5     #include "../dpdefs/dpextensions.qh"
6     #include "../warpzonelib/anglestransform.qh"
7     #include "../warpzonelib/util_server.qh"
8     #include "../common/constants.qh"
9     #include "../common/util.qh"
10     #include "../common/weapons/weapons.qh"
11     #include "autocvars.qh"
12     #include "defs.qh"
13     #include "../common/notifications.qh"
14     #include "../common/deathtypes.qh"
15     #include "mutators/mutators_include.qh"
16     #include "../csqcmodellib/sv_model.qh"
17     #include "portals.qh"
18     #include "g_hook.qh"
19 #endif
20
21 #define PORTALS_ARE_NOT_SOLID
22
23 const vector SAFENUDGE = '1 1 1';
24 const vector SAFERNUDGE = '8 8 8';
25
26 .vector portal_transform;
27 .vector portal_safe_origin;
28 .float portal_wants_to_vanish;
29 .float portal_activatetime;
30 .float savemodelindex;
31
32 float PlayerEdgeDistance(entity p, vector v)
33 {
34         vector vbest;
35
36         if(v.x < 0) vbest.x = p.mins.x; else vbest.x = p.maxs.x;
37         if(v.y < 0) vbest.y = p.mins.y; else vbest.y = p.maxs.y;
38         if(v.z < 0) vbest.z = p.mins.z; else vbest.z = p.maxs.z;
39
40         return vbest * v;
41 }
42
43 vector Portal_ApplyTransformToPlayerAngle(vector transform, vector vangle)
44 {
45         vector old_forward, old_up;
46         vector old_yawforward;
47         vector new_forward, new_up;
48         vector new_yawforward;
49
50         vector ang;
51         ang = vangle;
52         /*
53            ang_x = bound(-89, mod(-ang_x + 180, 360) - 180, 89);
54            ang = AnglesTransform_ApplyToVAngles(transform, ang);
55          */
56
57         // PLAYERS use different math
58 #ifndef POSITIVE_PITCH_IS_DOWN
59         ang.x = -ang.x;
60 #endif
61
62         //print("reference: ", vtos(AnglesTransform_ApplyToVAngles(transform, ang)), "\n");
63
64         fixedmakevectors(ang);
65         old_forward = v_forward;
66         old_up = v_up;
67         fixedmakevectors(ang.y * '0 1 0');
68         old_yawforward = v_forward;
69
70         // their aiming directions are portalled...
71         new_forward = AnglesTransform_Apply(transform, old_forward);
72         new_up = AnglesTransform_Apply(transform, old_up);
73         new_yawforward = AnglesTransform_Apply(transform, old_yawforward);
74
75         // but now find a new sense of direction
76         // this is NOT easy!
77         // assume new_forward points straight up.
78         // What is our yaw?
79         //
80         // new_up could now point forward OR backward... which direction to choose?
81
82         if(new_forward.z > 0.7 || new_forward.z < -0.7) // far up; in this case, the "up" vector points backwards
83         {
84                 // new_yawforward and new_yawup define the new aiming half-circle
85                 // we "just" need to find out whether new_up or -new_up is in that half circle
86                 ang = fixedvectoangles(new_forward); // this still gets us a nice pitch value...
87                 if(new_up * new_yawforward < 0)
88                         new_up = -1 * new_up;
89                 ang.y = vectoyaw(new_up); // this vector is the yaw we want
90                 //print("UP/DOWN path: ", vtos(ang), "\n");
91         }
92         else
93         {
94                 // good angles; here, "forward" suffices
95                 ang = fixedvectoangles(new_forward);
96                 //print("GOOD path: ", vtos(ang), "\n");
97         }
98
99 #ifndef POSITIVE_PITCH_IS_DOWN
100         ang.x = -ang.x;
101 #endif
102         ang.z = vangle.z;
103         return ang;
104 }
105
106 .vector right_vector;
107 float Portal_TeleportPlayer(entity teleporter, entity player)
108 {
109         vector from, to, safe, step, transform, ang, newvel;
110         float planeshift, s, t;
111
112         if (!teleporter.enemy)
113         {
114                 backtrace("Portal_TeleportPlayer called without other portal being set. Stop.");
115                 return 0;
116         }
117
118         from = teleporter.origin;
119         transform = teleporter.portal_transform;
120
121         to = teleporter.enemy.origin;
122         to = to + AnglesTransform_Apply(teleporter.portal_transform, player.origin - from);
123         newvel = AnglesTransform_Apply(transform, player.velocity);
124         // this now is INSIDE the plane... can't use that
125
126         // shift it out
127         fixedmakevectors(teleporter.enemy.mangle);
128
129         // first shift it ON the plane if needed
130         planeshift = ((teleporter.enemy.origin - to) * v_forward) + PlayerEdgeDistance(player, v_forward) + 1;
131         /*
132         if(planeshift > 0 && (newvel * v_forward) > vlen(newvel) * 0.01)
133                 // if we can't, let us not do the planeshift and do somewhat incorrect transformation in the end
134                 to += newvel * (planeshift / (newvel * v_forward));
135         else
136         */
137                 to += v_forward * planeshift;
138
139         s = (to - teleporter.enemy.origin) * v_right;
140         t = (to - teleporter.enemy.origin) * v_up;
141         s = bound(-48, s, 48);
142         t = bound(-48, t, 48);
143         to = teleporter.enemy.origin
144            + ((to - teleporter.enemy.origin) * v_forward) * v_forward
145            +     s                                        * v_right
146            +     t                                        * v_up;
147
148         safe = teleporter.enemy.portal_safe_origin; // a valid player origin
149         step = to + ((safe - to) * v_forward) * v_forward;
150         tracebox(safe, player.mins - SAFENUDGE, player.maxs + SAFENUDGE, step, MOVE_NOMONSTERS, player);
151         if(trace_startsolid)
152         {
153                 print("'safe' teleport location is not safe!\n");
154                 // FAIL TODO why does this happen?
155                 return 0;
156         }
157         safe = trace_endpos + normalize(safe - trace_endpos) * 0;
158         tracebox(safe, player.mins - SAFENUDGE, player.maxs + SAFENUDGE, to, MOVE_NOMONSTERS, player);
159         if(trace_startsolid)
160         {
161                 print("trace_endpos in solid, this can't be!\n");
162                 // FAIL TODO why does this happen? (reported by MrBougo)
163                 return 0;
164         }
165         to = trace_endpos + normalize(safe - trace_endpos) * 0;
166         //print(vtos(to), "\n");
167
168         // ang_x stuff works around weird quake angles
169         if(IS_PLAYER(player))
170                 ang = Portal_ApplyTransformToPlayerAngle(transform, player.v_angle);
171         else
172                 ang = AnglesTransform_ApplyToAngles(transform, player.angles);
173
174         // factor -1 allows chaining portals, but may be weird
175         player.right_vector = -1 * AnglesTransform_Apply(transform, player.right_vector);
176
177         entity oldself = self;
178         self = player;
179         MUTATOR_CALLHOOK(PortalTeleport);
180         player = self;
181         self = oldself;
182
183         if (!teleporter.enemy)
184         {
185                 backtrace("Portal_TeleportPlayer ended up without other portal being set BEFORE TeleportPlayer. Stop.");
186                 return 0;
187         }
188
189         tdeath_hit = 0;
190         TeleportPlayer(teleporter, player, to, ang, newvel, teleporter.enemy.absmin, teleporter.enemy.absmax, TELEPORT_FLAGS_PORTAL);
191         if(tdeath_hit)
192         {
193                 // telefrag within 1 second of portal creation = amazing
194                 if(time < teleporter.teleport_time + 1)
195                         Send_Notification(NOTIF_ONE, player, MSG_ANNCE, ANNCE_ACHIEVEMENT_AMAZING);
196         }
197
198         if (!teleporter.enemy)
199         {
200                 backtrace("Portal_TeleportPlayer ended up without other portal being set AFTER TeleportPlayer. Stop.");
201                 return 0;
202         }
203
204         // reset fade counter
205         teleporter.portal_wants_to_vanish = 0;
206         teleporter.fade_time = time + autocvar_g_balance_portal_lifetime;
207         teleporter.health = autocvar_g_balance_portal_health;
208         teleporter.enemy.health = autocvar_g_balance_portal_health;
209
210         return 1;
211 }
212
213 float Portal_FindSafeOrigin(entity portal)
214 {
215         vector o;
216         o = portal.origin;
217         portal.mins = PL_MIN - SAFERNUDGE;
218         portal.maxs = PL_MAX + SAFERNUDGE;
219         fixedmakevectors(portal.mangle);
220         portal.origin += 16 * v_forward;
221         if(!move_out_of_solid(portal))
222         {
223 #ifdef DEBUG
224                 print("NO SAFE ORIGIN\n");
225 #endif
226                 return 0;
227         }
228         portal.portal_safe_origin = portal.origin;
229         setorigin(portal, o);
230         return 1;
231 }
232
233 float Portal_WillHitPlane(vector eorg, vector emins, vector emaxs, vector evel, vector porg, vector pnorm, float psize)
234 {
235         float dist, distpersec, delta;
236         vector v;
237
238         dist = (eorg - porg) * pnorm;
239         dist += min(emins.x * pnorm.x, emaxs.x * pnorm.x);
240         dist += min(emins.y * pnorm.y, emaxs.y * pnorm.y);
241         dist += min(emins.z * pnorm.z, emaxs.z * pnorm.z);
242         if(dist < -1) // other side?
243                 return 0;
244 #ifdef PORTALS_ARE_NOT_SOLID
245         distpersec = evel * pnorm;
246         if(distpersec >= 0) // going away from the portal?
247                 return 0;
248         // we don't need this check with solid portals, them being SOLID_BSP should suffice
249         delta = dist / distpersec;
250         v = eorg - evel * delta - porg;
251         v = v - pnorm * (pnorm * v);
252         return vlen(v) < psize;
253 #else
254         return 1;
255 #endif
256 }
257
258 void Portal_Touch()
259 {
260         vector g;
261
262 #ifdef PORTALS_ARE_NOT_SOLID
263         // portal is being removed?
264         if(self.solid != SOLID_TRIGGER)
265                 return; // possibly engine bug
266
267         if(IS_PLAYER(other))
268                 return; // handled by think
269 #endif
270
271         if(other.classname == "item_flag_team")
272                 return; // never portal these
273
274         if(other.classname == "grapplinghook")
275                 return; // handled by think
276
277         if(!autocvar_g_vehicles_teleportable)
278         if(other.vehicle_flags & VHF_ISVEHICLE)
279                 return; // no teleporting vehicles?
280
281         if(!self.enemy)
282                 error("Portal_Touch called for a broken portal\n");
283
284 #ifdef PORTALS_ARE_NOT_SOLID
285         if(trace_fraction < 1)
286                 return; // only handle TouchAreaGrid ones (only these can teleport)
287 #else
288         if(trace_fraction >= 1)
289                 return; // only handle impacts
290 #endif
291
292         if(other.classname == "porto")
293         {
294                 if(other.portal_id == self.portal_id)
295                         return;
296         }
297         if(time < self.portal_activatetime)
298                 if(other == self.aiment)
299                 {
300                         self.portal_activatetime = time + 0.1;
301                         return;
302                 }
303         if(other != self.aiment)
304                 if(IS_PLAYER(other))
305                         if(IS_INDEPENDENT_PLAYER(other) || IS_INDEPENDENT_PLAYER(self.aiment))
306                                 return; // cannot go through someone else's portal
307         if(other.aiment != self.aiment)
308                 if(IS_PLAYER(other.aiment))
309                         if(IS_INDEPENDENT_PLAYER(other.aiment) || IS_INDEPENDENT_PLAYER(self.aiment))
310                                 return; // cannot go through someone else's portal
311         fixedmakevectors(self.mangle);
312         g = frametime * '0 0 -1' * autocvar_sv_gravity;
313         if(!Portal_WillHitPlane(other.origin, other.mins, other.maxs, other.velocity + g, self.origin, v_forward, self.maxs.x))
314                 return;
315
316         /*
317         if(other.mins_x < PL_MIN_x || other.mins_y < PL_MIN_y || other.mins_z < PL_MIN_z
318         || other.maxs_x > PL_MAX_x || other.maxs_y > PL_MAX_y || other.maxs_z > PL_MAX_z)
319         {
320                 // can't teleport this
321                 return;
322         }
323         */
324
325         if(Portal_TeleportPlayer(self, other))
326                 if(other.classname == "porto")
327                         if(other.effects & EF_RED)
328                                 other.effects += EF_BLUE - EF_RED;
329 }
330
331 void Portal_Think();
332 void Portal_MakeBrokenPortal(entity portal)
333 {
334         portal.skin = 2;
335         portal.solid = SOLID_NOT;
336         portal.touch = func_null;
337         portal.think = func_null;
338         portal.effects = 0;
339         portal.nextthink = 0;
340         portal.takedamage = DAMAGE_NO;
341 }
342
343 void Portal_MakeWaitingPortal(entity portal)
344 {
345         portal.skin = 2;
346         portal.solid = SOLID_NOT;
347         portal.touch = func_null;
348         portal.think = func_null;
349         portal.effects = EF_ADDITIVE;
350         portal.nextthink = 0;
351         portal.takedamage = DAMAGE_YES;
352 }
353
354 void Portal_MakeInPortal(entity portal)
355 {
356         portal.skin = 0;
357         portal.solid = SOLID_NOT; // this is done when connecting them!
358         portal.touch = Portal_Touch;
359         portal.think = Portal_Think;
360         portal.effects = EF_RED;
361         portal.nextthink = time;
362         portal.takedamage = DAMAGE_NO;
363 }
364
365 void Portal_MakeOutPortal(entity portal)
366 {
367         portal.skin = 1;
368         portal.solid = SOLID_NOT;
369         portal.touch = func_null;
370         portal.think = func_null;
371         portal.effects = EF_STARDUST | EF_BLUE;
372         portal.nextthink = 0;
373         portal.takedamage = DAMAGE_YES;
374 }
375
376 void Portal_Disconnect(entity teleporter, entity destination)
377 {
378         teleporter.enemy = world;
379         destination.enemy = world;
380         Portal_MakeBrokenPortal(teleporter);
381         Portal_MakeBrokenPortal(destination);
382 }
383
384 void Portal_Connect(entity teleporter, entity destination)
385 {
386         teleporter.portal_transform = AnglesTransform_RightDivide(AnglesTransform_TurnDirectionFR(destination.mangle), teleporter.mangle);
387
388         teleporter.enemy = destination;
389         destination.enemy = teleporter;
390         Portal_MakeInPortal(teleporter);
391         Portal_MakeOutPortal(destination);
392         teleporter.fade_time = time + autocvar_g_balance_portal_lifetime;
393         destination.fade_time = teleporter.fade_time;
394         teleporter.portal_wants_to_vanish = 0;
395         destination.portal_wants_to_vanish = 0;
396         teleporter.teleport_time = time;
397 #ifdef PORTALS_ARE_NOT_SOLID
398         teleporter.solid = SOLID_TRIGGER;
399 #else
400         teleporter.solid = SOLID_BSP;
401 #endif
402 }
403
404 void Portal_Remove(entity portal, float killed)
405 {
406         entity e;
407         e = portal.enemy;
408
409         if(e)
410         {
411                 Portal_Disconnect(portal, e);
412                 Portal_Remove(e, killed);
413         }
414
415         if(portal == portal.aiment.portal_in)
416                 portal.aiment.portal_in = world;
417         if(portal == portal.aiment.portal_out)
418                 portal.aiment.portal_out = world;
419         //portal.aiment = world;
420
421         // makes the portal vanish
422         if(killed)
423         {
424                 fixedmakevectors(portal.mangle);
425                 sound(portal, CH_SHOTS, "porto/explode.wav", VOL_BASE, ATTEN_NORM);
426                 pointparticles(particleeffectnum("rocket_explode"), portal.origin + v_forward * 16, v_forward * 1024, 4);
427                 remove(portal);
428         }
429         else
430         {
431                 Portal_MakeBrokenPortal(portal);
432                 sound(portal, CH_SHOTS, "porto/expire.wav", VOL_BASE, ATTEN_NORM);
433                 SUB_SetFade(portal, time, 0.5);
434         }
435 }
436
437 void Portal_Damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
438 {
439         if(deathtype == DEATH_TELEFRAG)
440                 return;
441         if(attacker != self.aiment)
442                 if(IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(self.aiment))
443                         return;
444         self.health -= damage;
445         if(self.health < 0)
446                 Portal_Remove(self, 1);
447 }
448
449 void Portal_Think_TryTeleportPlayer(entity e, vector g)
450 {
451         if(!Portal_WillHitPlane(e.origin, e.mins, e.maxs, e.velocity + g, self.origin, v_forward, self.maxs.x))
452                 return;
453
454         // if e would hit the portal in a frame...
455         // already teleport him
456         tracebox(e.origin, e.mins, e.maxs, e.origin + e.velocity * 2 * frametime, MOVE_NORMAL, e);
457         if(trace_ent == self)
458                 Portal_TeleportPlayer(self, e);
459 }
460
461 void Portal_Think()
462 {
463         entity e, o;
464         vector g;
465
466 #ifdef PORTALS_ARE_NOT_SOLID
467         // portal is being removed?
468         if(self.solid != SOLID_TRIGGER)
469                 return; // possibly engine bug
470
471         if(!self.enemy)
472                 error("Portal_Think called for a broken portal\n");
473
474         o = self.aiment;
475         self.solid = SOLID_BBOX;
476         self.aiment = world;
477
478         g = frametime * '0 0 -1' * autocvar_sv_gravity;
479
480         fixedmakevectors(self.mangle);
481
482         FOR_EACH_PLAYER(e)
483         {
484                 if(e != o)
485                         if(IS_INDEPENDENT_PLAYER(e) || IS_INDEPENDENT_PLAYER(o))
486                                 continue; // cannot go through someone else's portal
487
488                 if(e != o || time >= self.portal_activatetime)
489                         Portal_Think_TryTeleportPlayer(e, g);
490
491                 if(e.hook)
492                         Portal_Think_TryTeleportPlayer(e.hook, g);
493         }
494         self.solid = SOLID_TRIGGER;
495         self.aiment = o;
496 #endif
497
498         self.nextthink = time;
499
500         if(time > self.fade_time)
501                 Portal_Remove(self, 0);
502 }
503
504 float Portal_Customize()
505 {
506         if(IS_SPEC(other))
507                 other = other.enemy;
508         if(other == self.aiment)
509         {
510                 self.modelindex = self.savemodelindex;
511         }
512         else if(IS_INDEPENDENT_PLAYER(other) || IS_INDEPENDENT_PLAYER(self.aiment))
513         {
514                 self.modelindex = 0;
515         }
516         else
517         {
518                 self.modelindex = self.savemodelindex;
519         }
520         return true;
521 }
522
523 // cleanup:
524 //   when creating in-portal:
525 //     disconnect
526 //     clear existing in-portal
527 //     set as in-portal
528 //     connect
529 //   when creating out-portal:
530 //     disconnect
531 //     clear existing out-portal
532 //     set as out-portal
533 //   when player dies:
534 //     disconnect portals
535 //     clear both portals
536 //   after timeout of in-portal:
537 //     disconnect portals
538 //     clear both portals
539 //   TODO: ensure only one portal shot at once
540 float Portal_SetInPortal(entity own, entity portal)
541 {
542         if(own.portal_in)
543         {
544                 if(own.portal_out)
545                         Portal_Disconnect(own.portal_in, own.portal_out);
546                 Portal_Remove(own.portal_in, 0);
547         }
548         own.portal_in = portal;
549         if(own.portal_out)
550         {
551                 own.portal_out.portal_id = portal.portal_id;
552                 Portal_Connect(own.portal_in, own.portal_out);
553         }
554         return 2;
555 }
556 float Portal_SetOutPortal(entity own, entity portal)
557 {
558         if(own.portal_out)
559         {
560                 if(own.portal_in)
561                         Portal_Disconnect(own.portal_in, own.portal_out);
562                 Portal_Remove(own.portal_out, 0);
563         }
564         own.portal_out = portal;
565         if(own.portal_in)
566         {
567                 own.portal_in.portal_id = portal.portal_id;
568                 Portal_Connect(own.portal_in, own.portal_out);
569         }
570         return 1;
571 }
572 void Portal_ClearAll_PortalsOnly(entity own)
573 {
574         if(own.portal_in)
575                 Portal_Remove(own.portal_in, 0);
576         if(own.portal_out)
577                 Portal_Remove(own.portal_out, 0);
578 }
579 void Portal_ClearAll(entity own)
580 {
581         Portal_ClearAll_PortalsOnly(own);
582         W_Porto_Remove(own);
583 }
584 void Portal_RemoveLater_Think()
585 {
586         Portal_Remove(self, self.cnt);
587 }
588 void Portal_RemoveLater(entity portal, float kill)
589 {
590         Portal_MakeBrokenPortal(portal);
591         portal.cnt = kill;
592         portal.think = Portal_RemoveLater_Think;
593         portal.nextthink = time;
594 }
595 void Portal_ClearAllLater_PortalsOnly(entity own)
596 {
597         if(own.portal_in)
598                 Portal_RemoveLater(own.portal_in, 0);
599         if(own.portal_out)
600                 Portal_RemoveLater(own.portal_out, 0);
601 }
602 void Portal_ClearAllLater(entity own)
603 {
604         Portal_ClearAllLater_PortalsOnly(own);
605         W_Porto_Remove(own);
606 }
607 void Portal_ClearWithID(entity own, float id)
608 {
609         if(own.portal_in)
610                 if(own.portal_in.portal_id == id)
611                 {
612                         if(own.portal_out)
613                                 Portal_Disconnect(own.portal_in, own.portal_out);
614                         Portal_Remove(own.portal_in, 0);
615                 }
616         if(own.portal_out)
617                 if(own.portal_out.portal_id == id)
618                 {
619                         if(own.portal_in)
620                                 Portal_Disconnect(own.portal_in, own.portal_out);
621                         Portal_Remove(own.portal_out, 0);
622                 }
623 }
624
625 entity Portal_Spawn(entity own, vector org, vector ang)
626 {
627         entity portal;
628
629         fixedmakevectors(ang);
630         if(!CheckWireframeBox(own, org - 48 * v_right - 48 * v_up + 16 * v_forward, 96 * v_right, 96 * v_up, 96 * v_forward))
631                 return world;
632
633         portal = spawn();
634         portal.classname = "portal";
635         portal.aiment = own;
636         setorigin(portal, org);
637         portal.mangle = ang;
638         portal.angles = ang;
639         portal.angles_x = -portal.angles.x; // is a bmodel
640         portal.think = Portal_Think;
641         portal.nextthink = 0;
642         portal.portal_activatetime = time + 0.1;
643         portal.takedamage = DAMAGE_AIM;
644         portal.event_damage = Portal_Damage;
645         portal.fade_time = time + autocvar_g_balance_portal_lifetime;
646         portal.health = autocvar_g_balance_portal_health;
647         setmodel(portal, "models/portal.md3");
648         portal.savemodelindex = portal.modelindex;
649         portal.customizeentityforclient = Portal_Customize;
650
651         if(!Portal_FindSafeOrigin(portal))
652         {
653                 remove(portal);
654                 return world;
655         }
656
657         setsize(portal, '-48 -48 -48', '48 48 48');
658         Portal_MakeWaitingPortal(portal);
659
660         return portal;
661 }
662
663 float Portal_SpawnInPortalAtTrace(entity own, vector dir, float portal_id_val)
664 {
665         entity portal;
666         vector ang;
667         vector org;
668
669         org = trace_endpos;
670         ang = fixedvectoangles2(trace_plane_normal, dir);
671         fixedmakevectors(ang);
672
673         portal = Portal_Spawn(own, org, ang);
674         if(!portal)
675                 return 0;
676
677         portal.portal_id = portal_id_val;
678         Portal_SetInPortal(own, portal);
679
680         return 1;
681 }
682
683 float Portal_SpawnOutPortalAtTrace(entity own, vector dir, float portal_id_val)
684 {
685         entity portal;
686         vector ang;
687         vector org;
688
689         org = trace_endpos;
690         ang = fixedvectoangles2(trace_plane_normal, dir);
691         fixedmakevectors(ang);
692
693         portal = Portal_Spawn(own, org, ang);
694         if(!portal)
695                 return 0;
696
697         portal.portal_id = portal_id_val;
698         Portal_SetOutPortal(own, portal);
699
700         return 1;
701 }