diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp
index f61ad34bb..d89a89576 100644
--- a/src/p_mobj.cpp
+++ b/src/p_mobj.cpp
@@ -5824,6 +5824,15 @@ AActor *P_SpawnMissile (AActor *source, AActor *dest, PClassActor *type, AActor
 	return P_SpawnMissileXYZ (source->PosPlusZ(32 + source->GetBobOffset()), source, dest, type, true, owner);
 }
 
+DEFINE_ACTION_FUNCTION(AActor, P_SpawnMissile)
+{
+	PARAM_SELF_PROLOGUE(AActor);
+	PARAM_OBJECT(dest, AActor);
+	PARAM_CLASS(type, AActor);
+	PARAM_OBJECT_OPT(owner, AActor) { owner = self; }
+	ACTION_RETURN_OBJECT(P_SpawnMissile(self, dest, type, owner));
+}
+
 AActor *P_SpawnMissileZ (AActor *source, double z, AActor *dest, PClassActor *type)
 {
 	if (source == NULL)
diff --git a/src/sc_man_scanner.re b/src/sc_man_scanner.re
index caeb18f8c..cc9968ae3 100644
--- a/src/sc_man_scanner.re
+++ b/src/sc_man_scanner.re
@@ -151,7 +151,7 @@ std2:
 		'transient'					{ RET(TK_Transient); }
 		'final'						{ RET(TK_Final); }
 		'throws'					{ RET(TK_Throws); }
-		'extends'					{ RET(TK_Extends); }
+		'extend'					{ RET(TK_Extend); }
 		'public'					{ RET(TK_Public); }
 		'protected'					{ RET(TK_Protected); }
 		'private'					{ RET(TK_Private); }
diff --git a/src/sc_man_tokens.h b/src/sc_man_tokens.h
index 69927883c..19d331301 100644
--- a/src/sc_man_tokens.h
+++ b/src/sc_man_tokens.h
@@ -89,7 +89,7 @@ xx(TK_Transient,			"'transient'")
 xx(TK_Volatile,				"'volatile'")
 xx(TK_Final,				"'final'")
 xx(TK_Throws,				"'throws'")
-xx(TK_Extends,				"'extends'")
+xx(TK_Extend,				"'extend'")
 xx(TK_Public,				"'public'")
 xx(TK_Protected,			"'protected'")
 xx(TK_Private,				"'private'")
diff --git a/src/scripting/vm/vm.h b/src/scripting/vm/vm.h
index a37b7449b..12ce01273 100644
--- a/src/scripting/vm/vm.h
+++ b/src/scripting/vm/vm.h
@@ -1013,6 +1013,7 @@ struct AFuncDesc
 
 
 #define ACTION_RETURN_STATE(v) do { FState *state = v; if (numret > 0) { assert(ret != NULL); ret->SetPointer(state, ATAG_STATE); return 1; } return 0; } while(0)
+#define ACTION_RETURN_OBJECT(v) do { auto state = v; if (numret > 0) { assert(ret != NULL); ret->SetPointer(state, ATAG_OBJECT); return 1; } return 0; } while(0)
 #define ACTION_RETURN_FLOAT(v) do { double u = v; if (numret > 0) { assert(ret != nullptr); ret->SetFloat(u); return 1; } return 0; } while(0)
 #define ACTION_RETURN_INT(v) do { int u = v; if (numret > 0) { assert(ret != NULL); ret->SetInt(u); return 1; } return 0; } while(0)
 #define ACTION_RETURN_BOOL(v) ACTION_RETURN_INT(v)
diff --git a/src/scripting/zscript/zcc-parse.lemon b/src/scripting/zscript/zcc-parse.lemon
index eeeba6295..fc34fd2d7 100644
--- a/src/scripting/zscript/zcc-parse.lemon
+++ b/src/scripting/zscript/zcc-parse.lemon
@@ -171,6 +171,18 @@ class_definition(X) ::= class_head(A) class_body(B).
 	X = A; /*X-overwrites-A*/
 }
 
+class_head(X) ::= EXTEND CLASS(T) IDENTIFIER(A).
+{
+	NEW_AST_NODE(Class,head,T);
+	head->NodeName = A.Name();
+	head->ParentName = nullptr;
+	head->Flags =  ZCC_Extension;
+	head->Replaces = nullptr;
+	head->Type = nullptr;
+	head->Symbol = nullptr;
+	X = head;
+}
+
 class_head(X) ::= CLASS(T) IDENTIFIER(A) class_ancestry(B) class_flags(C).
 {
 	NEW_AST_NODE(Class,head,T);
diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp
index ed85ab6b3..8389975ef 100644
--- a/src/scripting/zscript/zcc_compile.cpp
+++ b/src/scripting/zscript/zcc_compile.cpp
@@ -61,8 +61,29 @@
 
 void ZCCCompiler::ProcessClass(ZCC_Class *cnode, PSymbolTreeNode *treenode)
 {
-	Classes.Push(new ZCC_ClassWork(static_cast<ZCC_Class *>(cnode), treenode));
-	auto cls = Classes.Last();
+	ZCC_ClassWork *cls = nullptr;
+	// If this is a class extension, put the new node directly into the existing class.
+	if (cnode->Flags == ZCC_Extension)
+	{
+		for (auto clss : Classes)
+		{
+			if (clss->NodeName() == cnode->NodeName)
+			{
+				cls = clss;
+				break;
+			}
+		}
+		if (cls == nullptr)
+		{
+			Error(cnode, "Class %s cannot be found in the current translation unit.");
+			return;
+		}
+	}
+	else
+	{
+		Classes.Push(new ZCC_ClassWork(static_cast<ZCC_Class *>(cnode), treenode));
+		cls = Classes.Last();
+	}
 
 	auto node = cnode->Body;
 	PSymbolTreeNode *childnode;
@@ -220,6 +241,12 @@ ZCCCompiler::ZCCCompiler(ZCC_AST &ast, DObject *_outer, PSymbolTable &_symbols,
 			switch (node->NodeType)
 			{
 			case AST_Class:
+				// a class extension should not check the tree node symbols.
+				if (static_cast<ZCC_Class *>(node)->Flags == ZCC_Extension)
+				{
+					ProcessClass(static_cast<ZCC_Class *>(node), tnode);
+					break;
+				}
 			case AST_Struct:
 			case AST_ConstantDef:
 			case AST_Enum:
diff --git a/src/scripting/zscript/zcc_parser.cpp b/src/scripting/zscript/zcc_parser.cpp
index 7d4edf78a..bc7857630 100644
--- a/src/scripting/zscript/zcc_parser.cpp
+++ b/src/scripting/zscript/zcc_parser.cpp
@@ -198,6 +198,7 @@ static void InitTokenMap()
 	TOKENDEF (TK_Offset,		ZCC_OFFSET);
 	TOKENDEF (TK_CanRaise,		ZCC_CANRAISE);
 	TOKENDEF (TK_Light,			ZCC_LIGHT);
+	TOKENDEF (TK_Extend,		ZCC_EXTEND);
 
 	ZCC_InitOperators();
 	ZCC_InitConversions();
diff --git a/src/scripting/zscript/zcc_parser.h b/src/scripting/zscript/zcc_parser.h
index 503ef7e9e..ca8c3f3cb 100644
--- a/src/scripting/zscript/zcc_parser.h
+++ b/src/scripting/zscript/zcc_parser.h
@@ -32,6 +32,7 @@ enum
 	ZCC_ReadOnly		= 1 << 9,
 	ZCC_FuncConst		= 1 << 10,
 	ZCC_Abstract		= 1 << 11,
+	ZCC_Extension		= 1 << 12,
 };
 
 // Function parameter modifiers
diff --git a/wadsrc/static/zscript/actor.txt b/wadsrc/static/zscript/actor.txt
index 5144a1bd0..502b8d0cf 100644
--- a/wadsrc/static/zscript/actor.txt
+++ b/wadsrc/static/zscript/actor.txt
@@ -47,6 +47,9 @@ class Actor : Thinker native
 	}
 
 	// Functions
+	native Actor P_SpawnMissile(Actor dest, class<Actor> type, Actor owner = null);
+	
+	// DECORATE compatible functions
 	native bool CheckClass(class<Actor> checkclass, int ptr_select = AAPTR_DEFAULT, bool match_superclass = false);
 	native bool IsPointerEqual(int ptr_select1, int ptr_select2);
 	native int	CountInv(class<Inventory> itemtype, int ptr_select = AAPTR_DEFAULT);
@@ -112,8 +115,8 @@ class Actor : Thinker native
 	native void A_BetaSkullAttack();
 	native void A_Metal();
 	native void A_SpidRefire();
-	native void A_BabyMetal();
-	native void A_BspiAttack();
+	//native void A_BabyMetal();
+	//native void A_BspiAttack();
 	native void A_Hoof();
 	native void A_CyberAttack();
 	native void A_PainAttack(class<Actor> spawntype = "LostSoul", float angle = 0, int flags = 0, int limit = -1);