-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathplats.qc
874 lines (698 loc) · 23.3 KB
/
plats.qc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
void() plat_center_touch;
void() plat_outside_touch;
void() plat_trigger_use;
void() plat_go_up;
void() plat_go_down;
void() plat_crush;
float PLAT_LOW_TRIGGER = 1;
void() plat_spawn_inside_trigger =
{
local entity trigger;
local vector tmin, tmax;
//
// middle trigger
//
trigger = spawn();
trigger.filtertarget = self.filtertarget;
trigger.touch = plat_center_touch;
trigger.movetype = MOVETYPE_NONE;
trigger.solid = SOLID_TRIGGER;
trigger.enemy = self;
tmin = self.mins + '25 25 0';
tmax = self.maxs - '25 25 -8';
tmin_z = tmax_z - (self.pos1_z - self.pos2_z + 8);
if (self.spawnflags & PLAT_LOW_TRIGGER)
tmax_z = tmin_z + 8;
if (self.size_x <= 50)
{
tmin_x = (self.mins_x + self.maxs_x) / 2;
tmax_x = tmin_x + 1;
}
if (self.size_y <= 50)
{
tmin_y = (self.mins_y + self.maxs_y) / 2;
tmax_y = tmin_y + 1;
}
setsize (trigger, tmin, tmax);
};
void() plat_hit_top =
{
sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
self.state = STATE_TOP;
self.think = plat_go_down;
self.nextthink = self.ltime + 3;
};
void() plat_hit_bottom =
{
sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
self.state = STATE_BOTTOM;
};
void() plat_go_down =
{
sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
self.state = STATE_DOWN;
SUB_CalcMove (self.pos2, self.speed, plat_hit_bottom);
};
void() plat_go_up =
{
sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
self.state = STATE_UP;
SUB_CalcMove (self.pos1, self.speed, plat_hit_top);
};
void() plat_center_touch =
{
// from Copper -- dumptruck_ds
if (!CheckValidTouch()) return;
self = self.enemy;
if (self.state == STATE_BOTTOM)
plat_go_up ();
else if (self.state == STATE_TOP)
self.nextthink = self.ltime + 1; // delay going down
};
void() plat_outside_touch =
{
// from Copper -- dumptruck_ds
if (!CheckValidTouch()) return;
//dprint ("plat_outside_touch\n");
self = self.enemy;
if (self.state == STATE_TOP)
plat_go_down ();
};
void() plat_trigger_use =
{
if (self.think)
return; // allready activated
plat_go_down();
};
void() plat_crush =
{
//dprint ("plat_crush\n");
T_Damage (other, self, self, 1);
if (self.state == STATE_UP)
plat_go_down ();
else if (self.state == STATE_DOWN)
plat_go_up ();
else
objerror ("plat_crush: bad self.state\n");
};
void() plat_use =
{
self.use = SUB_Null;
if (self.state != STATE_UP)
objerror ("plat_use: not in up state");
plat_go_down();
};
/*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER X X X X X X X NOT_ON_EASY NOT_ON_NORMAL NOT_ON_HARD_OR_NIGHTMARE NOT_IN_DEATHMATCH NOT_IN_COOP NOT_IN_SINGLEPLAYER X NOT_ON_HARD_ONLY NOT_ON_NIGHTMARE_ONLY
speed default 150
Plats are always drawn in the extended position, so they will light correctly.
If the plat is the target of another trigger or button, it will start out disabled in the extended position until it is trigger, when it will lower and become a normal plat.
If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determined by the model's height.
Set "sounds" to one of the following:
1) base fast
2) chain slow
*/
void() func_plat =
{
if (SUB_Inhibit ()) // new spawnflags for all entities -- iw
return;
if (!self.t_length)
self.t_length = 80;
if (!self.t_width)
self.t_width = 10;
if (self.sounds == 0)
self.sounds = 2;
// FIX THIS TO LOAD A GENERIC PLAT SOUND
if (self.sounds == 1)
{
precache_sound ("plats/plat1.wav");
precache_sound ("plats/plat2.wav");
self.noise = "plats/plat1.wav";
self.noise1 = "plats/plat2.wav";
}
if (self.sounds == 2)
{
precache_sound ("plats/medplat1.wav");
precache_sound ("plats/medplat2.wav");
self.noise = "plats/medplat1.wav";
self.noise1 = "plats/medplat2.wav";
}
self.mangle = self.angles;
self.angles = '0 0 0';
self.classname = "plat";
self.solid = SOLID_BSP;
self.movetype = MOVETYPE_PUSH;
setorigin (self, self.origin);
setmodel (self, self.model);
setsize (self, self.mins , self.maxs);
self.blocked = plat_crush;
if (!self.speed)
self.speed = 150;
// pos1 is the top position, pos2 is the bottom
self.pos1 = self.origin;
self.pos2 = self.origin;
if (self.height)
self.pos2_z = self.origin_z - self.height;
else
self.pos2_z = self.origin_z - self.size_z + 8;
self.use = plat_trigger_use;
plat_spawn_inside_trigger (); // the "start moving" trigger
if (self.targetname != "")
{
self.state = STATE_UP;
self.use = plat_use;
}
else
{
setorigin (self, self.pos2);
self.state = STATE_BOTTOM;
}
};
//============================================================================
// TRAINS
//============================================================================
void() train_next;
void() func_train_find;
float TRAIN_RETRIGGER = 1;
float TRAIN_MOVEONTRIGGER = 2;
float TRAIN_STOPONTRIGGER = 4;
float TRAIN_NONSOLID = 8;
float TRAIN_NOROTATE = 16;
float TRAIN_ROTATEY = 32;
float TRAIN_CUSTOMALIGN = 64;
float TRAIN_NEXT_WAIT = 0; //normal movement
float TRAIN_NEXT_STOP = 1; // force a stop on the next path_corner
float TRAIN_NEXT_CONTINUE = 2; // force continue on the next path_corner (ignores wait time)
float TRAIN_STYLE_SINGLEANIM = 1;
float TRAIN_ANIMTYPE_FORWARD = 1;
float TRAIN_ANIMTYPE_BACKFORTH = 2;
void() train_touch = {
// Deliver damage if player touches the train at all
if (self.dmg_take == DOOR_DMG_TOUCH)
{
T_Damage (other, self, self, self.dmg);
}
// Deliver damage only if the player touches the train as it's moving
if (self.dmg_take == DOOR_DMG_TOUCH_MOVING)
{
if (self.velocity != '0 0 0')
{
T_Damage (other, self, self, self.dmg);
}
}
};
void() train_blocked = {
if (time < self.attack_finished) return;
self.attack_finished = time + 0.5;
T_Damage (other, self, self, self.dmg);
};
// Use function for when the train is stopped.
// Makes the train continue on path_corners with wait -1,
// or forces a continue if the "move on trigger" spawnflag is set.
void() train_use = {
if (self.spawnflags & TRAIN_MOVEONTRIGGER || self.wait < 0) {
train_next();
return;
}
// Train has already moved after startup, and has no "stop on trigger" flag,
// so ignore activation.
if (self.think != func_train_find && self.plat2GoTo != TRAIN_NEXT_STOP)
return;
train_next();
};
// Use function for when the train is moving.
// Forces a stop or an instant continue on the next path_corner depending on the spawnflag set
void() train_moving_use = {
if (self.spawnflags & TRAIN_MOVEONTRIGGER || self.wait < 0)
self.plat2GoTo = TRAIN_NEXT_CONTINUE;
else if (self.spawnflags & TRAIN_STOPONTRIGGER)
self.plat2GoTo = TRAIN_NEXT_STOP;
}
// path_corner has been reached, so decide what to do next
void() train_wait = {
float localtime;
if (self.movetype == MOVETYPE_PUSH) localtime = self.ltime;
else localtime = time;
self.use = train_use; //ready to be re-triggered
// from Copper
// Trains now fire their path_corners' targets on arrival.
// If a player is riding the train, treat them as activator.
activator = nextent(world);
while (!EntitiesTouching(self,activator) && activator.classname == "player")
activator = nextent(activator);
if (activator.classname != "player")
activator = nextent(world); // default to player1 wherever they are
SUB_UseEntTargets(self.enemy);
if (self.enemy.speed) self.speed2 = self.enemy.speed; // sets the speed from the current path_corner
// copies the modeltrain animation parameters set in the path_corner, if any
if (self.classname == "misc_modeltrain") {
if (self.enemy.first_frame) self.first_frame = self.enemy.first_frame;
if (self.enemy.last_frame) self.last_frame = self.enemy.last_frame;
if (self.enemy.first_frame2) self.first_frame2 = self.enemy.first_frame2;
if (self.enemy.last_frame2) self.last_frame2 = self.enemy.last_frame2;
if (self.enemy.frtime) self.frtime = self.enemy.frtime;
if (self.enemy.frtime2) self.frtime2 = self.enemy.frtime2;
if (self.enemy.animtype) self.animtype = self.enemy.animtype;
if (self.enemy.animtype2) self.animtype2 = self.enemy.animtype2;
if (self.enemy.multiplier) self.multiplier = self.enemy.multiplier;
}
self.think = train_next;
// train is moving normally and path_corner has a wait set, so pause for that time.
if (self.wait > 0 && self.plat2GoTo == TRAIN_NEXT_WAIT && !(self.spawnflags & TRAIN_RETRIGGER)) {
// state: stopped
self.state = 0;
if (self.classname == "misc_modeltrain") SUB_CallAsSelf(self.animcontroller.think, self.animcontroller);
self.nextthink = localtime + self.wait;
//play stopping sound. If path_corner has a custom sound, play that instead
if (self.enemy.noise != "") sound(self, CHAN_WEAPON, self.enemy.noise, 1, ATTN_NORM);
else sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
}
// train is moving normally and path_corner has no wait time,
// or has been forced to move instantly through a triggering.
else if (self.plat2GoTo != TRAIN_NEXT_STOP && !self.wait && !(self.spawnflags & TRAIN_RETRIGGER)) {
//play "passing by" sound, if any. If path_corner has a custom one, play that instead
if (self.enemy.noise2 != "") sound(self, CHAN_WEAPON, self.enemy.noise2, 1, ATTN_NORM);
else if (self.noise2 != "") sound(self, CHAN_WEAPON, self.noise2, 1, ATTN_NORM);
self.nextthink = -1; // move instantly
train_next();
}
// path_corner has wait -1, or train has been forced to stop through a triggering.
// Also catches the backwards compatible case for the original rubicon2 "retrigger" flag.
else {
//play stopping sound. If path_corner has a custom sound, play that instead
if (self.enemy.noise != "") sound(self, CHAN_WEAPON, self.enemy.noise, 1, ATTN_NORM);
else sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
// state: stopped
self.state = 0;
if (self.classname == "misc_modeltrain") SUB_CallAsSelf(self.animcontroller.think, self.animcontroller);
self.nextthink = -1;
}
};
// searches for the next path_corner and sends the train on its way
void() train_next = {
local entity targ;
vector destang, displ;
targ = find(world, targetname, self.target);
if (!targ || self.target == "")
objerror("train_next: no next target");
self.target = targ.target;
// gets the wait time from the upcoming path_corner if set
if (targ.wait){
// wait -2 on the path_corner forces it to continue moving (no wait),
// even if the train has a default wait time
if (targ.wait == -2) self.wait = 0;
else self.wait = targ.wait;
}
// uses train's current wait time otherwise
else
self.wait = self.pausetime;
self.enemy = targ;
sound(self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
if(!(!self.wait && self.plat2GoTo == TRAIN_NEXT_CONTINUE)) self.plat2GoTo = TRAIN_NEXT_WAIT;
//store up any premature triggerings until current movement is finished
self.use = train_moving_use;
float waitwascalled =0;
if (self.classname == "misc_modeltrain") {
if (!(self.spawnflags & TRAIN_NOROTATE)) {
void() angleMoveDone = SUB_Null;
if (targ.origin == self.origin) angleMoveDone = train_wait;
if(targ.angles)
destang = targ.angles;
else if (targ.origin != self.origin)
destang = vectoangles(targ.origin - self.origin);
else
destang = '0 0 0';
if(destang != '0 0 0')
{
if (self.spawnflags & TRAIN_ROTATEY) {
destang_x = self.angles_x;
destang_z = self.angles_z;
}
if (self.multiplier > 0)
{
if(angleMoveDone == train_wait) waitwascalled = 1;
SUB_CalcAngleMoveController(destang, self.speed2*self.multiplier, angleMoveDone, self.rotatecontroller);
}
else
self.angles = destang;
}
}
}
if (!self.state) {
self.state = 1;
if (self.classname == "misc_modeltrain" && self.launchtime != TRAIN_STYLE_SINGLEANIM)
SUB_CallAsSelf(self.animcontroller.think, self.animcontroller);
}
if (targ.origin == self.origin) //Rotation done, no translation
{
if(!waitwascalled) train_wait();
return;
}
// if the TRAIN_CUSTOMALIGN flag is checked then the train should align
// with its path_corners based on the location of an "origin" brush added
// to the train by the mapper instead of the mins corner
// -therektafire
float doDisplace;
if (self.spawnflags & TRAIN_CUSTOMALIGN) doDisplace = 0.0;
else doDisplace = 1.0;
if (self.classname != "misc_modeltrain") displ = self.mins;
SUB_CalcMove(targ.origin - (displ * doDisplace), self.speed2, train_wait);
};
// searches for the first path_corner after the train entity is initialized
void() func_train_find = {
local entity targ;
float localtime;
vector displ;
if (self.movetype == MOVETYPE_PUSH) localtime = self.ltime;
else localtime = time;
targ = find(world, targetname, self.target);
self.target = targ.target;
float doDisplace;
if (self.spawnflags & TRAIN_CUSTOMALIGN) doDisplace = 0.0;
else doDisplace = 1.0;
if (self.classname != "misc_modeltrain") displ = self.mins;
self.enemy = targ;
setorigin(self, targ.origin - (displ * doDisplace));
if (targ.speed) self.speed2 = targ.speed; // uses speed from the 1st path corner if set
if (!self.targetname)
{ // not triggered, so start immediately
self.nextthink = localtime + 0.1;
self.think = train_next;
}
};
void() synchronizer_think = {
vector location;
location = self.owner.origin + self.owner.mins;
makevectors(self.owner.mangle);
vector dir = v_forward * 256;
traceline(location, location + dir, FALSE, self);
if (trace_ent.targetname == self.owner.synctarget && trace_ent != self.prev_entity)
{
SUB_UseSpecificTargetOnSpecificEntity(self.owner.synctarget, trace_ent, targetname);
self.prev_entity = trace_ent;
}
self.think = synchronizer_think;
self.nextthink = time + 0.01;
}
/*QUAKED func_train (0 .5 .8) ? RETRIGGER
Trains are moving platforms that players can ride.
The targets origin specifies the min point of the train at each corner.
The train spawns at the first target it is pointing at.
If the train is the target of a button or trigger, it will not begin moving until activated.
RETRIGGER: stop at each path_corner and don't resume until triggered again (ignores wait time)
speed default 100
dmg default 2
sounds
1) ratchet metal
2) base
*/
float INVISIBLE = 16;
void() func_train =
{
if (SUB_Inhibit ()) // new spawnflags for all entities -- iw
return;
if (self.spawnflags == /*Breakable*/128)
func_breakable();
if (!self.speed)
self.speed = 100;
if (!self.target)
objerror ("func_train without a target");
if (!self.dmg)
self.dmg = 2;
if(self.spawnflags & TRAIN_STOPONTRIGGER && self.spawnflags & TRAIN_MOVEONTRIGGER)
objerror("func_train: Stop and move on trigger set at the same time");
if (self.sounds == 1) {
if (self.noise == "") self.noise = ("plats/train2.wav");
if (self.noise1 == "") self.noise1 = ("plats/train1.wav");
}
else if (self.sounds == 2) { // base door sound
if (self.noise == "") self.noise = "doors/hydro2.wav";
if (self.noise1 == "") self.noise1 = "doors/hydro1.wav";
}
else {
if (self.noise == "") self.noise = ("misc/null.wav");
if (self.noise1 == "") self.noise1 = ("misc/null.wav");
}
precache_sound(self.noise);
precache_sound(self.noise1);
if (self.noise2 != "") precache_sound(self.noise2);
if (self.noise3 != "") precache_sound(self.noise3); //Breaking sound (applicable to breakable trains)
self.plat2GoTo = TRAIN_NEXT_WAIT;
if (self.spawnflags & TRAIN_NONSOLID) {
self.solid = SOLID_NOT;
self.movetype = MOVETYPE_NOCLIP;
}
else {
self.solid = SOLID_BSP;
self.movetype = MOVETYPE_PUSH;
}
self.blocked = train_blocked;
self.use = train_use;
self.touch = train_touch;
setmodel (self, self.model);
setsize (self, self.mins , self.maxs);
setorigin (self, self.origin);
if (self.synctarget)
{
entity synchronizer = spawn();
setorigin(synchronizer, self.origin);
synchronizer.owner = self;
synchronizer.think = synchronizer_think;
synchronizer.nextthink = time + 0.2;
}
if ( self.spawnflags & INVISIBLE )
{
self.model = string_null;
}
if (self.mangle)
{
self.mangle[1] += 90;
}
// start trains on the second frame, to make sure their targets have had
// a chance to spawn
if (self.movetype == MOVETYPE_PUSH) self.nextthink = self.ltime + 0.1;
else self.nextthink = time + 0.1;
self.think = func_train_find;
self.speed2 = self.speed;
};
//===================================================
void() animcontroller_think = {
entity tr;
tr = self.owner;
float first, last, step, atype, dir, nextframe;
// train just went from stopped to moving or vice-versa,
// and have both animations set
if (self.state != tr.state && tr.launchtime != TRAIN_STYLE_SINGLEANIM) {
if (tr.state) { // just started moving
tr.frame = zeroconvert(tr.first_frame2);
}
else { // just stopped
tr.frame = zeroconvert(tr.first_frame);
}
tr.distance = 1; // reset back/forth status
self.state = tr.state;
}
else {
if (self.state && tr.launchtime != TRAIN_STYLE_SINGLEANIM) { // moving train animation, if set
first = zeroconvert(tr.first_frame2);
last = zeroconvert(tr.last_frame2);
atype = tr.animtype2;
}
else { // stopped/default train animation
first = zeroconvert(tr.first_frame);
last = zeroconvert(tr.last_frame);
atype = tr.animtype;
}
if (first > last) step = dir = -1; // reverse direction
else step = dir = 1;
// back-and-forth is going the other way, so invert the step's signal
if (tr.distance < 0) step = step * (-1);
nextframe = tr.frame + step;
if (dir > 0 && (nextframe > last || nextframe < first) ||
dir < 0 && (nextframe > first || nextframe < last)
) {
if (atype == TRAIN_ANIMTYPE_BACKFORTH){
nextframe = tr.frame - step;
tr.distance = tr.distance * (-1);
}
else if (atype == /*Animate once*/3)
nextframe = tr.frame;
}
if (dir > 0) nextframe = wrap(nextframe, first, last);
else nextframe = wrap(nextframe, last, first);
tr.frame = nextframe;
}
self.think = animcontroller_think;
if (self.state || tr.launchtime == TRAIN_STYLE_SINGLEANIM) self.nextthink = time + tr.frtime;
else self.nextthink = time + tr.frtime2;
};
void() misc_modeltrain = {
if (SUB_Inhibit ()) // new spawnflags for all entities -- iw
return;
precache_model(self.mdl);
func_train();
if (self.spawnflags & TRAIN_NONSOLID)
self.solid = SOLID_NOT;
else {
self.solid = SOLID_BBOX;
if (self.cmins == VEC_ORIGIN) self.cmins = '-8 -8 -8';
if (self.cmaxs == VEC_ORIGIN) self.cmaxs = '8 8 8';
}
self.movetype = MOVETYPE_NOCLIP;
setmodel (self, self.mdl);
setsize (self, self.cmins , self.cmaxs);
setorigin (self, self.origin);
entity rot, anim;
rot = spawn();
self.rotatecontroller = rot;
rot.classname = "rotatecontroller";
rot.owner = self;
anim = spawn();
self.animcontroller = anim;
anim.classname = "animcontroller";
anim.owner = self;
anim.think = animcontroller_think;
anim.nextthink = time + 0.2;
self.distance = 1;
if (!self.frtime) self.frtime = 0.1;
if (!self.frtime2) self.frtime2 = self.frtime;
if (!self.multiplier) self.multiplier = 1;
// make sure all first and last frame fields are filled
if (self.first_frame && !self.last_frame) self.last_frame = self.first_frame;
else if (!self.first_frame && self.last_frame) self.first_frame = self.last_frame;
if (self.first_frame2 && !self.last_frame2) self.last_frame2 = self.first_frame2;
else if (!self.first_frame2 && self.last_frame2) self.first_frame2 = self.last_frame2;
if (!self.first_frame2) self.launchtime = TRAIN_STYLE_SINGLEANIM;
if (!self.animtype) self.animtype = TRAIN_ANIMTYPE_FORWARD;
if (!self.animtype2) self.animtype2 = self.animtype;
self.frame = self.first_frame;
};
//===================================================
//Code for the fixed teleporttrain written by c0burn and modified by ZungryWare
void() teleporttrain_calcmove;
void() teleporttrain_next =
{
local vector dir;
setorigin(self, self.enemy.origin + '16 16 16');
if (!self.target)
{
self.enemy = world;
return;
}
self.enemy = find(world, targetname, self.target);
if (self.enemy.classname == "path_corner")
{
dir = normalize((self.enemy.origin + '16 16 16') - self.origin);
self.velocity = dir * self.speed;
self.target = self.enemy.target;
}
else
{
objerror("unable to find target\n");
remove(self);
}
teleporttrain_calcmove();
};
void() teleporttrain_wait =
{
local float wait_time;
if (self.enemy.wait > 0)
wait_time = self.enemy.wait;
else
wait_time = .1;
self.velocity = '0 0 0';
self.nextthink = time + wait_time;
self.think = teleporttrain_next;
}
void() teleporttrain_calcmove =
{
local float len;
local vector delta;
local float spd;
delta = (self.enemy.origin + '16 16 16') - self.origin;
len = vlen(delta);
spd = vlen(self.velocity);
self.nextthink = time + (len / spd);
self.think = teleporttrain_wait;
};
void() teleporttrain_use =
{
if (self.velocity == '0 0 0')
{
teleporttrain_next();
}
};
void() teleporttrain_find =
{
// always start positioned on the first path_corner
self.enemy = find(world, targetname, self.target);
if (self.enemy.classname == "path_corner")
{
setorigin (self, self.enemy.origin + '16 16 16');
self.target = self.enemy.target;
}
else
{
objerror("unable to find target\n");
remove(self);
return;
}
if (self.spawnflags & 4) // start immediately even with a targetname
{
teleporttrain_next();
}
// not triggered, so start immediately
else if (!self.targetname)
{
teleporttrain_next();
}
else
{
self.use = teleporttrain_use;
}
};
/*QUAKED misc_teleporttrain (.5 .5 .5) (-16 -16 -16) (16 16 16) X DONT_ROTATE START_ON_WITH_TARGETNAME INVISIBLE X X X X NOT_ON_EASY NOT_ON_NORMAL NOT_ON_HARD_OR_NIGHTMARE NOT_IN_DEATHMATCH NOT_IN_COOP NOT_IN_SINGLEPLAYER X NOT_ON_HARD_ONLY NOT_ON_NIGHTMARE_ONLY
{ model("progs/teleport.mdl"); }
This was used for the final boss level in the original game. In progs_dump you can use this as a moving decoration or even target it as a spawn point for a func_monster_spawner. Make sure and select the Don't Rotate spawnflag though or you'll experience a pretty hilarious effect!
You set this up like a func_train using path_corners. By default, it will move automatically between path corners upon map load. However, you can have it wait to move by giving it a targetname. If you need to target it as a spawner and want it to move on map load, use the Start On spawnflag.
You can add effects, use a custom model using the mdl_body keyvalue and even make it invisible with spawnflag 8.
Effects
0: "None (Default)"
1 : "Brightfield (yellow particles)"
4 : "Bright light"
8 : "Dim light"
Teleporter target for final boss level. Must target a series of 'path_corner' entities.
It will position itself on its first target at map load.
If a targetname is set, it must be triggered to start moving, otherwise it will start automatically.
*/
void() misc_teleporttrain =
{
if (SUB_Inhibit ()) // new spawnflags for all entities -- iw
return;
if (!self.target)
{
objerror ("misc_teleporttrain has no target");
remove(self);
return;
}
if (self.speed <= 0)
{
self.speed = 100;
}
self.solid = SOLID_NOT;
self.movetype = MOVETYPE_FLY;
precache_body_model ("progs/teleport.mdl"); // custom custom_mdls -- dumptruck_ds
precache_model ("progs/s_null.spr"); // invisble -- dumptruck_ds
if (self.spawnflags & 8)
// setmodel (self, "progs/s_null.spr");
body_model ("progs/s_null.spr");
else
body_model ("progs/teleport.mdl");
// precache_model ("progs/teleport.mdl");
resize('-16 -16 -16', '16 16 16');
//Causes the ball to spin around like it was originally intended to.
if (!(self.spawnflags & 2)) //don't spin - helpful for invisible spawner -- dumptruck_ds
self.avelocity = '40 80 120';
// self.avelocity = '100 200 300';
self.think = teleporttrain_find;
self.nextthink = time + 0.1;
};