1 /*
2 ** thingdef_codeptr.cpp
3 **
4 **---------------------------------------------------------------------------
5 ** Copyright 2011 Braden Obrzut
6 ** All rights reserved.
7 **
8 ** Redistribution and use in source and binary forms, with or without
9 ** modification, are permitted provided that the following conditions
10 ** are met:
11 **
12 ** 1. Redistributions of source code must retain the above copyright
13 ** notice, this list of conditions and the following disclaimer.
14 ** 2. Redistributions in binary form must reproduce the above copyright
15 ** notice, this list of conditions and the following disclaimer in the
16 ** documentation and/or other materials provided with the distribution.
17 ** 3. The name of the author may not be used to endorse or promote products
18 ** derived from this software without specific prior written permission.
19 **
20 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 **---------------------------------------------------------------------------
31 **
32 ** Some action pointer code may closely resemble ZDoom code since they
33 ** should behave the same.
34 **
35 */
36
37 #include "actor.h"
38 #include "id_ca.h"
39 #include "id_sd.h"
40 #include "g_mapinfo.h"
41 #include "g_shared/a_deathcam.h"
42 #include "g_shared/a_inventory.h"
43 #include "lnspec.h"
44 #include "m_random.h"
45 #include "thingdef/thingdef.h"
46 #include "wl_act.h"
47 #include "wl_def.h"
48 #include "wl_agent.h"
49 #include "wl_draw.h"
50 #include "wl_game.h"
51 #include "wl_play.h"
52 #include "wl_state.h"
53
54 static ActionTable *actionFunctions = NULL;
ActionInfo(ActionPtr func,const FName & name)55 ActionInfo::ActionInfo(ActionPtr func, const FName &name) : func(func), name(name),
56 minArgs(0), maxArgs(0), varArgs(false)
57 {
58 if(actionFunctions == NULL)
59 actionFunctions = new ActionTable;
60 #if 0
61 // Debug code - Show registered action functions
62 printf("Adding %s @ %d\n", name.GetChars(), actionFunctions->Size());
63 #endif
64 actionFunctions->Push(this);
65 }
66
FunctionTableComp(const void * f1,const void * f2)67 int FunctionTableComp(const void *f1, const void *f2)
68 {
69 const ActionInfo * const func1 = *((const ActionInfo **)f1);
70 const ActionInfo * const func2 = *((const ActionInfo **)f2);
71 if(func1->name < func2->name)
72 return -1;
73 else if(func1->name > func2->name)
74 return 1;
75 return 0;
76 }
77
InitFunctionTable(ActionTable * table)78 void InitFunctionTable(ActionTable *table)
79 {
80 if(table == NULL)
81 table = actionFunctions;
82
83 qsort(&(*table)[0], table->Size(), sizeof((*table)[0]), FunctionTableComp);
84 for(unsigned int i = 1;i < table->Size();++i)
85 assert((*table)[i]->name > (*table)[i-1]->name);
86 }
87
ReleaseFunctionTable()88 void ReleaseFunctionTable()
89 {
90 delete actionFunctions;
91 }
92
LookupFunction(const FName & func,const ActionTable * table)93 ActionInfo *LookupFunction(const FName &func, const ActionTable *table)
94 {
95 if(table == NULL)
96 table = actionFunctions;
97
98 ActionInfo *inf;
99 unsigned int max = table->Size()-1;
100 unsigned int min = 0;
101 unsigned int mid = max/2;
102 do
103 {
104 inf = (*table)[mid];
105 if(inf->name == func)
106 return inf;
107
108 if(inf->name > func)
109 max = mid-1;
110 else if(inf->name < func)
111 min = mid+1;
112 mid = (max+min)/2;
113 }
114 while(max >= min && max < table->Size());
115 return NULL;
116 }
117
118 ////////////////////////////////////////////////////////////////////////////////
119
ACTION_FUNCTION(A_CallSpecial)120 ACTION_FUNCTION(A_CallSpecial)
121 {
122 ACTION_PARAM_INT(special, 0);
123 ACTION_PARAM_INT(arg1, 1);
124 ACTION_PARAM_INT(arg2, 2);
125 ACTION_PARAM_INT(arg3, 3);
126 ACTION_PARAM_INT(arg4, 4);
127 ACTION_PARAM_INT(arg5, 5);
128
129 int specialArgs[5] = {arg1, arg2, arg3, arg4, arg5};
130
131 Specials::LineSpecialFunction function = Specials::LookupFunction(static_cast<Specials::LineSpecials>(special));
132 return function(map->GetSpot(self->tilex, self->tiley, 0), specialArgs, MapTrigger::East, self) != 0;
133 }
134
135 ////////////////////////////////////////////////////////////////////////////////
136
137 extern FRandom pr_chase;
ACTION_FUNCTION(A_ActiveSound)138 ACTION_FUNCTION(A_ActiveSound)
139 {
140 ACTION_PARAM_INT(chance, 0);
141
142 // If chance == 3 this has the same chance as A_Chase. Useful for giving
143 // wolfenstein style monsters activesounds without making it 8x as likely
144 if(chance >= 256 || pr_chase() < chance)
145 PlaySoundLocActor(self->activesound, self);
146 return true;
147 }
148
ACTION_FUNCTION(A_AlertMonsters)149 ACTION_FUNCTION(A_AlertMonsters)
150 {
151 madenoise = true;
152 return true;
153 }
154
ACTION_FUNCTION(A_BossDeath)155 ACTION_FUNCTION(A_BossDeath)
156 {
157 // Deathcam involves a little bit of spaghetti code. To sum it up:
158 // 1.) This function creates the death cam and lets it's spawn state run.
159 // Victory flag is set by the death cam.
160 // 2.) After the actor's death state runs again this function should be
161 // called. It will restart the spawn state for timing.
162 // 3.) The deathcam signals this function for the third time with the
163 // victory flag removed. We'll call the action specials and then if
164 // we're still in the game, return control to the player.
165 ADeathCam *deathcam = NULL;
166
167 bool alldead = true;
168 AActor::Iterator iter = AActor::GetIterator();
169 while(iter.Next())
170 {
171 AActor * const other = iter;
172
173 if(other != self)
174 {
175 if(other->GetClass() == NATIVE_CLASS(DeathCam))
176 deathcam = static_cast<ADeathCam *> (other);
177 else if(other->GetClass() == self->GetClass())
178 {
179 if(other->health > 0)
180 {
181 alldead = false;
182 break;
183 }
184 }
185 }
186 }
187
188 if(!alldead)
189 return false;
190
191 if(levelInfo->DeathCam && (!deathcam || deathcam->camState != ADeathCam::CAM_FINISHED))
192 {
193 if(!deathcam)
194 {
195 ADeathCam *dc = (ADeathCam*)AActor::Spawn(NATIVE_CLASS(DeathCam), 0, 0, 0, SPAWN_AllowReplacement);
196 dc->SetupDeathCam(self, players[0].mo);
197 }
198 else
199 {
200 // Let the deathcam reanimate
201 deathcam->SetState(deathcam->SpawnState);
202 }
203 }
204 else
205 {
206 for(unsigned int i = 0;i < levelInfo->SpecialActions.Size();++i)
207 {
208 const LevelInfo::SpecialAction &action = levelInfo->SpecialActions[i];
209 if(action.Class == self->GetClass())
210 {
211 const Specials::LineSpecials special = static_cast<Specials::LineSpecials>(action.Special);
212 Specials::LineSpecialFunction function = Specials::LookupFunction(special);
213 function(map->GetSpot(self->tilex, self->tiley, 0), action.Args, MapTrigger::East, self);
214 }
215 }
216
217 if(deathcam && playstate == ex_stillplaying)
218 {
219 // Return the camera to the player if we're still going
220 players[0].camera = players[0].mo;
221 players[0].BringUpWeapon();
222 gamestate.victoryflag = false;
223 }
224 }
225 return true;
226 }
227
228 // Sets or unsets a flag on an actor.
ACTION_FUNCTION(A_ChangeFlag)229 ACTION_FUNCTION(A_ChangeFlag)
230 {
231 ACTION_PARAM_STRING(flag, 0);
232 ACTION_PARAM_BOOL(value, 1);
233
234 // We'll also want to keep the counts consistant
235 const bool countedKill = !!(self->flags & FL_COUNTKILL);
236 const bool countedSecret = !!(self->flags & FL_COUNTSECRET);
237 const bool countedItem = !!(self->flags & FL_COUNTITEM);
238
239 FString prefix;
240 if(flag.IndexOf(".") != -1)
241 {
242 prefix = flag.Left(flag.IndexOf("."));
243 flag = flag.Mid(flag.IndexOf(".")+1);
244 }
245 if(!ClassDef::SetFlag(self->GetClass(), self, prefix, flag, value))
246 {
247 Printf("A_ChangeFlag: Attempt to change unknown flag '%s'.\n", (prefix.IsEmpty() ? flag.GetChars() : (prefix + "." + flag).GetChars()));
248 return false;
249 }
250
251 const bool countsKill = !!(self->flags & FL_COUNTKILL);
252 const bool countsSecret = !!(self->flags & FL_COUNTSECRET);
253 const bool countsItem = !!(self->flags & FL_COUNTITEM);
254 if(countedKill != countsKill)
255 {
256 if(countsKill) ++gamestate.killtotal;
257 else --gamestate.killtotal;
258 }
259 if(countedItem != countsItem)
260 {
261 if(countsItem) ++gamestate.treasuretotal;
262 else --gamestate.treasuretotal;
263 }
264 if(countedSecret != countsSecret)
265 {
266 if(countsSecret) ++gamestate.secrettotal;
267 else --gamestate.secrettotal;
268 }
269 return true;
270 }
271
ACTION_FUNCTION(A_ChangeVelocity)272 ACTION_FUNCTION(A_ChangeVelocity)
273 {
274 enum
275 {
276 CVF_RELATIVE = 1,
277 CVF_REPLACE = 2
278 };
279
280 ACTION_PARAM_DOUBLE(x, 0);
281 ACTION_PARAM_DOUBLE(y, 1);
282 ACTION_PARAM_DOUBLE(z, 2);
283 ACTION_PARAM_INT(flags, 3);
284
285 fixed fx, fy;
286 if(flags & CVF_RELATIVE)
287 {
288 fx = static_cast<fixed>(((x * finecosine[self->angle>>ANGLETOFINESHIFT]) + (y * finesine[self->angle>>ANGLETOFINESHIFT]))/64);
289 fy = static_cast<fixed>(((y * finecosine[self->angle>>ANGLETOFINESHIFT]) - (x * finesine[self->angle>>ANGLETOFINESHIFT]))/64);
290 }
291 else
292 {
293 fx = static_cast<fixed>(x*(FRACUNIT/64));
294 fy = static_cast<fixed>(y*(FRACUNIT/64));
295 }
296
297 if(flags & CVF_REPLACE)
298 {
299 self->velx = fx;
300 self->vely = fy;
301 }
302 else
303 {
304 self->velx += fx;
305 self->vely += fy;
306 }
307 return true;
308 }
309
ACTION_FUNCTION(A_Explode)310 ACTION_FUNCTION(A_Explode)
311 {
312 enum
313 {
314 XF_HURTSOURCE = 1
315 };
316
317 ACTION_PARAM_INT(damage, 0);
318 ACTION_PARAM_INT(radius, 1);
319 ACTION_PARAM_INT(flags, 2);
320 ACTION_PARAM_BOOL(alert, 3);
321 ACTION_PARAM_INT(fulldamageradius, 4);
322
323 if(alert)
324 madenoise = true;
325
326 const double rolloff = 1.0/static_cast<double>(radius - fulldamageradius);
327 for(AActor::Iterator iter = AActor::GetIterator();iter.Next();)
328 {
329 AActor * const target = iter;
330
331 // Calculate distance from origin to outer bound of target actor
332 const fixed dist = MAX(0, MAX(abs(target->x - self->x), abs(target->y - self->y)) - target->radius) >> (FRACBITS - 6);
333
334 // First check if the target is in range (also don't mess with ourself)
335 if(dist >= radius || target == self || !(target->flags & FL_SHOOTABLE))
336 continue;
337 // Next see if we should damage the target
338 if(!(flags&XF_HURTSOURCE) &&
339 !(!!(self->flags & FL_PLAYERMISSILE) ^ (target == players[0].mo)))
340 continue;
341
342 double output = damage;
343 if(dist > fulldamageradius)
344 output *= 1.0 - static_cast<double>(dist - fulldamageradius)*rolloff;
345 if(output <= 0.0)
346 continue;
347
348 if(target->player)
349 TakeDamage(static_cast<int>(output), self);
350 else
351 DamageActor(target, static_cast<unsigned int>(output));
352 }
353 return true;
354 }
355
ACTION_FUNCTION(A_FaceTarget)356 ACTION_FUNCTION(A_FaceTarget)
357 {
358 ACTION_PARAM_DOUBLE(max_turn, 0);
359 ACTION_PARAM_DOUBLE(max_pitch, 1);
360
361 A_Face(self, players[0].mo, angle_t(max_turn*ANGLE_45/45));
362 return true;
363 }
364
ACTION_FUNCTION(A_Fall)365 ACTION_FUNCTION(A_Fall)
366 {
367 self->flags &= ~FL_SOLID;
368 return true;
369 }
370
ACTION_FUNCTION(A_GiveExtraMan)371 ACTION_FUNCTION(A_GiveExtraMan)
372 {
373 ACTION_PARAM_INT(amount, 0);
374
375 GiveExtraMan(amount);
376 return true;
377 }
378
ACTION_FUNCTION(A_GiveInventory)379 ACTION_FUNCTION(A_GiveInventory)
380 {
381 ACTION_PARAM_STRING(className, 0);
382 ACTION_PARAM_INT(amount, 1);
383
384 const ClassDef *cls = ClassDef::FindClass(className);
385
386 if(amount == 0)
387 amount = 1;
388
389 if(cls && cls->IsDescendantOf(NATIVE_CLASS(Inventory)))
390 {
391 return self->GiveInventory(cls, amount);
392 }
393 return true;
394 }
395
ACTION_FUNCTION(A_GunFlash)396 ACTION_FUNCTION(A_GunFlash)
397 {
398 if(!self->player)
399 return false;
400
401 ACTION_PARAM_STATE(flash, 0, self->player->ReadyWeapon->FindState(self->player->ReadyWeapon->mode != AWeapon::AltFire ? NAME_Flash : NAME_AltFlash));
402
403 self->player->SetPSprite(flash, player_t::ps_flash);
404 return true;
405 }
406
407 #define STATE_JUMP(frame) DoStateJump(frame, self, caller, args, result)
DoStateJump(const Frame * frame,AActor * self,const Frame * const caller,const CallArguments & args,ActionResult * result)408 static void DoStateJump(const Frame *frame, AActor *self, const Frame * const caller, const CallArguments &args, ActionResult *result)
409 {
410 if(!frame)
411 return;
412
413 if(result)
414 {
415 result->JumpFrame = frame;
416 return;
417 }
418
419 if(self->player)
420 {
421 if(self->player->psprite[player_t::ps_weapon].frame == caller)
422 {
423 self->player->SetPSprite(frame, player_t::ps_weapon);
424 return;
425 }
426 else if(self->player->psprite[player_t::ps_flash].frame == caller)
427 {
428 self->player->SetPSprite(frame, player_t::ps_flash);
429 return;
430 }
431 }
432
433 self->SetState(frame);
434 }
435
436 static FRandom pr_cajump("CustomJump");
ACTION_FUNCTION(A_Jump)437 ACTION_FUNCTION(A_Jump)
438 {
439 ACTION_PARAM_INT(chance, 0);
440
441 if(chance >= 256 || pr_cajump() < chance)
442 {
443 ACTION_PARAM_STATE(frame, (ACTION_PARAM_COUNT == 2 ? 1 : (1 + pr_cajump() % (ACTION_PARAM_COUNT - 1))), NULL);
444
445 STATE_JUMP(frame);
446 }
447
448 // Jumps will always return false so that they don't trigger success as a whole
449 return false;
450 }
451
ACTION_FUNCTION(A_JumpIf)452 ACTION_FUNCTION(A_JumpIf)
453 {
454 ACTION_PARAM_BOOL(expr, 0);
455 ACTION_PARAM_STATE(frame, 1, NULL);
456
457 if(expr)
458 STATE_JUMP(frame);
459
460 // Jumps will always return false so that they don't trigger success as a whole
461 return false;
462 }
463
ACTION_FUNCTION(A_JumpIfCloser)464 ACTION_FUNCTION(A_JumpIfCloser)
465 {
466 ACTION_PARAM_DOUBLE(distance, 0);
467 ACTION_PARAM_STATE(frame, 1, NULL);
468
469 AActor *check;
470 if(self->player)
471 check = self->player->FindTarget();
472 else
473 check = players[0].mo;
474
475 // << 6 - Adjusts to Doom scale
476 if(check && P_AproxDistance((self->x-check->x)<<6, (self->y-check->y)<<6) < (fixed)(distance*FRACUNIT))
477 {
478 STATE_JUMP(frame);
479 }
480
481 // Jumps will always return false so that they don't trigger success as a whole
482 return false;
483 }
484
ACTION_FUNCTION(A_JumpIfInventory)485 ACTION_FUNCTION(A_JumpIfInventory)
486 {
487 ACTION_PARAM_STRING(className, 0);
488 ACTION_PARAM_INT(amount, 1);
489 ACTION_PARAM_STATE(frame, 2, NULL);
490
491 const ClassDef *cls = ClassDef::FindClass(className);
492 AInventory *inv = self->FindInventory(cls);
493
494 if(!inv)
495 return false;
496
497 // Amount of 0 means check if the amount is the maxamount.
498 // Otherwise check if we have at least that amount.
499 if((amount == 0 && inv->amount == inv->maxamount) ||
500 (amount > 0 && inv->amount >= static_cast<unsigned int>(amount)))
501 {
502 STATE_JUMP(frame);
503 }
504
505 // Jumps will always return false so that they don't trigger success as a whole
506 return false;
507 }
508
ACTION_FUNCTION(A_Light)509 ACTION_FUNCTION(A_Light)
510 {
511 ACTION_PARAM_INT(level, 0);
512
513 self->player->extralight = clamp(level, -20, 20);
514 return true;
515 }
516 // Might as well support these as well since they're far more popular than the
517 // generic version and don't require much code.
ACTION_FUNCTION(A_Light0)518 ACTION_FUNCTION(A_Light0) { self->player->extralight = 0; return true; }
ACTION_FUNCTION(A_Light1)519 ACTION_FUNCTION(A_Light1) { self->player->extralight = 1; return true; }
ACTION_FUNCTION(A_Light2)520 ACTION_FUNCTION(A_Light2) { self->player->extralight = 2; return true; }
521
522 static FRandom pr_meleeattack("MeleeAccuracy");
ACTION_FUNCTION(A_MeleeAttack)523 ACTION_FUNCTION(A_MeleeAttack)
524 {
525 ACTION_PARAM_INT(damage, 0);
526 ACTION_PARAM_DOUBLE(accuracy, 1);
527 ACTION_PARAM_STRING(hitsound, 2);
528 ACTION_PARAM_STRING(misssound, 3);
529
530 if(misssound.Compare("*") == 0)
531 misssound = hitsound;
532
533 A_Face(self, players[0].mo);
534 if(CheckMeleeRange(self, players[0].mo, self->speed))
535 {
536 if(pr_meleeattack() < static_cast<int>(accuracy*256))
537 {
538 TakeDamage(damage, self);
539 if(!hitsound.IsEmpty())
540 PlaySoundLocActor(hitsound, self);
541 return true;
542 }
543 }
544 if(!misssound.IsEmpty())
545 PlaySoundLocActor(misssound, self);
546 return false;
547 }
548
549 static FRandom pr_monsterrefire("MonsterRefire");
ACTION_FUNCTION(A_MonsterRefire)550 ACTION_FUNCTION(A_MonsterRefire)
551 {
552 ACTION_PARAM_INT(probability, 0);
553 ACTION_PARAM_STATE(jump, 1, NULL);
554
555 AActor *target = players[0].mo;
556 A_Face(self, target);
557
558 if(pr_monsterrefire() < probability)
559 return false;
560
561 if(jump && (
562 !(self->flags & FL_ATTACKMODE) ||
563 !target ||
564 target->health <= 0 ||
565 !CheckLine(self)
566 ))
567 {
568 STATE_JUMP(jump);
569 }
570 return true;
571 }
572
ACTION_FUNCTION(A_Pain)573 ACTION_FUNCTION(A_Pain)
574 {
575 PlaySoundLocActor(self->painsound, self);
576 return true;
577 }
578
ACTION_FUNCTION(A_PlaySound)579 ACTION_FUNCTION(A_PlaySound)
580 {
581 ACTION_PARAM_STRING(sound, 0);
582
583 PlaySoundLocActor(sound, self);
584 return true;
585 }
586
ACTION_FUNCTION(A_ScaleVelocity)587 ACTION_FUNCTION(A_ScaleVelocity)
588 {
589 ACTION_PARAM_DOUBLE(scale, 0);
590
591 self->velx = FLOAT2FIXED(self->velx*scale);
592 self->vely = FLOAT2FIXED(self->vely*scale);
593 return true;
594 }
595
ACTION_FUNCTION(A_SetTics)596 ACTION_FUNCTION(A_SetTics)
597 {
598 ACTION_PARAM_DOUBLE(duration, 0);
599
600 if(self->player)
601 {
602 if(self->player->psprite[player_t::ps_weapon].frame == caller)
603 {
604 self->player->psprite[player_t::ps_weapon].ticcount = static_cast<int> (duration*2);
605 return true;
606 }
607 else if(self->player->psprite[player_t::ps_flash].frame == caller)
608 {
609 self->player->psprite[player_t::ps_flash].ticcount = static_cast<int> (duration*2);
610 return true;
611 }
612 }
613
614 self->ticcount = static_cast<int> (duration*2);
615 return true;
616 }
617
ACTION_FUNCTION(A_SpawnItem)618 ACTION_FUNCTION(A_SpawnItem)
619 {
620 ACTION_PARAM_STRING(className, 0);
621 ACTION_PARAM_DOUBLE(distance, 1);
622 ACTION_PARAM_DOUBLE(zheight, 2);
623
624 const ClassDef *cls = ClassDef::FindClass(className);
625 if(cls == NULL)
626 return false;
627
628 AActor *newobj = AActor::Spawn(cls,
629 self->x + fixed(distance*finecosine[self->angle>>ANGLETOFINESHIFT])/64,
630 self->y - fixed(distance*finesine[self->angle>>ANGLETOFINESHIFT])/64,
631 0, SPAWN_AllowReplacement);
632 return true;
633 }
634
635 static FRandom pr_spawnitemex("SpawnItemEx");
ACTION_FUNCTION(A_SpawnItemEx)636 ACTION_FUNCTION(A_SpawnItemEx)
637 {
638 enum
639 {
640 SXF_TRANSFERPOINTERS = 0x1
641 };
642
643 ACTION_PARAM_STRING(className, 0);
644 ACTION_PARAM_DOUBLE(xoffset, 1);
645 ACTION_PARAM_DOUBLE(yoffset, 2);
646 ACTION_PARAM_DOUBLE(zoffset, 3);
647 ACTION_PARAM_DOUBLE(xvel, 4);
648 ACTION_PARAM_DOUBLE(yvel, 5);
649 ACTION_PARAM_DOUBLE(zvel, 6);
650 ACTION_PARAM_DOUBLE(angle, 7);
651 ACTION_PARAM_INT(flags, 8);
652 ACTION_PARAM_INT(chance, 9);
653
654 if(chance > 0 && pr_spawnitemex() < chance)
655 return false;
656
657 const ClassDef *cls = ClassDef::FindClass(className);
658 if(cls == NULL)
659 return false;
660
661 angle_t ang = self->angle>>ANGLETOFINESHIFT;
662
663 fixed x = self->x + fixed(xoffset*finecosine[ang])/64 + fixed(yoffset*finesine[ang])/64;
664 fixed y = self->y - fixed(xoffset*finesine[ang])/64 + fixed(yoffset*finecosine[ang])/64;
665 angle = angle_t((angle*ANGLE_45)/45) + self->angle;
666
667 AActor *newobj = AActor::Spawn(cls, x, y, 0, SPAWN_AllowReplacement);
668
669 if(flags & SXF_TRANSFERPOINTERS)
670 {
671 newobj->flags |= self->flags&(FL_ATTACKMODE|FL_FIRSTATTACK);
672 newobj->flags &= ~(~self->flags&(FL_PATHING));
673 if(newobj->flags & FL_ATTACKMODE)
674 newobj->speed = newobj->runspeed;
675 }
676
677 newobj->angle = static_cast<angle_t>(angle);
678
679 //We divide by 128 here since Wolf is 70hz instead of 35.
680 newobj->velx = (fixed(xvel*finecosine[ang]) + fixed(yvel*finesine[ang]))/128;
681 newobj->vely = (-fixed(xvel*finesine[ang]) + fixed(yvel*finecosine[ang]))/128;
682 return true;
683 }
684
ACTION_FUNCTION(A_Stop)685 ACTION_FUNCTION(A_Stop)
686 {
687 self->velx = 0;
688 self->vely = 0;
689 self->dir = nodir;
690 return true;
691 }
692
ACTION_FUNCTION(A_TakeInventory)693 ACTION_FUNCTION(A_TakeInventory)
694 {
695 ACTION_PARAM_STRING(className, 0);
696 ACTION_PARAM_INT(amount, 1);
697
698 const ClassDef *cls = ClassDef::FindClass(className);
699 AInventory *inv = self->FindInventory(cls);
700 if(inv)
701 {
702 // Taking an amount of 0 mean take all
703 if(amount == 0 || amount >= static_cast<int>(inv->amount))
704 inv->Destroy();
705 else
706 inv->amount -= amount;
707 return true;
708 }
709 return false;
710 }
711
712 #include "wl_main.h"
ACTION_FUNCTION(A_ZoomFactor)713 ACTION_FUNCTION(A_ZoomFactor)
714 {
715 enum
716 {
717 ZOOM_INSTANT = 1,
718 ZOOM_NOSCALETURNING = 2
719 };
720
721 ACTION_PARAM_DOUBLE(zoom, 0);
722 ACTION_PARAM_INT(flags, 1);
723
724 if(!self->player || !self->player->ReadyWeapon)
725 return false;
726
727 self->player->ReadyWeapon->fovscale = 1.0f / clamp<float>((float)zoom, 0.1f, 50.0f);
728 if(flags & ZOOM_INSTANT)
729 self->player->FOV = -self->player->DesiredFOV*self->player->ReadyWeapon->fovscale;
730 if(flags & ZOOM_NOSCALETURNING)
731 self->player->ReadyWeapon->fovscale *= -1;
732 return true;
733 }
734