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