diff --git a/src/dobject.cpp b/src/dobject.cpp
index e3e37432b8..849f6a477e 100644
--- a/src/dobject.cpp
+++ b/src/dobject.cpp
@@ -363,12 +363,6 @@ DEFINE_ACTION_FUNCTION(DObject, Destroy)
 	return 0;	
 }
 
-DEFINE_ACTION_FUNCTION(DObject, GetClass)
-{
-	PARAM_SELF_PROLOGUE(DObject);
-	ACTION_RETURN_OBJECT(self->GetClass());
-}
-
 //==========================================================================
 //
 //
diff --git a/src/g_heretic/a_hereticweaps.cpp b/src/g_heretic/a_hereticweaps.cpp
index cc964ecc05..3b2711e246 100644
--- a/src/g_heretic/a_hereticweaps.cpp
+++ b/src/g_heretic/a_hereticweaps.cpp
@@ -46,133 +46,6 @@ void P_DSparilTeleport (AActor *actor);
 extern bool P_AutoUseChaosDevice (player_t *player);
 
 
-// Blaster FX 1 -------------------------------------------------------------
-
-//----------------------------------------------------------------------------
-//
-// Thinker for the ultra-fast blaster PL2 ripper-spawning missile.
-//
-//----------------------------------------------------------------------------
-
-class ABlasterFX1 : public AFastProjectile
-{
-	DECLARE_CLASS(ABlasterFX1, AFastProjectile)
-public:
-	void Effect ();
-	int DoSpecialDamage (AActor *target, int damage, FName damagetype);
-};
-
-int ABlasterFX1::DoSpecialDamage (AActor *target, int damage, FName damagetype)
-{
-	if (target->IsKindOf (PClass::FindClass ("Ironlich")))
-	{ // Less damage to Ironlich bosses
-		damage = pr_bfx1() & 1;
-		if (!damage)
-		{
-			return -1;
-		}
-	}
-	return damage;
-}
-
-void ABlasterFX1::Effect ()
-{
-	if (pr_bfx1t() < 64)
-	{
-		Spawn("BlasterSmoke", PosAtZ(MAX(Z() - 8., floorz)), ALLOW_REPLACE);
-	}
-}
-
-IMPLEMENT_CLASS(ABlasterFX1, false, false, false, false)
-
-// Ripper -------------------------------------------------------------------
-
-
-class ARipper : public AActor
-{
-	DECLARE_CLASS (ARipper, AActor)
-public:
-	int DoSpecialDamage (AActor *target, int damage, FName damagetype);
-};
-
-IMPLEMENT_CLASS(ARipper, false, false, false, false)
-
-int ARipper::DoSpecialDamage (AActor *target, int damage, FName damagetype)
-{
-	if (target->IsKindOf (PClass::FindClass ("Ironlich")))
-	{ // Less damage to Ironlich bosses
-		damage = pr_ripd() & 1;
-		if (!damage)
-		{
-			return -1;
-		}
-	}
-	return damage;
-}
-
-//----------------------------------------------------------------------------
-//
-// PROC A_FireBlasterPL1
-//
-//----------------------------------------------------------------------------
-
-DEFINE_ACTION_FUNCTION(AActor, A_FireBlasterPL1)
-{
-	PARAM_ACTION_PROLOGUE(AActor);
-
-	DAngle ang;
-	int damage;
-	player_t *player;
-
-	if (NULL == (player = self->player))
-	{
-		return 0;
-	}
-
-	AWeapon *weapon = self->player->ReadyWeapon;
-	if (weapon != NULL)
-	{
-		if (!weapon->DepleteAmmo (weapon->bAltFire))
-			return 0;
-	}
-	DAngle pitch = P_BulletSlope(self);
-	damage = pr_fb1.HitDice (4);
-	ang = self->Angles.Yaw;
-	if (player->refire)
-	{
-		ang += pr_fb1.Random2() * (5.625 / 256);
-	}
-	P_LineAttack (self, ang, PLAYERMISSILERANGE, pitch, damage, NAME_Hitscan, "BlasterPuff");
-	S_Sound (self, CHAN_WEAPON, "weapons/blastershoot", 1, ATTN_NORM);
-	return 0;
-}
-
-//----------------------------------------------------------------------------
-//
-// PROC A_SpawnRippers
-//
-//----------------------------------------------------------------------------
-
-DEFINE_ACTION_FUNCTION(AActor, A_SpawnRippers)
-{
-	PARAM_SELF_PROLOGUE(AActor);
-
-	unsigned int i;
-	DAngle ang;
-	AActor *ripper;
-
-	for(i = 0; i < 8; i++)
-	{
-		ripper = Spawn<ARipper> (self->Pos(), ALLOW_REPLACE);
-		ang = i*45.;
-		ripper->target = self->target;
-		ripper->Angles.Yaw = ang;
-		ripper->VelFromAngle();
-		P_CheckMissileSpawn (ripper, self->radius);
-	}
-	return 0;
-}
-
 // --- Skull rod ------------------------------------------------------------
 
 
diff --git a/src/g_hexen/a_clericflame.cpp b/src/g_hexen/a_clericflame.cpp
index 2c66cb42d1..d4f86ecd5b 100644
--- a/src/g_hexen/a_clericflame.cpp
+++ b/src/g_hexen/a_clericflame.cpp
@@ -25,38 +25,6 @@ void A_CFlameMissile (AActor *);
 
 // Flame Missile ------------------------------------------------------------
 
-class ACFlameMissile : public AFastProjectile
-{
-	DECLARE_CLASS (ACFlameMissile, AFastProjectile)
-public:
-	void BeginPlay ();
-	void Effect ();
-};
-
-IMPLEMENT_CLASS(ACFlameMissile, false, false, false, false)
-
-void ACFlameMissile::BeginPlay ()
-{
-	special1 = 2;
-}
-
-void ACFlameMissile::Effect ()
-{
-	if (!--special1)
-	{
-		special1 = 4;
-		double newz = Z() - 12;
-		if (newz < floorz)
-		{
-			newz = floorz;
-		}
-		AActor *mo = Spawn ("CFlameFloor", PosAtZ(newz), ALLOW_REPLACE);
-		if (mo)
-		{
-			mo->Angles.Yaw = Angles.Yaw;
-		}
-	}
-}
 
 //============================================================================
 //
@@ -80,7 +48,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_CFlameAttack)
 		if (!weapon->DepleteAmmo (weapon->bAltFire))
 			return 0;
 	}
-	P_SpawnPlayerMissile (self, RUNTIME_CLASS(ACFlameMissile));
+	P_SpawnPlayerMissile (self, PClass::FindActor("CFlameMissile"));
 	S_Sound (self, CHAN_WEAPON, "ClericFlameFire", 1, ATTN_NORM);
 	return 0;
 }
diff --git a/src/g_shared/a_fastprojectile.cpp b/src/g_shared/a_fastprojectile.cpp
index ffca8a8fad..1ded466a9e 100644
--- a/src/g_shared/a_fastprojectile.cpp
+++ b/src/g_shared/a_fastprojectile.cpp
@@ -6,6 +6,7 @@
 #include "p_lnspec.h"
 #include "b_bot.h"
 #include "p_checkposition.h"
+#include "virtual.h"
 
 IMPLEMENT_CLASS(AFastProjectile, false, false, false, false)
 
@@ -132,7 +133,13 @@ void AFastProjectile::Tick ()
 			if (!frac.isZero() && ripcount <= 0) 
 			{
 				ripcount = count >> 3;
-				Effect();
+
+				// call the scripted 'Effect' method.
+				VINDEX(AFastProjectile, Effect);
+				// Without the type cast this picks the 'void *' assignment...
+				VMValue params[1] = { (DObject*)this };
+				VMFrameStack stack;
+				stack.Call(VFUNC, params, 1, nullptr, 0, nullptr);
 			}
 		}
 	}
@@ -153,35 +160,3 @@ void AFastProjectile::Tick ()
 }
 
 
-void AFastProjectile::Effect()
-{
-	FName name = GetClass()->MissileName;
-	if (name != NAME_None)
-	{
-		double hitz = Z()-8;
-
-		if (hitz < floorz)
-		{
-			hitz = floorz;
-		}
-		// Do not clip this offset to the floor.
-		hitz += GetClass()->MissileHeight;
-		
-		PClassActor *trail = PClass::FindActor(name);
-		if (trail != NULL)
-		{
-			AActor *act = Spawn (trail, PosAtZ(hitz), ALLOW_REPLACE);
-			if (act != nullptr)
-			{
-				if ((flags5 & MF5_GETOWNER) && (target != nullptr))
-					act->target = target;
-				else
-					act->target = this;
-				
-				act->Angles.Pitch = Angles.Pitch;
-				act->Angles.Yaw = Angles.Yaw;
-			}
-		}
-	}
-}
-
diff --git a/src/g_shared/a_sharedglobal.h b/src/g_shared/a_sharedglobal.h
index 6911c6688b..bb85a023c2 100644
--- a/src/g_shared/a_sharedglobal.h
+++ b/src/g_shared/a_sharedglobal.h
@@ -228,7 +228,6 @@ class AFastProjectile : public AActor
 	DECLARE_CLASS(AFastProjectile, AActor)
 public:
 	void Tick ();
-	virtual void Effect();
 };
 
 
diff --git a/src/info.cpp b/src/info.cpp
index 8ef594d1bf..fec5af63e0 100644
--- a/src/info.cpp
+++ b/src/info.cpp
@@ -230,7 +230,6 @@ PClassActor::PClassActor()
 	BurnHeight = -1;
 	GibHealth = INT_MIN;
 	WoundHealth = 6;
-	PoisonDamage = 0;
 	FastSpeed = -1.;
 	RDFactor = 1.;
 	CameraHeight = INT_MIN;
@@ -291,7 +290,6 @@ void PClassActor::DeriveData(PClass *newclass)
 	newa->BloodColor = BloodColor;
 	newa->GibHealth = GibHealth;
 	newa->WoundHealth = WoundHealth;
-	newa->PoisonDamage = PoisonDamage;
 	newa->FastSpeed = FastSpeed;
 	newa->RDFactor = RDFactor;
 	newa->CameraHeight = CameraHeight;
diff --git a/src/info.h b/src/info.h
index a41a6def8d..bb21524a52 100644
--- a/src/info.h
+++ b/src/info.h
@@ -296,7 +296,6 @@ public:
 	PalEntry BloodColor;	// Colorized blood
 	int GibHealth;			// Negative health below which this monster dies an extreme death
 	int WoundHealth;		// Health needed to enter wound state
-	int PoisonDamage;		// Amount of poison damage
 	double FastSpeed;		// speed in fast mode
 	double RDFactor;		// Radius damage factor
 	double CameraHeight;	// Height of camera when used as such
diff --git a/src/namedef.h b/src/namedef.h
index 639c762ebd..b7365b2b21 100644
--- a/src/namedef.h
+++ b/src/namedef.h
@@ -285,6 +285,7 @@ xx(FRandom)
 xx(Random2)
 xx(RandomPick)
 xx(FRandomPick)
+xx(GetClass)
 xx(Exp)
 xx(Log10)
 xx(Ceil)
diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp
index 46ecbd3faf..3c8d1173b9 100644
--- a/src/p_mobj.cpp
+++ b/src/p_mobj.cpp
@@ -310,6 +310,28 @@ DEFINE_FIELD(AActor, ConversationRoot)
 DEFINE_FIELD(AActor, Conversation)
 DEFINE_FIELD(AActor, DecalGenerator)
 
+DEFINE_FIELD(PClassActor, Obituary)
+DEFINE_FIELD(PClassActor, HitObituary)
+DEFINE_FIELD(PClassActor, DeathHeight)
+DEFINE_FIELD(PClassActor, BurnHeight)
+DEFINE_FIELD(PClassActor, BloodColor)
+DEFINE_FIELD(PClassActor, GibHealth)
+DEFINE_FIELD(PClassActor, WoundHealth)
+DEFINE_FIELD(PClassActor, FastSpeed)
+DEFINE_FIELD(PClassActor, RDFactor)
+DEFINE_FIELD(PClassActor, CameraHeight)
+DEFINE_FIELD(PClassActor, HowlSound)
+DEFINE_FIELD(PClassActor, BloodType)
+DEFINE_FIELD(PClassActor, BloodType2)
+DEFINE_FIELD(PClassActor, BloodType3)
+DEFINE_FIELD(PClassActor, DontHurtShooter)
+DEFINE_FIELD(PClassActor, ExplosionRadius)
+DEFINE_FIELD(PClassActor, ExplosionDamage)
+DEFINE_FIELD(PClassActor, MeleeDamage)
+DEFINE_FIELD(PClassActor, MeleeSound)
+DEFINE_FIELD(PClassActor, MissileName)
+DEFINE_FIELD(PClassActor, MissileHeight)
+
 //==========================================================================
 //
 // AActor :: Serialize
diff --git a/src/scripting/codegeneration/codegen.cpp b/src/scripting/codegeneration/codegen.cpp
index 4bf6f076eb..b7e20f7fc3 100644
--- a/src/scripting/codegeneration/codegen.cpp
+++ b/src/scripting/codegeneration/codegen.cpp
@@ -5637,12 +5637,6 @@ FxExpression *FxIdentifier::ResolveMember(FCompileContext &ctx, PStruct *classct
 				return nullptr;
 			}
 
-			if (vsym->Flags & VARF_Static)
-			{
-				// todo. For now these cannot be defined so let's just exit.
-				ScriptPosition.Message(MSG_ERROR, "Static members not implemented yet.");
-				return nullptr;
-			}
 			auto x = isclass ? new FxClassMember(object, vsym, ScriptPosition) : new FxStructMember(object, vsym, ScriptPosition);
 			object = nullptr;
 			return x->Resolve(ctx);
@@ -5918,40 +5912,14 @@ FxExpression *FxClassDefaults::Resolve(FCompileContext& ctx)
 //
 //==========================================================================
 
-int BuiltinGetDefault(VMFrameStack *stack, VMValue *param, TArray<VMValue> &defaultparam, int numparam, VMReturn *ret, int numret)
-{
-	assert(numparam == 1);
-	PARAM_POINTER_AT(0, obj, DObject);
-	ACTION_RETURN_OBJECT(obj->GetClass()->Defaults);
-}
-
-//==========================================================================
-//
-//
-//
-//==========================================================================
-
 ExpEmit FxClassDefaults::Emit(VMFunctionBuilder *build)
 {
-	EmitParameter(build, obj, ScriptPosition);
-	PSymbol *sym = FindBuiltinFunction(NAME_BuiltinGetDefault, BuiltinGetDefault);
-
-	assert(sym->IsKindOf(RUNTIME_CLASS(PSymbolVMFunction)));
-	assert(((PSymbolVMFunction *)sym)->Function != nullptr);
-	auto callfunc = ((PSymbolVMFunction *)sym)->Function;
-	int opcode = (EmitTail ? OP_TAIL_K : OP_CALL_K);
-	build->Emit(opcode, build->GetConstantAddress(callfunc, ATAG_OBJECT), 1, 1);
-
-	if (EmitTail)
-	{
-		ExpEmit call;
-		call.Final = true;
-		return call;
-	}
-
-	ExpEmit out(build, REGT_POINTER);
-	build->Emit(OP_RESULT, 0, REGT_POINTER, out.RegNum);
-	return out;
+	ExpEmit ob = obj->Emit(build);
+	ob.Free(build);
+	ExpEmit meta(build, REGT_POINTER);
+	build->Emit(OP_META, meta.RegNum, ob.RegNum);
+	build->Emit(OP_LO, meta.RegNum, meta.RegNum, build->GetConstantInt(myoffsetof(PClass, Defaults)));
+	return meta;
 
 }
 
@@ -6272,6 +6240,11 @@ FxStructMember::~FxStructMember()
 
 bool FxStructMember::RequestAddress(FCompileContext &ctx, bool *writable)
 {
+	// Cannot take the address of metadata variables.
+	if (membervar->Flags & VARF_Static)
+	{
+		return false;
+	}
 	AddressRequested = true;
 	if (writable != nullptr) *writable = (AddressWritable && !ctx.CheckReadOnly(membervar->Flags) &&
 											(!classx->ValueType->IsKindOf(RUNTIME_CLASS(PPointer)) || !static_cast<PPointer*>(classx->ValueType)->IsConst));
@@ -6411,6 +6384,14 @@ ExpEmit FxStructMember::Emit(VMFunctionBuilder *build)
 		obj = newobj;
 	}
 
+	if (membervar->Flags & VARF_Static)
+	{
+		obj.Free(build);
+		ExpEmit meta(build, REGT_POINTER);
+		build->Emit(OP_META, meta.RegNum, obj.RegNum);
+		obj = meta;
+	}
+
 	if (AddressRequested)
 	{
 		if (membervar->Offset == 0)
@@ -6953,6 +6934,13 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx)
 		}
 		break;
 
+	case NAME_GetClass:
+		if (CheckArgSize(NAME_GetClass, ArgList, 0, 0, ScriptPosition))
+		{
+			func = new FxGetClass(new FxSelf(ScriptPosition));
+		}
+		break;
+
 	case NAME_Random:
 		// allow calling Random without arguments to default to (0, 255)
 		if (ArgList.Size() == 0)
@@ -7132,6 +7120,12 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
 		// handle builtins: Vectors got 2: Length and Unit.
 		if (MethodName == NAME_Length || MethodName == NAME_Unit)
 		{
+			if (ArgList.Size() > 0)
+			{
+				ScriptPosition.Message(MSG_ERROR, "too many parameters in call to %s", MethodName.GetChars());
+				delete this;
+				return nullptr;
+			}
 			auto x = new FxVectorBuiltin(Self, MethodName);
 			Self = nullptr;
 			delete this;
@@ -7144,6 +7138,17 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
 		auto ptype = static_cast<PPointer *>(Self->ValueType)->PointedType;
 		if (ptype->IsKindOf(RUNTIME_CLASS(PStruct)))
 		{
+			if (ptype->IsKindOf(RUNTIME_CLASS(PClass)) && MethodName == NAME_GetClass)
+			{
+				if (ArgList.Size() > 0)
+				{
+					ScriptPosition.Message(MSG_ERROR, "too many parameters in call to %s", MethodName.GetChars());
+					delete this;
+					return nullptr;
+				}
+				auto x = new FxGetClass(Self);
+				return x->Resolve(ctx);
+			}
 			cls = static_cast<PStruct *>(ptype);
 		}
 		else
@@ -7976,6 +7981,44 @@ ExpEmit FxVectorBuiltin::Emit(VMFunctionBuilder *build)
 	return to;
 }
 
+//==========================================================================
+//
+//
+//==========================================================================
+
+FxGetClass::FxGetClass(FxExpression *self)
+	:FxExpression(EFX_GetClass, self->ScriptPosition)
+{
+	Self = self;
+}
+
+FxGetClass::~FxGetClass()
+{
+	SAFE_DELETE(Self);
+}
+
+FxExpression *FxGetClass::Resolve(FCompileContext &ctx)
+{
+	SAFE_RESOLVE(Self, ctx);
+	if (!Self->IsObject())
+	{
+		ScriptPosition.Message(MSG_ERROR, "GetClass() requires an object");
+		delete this;
+		return nullptr;
+	}
+	ValueType = NewClassPointer(static_cast<PClass*>(static_cast<PPointer*>(Self->ValueType)->PointedType));
+	return this;
+}
+
+ExpEmit FxGetClass::Emit(VMFunctionBuilder *build)
+{
+	ExpEmit op = Self->Emit(build);
+	op.Free(build);
+	ExpEmit to(build, REGT_POINTER);
+	build->Emit(OP_META, to.RegNum, op.RegNum);
+	return to;
+}
+
 //==========================================================================
 //
 // FxSequence :: Resolve
diff --git a/src/scripting/codegeneration/codegen.h b/src/scripting/codegeneration/codegen.h
index f004e469a3..d7547a198e 100644
--- a/src/scripting/codegeneration/codegen.h
+++ b/src/scripting/codegeneration/codegen.h
@@ -286,6 +286,7 @@ enum EFxType
 	EFX_StaticArrayVariable,
 	EFX_CVar,
 	EFX_NamedNode,
+	EFX_GetClass,
 	EFX_COUNT
 };
 
@@ -320,6 +321,7 @@ public:
 	bool IsPointer() const { return ValueType->GetRegType() == REGT_POINTER; }
 	bool IsVector() const { return ValueType == TypeVector2 || ValueType == TypeVector3; };
 	bool IsBoolCompat() const { return ValueType->GetRegCount() == 1 && (ValueType->GetRegType() == REGT_INT || ValueType->GetRegType() == REGT_FLOAT || ValueType->GetRegType() == REGT_POINTER); }
+	bool IsObject() const { return ValueType->IsKindOf(RUNTIME_CLASS(PPointer)) && !ValueType->IsKindOf(RUNTIME_CLASS(PClassPointer)) && ValueType != TypeNullPtr && static_cast<PPointer*>(ValueType)->PointedType->IsKindOf(RUNTIME_CLASS(PClass)); }
 
 	virtual ExpEmit Emit(VMFunctionBuilder *build);
 
@@ -1520,6 +1522,24 @@ public:
 	ExpEmit Emit(VMFunctionBuilder *build);
 };
 
+//==========================================================================
+//
+//	FxFlopFunctionCall
+//
+//==========================================================================
+
+class FxGetClass : public FxExpression
+{
+	FxExpression *Self;
+
+public:
+
+	FxGetClass(FxExpression *self);
+	~FxGetClass();
+	FxExpression *Resolve(FCompileContext&);
+	ExpEmit Emit(VMFunctionBuilder *build);
+};
+
 //==========================================================================
 //
 // FxVMFunctionCall
diff --git a/src/scripting/vm/vmexec.cpp b/src/scripting/vm/vmexec.cpp
index 24beb7f9c6..750c384e0d 100644
--- a/src/scripting/vm/vmexec.cpp
+++ b/src/scripting/vm/vmexec.cpp
@@ -75,6 +75,7 @@
 #define ASSERTF(x)		assert((unsigned)(x) < f->NumRegF)
 #define ASSERTA(x)		assert((unsigned)(x) < f->NumRegA)
 #define ASSERTS(x)		assert((unsigned)(x) < f->NumRegS)
+#define ASSERTO(x)		assert((unsigned)(x) < f->NumRegA && reg.atag[x] == ATAG_OBJECT)
 
 #define ASSERTKD(x)		assert(sfunc != NULL && (unsigned)(x) < sfunc->NumKonstD)
 #define ASSERTKF(x)		assert(sfunc != NULL && (unsigned)(x) < sfunc->NumKonstF)
diff --git a/src/scripting/vm/vmexec.h b/src/scripting/vm/vmexec.h
index 6f25318d61..4df76a73e7 100644
--- a/src/scripting/vm/vmexec.h
+++ b/src/scripting/vm/vmexec.h
@@ -110,6 +110,12 @@ begin:
 		reg.atag[a] = ATAG_GENERIC;	// using ATAG_FRAMEPOINTER will cause endless asserts.
 		NEXTOP;
 
+	OP(META):
+		ASSERTA(a); ASSERTO(B);
+		reg.a[a] = ((DObject*)reg.a[B])->GetClass();	// I wish this could be done without a special opcode but there's really no good way to guarantee initialization of the Class pointer...
+		reg.atag[a] = ATAG_OBJECT;
+		NEXTOP;
+
 	OP(LB):
 		ASSERTD(a); ASSERTA(B); ASSERTKD(C);
 		GETADDR(PB,KC,X_READ_NIL);
diff --git a/src/scripting/vm/vmops.h b/src/scripting/vm/vmops.h
index cf2a757469..b36f93881c 100644
--- a/src/scripting/vm/vmops.h
+++ b/src/scripting/vm/vmops.h
@@ -23,6 +23,7 @@ xx(LKF_R,	lk,		RFRII8,		NOP,	0, 0),		// load float constant indexed
 xx(LKS_R,	lk,		RSRII8,		NOP,	0, 0),		// load string constant indexed
 xx(LKP_R,	lk,		RPRII8,		NOP,	0, 0),		// load pointer constant indexed
 xx(LFP,		lf,		LFP,		NOP,	0, 0),		// load frame pointer
+xx(META,	meta,	RPRP,		NOP,	0, 0),		// load a class's meta class address
 
 // Load from memory. rA = *(rB + rkC)
 xx(LB,		lb,		RIRPKI,		LB_R,	4, REGT_INT),	// load byte
diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp
index 3437c848cb..a4a2cc4533 100644
--- a/src/scripting/zscript/zcc_compile.cpp
+++ b/src/scripting/zscript/zcc_compile.cpp
@@ -1298,8 +1298,12 @@ bool ZCCCompiler::CompileFields(PStruct *type, TArray<ZCC_VarDeclarator *> &Fiel
 
 		if (field->Flags & ZCC_Meta)
 		{
-			varflags |= VARF_ReadOnly;	// metadata implies readonly
-			// todo: this needs to go into the metaclass and needs some handling
+			varflags |= VARF_Static|VARF_ReadOnly;	// metadata implies readonly
+			if (!(field->Flags & ZCC_Native))
+			{
+				// Non-native meta data is not implemented yet and requires some groundwork in the class copy code.
+				Error(field, "Metadata member %s must be native", FName(field->Names->Name).GetChars());
+			}
 		}
 
 		if (field->Type->ArraySize != nullptr)
@@ -1320,7 +1324,8 @@ bool ZCCCompiler::CompileFields(PStruct *type, TArray<ZCC_VarDeclarator *> &Fiel
 				
 				if (varflags & VARF_Native)
 				{
-					fd = FindField(type, FName(name->Name).GetChars());
+					auto querytype = (varflags & VARF_Static) ? type->GetClass() : type;
+					fd = FindField(querytype, FName(name->Name).GetChars());
 					if (fd == nullptr)
 					{
 						Error(field, "The member variable '%s.%s' has not been exported from the executable.", type->TypeName.GetChars(), FName(name->Name).GetChars());
diff --git a/wadsrc/static/zscript.txt b/wadsrc/static/zscript.txt
index 074bb89681..3507e45a8b 100644
--- a/wadsrc/static/zscript.txt
+++ b/wadsrc/static/zscript.txt
@@ -31,6 +31,7 @@ zscript/shared/setcolor.txt
 zscript/shared/sectoraction.txt
 zscript/shared/ice.txt
 zscript/shared/dog.txt
+zscript/shared/fastprojectile.txt
 
 zscript/doom/doomplayer.txt
 zscript/doom/possessed.txt
@@ -100,6 +101,7 @@ zscript/heretic/weaponwand.txt
 zscript/heretic/weaponcrossbow.txt
 zscript/heretic/weapongauntlets.txt
 zscript/heretic/weaponmace.txt
+zscript/heretic/weaponblaster.txt
 
 zscript/hexen/baseweapons.txt
 zscript/hexen/korax.txt
diff --git a/wadsrc/static/zscript/actor.txt b/wadsrc/static/zscript/actor.txt
index 0f0acc3077..3eb855007e 100644
--- a/wadsrc/static/zscript/actor.txt
+++ b/wadsrc/static/zscript/actor.txt
@@ -145,6 +145,29 @@ class Actor : Thinker native
 	native readonly State SeeState;
 	native State MeleeState;
 	native State MissileState;
+	
+	native meta String Obituary;		// Player was killed by this actor
+	native meta String HitObituary;		// Player was killed by this actor in melee
+	native meta double DeathHeight;		// Height on normal death
+	native meta double BurnHeight;		// Height on burning death
+	native meta color BloodColor;		// Colorized blood
+	native meta int GibHealth;			// Negative health below which this monster dies an extreme death
+	native meta int WoundHealth;		// Health needed to enter wound state
+	native meta double FastSpeed;		// speed in fast mode
+	native meta double RDFactor;		// Radius damage factor
+	native meta double CameraHeight;	// Height of camera when used as such
+	native meta Sound HowlSound;		// Sound being played when electrocuted or poisoned
+	native meta Name BloodType;			// Blood replacement type
+	native meta Name BloodType2;		// Bloopsplatter replacement type
+	native meta Name BloodType3;		// AxeBlood replacement type
+	native meta bool DontHurtShooter;
+	native meta int ExplosionRadius;
+	native meta int ExplosionDamage;
+	native meta int MeleeDamage;
+	native meta Sound MeleeSound;
+	native meta Name MissileName;
+	native meta double MissileHeight;
+	
 
 	// need some definition work first
 	//FRenderStyle RenderStyle;
diff --git a/wadsrc/static/zscript/base.txt b/wadsrc/static/zscript/base.txt
index a63020bbf5..c899bbd373 100644
--- a/wadsrc/static/zscript/base.txt
+++ b/wadsrc/static/zscript/base.txt
@@ -7,7 +7,6 @@ class Object native
 	native static double G_SkillPropertyFloat(int p);
 
 	virtual native void Destroy();
-	native class<Object> GetClass();
 }
 
 class Thinker : Object native
diff --git a/wadsrc/static/zscript/heretic/hereticweaps.txt b/wadsrc/static/zscript/heretic/hereticweaps.txt
index 07f993efe1..c3c641b4ea 100644
--- a/wadsrc/static/zscript/heretic/hereticweaps.txt
+++ b/wadsrc/static/zscript/heretic/hereticweaps.txt
@@ -7,179 +7,6 @@ class HereticWeapon : Weapon
 	}
 }
 
-// Blaster ------------------------------------------------------------------
-
-class Blaster : HereticWeapon
-{
-	Default
-	{
-		+BLOODSPLATTER
-		Weapon.SelectionOrder 500;
-		Weapon.AmmoUse 1;
-		Weapon.AmmoGive 30;
-		Weapon.YAdjust 15;
-		Weapon.AmmoType "BlasterAmmo";
-		Weapon.SisterWeapon "BlasterPowered";
-		Inventory.PickupMessage "$TXT_WPNBLASTER";
-		Tag "$TAG_BLASTER";
-		Obituary "$OB_MPBLASTER";
-	}
-
-	action native void A_FireBlasterPL1();
-
-	States
-	{
-	Spawn:
-		WBLS A -1;
-		Stop;
-	Ready:
-		BLSR A 1 A_WeaponReady;
-		Loop;
-	Deselect:
-		BLSR A 1 A_Lower;
-		Loop;
-	Select:
-		BLSR A 1 A_Raise;
-		Loop;
-	Fire:
-		BLSR BC 3;
-	Hold:
-		BLSR D 2 A_FireBlasterPL1;
-		BLSR CB 2;
-		BLSR A 0 A_ReFire;
-		Goto Ready;
-	}
-}
-
-class BlasterPowered : Blaster
-{
-	Default
-	{
-		+WEAPON.POWERED_UP
-		Weapon.AmmoUse 5;
-		Weapon.AmmoGive 0;
-		Weapon.SisterWeapon "Blaster";
-		Tag "$TAG_BLASTERP";
-	}
-
-	States
-	{
-	Fire:
-		BLSR BC 0;
-	Hold:
-		BLSR D 3 A_FireCustomMissile("BlasterFX1");
-		BLSR CB 4;
-		BLSR A 0 A_ReFire;
-		Goto Ready;
-	}
-}
-
-// Blaster FX 1 -------------------------------------------------------------
-
-class BlasterFX1 : FastProjectile native
-{
-	Default
-	{
-		Radius 12;
-		Height 8;
-		Speed 184;
-		Damage 2;
-		SeeSound "weapons/blastershoot";
-		DeathSound "weapons/blasterhit";
-		+SPAWNSOUNDSOURCE
-		Obituary "$OB_MPPBLASTER";
-	}
-
-	native void A_SpawnRippers();
-
-	States
-	{
-	Spawn:
-		ACLO E 200;
-		Loop;
-	Death:
-		FX18 A 3 BRIGHT A_SpawnRippers;
-		FX18 B 3 BRIGHT;
-		FX18 CDEFG 4 BRIGHT;
-		Stop;
-	}
-}
-
-// Blaster smoke ------------------------------------------------------------
-
-class BlasterSmoke : Actor
-{
-	Default
-	{
-		+NOBLOCKMAP
-		+NOGRAVITY
-		+NOTELEPORT
-		+CANNOTPUSH
-		RenderStyle "Translucent";
-		Alpha 0.4;
-	}
-
-	States
-	{
-	Spawn:
-		FX18 HIJKL 4;
-		Stop;
-	}
-}
-
-// Ripper -------------------------------------------------------------------
-
-class Ripper : Actor native
-{
-	Default
-	{
-		Radius 8;
-		Height 6;
-		Speed 14;
-		Damage 1;
-		Projectile;
-		+RIPPER
-		DeathSound "weapons/blasterpowhit";
-		Obituary "$OB_MPPBLASTER";
-	}
-
-	States
-	{
-	Spawn:
-		FX18 M 4;
-		FX18 N 5;
-		Loop;
-	Death:
-		FX18 OPQRS 4 BRIGHT;
-		Stop;
-	}
-}
-
-// Blaster Puff -------------------------------------------------------------
-
-class BlasterPuff : Actor
-{
-	Default
-	{
-		+NOBLOCKMAP
-		+NOGRAVITY
-		+PUFFONACTORS
-		RenderStyle "Add";
-		SeeSound "weapons/blasterhit";
-	}
-
-	States
-	{
-	Crash:
-		FX17 ABCDE 4 BRIGHT;
-		Stop;
-	Spawn:
-		FX17 FG 3 BRIGHT;
-		FX17 HIJKL 4 BRIGHT;
-		Stop;
-	}
-}
-
 
 // Skull (Horn) Rod ---------------------------------------------------------
 
diff --git a/wadsrc/static/zscript/heretic/weaponblaster.txt b/wadsrc/static/zscript/heretic/weaponblaster.txt
new file mode 100644
index 0000000000..8f4756b5b6
--- /dev/null
+++ b/wadsrc/static/zscript/heretic/weaponblaster.txt
@@ -0,0 +1,259 @@
+// Blaster ------------------------------------------------------------------
+
+class Blaster : HereticWeapon
+{
+	Default
+	{
+		+BLOODSPLATTER
+		Weapon.SelectionOrder 500;
+		Weapon.AmmoUse 1;
+		Weapon.AmmoGive 30;
+		Weapon.YAdjust 15;
+		Weapon.AmmoType "BlasterAmmo";
+		Weapon.SisterWeapon "BlasterPowered";
+		Inventory.PickupMessage "$TXT_WPNBLASTER";
+		Tag "$TAG_BLASTER";
+		Obituary "$OB_MPBLASTER";
+	}
+
+	States
+	{
+	Spawn:
+		WBLS A -1;
+		Stop;
+	Ready:
+		BLSR A 1 A_WeaponReady;
+		Loop;
+	Deselect:
+		BLSR A 1 A_Lower;
+		Loop;
+	Select:
+		BLSR A 1 A_Raise;
+		Loop;
+	Fire:
+		BLSR BC 3;
+	Hold:
+		BLSR D 2 A_FireBlasterPL1;
+		BLSR CB 2;
+		BLSR A 0 A_ReFire;
+		Goto Ready;
+	}
+	
+	//----------------------------------------------------------------------------
+	//
+	// PROC A_FireBlasterPL1
+	//
+	//----------------------------------------------------------------------------
+
+	action void A_FireBlasterPL1()
+	{
+		if (player == null)
+		{
+			return;
+		}
+
+		Weapon weapon = player.ReadyWeapon;
+		if (weapon != null)
+		{
+			if (!weapon.DepleteAmmo (weapon.bAltFire))
+				return;
+		}
+
+		double pitch = BulletSlope();
+		int damage = random[FireBlaster](1, 8) * 4;
+		double ang = angle;
+		if (player.refire)
+		{
+			ang += Random2[FireBlaster]() * (5.625 / 256);
+		}
+		LineAttack (ang, PLAYERMISSILERANGE, pitch, damage, 'Hitscan', "BlasterPuff");
+		A_PlaySound ("weapons/blastershoot", CHAN_WEAPON);
+	}
+}
+
+class BlasterPowered : Blaster
+{
+	Default
+	{
+		+WEAPON.POWERED_UP
+		Weapon.AmmoUse 5;
+		Weapon.AmmoGive 0;
+		Weapon.SisterWeapon "Blaster";
+		Tag "$TAG_BLASTERP";
+	}
+
+	States
+	{
+	Fire:
+		BLSR BC 0;
+	Hold:
+		BLSR D 3 A_FireCustomMissile("BlasterFX1");
+		BLSR CB 4;
+		BLSR A 0 A_ReFire;
+		Goto Ready;
+	}
+}
+
+// Blaster FX 1 -------------------------------------------------------------
+
+class BlasterFX1 : FastProjectile
+{
+	Default
+	{
+		Radius 12;
+		Height 8;
+		Speed 184;
+		Damage 2;
+		SeeSound "weapons/blastershoot";
+		DeathSound "weapons/blasterhit";
+		+SPAWNSOUNDSOURCE
+		Obituary "$OB_MPPBLASTER";
+	}
+
+	States
+	{
+	Spawn:
+		ACLO E 200;
+		Loop;
+	Death:
+		FX18 A 3 BRIGHT A_SpawnRippers;
+		FX18 B 3 BRIGHT;
+		FX18 CDEFG 4 BRIGHT;
+		Stop;
+	}
+	
+	//----------------------------------------------------------------------------
+	//
+	// 
+	//
+	//----------------------------------------------------------------------------
+
+	override int DoSpecialDamage (Actor target, int damage, Name damagetype)
+	{
+		if (target is "Ironlich")
+		{ // Less damage to Ironlich bosses
+			damage = random[BlasterFX]() & 1;
+			if (!damage)
+			{
+				return -1;
+			}
+		}
+		return damage;
+	}
+
+	override void Effect ()
+	{
+		if (random[BlasterFX]() < 64)
+		{
+			Spawn("BlasterSmoke", (pos.xy, max(pos.z - 8, floorz)), ALLOW_REPLACE);
+		}
+	}
+
+	//----------------------------------------------------------------------------
+	//
+	// PROC A_SpawnRippers
+	//
+	//----------------------------------------------------------------------------
+
+	void A_SpawnRippers()
+	{
+		for(int i = 0; i < 8; i++)
+		{
+			Actor ripper = Spawn("Ripper", pos, ALLOW_REPLACE);
+			ripper.target = target;
+			ripper.angle = i*45;
+			ripper.VelFromAngle();
+			ripper.CheckMissileSpawn (radius);
+		}
+	}
+}
+
+// Blaster smoke ------------------------------------------------------------
+
+class BlasterSmoke : Actor
+{
+	Default
+	{
+		+NOBLOCKMAP
+		+NOGRAVITY
+		+NOTELEPORT
+		+CANNOTPUSH
+		RenderStyle "Translucent";
+		Alpha 0.4;
+	}
+
+	States
+	{
+	Spawn:
+		FX18 HIJKL 4;
+		Stop;
+	}
+}
+
+// Ripper -------------------------------------------------------------------
+
+class Ripper : Actor
+{
+	Default
+	{
+		Radius 8;
+		Height 6;
+		Speed 14;
+		Damage 1;
+		Projectile;
+		+RIPPER
+		DeathSound "weapons/blasterpowhit";
+		Obituary "$OB_MPPBLASTER";
+	}
+
+	States
+	{
+	Spawn:
+		FX18 M 4;
+		FX18 N 5;
+		Loop;
+	Death:
+		FX18 OPQRS 4 BRIGHT;
+		Stop;
+	}
+	
+	int DoSpecialDamage (Actor target, int damage, Name damagetype)
+	{
+		if (target is "Ironlich")
+		{ // Less damage to Ironlich bosses
+			damage = random[Ripper]() & 1;
+			if (!damage)
+			{
+				return -1;
+			}
+		}
+		return damage;
+	}
+	
+}
+
+// Blaster Puff -------------------------------------------------------------
+
+class BlasterPuff : Actor
+{
+	Default
+	{
+		+NOBLOCKMAP
+		+NOGRAVITY
+		+PUFFONACTORS
+		RenderStyle "Add";
+		SeeSound "weapons/blasterhit";
+	}
+
+	States
+	{
+	Crash:
+		FX17 ABCDE 4 BRIGHT;
+		Stop;
+	Spawn:
+		FX17 FG 3 BRIGHT;
+		FX17 HIJKL 4 BRIGHT;
+		Stop;
+	}
+}
+
diff --git a/wadsrc/static/zscript/hexen/clericflame.txt b/wadsrc/static/zscript/hexen/clericflame.txt
index 13fca8ad10..37e4149f92 100644
--- a/wadsrc/static/zscript/hexen/clericflame.txt
+++ b/wadsrc/static/zscript/hexen/clericflame.txt
@@ -170,7 +170,7 @@ class CircleFlame : Actor
 
 // Flame Missile ------------------------------------------------------------
 
-class CFlameMissile : FastProjectile native
+class CFlameMissile : FastProjectile
 {
 	Default
 	{
@@ -208,4 +208,28 @@ class CFlameMissile : FastProjectile native
 		CFFX M 3 Bright;
 		Stop;
 	}
+	
+	override void BeginPlay ()
+	{
+		special1 = 2;
+	}
+
+	override void Effect ()
+	{
+		if (!--special1)
+		{
+			special1 = 4;
+			double newz = pos.z - 12;
+			if (newz < floorz)
+			{
+				newz = floorz;
+			}
+			Actor mo = Spawn ("CFlameFloor", (pos.xy, newz), ALLOW_REPLACE);
+			if (mo)
+			{
+				mo.angle = angle;
+			}
+		}
+	}
+	
 }
diff --git a/wadsrc/static/zscript/shared/fastprojectile.txt b/wadsrc/static/zscript/shared/fastprojectile.txt
new file mode 100644
index 0000000000..b1d5090738
--- /dev/null
+++ b/wadsrc/static/zscript/shared/fastprojectile.txt
@@ -0,0 +1,41 @@
+// Fast projectiles --------------------------------------------------------
+
+class FastProjectile : Actor native
+{
+	Default
+	{
+		Projectile;
+		MissileHeight 0;
+	}
+	
+	
+	virtual void Effect()
+	{
+		class<Actor> trail = MissileName;
+		if (trail != null)
+		{
+			double hitz = pos.z - 8;
+
+			if (hitz < floorz)
+			{
+				hitz = floorz;
+			}
+			// Do not clip this offset to the floor.
+			hitz += MissileHeight;
+			
+			Actor act = Spawn (trail, (pos.xy, hitz), ALLOW_REPLACE);
+			if (act != null)
+			{
+				if (bGetOwner && target != null)
+					act.target = target;
+				else
+					act.target = self;
+				
+				act.angle = angle;
+				act.pitch = pitch;
+			}
+		}
+	}
+	
+}
+
diff --git a/wadsrc/static/zscript/shared/sharedmisc.txt b/wadsrc/static/zscript/shared/sharedmisc.txt
index bebf285492..5989702fdf 100644
--- a/wadsrc/static/zscript/shared/sharedmisc.txt
+++ b/wadsrc/static/zscript/shared/sharedmisc.txt
@@ -179,17 +179,6 @@ class RandomSpawner : Actor native
 	}
 }
 
-// Fast projectiles --------------------------------------------------------
-
-class FastProjectile : Actor native
-{
-	Default
-	{
-		Projectile;
-		MissileHeight 0;
-	}
-}
-
 // Sector flag setter ------------------------------------------------------
 
 class SectorFlagSetter : Actor native