
class ZBaseActor : Actor
{

	bool burning;
	bool burned;
	int bcount;
	uint otranslation;
	bool goforward;
	bool goback;
	int bullethit;
	bool plasma;
	bool plasmadeath;
	int pcount;
	double distancepossessed;
	Actor possessor;
	int bootcount;
	Vector3 mwoldpos;
	int mwblastdamage;
	int mwfalldamage;
	int childrencount;
	string bullethitsound;
	bool thrumastersiblings;

	Default
	{
		+SLIDESONWALLS
	}

	void A_AdjustTics(double possessedtics, double resurrectedtics)
	{
		if(self.possessor) self.A_SetTics(floor(self.CurState.Tics*possessedtics));
		else self.A_SetTics(floor(self.CurState.Tics*resurrectedtics));
	}

	bool RaiseMonster(Actor t, Actor a)
	{
		if (t.TestMobjLocation() && CheckSafeSpawn(t)) {
			String st = t.GetClassName();
			st.Replace("Dead", "");
			uint totranslation = t.translation;
			t.A_SetTranslation("Burned");
			uint btranslation = t.translation;
			t.translation = totranslation;

			let apossessed = ZBaseActor(Spawn(st, t.pos));
			apossessed.target = a.target;
			apossessed.angle = a.angle;
			apossessed.translation = t.translation;

			if (apossessed.translation == btranslation) {
				apossessed.A_SetTranslation("Burned2");
			}

			t.A_SetTranslation("Burned2");
			btranslation = t.translation;
			t.translation = totranslation;

			if (apossessed.translation == btranslation) {
				apossessed.possessor = null;
				apossessed.A_GiveInventory("MonsterPossessed", 1);
			}

			apossessed.SetStateLabel("Possessed");

			if(t.master) t.master.destroy();
			t.Destroy();

			if (!apossessed.TestMobjLocation()) {
				apossessed.A_Die("Extreme");
				return false;
			} else {
				apossessed.A_Explode(1,1,XF_HURTSOURCE|XF_EXPLICITDAMAGETYPE,0,1,0,0,"none","FakeExplosion");
				apossessed.health = apossessed.default.health;
				apossessed.A_SpawnItemEx("Doom_ArchvileResurrectLight",0,0,0,0,0,0,0);
				return true;
			}
		} else {
			t.A_Die();
			return false;
		}
	}

	action state A_RaiseDeadMonsters(float distancerange, StateLabel raisestate, StateLabel failstate, int foundlimit, int failchance)
	{
	    int found = -1;

	    if(random[RIDMONSTERPROB](1,256)>failchance) {
	    	found = 0;
		BlockThingsIterator it = BlockThingsIterator.Create(self, distancerange);
		Actor mo = null;
		while ( it.Next() && found<foundlimit )
		{
			mo = it.thing;
			if (!mo || mo == self || !mo.bCORPSE  || mo.health <= 0) { continue; }
			if (!(mo.GetClass() is "DeadDukeNukemP" || mo.GetClass() is "DeadDukeNukemR")) { continue; }
			if (!CheckSight(mo)) { continue; }

			if(invoker.RaiseMonster(mo, self)) found++;
		}
	    }

	    if(found>0) {
		return ResolveState(raisestate);
	    } else if(found==0) {
		return ResolveState(failstate);
	    } else {
		return null;
	    }
	}

	void A_ThruMasterSiblings(bool bvalue)
	{
		thrumastersiblings = bvalue;
	}

	void A_SetHeightOffset(int heightoff, int accoff=0)
	{
		self.A_SetSize(-1,self.default.height+heightoff);
		self.Accuracy = self.default.Accuracy+accoff;
	}

	void A_PlayVoiceSoundPitch(string stype="", string ssound="", int sslot=CHAN_VOICE, float spitchmin=0.9, float spitchmax=1.1)
	{
		if (stype=="Pain") ssound = self.PainSound;
		else if (stype=="Death") ssound = self.DeathSound;

		if( (ssound!="" && stype!="Pain") || (ssound!="" && stype=="Pain" && self.health>0) ) self.A_StartSound(ssound, sslot, 0, 1.0, (stype=="Death" && self.bBoss)? ATTN_NONE : ATTN_NORM, frandom[RIDMONSTER](spitchmin,spitchmax));
	}

	void A_InvulnerablePlus(int iflag)
	{
		Actor a = self;
		a.bINVULNERABLE=iflag; a.bNODAMAGE=iflag; a.bNOPAIN=iflag; a.bTHRUACTORS=iflag; a.bGHOST=iflag; a.bNORADIUSDMG=iflag; a.bDONTTHRUST=iflag; a.ThruBits=iflag;
	}

	action state A_CheckBlockedPath(StateLabel label1, StateLabel label2)
	{
		if ( target && target.health>0) {
			if ( ((abs(pos.z-GetZAt())<=1 && GetZAt()==floorz) || vel.z==0) ) {
				if (CheckSight(target) && absangle(self.angle,self.angleto(target))<=45) {
					int step = int(radius/2);
					vector3 checkdirection = (target.pos-pos).Unit()*step;
					if ( (ceilingz-(pos.z+default.height)>10) && !CheckMove(pos.xy+checkdirection.xy,PCM_NOACTORS) )
						return ResolveState(label1);
					else if ( distance2DSquared(target)<=250*250 && abs(target.pos.z-pos.z) > Default.MaxStepHeight ) {
						if ( (ceilingz-(pos.z+default.height)>10) && target.pos.z>pos.z )
							return ResolveState(label1);
						else if ( target.pos.z<pos.z )
							return ResolveState(label2);
					}
				}
			}
		}

		return null;
	}

	action state A_CheckSafePath(StateLabel label)
	{
		if (!CheckSight(target) || distance3DSquared(target)>300*300) return ResolveState(label);

		if (target.pos.z==pos.z && distance2DSquared(target)<=100*100 && !random[RIDMONSTER](0, 1)) return null;

		int step = int(radius/2);
		vector3 checkdirection = (target.pos-pos).Unit()*step;
		int steps = Distance2D(target)/step;
		double curz = pos.z;
		SetXYZ((pos.x, pos.y, pos.z+64));
		for (int i = 0; i < steps; i++)
		{
			double zat = GetZAt(pos.x+checkdirection.x*i, pos.y+checkdirection.y*i, 0, GZF_ABSOLUTEPOS|GZF_ABSOLUTEANG);
			if (curz-zat > MaxStepHeight*2 || zat-curz > MaxStepHeight)
			{
				SetXYZ((pos.x, pos.y, curz));
				return ResolveState(label);
			}
		}
		
		SetXYZ((pos.x, pos.y, curz));
		return null;
	}

	action state A_CheckActorMorph(string actortomorph, float distancerange, StateLabel morphstate, int foundlimit, int failchance)
	{
	    int found = 0;
	    let invml = self.FindInventory("MorphLimit");

	    if(random[RIDMONSTERPROB](1,256)>failchance && invml!=null) {
		foundlimit = min(foundlimit,invml.Amount);
		ThinkerIterator Finder = ThinkerIterator.Create(actortomorph, STAT_DEFAULT);
		Actor mo = null;

		while ( (mo = Actor(Finder.Next())) && foundlimit>0)
		{
			if (mo == self || mo.health <= 0 || mo.bDormant || !mo.bShootable) { continue; }
			if (!mo.bISMONSTER || !mo.bFriendly) { continue; }
			if (mo.bInvisible || mo.bINVULNERABLE) { continue; }
			let inv = mo.FindInventory("NoShrinkerDamage");
			if (inv!=null && inv.Amount>0) { continue; }
			if (!CheckSight(mo)) { continue; }
			if (absangle(self.angle,self.angleto(mo))>90) { continue; }
			double mo_distance = self.distance3DSquared(mo);
			if (mo_distance > distancerange*distancerange) { continue; }
			found++;
			if (found > foundlimit) { break; }

			self.A_Face(mo);

			let ashrinkerfx = Spawn("ShrinkerMorphFXOrigin", self.pos);
			ashrinkerfx.master = self;
			if (found > 1) {ashrinkerfx.bINVISIBLE = true;}

			int mhealth = mo.health;
			mo.tracer = ashrinkerfx;
			mo.A_Die("Morph");
			mo.health = max(mhealth/2.0,1);

			invml.Amount--;
		}
	    }

	    if(found>0) {
		return ResolveState(morphstate);
	    } else {
		return null;
	    }
	}

	private void Zap(actor victim, color pcolor, double palpha)
	{
		double ztr=victim.default.radius;
		vector3 nodes[4];
		int len=10;
		nodes[0]=victim.pos+(frandom[RIDFX](-ztr,ztr),frandom[RIDFX](-ztr,ztr),frandom[RIDFX](victim.default.height/2,victim.default.height));
		nodes[1]=nodes[0]+(frandom[RIDFX](-len,len),frandom[RIDFX](-len,len),frandom[RIDFX](-len,len));
		nodes[2]=nodes[1]+(frandom[RIDFX](-len,len),frandom[RIDFX](-len,len),frandom[RIDFX](-(len>>1),len));
		nodes[3]=nodes[2]+(frandom[RIDFX](-len,len),frandom[RIDFX](-len,len),frandom[RIDFX](-len*2/3,(len>>1)));
		for(int i=1;i<4;i++){
			vector3 pastnode=nodes[i-1];
			vector3 particlepos=nodes[i]-pastnode;
			int iterations=int(particlepos.length());
			vector3 particlemove=particlepos/iterations;
			particlepos=pastnode-victim.pos;
			for(int i=0;i<iterations;i++){
				victim.A_SpawnParticle(pcolor,
					SPF_RELATIVE|SPF_FULLBRIGHT,(len>>1),frandom[RIDFX](1,5),0,
					particlepos.x,particlepos.y,particlepos.z,
					frandom[RIDFX](-0.1,0.1),frandom[RIDFX](-0.1,0.1),frandom[RIDFX](0.1,0.2),
					frandom[RIDFX](-0.1,0.1),frandom[RIDFX](-0.1,0.1),0.5, palpha
				);
				particlepos+=particlemove+(frandom[RIDFX](-1,1),frandom[RIDFX](-1,1),frandom[RIDFX](-1,1));
			}
		}
	}

	void A_ShrinkBeamMorph(bool playerorigin=true)
	{
		if(!tracer) return;

		if(tracer && !tracer.master) {tracer.Destroy(); return;}

		if(tracer && tracer.master && tracer.master.health<1) {tracer.Destroy(); return;}

		double xwarp;
		double ywarp;
		double zwarp;
		if(playerorigin) {
			xwarp = cos(tracer.master.pitch)*(tracer.master.radius);
			ywarp = 10;
			zwarp = (PlayerPawn(tracer.master).ViewHeight-9-(tracer.master.GetFloorTerrain().footclip*!tracer.master.bNoGravity))-(sin(tracer.master.pitch)*(tracer.master.radius));
		} else {
			xwarp = tracer.master.radius;
			ywarp = 0;
			zwarp = 8-(tracer.master.GetFloorTerrain().footclip*!tracer.master.bFLOAT);
		}

		tracer.A_Warp(AAPTR_MASTER, xwarp, ywarp, zwarp, 0, WARPF_INTERPOLATE|WARPF_NOCHECKPOSITION|WARPF_STOP|WARPF_COPYPITCH);

		color beamColor1 = "f7 e8 66";
		color beamColor2 = "a4 a0 6e";
		color beamColor3 = "d7 d7 d8";

		vector3 apostemp = tracer.pos;
		vector3 targetPos = self.pos;
		targetPos.z = targetPos.z + self.default.height*0.5;

		Vector3 sphericalCoords = LevelLocals.SphericalCoords(tracer.pos, self.pos, (tracer.angle,tracer.pitch));

		double distance = sphericalCoords.Z;
		double xcurvedir = -cos(tracer.angle + sphericalCoords.X);
		double ycurvedir = -sin(tracer.angle + sphericalCoords.X);
		double zcurvedir = sin(sphericalCoords.Y);

		vector3 relPos = targetPos - apostemp;
		int     nSteps = int(distance / 10.0);

		if (nSteps == 0) { return; }

		double  xStep     = relPos.x / nSteps;
		double  yStep     = relPos.y / nSteps;
		double  zStep     = relPos.z / nSteps;
		double  alpha1     = 1;
		double  alpha2     = 0.8;
		int     drawSteps = nSteps - 0;

		for (int i = 1; i < drawSteps; ++i)
		{
			double xoff;
			double yoff;
			double zoff;
			if(i<(drawSteps/2)){
				xoff = ((xStep * i) -(3*i)*xcurvedir) + ((i*i)*0.03)/(drawSteps*0.01)*xcurvedir;
				yoff = ((yStep * i) -(3*i)*ycurvedir) + ((i*i)*0.03)/(drawSteps*0.01)*ycurvedir;
				zoff = ((zStep * i) -(3*i)*zcurvedir) + ((i*i)*0.03)/(drawSteps*0.01)*zcurvedir;
			} else {
				xoff = ((xStep * i) -(3*(drawSteps-i))*xcurvedir) +(((drawSteps-i)*(drawSteps-i))*0.03)/(drawSteps*0.01)*xcurvedir;
				yoff = ((yStep * i) -(3*(drawSteps-i))*ycurvedir) +(((drawSteps-i)*(drawSteps-i))*0.03)/(drawSteps*0.01)*ycurvedir;
				zoff = ((zStep * i) -(3*(drawSteps-i))*zcurvedir) +(((drawSteps-i)*(drawSteps-i))*0.03)/(drawSteps*0.01)*zcurvedir;
			}

			tracer.A_SpawnParticle( beamColor1, SPF_FULLBRIGHT, 1, frandom[RIDFX](1,7), 0.0
				, xoff+frandom[RIDFX](-1,1), yoff+frandom[RIDFX](-1,1), zoff+frandom[RIDFX](-1,1)
				, frandom[RIDFX](-0.1,0.1),frandom[RIDFX](-0.1,0.1),frandom[RIDFX](0.1,0.2), frandom[RIDFX](-0.1,0.1),frandom[RIDFX](-0.1,0.1)
				, -0.05, alpha1
				);
			tracer.A_SpawnParticle( beamColor1, SPF_FULLBRIGHT, 1, frandom[RIDFX](1,7), 0.0
				, xoff+frandom[RIDFX](-1,1), yoff+frandom[RIDFX](-1,1), zoff+frandom[RIDFX](-1,1)
				, frandom[RIDFX](-0.1,0.1),frandom[RIDFX](-0.1,0.1),frandom[RIDFX](0.1,0.2), frandom[RIDFX](-0.1,0.1),frandom[RIDFX](-0.1,0.1)
				, -0.05, alpha1
				);

			if(random[RIDFXPROB](1,256)>233) tracer.A_SpawnParticle( beamColor2, SPF_FULLBRIGHT, random[RIDFX](1,35/2), frandom[RIDFX](1,3), 0.0
				, xoff+frandom[RIDFX](-4,4), yoff+frandom[RIDFX](-4,4), zoff+frandom[RIDFX](-4,4)
				, frandom[RIDFX](-0.1,0.1),frandom[RIDFX](-0.1,0.1),frandom[RIDFX](0.1,0.2), frandom[RIDFX](-0.1,0.1),frandom[RIDFX](-0.1,0.1)
				, -0.03, alpha2 
				);
			if(random[RIDFXPROB](1,256)>233) tracer.A_SpawnParticle( beamColor2, SPF_FULLBRIGHT, random[RIDFX](1,35/2), frandom[RIDFX](1,3), 0.0
				, xoff+frandom[RIDFX](-4,4), yoff+frandom[RIDFX](-4,4), zoff+frandom[RIDFX](-4,4)
				, frandom[RIDFX](-0.1,0.1),frandom[RIDFX](-0.1,0.1),frandom[RIDFX](0.1,0.2), frandom[RIDFX](-0.1,0.1),frandom[RIDFX](-0.1,0.1)
				, -0.03, alpha2
				);
			if(random[RIDFXPROB](1,256)>233) tracer.A_SpawnParticle( beamColor2, SPF_FULLBRIGHT, random[RIDFX](1,35/2), frandom[RIDFX](1,3), 0.0
				, xoff+frandom[RIDFX](-4,4), yoff+frandom[RIDFX](-4,4), zoff+frandom[RIDFX](-4,4)
				, frandom[RIDFX](-0.1,0.1),frandom[RIDFX](-0.1,0.1),frandom[RIDFX](0.1,0.2), frandom[RIDFX](-0.1,0.1),frandom[RIDFX](-0.1,0.1)
				, -0.03, alpha2
				);
			if(random[RIDFXPROB](1,256)>233) tracer.A_SpawnParticle( beamColor2, SPF_FULLBRIGHT, random[RIDFX](1,35/2), frandom[RIDFX](1,3), 0.0
				, xoff+frandom[RIDFX](-4,4), yoff+frandom[RIDFX](-4,4), zoff+frandom[RIDFX](-4,4)
				, frandom[RIDFX](-0.1,0.1),frandom[RIDFX](-0.1,0.1),frandom[RIDFX](0.1,0.2), frandom[RIDFX](-0.1,0.1),frandom[RIDFX](-0.1,0.1)
				, -0.03, alpha2
				);
			if(random[RIDFXPROB](1,256)>233) tracer.A_SpawnParticle( beamColor2, SPF_FULLBRIGHT, random[RIDFX](1,35/2), frandom[RIDFX](1,3), 0.0
				, xoff+frandom[RIDFX](-4,4), yoff+frandom[RIDFX](-4,4), zoff+frandom[RIDFX](-4,4)
				, frandom[RIDFX](-0.1,0.1),frandom[RIDFX](-0.1,0.1),frandom[RIDFX](0.1,0.2), frandom[RIDFX](-0.1,0.1),frandom[RIDFX](-0.1,0.1)
				, -0.03, alpha2
				);
		}

		Zap(self, beamColor3, alpha1);
	}

	void A_Possess()
	{
		if (tracer && tracer.health>0)
		{
			Actor a = self;
			Actor t = tracer;

			if (t.TestMobjLocation() && CheckSafeSpawn(t)) {
				String st = t.GetClassName();
				st.Replace("Dead", "");
				uint totranslation = t.translation;
				t.A_SetTranslation("Burned");
				uint btranslation = t.translation;
				t.translation = totranslation;

        			if (burning) {
					self.translation = otranslation;
					self.bFRIGHTENED = false;
					self.A_StopSound(7);
					burning = false;
					bcount = 0;
				}
        			if (plasma) {
					plasma = false;
					pcount = 0;
				}

				a.bINVULNERABLE=1; a.bNODAMAGE=1; a.bNOPAIN=1; a.bTHRUACTORS=1; a.bGHOST=1; a.bNORADIUSDMG=1; a.bDONTTHRUST=1; a.ThruBits=1;

				a.A_Stop();
				a.A_Warp(AAPTR_TRACER, 0, 0, 0, 0, WARPF_Interpolate|WARPF_NOCHECKPOSITION|WARPF_USECALLERANGLE|WARPF_STOP);

				let apossessed = ZBaseActor(Spawn(st, t.pos));
				apossessed.angle = a.angle;
				apossessed.target = a.target;
				apossessed.possessor = a;
				apossessed.A_GiveInventory("MonsterPossessed", 1);
				apossessed.translation = t.translation;

				if (apossessed.translation == btranslation) {
					apossessed.A_SetTranslation("Burned2");
				}

				apossessed.SetStateLabel("Possessed");

				a.tracer = apossessed;
				if(t.master) t.master.destroy();
				t.Destroy();

				if (!apossessed.TestMobjLocation()) {
					apossessed.A_Die("Extreme");
				} else {
					apossessed.A_Explode(1,1,XF_HURTSOURCE|XF_EXPLICITDAMAGETYPE,0,1,0,0,"none","FakeExplosion");
					apossessed.health = apossessed.default.health;
				}
			} else {
				a.A_Stop();
				t.A_Die();
			}
		}
	}

	void A_PossessCheck()
	{
		Actor a = self;
		Actor t = tracer;

		if (tracer && tracer.health>0) {
			if (tracer.bFLOATBOB==1 || tracer.Species=="Gorillas" || tracer.Species=="Cobras" || tracer.Species=="NetherWorldQueens" || tracer.Species=="Zombies" || tracer.Species=="Imps" || tracer.Species=="Demons" || tracer.Species=="Bruisers" || tracer.Species=="Mancubuses" || tracer.Species=="Archviles")
				a.A_Warp(AAPTR_TRACER, random[RIDMONSTER](-1,1), 0, max(-(a.height-t.height)+(a.height*3.5/8.0),0), 0, WARPF_Interpolate|WARPF_NOCHECKPOSITION|WARPF_STOP|WARPF_BOB);
			else
				a.A_Warp(AAPTR_TRACER, random[RIDMONSTER](-1,1), 0, max(-(a.height-t.height)+(a.height*2.0/8.0),0), 0, WARPF_Interpolate|WARPF_NOCHECKPOSITION|WARPF_STOP);
		}
		else {
			a.bINVULNERABLE=0; a.bNODAMAGE=0; a.bNOPAIN=0; a.bTHRUACTORS=0; a.bGHOST=0; a.bNORADIUSDMG=0; a.bDONTTHRUST=0; a.ThruBits=0;
			if (a.TestMobjLocation()) {
				a.A_FaceTarget();
				A_ThrustToTarget();
				a.SetStateLabel("SeeStop");
			} else {
				a.bNOPAIN=1;
				distancepossessed = -1;
				a.tracer = MakeBeacon("OctobrainLameBeacon", 200);
				if(!a.tracer) a.tracer = a.target;
				a.SetStateLabel("PossessApproach");
			}
		}
	}

	private actor MakeBeacon(String smonster, int maxradiuslimit)
	{
		actor beacon = null;

		int monsterlimit = 1;
		int monsterspawnsuccess = 0;
		int monsterspawnmaxtries = 100;
		int monsterspawntries = 0;

		while (monsterspawnsuccess < monsterlimit && monsterspawntries < monsterspawnmaxtries)
		{
			int x = random[RIDMONSTER](100, maxradiuslimit)*random[RIDMONSTER](-1,1);
			int y = random[RIDMONSTER](100, maxradiuslimit)*random[RIDMONSTER](-1,1);

			bool bsuccess;
			Actor asuccess;
			[bsuccess,asuccess] = self.A_SpawnItemEx(smonster,x,y,10,0,0,0,0);
			if (bsuccess && asuccess) {
				if( asuccess.TestMobjLocation() && asuccess.CheckSight(self) ) {
					beacon = asuccess;
					monsterspawnsuccess++;
				} else {
					asuccess.Destroy();
				}
			}

			monsterspawntries++;
		}

		return beacon;
	}

	void A_PossessApproach(StateLabel possessstate)
	{
		Actor a = self;
		Actor t = tracer;

		if (tracer && tracer.health>0) {
			double distance=a.Distance3D(t);
			vector3 relPos = t.pos - a.pos;
			int     nSteps = int(distance / 10.0);

			String st = t.GetClassName();
			if(st.IndexOf("Dead")>=0) {
				if (distance<=10 || nSteps == 0) {
					a.SetStateLabel(possessstate);
					return;
				}
			} else if(st.IndexOf("Beacon")>=0) {
				if (distance<=10 || nSteps == 0) {
					a.bNOPAIN=0;
					a.A_FaceTarget();
					A_ThrustToTarget();
					a.SetStateLabel("SeeStop");
					return;
				}
			} else {
				if (distance<=70 || nSteps == 0) {
					a.bNOPAIN=0;
					a.SetStateLabel("See");
					return;
				}
			}

			double  xoff     = relPos.x / nSteps;
			double  yoff     = relPos.y / nSteps;
			double  zoff     = relPos.z / nSteps;

			a.A_FaceTracer();
			a.A_Warp(AAPTR_TRACER, a.pos.x+xoff, a.pos.y+yoff, a.pos.z+zoff, 0, WARPF_Interpolate|WARPF_NOCHECKPOSITION|WARPF_ABSOLUTEPOSITION|WARPF_USECALLERANGLE|WARPF_STOP);

			if(distancepossessed==-1) {
				distancepossessed = distance;
			} else {
				if (distance == distancepossessed) {
					a.bNOPAIN=0;
					a.A_FaceTarget();
					A_ThrustToTarget();
					a.SetStateLabel("SeeStop");
					return;
				} else {
					distancepossessed = distance;
				}
			}
		}
		else {
			a.bNOPAIN=0;
			a.SetStateLabel("See");
		}
	}

	void A_CheckDeadMonsters(StateLabel possessapproachstate, float distancerange)
	{
		Actor a = self;

		distancepossessed = -1;
		bool b = false;

		BlockThingsIterator it = BlockThingsIterator.Create(self, distancerange);
		Actor mo = null;
		while ( it.Next() )
		{
			mo = it.thing;
			if (!mo || mo == self || !mo.bCORPSE) { continue; }
			if (!(mo.GetClass() is "DeadDukeNukemP" || mo.GetClass() is "DeadDukeNukemQ")) { continue; }
			if (!CheckSight(mo)) { continue; }
			a.tracer = mo;
			b = true;
			break;
		}

		if (b) {a.SetStateLabel(possessapproachstate); return;}

		tracer=null;
	}

	private bool CheckSafeSpawn(Actor t)
	{
		bool bsuccess = false;
		Actor asuccess = null;

		if(t) {
			[bsuccess,asuccess] = t.A_SpawnItemEx(t.GetClassName(),0,0,10,0,0,0,0);
			if (bsuccess && asuccess) {
				if( asuccess.TestMobjLocation() ) {
					asuccess.ClearCounters();
					asuccess.Destroy();
				} else {
					bsuccess = false;
					asuccess.ClearCounters();
					asuccess.Destroy();
				}
			}

			if (abs(t.pos.z - t.GetZAt()) <= 1)
			{
				double aradius = t.radius;
				if (t.GetZAt(aradius,0) != t.floorz || t.GetZAt(-aradius,0) != t.floorz || t.GetZAt(0,aradius) != t.floorz || t.GetZAt(0,-aradius) != t.floorz) {
					bsuccess = false;
				}
			}
		}

		return bsuccess;
	}

	void A_ThrustToTarget(float force = 10, bool applydamage = false, bool adjustangle = true)
	{
		if(target) {
			if(applydamage) bSkullfly = true;

			float aangle = self.angle;
			if(adjustangle) aangle = self.angle + deltaangle(self.angle,self.angleto(target));
			float ppitch = (target.pos.Z + target.Height/2.0 - pos.Z) / DistanceBySpeed(target, force*2);

			self.A_ChangeVelocity(cos(aangle)*force, sin(aangle)*force, ppitch );
		} else if (!target && !adjustangle) {
			if(applydamage) bSkullfly = true;

			float aangle = self.angle;
			float ppitch = self.pitch;

			self.A_ChangeVelocity(cos(aangle)*force, sin(aangle)*force, ppitch );
		}
	}

	void A_CreateShield(String sactor, double shealth, int sradius, int sheight, int sxoffset, int syoffset, int szoffset, int sxattackoffset, int syattackoffset, int szattackoffset, double sbrokenhealth, int sxoffsetap, int syoffsetap, int szoffsetap)
	{
		Actor a = self;
		let ashield = ZBaseMetalShield(Spawn(sactor, a.pos));

		ashield.health = shealth;
  		ashield.master = a;
		ashield.A_SetSize(sradius, sheight);
  		ashield.Sxoffset = sxoffset;
  		ashield.Syoffset = syoffset;
  		ashield.Szoffset = szoffset;
  		ashield.Sxattackoffset = sxattackoffset;
  		ashield.Syattackoffset = syattackoffset;
  		ashield.Szattackoffset = szattackoffset;
		ashield.Sbrokenhealth = sbrokenhealth;
		ashield.Sappendix = false;
  		ashield.Sxoffsetap = sxoffsetap;
  		ashield.Syoffsetap = syoffsetap;
  		ashield.Szoffsetap = szoffsetap;
	}

	void A_ChaseD3D(statelabel melee = '_a_chase_default', statelabel missile = '_a_chase_default', int flags = 0, bool shield = false)
	{
		int distance = 0;
		if (target) distance =  self.Distance3D(target);

		if (target && target.health>0 && distance>300 && !goback) {
			goback = false;
			goforward = false;
			if(self.CurState.Tics>2) self.A_SetTics(self.CurState.Tics-1);
			if(random[RIDMONSTERPROB](1,256)>192) self.A_Chase(null,null,flags); else self.A_Chase(melee,missile,flags);
		} else if (target && target.health>0 && (!self.bBOSS || shield)) {
			int distanceold = distance;
			Vector3 oldpos = self.pos;
			self.A_Chase(melee,missile,flags);
			distance = self.Distance3D(target);
			if(absangle(self.angle,self.angleto(target))<=45 && distance<distanceold) {
				if( (distance<=100 && !goforward) || (goback && distance<=500) ) {
					goback = true;
					self.A_Warp(AAPTR_Default, (distance-distanceold)*2, 0, 0, flags: WARPF_Interpolate | WARPF_NOCHECKPOSITION);
					if (!CheckMove(pos.xy))
						self.A_Warp(AAPTR_Default, oldpos.x, oldpos.y, oldpos.z, flags: WARPF_AbsolutePosition | WARPF_Interpolate);
				} else {
					goback = false;
					goforward = true;
				}
			} else {
				goback = false;
				goforward = false;
			}
		} else {
			goback = false;
			goforward = false;
			self.A_Chase(melee,missile,flags);
		}
	}

	action state A_CheckFloorPlus(StateLabel label)
	{
		if (abs(pos.z - GetZAt()) <= 1)
		{
			double aradius = self.radius;
			if (GetZAt(aradius,0) == floorz && GetZAt(-aradius,0) == floorz && GetZAt(0,aradius) == floorz && GetZAt(0,-aradius) == floorz) {
				return ResolveState(label);
			}
		}

		return null;
	}

	action state A_CheckThump(StateLabel label)
	{
		invoker.Zap(self, "d7 d7 d8", 1);
		invoker.Zap(self, "d7 d7 d8", 1);
		invoker.Zap(self, "d7 d7 d8", 1);
		self.A_SpawnProjectile("Duke_Smoke", height/2.0, 0, random[RIDFX](0, 360), 2, random[RIDFX](0, 360));
		self.A_SpawnProjectile("Duke_Smoke", height/2.0, 0, random[RIDFX](0, 360), 2, random[RIDFX](0, 360));
		self.A_SpawnProjectile("Duke_Smoke", height/2.0, 0, random[RIDFX](0, 360), 2, random[RIDFX](0, 360));

		self.A_SpawnProjectile("MuzzleFlashFreezer", height/2.0, 0, random[RIDFX](0, 360), 2, random[RIDFX](0, 360));

		vector2 newpos = pos.xy + (pos.xy - invoker.mwoldpos.xy);

		if ( newpos == pos.xy || !CheckMove(newpos) || ( abs(pos.z-GetZAt())<=1 && GetZAt()==floorz ) || vel.z==0 )
		{
			self.A_SpawnProjectile("Duke_Smoke", height/2.0, 0, random[RIDFX](0, 360), 2, random[RIDFX](0, 360));
			self.A_SpawnProjectile("Duke_Smoke", height/2.0, 0, random[RIDFX](0, 360), 2, random[RIDFX](0, 360));
			self.A_SpawnProjectile("Duke_Smoke", height/2.0, 0, random[RIDFX](0, 360), 2, random[RIDFX](0, 360));

			invoker.bullethit = 128;
			self.bNOBLOCKMONST = self.default.bNOBLOCKMONST;
			self.bFLOAT = self.default.bFLOAT;
			self.bNOGRAVITY = self.default.bNOGRAVITY;
			if(self.bFLOAT) invoker.mwfalldamage = -1;
			self.bDONTHARMCLASS = false;
			self.A_Explode(invoker.mwblastdamage*1.0,96,XF_EXPLICITDAMAGETYPE|XF_NOTMISSILE,0,96,0,0,"none","MightyBoot");
			self.A_Explode(invoker.mwblastdamage*1.0,96,XF_EXPLICITDAMAGETYPE|XF_NOTMISSILE,0,96,0,0,"none","MightyBoot");
			invoker.bootcount=0;
			self.A_DamageSelf(invoker.mwblastdamage*2.0,"MightyBoot");
			invoker.bootcount=0;
			self.A_DamageSelf(invoker.mwblastdamage*2.0,"MightyBoot");
			self.bDONTHARMCLASS = true;
			if(self.health>0) return ResolveState(label);
		}

		invoker.mwoldpos = pos;
		return null;
	}

	private void updateTargets(Actor oldactor, Actor newactor)
	{
		ThinkerIterator actors = ThinkerIterator.Create("Actor",STAT_DEFAULT);
		Actor monster;
		while (monster = Actor(actors.Next()))
		{
			if (monster.bISMONSTER && monster!=oldactor && monster!=newactor && monster.target && monster.target==oldactor)
			{
				monster.target = newactor;
			}
		}
	}

	void A_Shrink(string sframe1="A", string sframe2="B", string sframe3="C", string sframe4="D", double sScale=0.2, int sMass=1000, double sSpeed=2, StateLabel unshrinkbeginstate="See", string actortounshrink="", StateLabel beginstate="See", string actortoshrink="ShrunkEnemy")
	{
		Actor a = self;

		if (actortoshrink == "ShrunkEnemy") {
			if (Color(a.bloodcolor & 0xffffff)==Color("AA D1 5C")) {
				actortoshrink = "ShrunkEnemyb";
			}
		}

		let ashrink = ZShrunkEnemy(Spawn(actortoshrink, a.pos));
		ashrink.Ahealth = a.health;
		ashrink.target = a.target;
		ashrink.master = a.master;
		ashrink.Aactor = (class<Actor>)(a.GetClass());
		ashrink.Asactor = actortounshrink;
		ashrink.Aunshrinkbeginstate = unshrinkbeginstate;

		if (actortoshrink == "ShrunkEnemy" || actortoshrink == "ShrunkEnemyb") {
			ashrink.A_SetSize(a.default.radius, -1);
			ashrink.ActiveSound = a.ActiveSound;
			ashrink.A_SetScale(sScale);
			ashrink.speed = sSpeed;
			ashrink.mass = sMass;
			ashrink.Asprite = a.sprite;
	  		ashrink.Aframe1 = sframe1.CharCodeAt(0)-65;
	  		ashrink.Aframe2 = sframe2.CharCodeAt(0)-65;
	  		ashrink.Aframe3 = sframe3.CharCodeAt(0)-65;
	  		ashrink.Aframe4 = sframe4.CharCodeAt(0)-65;
			ashrink.translation = a.default.translation;
			ashrink.ChangeTid(3500);

			if (actortounshrink == "") {
				ashrink.Aheight = a.default.height;
				ashrink.Ascale = a.default.scale;
				ashrink.Afloat = a.default.bFloat;
				ashrink.Aboss = a.default.bBoss;
			} else {
				actor aunshrinktemp = Spawn(actortounshrink, a.pos);
				ashrink.Aheight = aunshrinktemp.default.height;
				ashrink.Ascale = aunshrinktemp.default.scale;
				ashrink.Afloat = aunshrinktemp.default.bFloat;
				ashrink.Aboss = aunshrinktemp.default.bBoss;
				aunshrinktemp.ClearCounters();
				aunshrinktemp.Destroy();
			}
		}

		let inv = a.FindInventory("MonsterPossessed");
		if (inv!=null && inv.Amount>0) {
			ashrink.SetStateLabel("DeathPossess");
		} else {
			ashrink.SetStateLabel(beginstate);
			updateTargets(a, ashrink);
		}
	}

	void A_SwitchActorTransferHealth(string newactor, StateLabel beginstate, bool samekind=true)
	{
		Actor a = self;

		if(samekind) {
			let anewactor = ZBaseActor(Spawn(newactor, a.pos));
			anewactor.health = a.health;
			anewactor.target = a.target;
			anewactor.master = a.master;

			if (burning)
			{
				anewactor.burning = burning;
				anewactor.bcount = bcount;
				anewactor.otranslation = anewactor.translation;
				anewactor.A_SetTranslation("Burning");
				anewactor.bFRIGHTENED = a.bFRIGHTENED;
				anewactor.A_PlaySound("FireLoop/Monsters",7,1,1);
			}

			anewactor.SetStateLabel(beginstate);

			updateTargets(a, anewactor);
		} else {
			let anewactor = Spawn(newactor, a.pos);
			anewactor.health = a.health;

			anewactor.SetStateLabel(beginstate);
		}
	}

	void A_Burning(bool ignite=true, bool runaway=true)
	{
		if(self.bNOBLOOD) return;

        	if(!self.bBOSS && ignite) {
			if (!burning) otranslation = self.translation;
			self.A_SetTranslation("Burning");
			self.A_PlaySound("FireLoop/Monsters",7,1,1);
			bcount = 0;
			if(runaway) self.bFRIGHTENED = true;
		} else if(!self.bBOSS && !ignite && !burning) {
			if (!burning) otranslation = self.translation;
			bcount = 1000;
		}
		burning = true;
	}

	void A_Freeze(StateLabel sourcebeginstate)
	{
		Actor a = self;
		a.A_PlaySound ("misc/freeze", CHAN_BODY);
		let afreeze = ZFrozenEnemy(Spawn("FrozenEnemy", a.pos));
		afreeze.Sactor = (class<Actor>)(a.GetClass());
		afreeze.Sbeginstate = sourcebeginstate;
		afreeze.Shealth = a.health;
		afreeze.Ssprite = a.sprite;
  		afreeze.Sframe = a.frame;
  		afreeze.target = a.target;
  		afreeze.master = a.master;
 		afreeze.angle = a.angle;
		afreeze.A_SetSize(a.radius, a.height);
		afreeze.scale = a.scale;
		afreeze.Sdropitem = a.GetDropItems();
		afreeze.ChangeTid(3500);

		let inv = a.FindInventory("MonsterPossessed");
		if (inv!=null && inv.Amount>0) {
			afreeze.SetStateLabel("Break");
		} else {
			updateTargets(a, afreeze);
		}
	}

	void A_CheckMonsters(StateLabel eatingmonsterstate)
	{
		bool found = false;
		double maxDistance = 35;
		for (let i = BlockThingsIterator.Create(self, maxDistance); i.Next();)
		{
			Actor mo = i.thing;
			if (mo == self || mo.health <= 0 || mo.bDormant || !mo.bShootable) { continue; }
			if (!mo.bISMONSTER) { continue; }
			if (mo.bInvisible || mo.bINVULNERABLE) { continue; }
			double mo_distance = self.distance3DSquared(mo);
			if (mo_distance > maxDistance*maxDistance) { continue; }

			string mo_name = mo.GetClassName();
			switch (mo.species)
			{
			case 'Troopers':
				if(mo_name=="Alien_Green" || mo_name=="Alien_Red" || mo_name=="Alien_Blue") {found=true;}
				break;
			case 'Pigcops':
				if(mo_name=="PigCop" || mo_name=="PigCopHurt" || mo_name=="PigHumanFoe") {found=true;}
				break;
			case 'AlienQueenDrones':
				if(mo_name=="DukeAlien" || mo_name=="queendrone") {found=true;}
				break;
			case 'CycloidIncineratorFireflys':
				if(mo_name=="FireflyTrooper" || mo_name=="CycloidIncineratorFirefly") {found=true;}
				break;
			case 'TrooperLames':
				if(mo_name=="TrooperLame" || mo_name=="TrooperLame2") {found=true;}
				break;
			case 'Enforcers':
				if(mo_name=="LizardGunner") {found=true;}
				break;
			case 'AlienSnipers':
				if(mo_name=="AlienSniper") {found=true;}
				break;
			case 'Nazis':
				if(mo_name=="AMCNazi" || mo_name=="Doom_WolfensteinSS") {found=true;}
				break;
			case 'DukeFoes':
				if(mo_name=="DukeNukemFoe") {found=true;}
				break;
			case 'Zombies':
				if(mo_name=="Doom_ZombieMan" || mo_name=="Doom_ShotgunGuy" || mo_name=="Doom_ChaingunGuy") {found=true;}
				break;
			case 'Imps':
				if(mo_name=="Doom_Imp") {found=true;}
				break;
			}

			if (!found) { continue; }

			mo.A_GiveInventory("MonsterGettingSlimed",1);
			break;
                }

		if (found) {self.SetStateLabel(eatingmonsterstate);}
	}

	action state A_CheckChildren(int ilimit, StateLabel label)
	{
		invoker.childrencount = 0;

		ThinkerIterator Finder = ThinkerIterator.Create("Actor", STAT_DEFAULT);
		Actor mo = null;

		while ( mo = Actor(Finder.Next()) )
		{
			if (mo == self || mo.health <= 0 || mo.species=="HeadshotTarget" || mo.species=="MetalShield") { continue; }
			if (mo.master == self) { invoker.childrencount++; }
		}

		if (invoker.childrencount >= ilimit)
		{
			return ResolveState(label);
		}

		return null;
	}

	void A_SpawnChildren(String smonster, double dxofs, double dyofs, double dzofs, double startangle, int icount, int ilimit, int ifailchance)
	{
		Actor a = self;
		bool bsuccess;
		Actor asuccess;
		ilimit = ilimit - childrencount;

		double anglejump = 70.0;
		if(ilimit!=0) anglejump = 70.0/ilimit;

		[bsuccess,asuccess] = a.A_SpawnItemEx(smonster,dxofs,dyofs,dzofs,0,0,0,startangle,SXF_SETMASTER,0);
		if(asuccess && a.target && a.target.health>0) asuccess.target = a.target;

		for (int i=0; i<(ilimit-1)&&i<(icount-1); i++)
		{
			startangle = startangle + anglejump;
			[bsuccess,asuccess] = a.A_SpawnItemEx(smonster,dxofs,dyofs,dzofs,0,0,0,startangle,SXF_SETMASTER,ifailchance);
			if (asuccess == null) { icount++; } else if (asuccess && a.target && a.target.health>0) { asuccess.target = a.target; }
		}
	}

	override void PostBeginPlay()
	{
		bootcount = 0;
		mwfalldamage = -1;
		thrumastersiblings = false;
		if(!self.bNOBLOOD) {bullethitsound = "flesh/hit";}
		else {bullethitsound = "concrete/hit";}

		if (self.default.height>10 && self.mass<100000 && d3d_extrahsgore==0 && !self.bNOBLOOD) {
			if (Color(self.bloodcolor & 0xffffff)==Color("AA D1 5C")) {
				self.A_SpawnItemEx("HeadshotTargetb",1,0,self.height-self.height/7.0-self.Accuracy,0,0,0,0,SXF_SETMASTER);
			} else {
				self.A_SpawnItemEx("HeadshotTarget",1,0,self.height-self.height/7.0-self.Accuracy,0,0,0,0,SXF_SETMASTER);
			}
		}

		Super.PostBeginPlay();
	}

	override int DamageMobj(Actor inflictor, Actor source, int damage, Name mod, int flags, double angle)
    	{
	    if(inflictor && inflictor.GetClass() is 'Doom_ArchvileFireShield') damage = 0;

	    if((source || inflictor) && self.bINVULNERABLE == false)
	    {
		if(!source && inflictor) source = inflictor;

        	if(mod == 'Ice')
        	{
			if ( (self.health-damage)<=0 ) {
				self.health = self.woundhealth-1;
				damage = 1;
			} else {
				self.speed = self.speed - (self.speed*0.1);
			}
        	}
        	else if(mod == 'Shrinker')
        	{
			self.health = self.health + 1;
        	}
        	else if(mod == 'Bullet' || mod == 'Chaingun' || mod == 'FireChaingun')
        	{
			bullethit = 32;
			self.A_PlaySound(bullethitsound);
			if(self.health>0) self.A_PlayVoiceSoundPitch("Pain");
			if(self.mass<100000) {
				if(self.default.height<72 && self.default.radius<30) {
					float knockbackangle = self.angle + deltaangle(self.angle,self.angleto(source));
					float knockback = 2.0;
					float knockbackz = 0.0;
					self.A_ChangeVelocity(-cos(knockbackangle)*knockback, -sin(knockbackangle)*knockback, knockbackz/5.0 );
				} else if(self.default.height<100) {
					float knockbackangle = self.angle + deltaangle(self.angle,self.angleto(source));
					float knockback = 0.5;
					float knockbackz = 0.0;
					self.A_ChangeVelocity(-cos(knockbackangle)*knockback, -sin(knockbackangle)*knockback, knockbackz/5.0 );
				}
			}
        	}
        	else if(mod == 'Shotgun' || mod == 'ExplosiveShotgun')
        	{
			bullethit = 64;
			if(mod == 'ExplosiveShotgun') bullethit = 128;
			self.A_PlaySound(bullethitsound,random[RIDMONSTER](1,7));
			if(self.health>0) self.A_PlayVoiceSoundPitch("Pain");
			if(self.mass<100000) {
				if(self.default.height<72 && self.default.radius<30) {
					float knockbackangle = self.angle + deltaangle(self.angle,self.angleto(source));
					float knockback = 1.0;
					float knockbackz = 3.5;
					self.A_ChangeVelocity(-cos(knockbackangle)*knockback, -sin(knockbackangle)*knockback, knockbackz/5.0 );
				} else if(self.default.height<100) {
					float knockbackangle = self.angle + deltaangle(self.angle,self.angleto(source));
					float knockback = 0.5;
					float knockbackz = 2.5;
					self.A_ChangeVelocity(-cos(knockbackangle)*knockback, -sin(knockbackangle)*knockback, knockbackz/5.0 );
				}
			}
        	}
        	else if(mod == 'SSG')
        	{
			bullethit = 128;
			self.A_PlaySound(bullethitsound,random[RIDMONSTER](1,7));
			if(self.health>0) self.A_PlayVoiceSoundPitch("Pain");
			if(self.mass<100000) {
				if(self.default.height<72 && self.default.radius<30) {
					float knockbackangle = self.angle + deltaangle(self.angle,self.angleto(source));
					float knockback = 0.5;
					float knockbackz = 2.0;
					self.A_ChangeVelocity(-cos(knockbackangle)*knockback, -sin(knockbackangle)*knockback, knockbackz/5.0 );
				} else if(self.default.height<100) {
					float knockbackangle = self.angle + deltaangle(self.angle,self.angleto(source));
					float knockback = 0.25;
					float knockbackz = 1.0;
					self.A_ChangeVelocity(-cos(knockbackangle)*knockback, -sin(knockbackangle)*knockback, knockbackz/5.0 );
				}
			}
        	}
        	else if(mod == 'Fire' || mod == 'BulletFlame')
        	{
			if(mod == 'BulletFlame') {
				if (random[RIDMONSTERPROB](1,256)>32) {
					self.A_Burning(false);
				} else {
					if (random[RIDMONSTERPROB](1,256)>32) { self.A_Burning(true, false); }
					else { self.A_Burning(); }
				}
			} else {
				self.A_Burning();
			}
			if ( (self.health-damage)<=0 ) {
				if(mod == 'BulletFlame' && random[RIDMONSTERPROB](1,256)>224) {
					self.A_Die("ShellExplosion");
				} else {
					self.A_Die();
				}
			}
        	}
        	else if(mod == 'Plasma')
        	{
			bullethit = 32;
			pcount = 0;
			plasma = true;
			if ( (self.health-damage)<=self.WoundHealth ) {
				if (random[RIDMONSTERPROB](1,256)>128) {
					self.A_Die("PlasmaGib");
					self.health=self.GetGibHealth()-20;
				}
			}
        	}
        	else if(mod == 'PlasmaHeadshot')
        	{
			bullethit = 32;
			pcount = 0;
			plasma = true;
			if ( (self.health-damage)<=self.WoundHealth ) {
				if (random[RIDMONSTERPROB](1,256)>128) {
					self.A_Die("PlasmaGib");
					self.health=self.GetGibHealth()-20;
				}
				else mod = "Headshot";
			}
        	}
        	else if(mod == 'MicrowaveBlast')
        	{
			mwoldpos = self.pos;
			mwfalldamage = 0;
			self.bNOBLOCKMONST = true;
			self.bFLOAT = false;
			self.bNOGRAVITY = false;
			self.bSKULLFLY=false;

			if ( (self.health-damage)>0 ) {
	       			if (burning) {
					self.translation = otranslation;
					self.bFRIGHTENED = false;
					self.A_StopSound(7);
					burning = false;
					bcount = 0;
				}
       				if (plasma) {
					plasma = false;
					pcount = 0;
				}
			}

			let amwfx = Spawn("ExpanderMicrowaveBlastFire", (self.pos.x,self.pos.y,self.pos.z+self.height/2.0) );
			amwfx.A_SetScale(0.015*damage);

			if(self.health>0) self.A_PlayVoiceSoundPitch("Pain");
			if(self.default.height<72 && self.default.radius<30 && self.mass<100000) {
				float knockbackangle = self.angle + deltaangle(self.angle,self.angleto(inflictor));
				float knockback = 1.0 * damage;
				float knockbackz = 1.0 * (damage + max(0,2*((self.pos.z+self.height/2.0) - inflictor.pos.z)));
				mwblastdamage = knockback;
				self.A_ChangeVelocity(-cos(knockbackangle)*knockback, -sin(knockbackangle)*knockback, knockbackz/5.0 );
			} else if(self.default.height<100 && self.mass<100000) {
				float knockbackangle = self.angle + deltaangle(self.angle,self.angleto(inflictor));
				float knockback = 0.5 * damage;
				float knockbackz = 0.5 * (damage + max(0,2*((self.pos.z+self.height/2.0) - inflictor.pos.z)));
				mwblastdamage = knockback;
				self.A_ChangeVelocity(-cos(knockbackangle)*knockback, -sin(knockbackangle)*knockback, knockbackz/5.0 );
			} else {
				float knockbackangle = self.angle + deltaangle(self.angle,self.angleto(inflictor));
				float knockback = 0.25 * damage;
				float knockbackz = 0.25 * (damage + max(0,2*((self.pos.z+self.height/2.0) - inflictor.pos.z)));
				mwblastdamage = knockback;
				self.A_ChangeVelocity(-cos(knockbackangle)*knockback, -sin(knockbackangle)*knockback, knockbackz/5.0 );
			}
        	}
        	else if(mod == 'MightyBoot')
        	{
			float bboost1 = 0;
			float bboost2 = 0;
			float bpowerdiv = 1.0;

			if(source.species=="Dukes") {
				if(bootcount==0) {
					bootcount = 150;
					mod = "BootStun";
					bboost1 = 30.0;
					bboost2 = 20.0;
				}

				let invst = source.FindInventory("SteroidsActive");
				if(invst!=null && invst.Amount==1) {
					if ( (self.health-damage)<=0 || damage==0 ) {
						mod = "SSG";
						if(bboost1>0) bboost1 = 10.0;
						if(bboost2>0) bboost2 = 5.0;
					}
				}
			} else {
				if(source==self && bootcount==0) {
					bootcount = 150;
					mod = "BootStun";
					bboost1 = 15.0;
					bboost2 = 10.0;
					bpowerdiv = 2.0;
				} else if(source!=self) {
					mod = "BootStun";
					bboost1 = 15.0;
					bboost2 = 10.0;
					bpowerdiv = 2.0;
				}
			}

			self.A_PlaySound(bullethitsound);
			if(self.health>0) self.A_PlayVoiceSoundPitch("Pain");
			if(self.mass<100000) {
				if(self.default.height<72 && self.default.radius<30) {
					float knockbackangle = self.angle + deltaangle(self.angle,self.angleto(source));
					float knockback = 6.0 / bpowerdiv;
					float knockbackz = 0.0 + bboost1;
					self.A_ChangeVelocity(-cos(knockbackangle)*knockback, -sin(knockbackangle)*knockback, knockbackz/5.0 );
				} else if(self.default.height<100) {
					float knockbackangle = self.angle + deltaangle(self.angle,self.angleto(source));
					float knockback = 1.5 / bpowerdiv;
					float knockbackz = 0.0 + bboost2;
					self.A_ChangeVelocity(-cos(knockbackangle)*knockback, -sin(knockbackangle)*knockback, knockbackz/5.0 );
				}
			}
        	}
        	else if(mod == 'RocketExplosion' || mod == 'Explosion' || mod == 'ShellExplosion')
        	{
			bullethit = 128;
        	}
        	else if(mod == 'ShieldSingleHit')
        	{
			float knockbackangle = self.angle + deltaangle(self.angle,self.angleto(source));
			float knockback = 0.5;
			float knockbackz = 0.0;
			self.A_ChangeVelocity(-cos(knockbackangle)*knockback, -sin(knockbackangle)*knockback, knockbackz/5.0 );
        	}
        	else if(mod == 'ShieldMultiHit')
        	{
			float knockbackangle = self.angle + deltaangle(self.angle,self.angleto(source));
			float knockback = 0.25;
			float knockbackz = 1.0;
			self.A_ChangeVelocity(-cos(knockbackangle)*knockback, -sin(knockbackangle)*knockback, knockbackz/5.0 );
        	}

	    }

	    return super.DamageMobj(inflictor, source, damage, mod, flags, angle);
	}

	override bool Slam(Actor victim)
    	{
        	if(victim && (victim.species=="HeadshotTarget" || victim.species=="MetalShield"))
        	{
			if(victim.master && victim.master==self) return true;

			if(victim.master && victim.master.tracer && victim.master.tracer==self) {
				let inv = self.FindInventory("MonsterPossessed");
				if (inv!=null && inv.Amount>0) {
					if(self.possessor && self.possessor==victim.master) {
						return true;
					}
				}
			}
		}

		if(victim && victim is "DeadDukeNukem")
		{
			return true;
		}

		if(victim && victim is "Inventory")
		{
			return true;
		}

		if(thrumastersiblings)
		{
			if (victim && master && victim==master)
    			{
				return true;
			}
        		if (victim && master && victim.master && victim.master==master)
        		{
				return true;
			}
		}

	        return super.Slam(victim);
	}

	override bool CanCollideWith(Actor other, bool passive)
	{
		if (other && tracer && other==tracer)
    		{
			let inv = tracer.FindInventory("MonsterPossessed");
			if (inv!=null && inv.Amount>0) {
				let t = ZBaseActor(tracer);
				if(t.possessor && t.possessor==self) {
					return false;
				}
			}
		}

		if(thrumastersiblings)
		{
			if (other && master && other==master)
    			{
				return false;
			}
        		if (other && master && other.master && other.master==master)
        		{
				return false;
			}
		}

		return super.CanCollideWith(other, passive);
	}

	override void Die(Actor source, Actor inflictor, int dmgflags)
	{
		self.bDONTHARMCLASS = false;
		self.bDONTGIB = true;

        	if (burning) {
			self.A_SetTranslation("Burned");
			self.bFRIGHTENED = false;
			self.A_StopSound(7);
			burning = false;
			bcount = 0;
			burned = true;
		}
        	if (plasma) {
			self.SetShade("0000FF");
			plasma = false;
			pcount = 0;
			plasmadeath = true;
		}

		super.Die(source, inflictor, dmgflags);
	}

	override void Tick()
	{
        	if (burning && self.health>0 && self.bINVULNERABLE == false) {
			if (self.bBOSS) {
				burning = false;
			}
			else if ( bcount > (60+180*!self.bFRIGHTENED) ) {
				self.translation = otranslation;
				self.bFRIGHTENED = false;
				self.A_StopSound(7);
				burning = false;
				bcount = 0;
			}
			else if (bcount%3==0) {
				if(self.bFRIGHTENED) {
					self.A_Wander();
					self.A_SpawnItemEx("FirePuffDuke",Random[RIDFX](-radius,radius),Random[RIDFX](-radius,radius),Random[RIDFX](5,height),0,0,1,0,32);
				} else {
					self.A_SpawnItemEx("FirePuffDuke",Random[RIDFX](-radius,radius),Random[RIDFX](-radius,radius),Random[RIDFX](5,height),0,0,1,0,32);
					self.A_SpawnItemEx("FirePuffDuke",Random[RIDFX](-radius,radius),Random[RIDFX](-radius,radius),Random[RIDFX](5,height),0,0,1,0,32);
					self.A_SpawnItemEx("FirePuffDuke",Random[RIDFX](-radius,radius),Random[RIDFX](-radius,radius),Random[RIDFX](5,height),0,0,1,0,32);
				}
			}
			else if (bcount%10==0) {
				if ( self.health<=self.WoundHealth) {
					self.A_DamageSelf(100,"Fire");
				} else {
					if(self.bFRIGHTENED) {
						self.A_DamageSelf(1,"FireBurning");
					} else {
						if (random[RIDFXPROB](1,256)>64) { self.A_DamageSelf(5,"FireBurning"); }
						else { self.A_DamageSelf(5); }
					}
				}
			}
			bcount++;
		}
        	if (plasma && self.health>0 && self.bINVULNERABLE == false) {
			if (pcount>15) {
				plasma = false;
				pcount = 0;
			}
			self.A_SpawnItemEx("PlasmaSpark",frandom[RIDFX](-radius/2.0,radius/2.0),frandom[RIDFX](-radius/2.0,radius/2.0),frandom[RIDFX](5,height/8.0*7.0),0,0,0,0,32, 200+pcount*2);
			pcount++;
		}

	       	if (burned) {
			if (bcount>60) {
				burned = false;
				bcount = 0;
			}
			else if (bcount%5==0) {
				self.A_SpawnProjectile("FlameTrailsSmall1", max(0,self.default.height*0.66-bcount), 0, random[RIDFX](0, 360), CMF_AIMDIRECTION|CMF_BADPITCH, random[RIDFX](60, 90));
			}
			bcount++;
		}
	       	if (plasmadeath && self.health>self.GetGibHealth()) {
			if (pcount>60) {
				plasmadeath = false;
				pcount = 0;
			}
			self.A_SpawnItemEx("PlasmaSparkSmall",frandom[RIDFX](-self.default.radius/2.0,self.default.radius/2.0),frandom[RIDFX](-self.default.radius/2.0,self.default.radius/2.0),frandom[RIDFX](5,self.default.height/8.0*7.0-pcount/2.0),0,0,0,0,32, 200);
			pcount++;
		}

		let inv = self.FindInventory("DukeDamagePickup");
		if (inv!=null && inv.Amount>0) {
			self.A_TakeInventory("DukeDamagePickup",1);
		}

		if (self.speed < self.default.speed) {
			self.speed = self.speed + 0.02;
			self.A_SpawnItemEx("Duke_Smoke",Random[RIDFX](-radius*2,radius*2),Random[RIDFX](-radius*2,radius*2),Random[RIDFX](5,height),frandom[RIDFX](-1,1),frandom[RIDFX](-1,1),frandom[RIDFX](-1,1),random[RIDFX](0,360),0,(self.speed/self.default.speed)*256);
			self.A_SpawnItemEx("Duke_Smoke",Random[RIDFX](-radius*2,radius*2),Random[RIDFX](-radius*2,radius*2),Random[RIDFX](5,height),frandom[RIDFX](-1,1),frandom[RIDFX](-1,1),frandom[RIDFX](-1,1),random[RIDFX](0,360),0,(self.speed/self.default.speed)*256);
			self.A_SpawnItemEx("Duke_Smoke",Random[RIDFX](-radius*2,radius*2),Random[RIDFX](-radius*2,radius*2),Random[RIDFX](5,height),frandom[RIDFX](-1,1),frandom[RIDFX](-1,1),frandom[RIDFX](-1,1),random[RIDFX](0,360),0,(self.speed/self.default.speed)*256);
			self.A_SpawnItemEx("Duke_Smoke",Random[RIDFX](-radius*2,radius*2),Random[RIDFX](-radius*2,radius*2),Random[RIDFX](5,height),frandom[RIDFX](-1,1),frandom[RIDFX](-1,1),frandom[RIDFX](-1,1),random[RIDFX](0,360),0,(self.speed/self.default.speed)*256);
			self.A_SpawnItemEx("Duke_Smoke",Random[RIDFX](-radius*2,radius*2),Random[RIDFX](-radius*2,radius*2),Random[RIDFX](5,height),frandom[RIDFX](-1,1),frandom[RIDFX](-1,1),frandom[RIDFX](-1,1),random[RIDFX](0,360),0,(self.speed/self.default.speed)*256);
		} else if (self.speed > self.default.speed) {
			self.speed = self.default.speed;
		}

		if(bootcount>0) {
			bootcount--;
		}

        	Super.Tick();

		if(mwfalldamage>-1) {
			if ( ( abs(pos.z-GetZAt())<=1 && GetZAt()==floorz ) || ( mwfalldamage>0 && vel.z==0 ) ) {
				self.A_SpawnProjectile("Duke_Smoke", height/2.0, 0, random[RIDFX](0, 360), 2, random[RIDFX](0, 360));
				self.A_SpawnProjectile("Duke_Smoke", height/2.0, 0, random[RIDFX](0, 360), 2, random[RIDFX](0, 360));
				self.A_SpawnProjectile("Duke_Smoke", height/2.0, 0, random[RIDFX](0, 360), 2, random[RIDFX](0, 360));

				if(self.health>0) {
					self.bNOBLOCKMONST = self.default.bNOBLOCKMONST;
					self.bFLOAT = self.default.bFLOAT;
					self.bNOGRAVITY = self.default.bNOGRAVITY;
				}
				if(mwfalldamage>=20) {bullethit = 32; self.A_DamageSelf(mwfalldamage*1.0,"MightyBoot"); self.A_DamageSelf(mwfalldamage*1.0,"MightyBoot");}
				mwfalldamage=-1;
			} else if (vel.z<0) {
				Zap(self, "d7 d7 d8", 1);
				self.A_SpawnProjectile("Duke_Smoke", height/2.0, 0, random[RIDFX](0, 360), 2, random[RIDFX](0, 360));
				mwfalldamage++;
			}
		}

		if (bullethit>0 && d3d_extragore==0 && !self.bNOBLOOD) {
			if (Color(self.bloodcolor & 0xffffff)==Color("AA D1 5C")) {
				if(random[RIDMONSTERPROB](1,256)>(256-bullethit)) self.A_SpawnDebris("AlienPainDukeGibs1", mult_v: 0.8);
				if(random[RIDMONSTERPROB](1,256)>(256-bullethit)) self.A_SpawnDebris("AlienPainDukeGibs2", mult_v: 0.8);
				if(random[RIDMONSTERPROB](1,256)>(256-bullethit)) self.A_SpawnDebris("AlienPainDukeGibs3", mult_v: 0.8);
				if(random[RIDMONSTERPROB](1,256)>(256-bullethit)) self.A_SpawnDebris("AlienPainDukeGibs4", mult_v: 0.8);
				if(random[RIDMONSTERPROB](1,256)>(256-bullethit)) self.A_SpawnDebris("AlienPainDukeGibs5", mult_v: 0.8);
				if(random[RIDMONSTERPROB](1,256)>(256-bullethit)) self.A_SpawnDebris("AlienPainDukeGibs6", mult_v: 0.8);
			} else {
				if(random[RIDMONSTERPROB](1,256)>(256-bullethit)) self.A_SpawnDebris("PainDukeGibs1", mult_v: 0.8);
				if(random[RIDMONSTERPROB](1,256)>(256-bullethit)) self.A_SpawnDebris("PainDukeGibs2", mult_v: 0.8);
				if(random[RIDMONSTERPROB](1,256)>(256-bullethit)) self.A_SpawnDebris("PainDukeGibs3", mult_v: 0.8);
				if(random[RIDMONSTERPROB](1,256)>(256-bullethit)) self.A_SpawnDebris("PainDukeGibs4", mult_v: 0.8);
				if(random[RIDMONSTERPROB](1,256)>(256-bullethit)) self.A_SpawnDebris("PainDukeGibs5", mult_v: 0.8);
				if(random[RIDMONSTERPROB](1,256)>(256-bullethit)) self.A_SpawnDebris("PainDukeGibs6", mult_v: 0.8);
			}
			bullethit = 0;
		}
		else if (bullethit>0 && d3d_extragore==1 && !self.bNOBLOOD) {
			if (Color(self.bloodcolor & 0xffffff)==Color("AA D1 5C")) {
				if(random[RIDMONSTERPROB](1,256)>(256-bullethit)) self.A_SpawnDebris("AlienPainDukeGibs6", mult_v: 0.8);
				if(random[RIDMONSTERPROB](1,256)>(256-bullethit)) self.A_SpawnDebris("AlienPainDukeGibs6", mult_v: 0.8);
				if(random[RIDMONSTERPROB](1,256)>(256-bullethit)) self.A_SpawnDebris("AlienPainDukeGibs6", mult_v: 0.8);
				if(random[RIDMONSTERPROB](1,256)>(256-bullethit)) self.A_SpawnDebris("AlienPainDukeGibs6", mult_v: 0.8);
				if(random[RIDMONSTERPROB](1,256)>(256-bullethit)) self.A_SpawnDebris("AlienPainDukeGibs6", mult_v: 0.8);
				if(random[RIDMONSTERPROB](1,256)>(256-bullethit)) self.A_SpawnDebris("AlienPainDukeGibs6", mult_v: 0.8);
			} else {
				if(random[RIDMONSTERPROB](1,256)>(256-bullethit)) self.A_SpawnDebris("PainDukeGibs6", mult_v: 0.8);
				if(random[RIDMONSTERPROB](1,256)>(256-bullethit)) self.A_SpawnDebris("PainDukeGibs6", mult_v: 0.8);
				if(random[RIDMONSTERPROB](1,256)>(256-bullethit)) self.A_SpawnDebris("PainDukeGibs6", mult_v: 0.8);
				if(random[RIDMONSTERPROB](1,256)>(256-bullethit)) self.A_SpawnDebris("PainDukeGibs6", mult_v: 0.8);
				if(random[RIDMONSTERPROB](1,256)>(256-bullethit)) self.A_SpawnDebris("PainDukeGibs6", mult_v: 0.8);
				if(random[RIDMONSTERPROB](1,256)>(256-bullethit)) self.A_SpawnDebris("PainDukeGibs6", mult_v: 0.8);
			}
			bullethit = 0;
		}
	}

}

class ZActorDodgeMissile : ZBaseActor
{
	int clock;

	override void Tick()
	{
	    if(clock==0) {
        	if ( InStateSequence(CurState, ResolveState("Missile")) || InStateSequence(CurState, ResolveState("See")) || InStateSequence(CurState, ResolveState("MissileEnd")) || InStateSequence(CurState, ResolveState("Pain")) ) {
			ActorIterator Finder = Level.CreateActorIterator(4500,"Actor");
			Actor mo;
			while ( (mo = Actor(Finder.Next())) )
			{
				if (!mo || mo == self || !mo.bMISSILE) { continue; }
				if (!CheckSight(mo)) { continue; }
				double mo_distance = self.distance2DSquared(mo);
				if (mo_distance > 350*350) { continue; }	
				if (absangle(self.angle,self.angleto(mo))>135) { continue; }
				self.tracer = mo;
				if (tracer.target) self.A_Face(tracer.target);
				if(deltaangle(self.angle,self.angleto(tracer))>=0)
					self.SetStateLabel("Dodge1");
				else
					self.SetStateLabel("Dodge2");
				break;
			}
		}
	    }
	    clock++;
	    if(clock==5) clock=0;

	    Super.Tick();   
	}

}