diff --git a/src/scripting/codegeneration/codegen.cpp b/src/scripting/codegeneration/codegen.cpp
index d0713c50b..9568c34a5 100644
--- a/src/scripting/codegeneration/codegen.cpp
+++ b/src/scripting/codegeneration/codegen.cpp
@@ -5606,6 +5606,234 @@ bool FxCompoundStatement::CheckLocalVariable(FName name)
 	return false;
 }
 
+//==========================================================================
+//
+// FxSwitchStatement
+//
+//==========================================================================
+
+FxSwitchStatement::FxSwitchStatement(FxExpression *cond, FArgumentList *content, const FScriptPosition &pos)
+	: FxExpression(EFX_SwitchStatement, pos)
+{
+	Condition = new FxIntCast(cond, false);
+	Content = content;
+}
+
+FxSwitchStatement::~FxSwitchStatement()
+{
+	SAFE_DELETE(Condition);
+	SAFE_DELETE(Content);
+}
+
+FxExpression *FxSwitchStatement::Resolve(FCompileContext &ctx)
+{
+	CHECKRESOLVED();
+	SAFE_RESOLVE(Condition, ctx);
+
+	if (Content == nullptr || Content->Size() == 0)
+	{
+		ScriptPosition.Message(MSG_WARNING, "Empty switch statement");
+		if (Condition->isConstant())
+		{
+			return new FxNop(ScriptPosition);
+		}
+		else
+		{
+			// The condition may have a side effect so it should be processed (possible to-do: Analyze all nodes in there and delete if not.)
+			auto x = Condition;
+			Condition = nullptr;
+			delete this;
+			return Condition;
+		}
+	}
+
+	for (auto &line : *Content)
+	{
+		// Do not resolve breaks, they need special treatment inside switch blocks.
+		if (line->ExprType != EFX_JumpStatement || static_cast<FxJumpStatement *>(line)->Token != TK_Break)
+		{
+			SAFE_RESOLVE(line, ctx);
+		}
+	}
+
+	if (Condition->isConstant())
+	{
+		ScriptPosition.Message(MSG_WARNING, "Case expression is constant");
+		auto &content = *Content;
+		unsigned defaultindex = -1;
+		unsigned defaultbreak = -1;
+		unsigned caseindex = -1;
+		unsigned casebreak = -1;
+		// look for a case label with a matching value
+		for (unsigned i = 0; i < content.Size(); i++)
+		{
+			if (content[i] != nullptr)
+			{
+				if (content[i]->ExprType == EFX_CaseStatement)
+				{
+					auto casestmt = static_cast<FxCaseStatement *>(content[i]);
+					if (casestmt->Condition == nullptr) defaultindex = i;
+					else if (casestmt->CaseValue == static_cast<FxConstant *>(Condition)->GetValue().GetInt()) caseindex = i;
+				}
+				if (content[i]->ExprType == EFX_JumpStatement && static_cast<FxJumpStatement *>(content[i])->Token == TK_Break)
+				{
+					if (defaultindex >= 0 && defaultbreak < 0) defaultbreak = i;
+					if (caseindex >= 0 && casebreak < 0)
+					{
+						casebreak = i;
+						break;	// when we find this we do not need to look any further.
+					}
+				}
+			}
+		}
+		if (caseindex < 0)
+		{
+			caseindex = defaultindex;
+			casebreak = defaultbreak;
+		}
+		if (caseindex > 0 && casebreak - caseindex > 1)
+		{
+			auto seq = new FxSequence(ScriptPosition);
+			for (unsigned i = caseindex + 1; i < casebreak; i++)
+			{
+				if (content[i] != nullptr && content[i]->ExprType != EFX_CaseStatement)
+				{
+					seq->Add(content[i]);
+					content[i] = nullptr;
+				}
+			}
+			delete this;
+			return seq->Resolve(ctx);
+		}
+		delete this;
+		return new FxNop(ScriptPosition);
+	}
+
+	int mincase = INT_MAX;
+	int maxcase = INT_MIN;
+	for (auto line : *Content)
+	{
+		if (line->ExprType == EFX_CaseStatement)
+		{
+			auto casestmt = static_cast<FxCaseStatement *>(line);
+			if (casestmt->Condition != nullptr)
+			{
+				CaseAddr ca = { casestmt->CaseValue, 0 };
+				CaseAddresses.Push(ca);
+				if (ca.casevalue < mincase) mincase = ca.casevalue;
+				if (ca.casevalue > maxcase) maxcase = ca.casevalue;
+			}
+		}
+	}
+	return this;
+}
+
+ExpEmit FxSwitchStatement::Emit(VMFunctionBuilder *build)
+{
+	assert(Condition != nullptr);
+	ExpEmit emit = Condition->Emit(build);
+	assert(emit.RegType == REGT_INT);
+	// todo: 
+	// - sort jump table by value.
+	// - optimize the switch dispatcher to run in native code instead of executing each single branch instruction on its own.
+	// e.g.: build->Emit(OP_SWITCH, emit.RegNum, build->GetConstantInt(CaseAddresses.Size());
+	for (auto &ca : CaseAddresses)
+	{
+		if (ca.casevalue >= 0 && ca.casevalue <= 0xffff)
+		{
+			build->Emit(OP_TEST, emit.RegNum, (VM_SHALF)ca.casevalue);
+		}
+		else if (ca.casevalue < 0 && ca.casevalue >= -0xffff)
+		{
+			build->Emit(OP_TESTN, emit.RegNum, (VM_SHALF)-ca.casevalue);
+		}
+		else
+		{
+			build->Emit(OP_EQ_K, 1, emit.RegNum, build->GetConstantInt(ca.casevalue));
+		}
+		ca.jumpaddress = build->Emit(OP_JMP, 0);
+	}
+	size_t DefaultAddress = build->Emit(OP_JMP, 0);
+	TArray<size_t> BreakAddresses;
+
+	for (auto line : *Content)
+	{
+		switch (line->ExprType)
+		{
+		case EFX_CaseStatement:
+			if (static_cast<FxCaseStatement *>(line)->Condition != nullptr)
+			{
+				for (auto &ca : CaseAddresses)
+				{
+					if (ca.casevalue == static_cast<FxCaseStatement *>(line)->CaseValue)
+					{
+						build->BackpatchToHere(ca.jumpaddress);
+						break;
+					}
+				}
+			}
+			else
+			{
+				build->BackpatchToHere(DefaultAddress);
+			}
+			break;
+
+		case EFX_JumpStatement:
+			if (static_cast<FxJumpStatement *>(line)->Token == TK_Break)
+			{
+				BreakAddresses.Push(build->Emit(OP_JMP, 0));
+				break;
+			}
+			// fall through for continue.
+
+		default:
+			line->Emit(build);
+			break;
+		}
+	}
+	for (auto addr : BreakAddresses)
+	{
+		build->BackpatchToHere(addr);
+	}
+	return ExpEmit();
+}
+
+
+//==========================================================================
+//
+// FxCaseStatement
+//
+//==========================================================================
+
+FxCaseStatement::FxCaseStatement(FxExpression *cond, const FScriptPosition &pos)
+	: FxExpression(EFX_CaseStatement, pos)
+{
+	Condition = cond? new FxIntCast(cond, false) : nullptr;
+}
+
+FxCaseStatement::~FxCaseStatement()
+{
+	SAFE_DELETE(Condition);
+}
+
+FxExpression *FxCaseStatement::Resolve(FCompileContext &ctx)
+{
+	CHECKRESOLVED();
+	SAFE_RESOLVE_OPT(Condition, ctx);
+
+	if (Condition != nullptr)
+	{
+		if (!Condition->isConstant())
+		{
+			ScriptPosition.Message(MSG_ERROR, "Case label must be a constant value");
+			delete this;
+			return nullptr;
+		}
+		CaseValue = static_cast<FxConstant *>(Condition)->GetValue().GetInt();
+	}
+	return this;
+}
+
 //==========================================================================
 //
 // FxIfStatement
diff --git a/src/scripting/codegeneration/codegen.h b/src/scripting/codegeneration/codegen.h
index 8cce34a15..3ea97ba66 100644
--- a/src/scripting/codegeneration/codegen.h
+++ b/src/scripting/codegeneration/codegen.h
@@ -261,6 +261,8 @@ enum EFxType
 	EFX_DamageValue,
 	EFX_Nop,
 	EFX_LocalVariableDeclaration,
+	EFX_SwitchStatement,
+	EFX_CaseStatement,
 	EFX_COUNT
 };
 
@@ -1250,6 +1252,51 @@ public:
 	bool CheckLocalVariable(FName name);
 };
 
+//==========================================================================
+//
+// FxSwitchStatement
+//
+//==========================================================================
+
+class FxSwitchStatement : public FxExpression
+{
+	FxExpression *Condition;
+	FArgumentList *Content;
+
+	struct CaseAddr
+	{
+		int casevalue;
+		size_t jumpaddress;
+	};
+
+	TArray<CaseAddr> CaseAddresses;
+
+public:
+	FxSwitchStatement(FxExpression *cond, FArgumentList *content, const FScriptPosition &pos);
+	~FxSwitchStatement();
+	FxExpression *Resolve(FCompileContext&);
+	ExpEmit Emit(VMFunctionBuilder *build);
+};
+
+//==========================================================================
+//
+// FxSwitchStatement
+//
+//==========================================================================
+
+class FxCaseStatement : public FxExpression
+{
+	FxExpression *Condition;
+	int CaseValue;	// copy the value to here for easier access.
+
+	friend class FxSwitchStatement;
+
+public:
+	FxCaseStatement(FxExpression *cond, const FScriptPosition &pos);
+	~FxCaseStatement();
+	FxExpression *Resolve(FCompileContext&);
+};
+
 //==========================================================================
 //
 // FxIfStatement
diff --git a/src/scripting/vm/vmexec.h b/src/scripting/vm/vmexec.h
index d3c1632b8..2ddc50599 100644
--- a/src/scripting/vm/vmexec.h
+++ b/src/scripting/vm/vmexec.h
@@ -372,6 +372,13 @@ begin:
 			pc++;
 		}
 		NEXTOP;
+	OP(TESTN):
+		ASSERTD(a);
+		if (-reg.d[a] != BC)
+		{
+			pc++;
+		}
+		NEXTOP;
 	OP(JMP):
 		pc += JMPOFS(pc);
 		NEXTOP;
diff --git a/src/scripting/vm/vmops.h b/src/scripting/vm/vmops.h
index 1b34dcc0f..9ffe2d847 100644
--- a/src/scripting/vm/vmops.h
+++ b/src/scripting/vm/vmops.h
@@ -69,6 +69,7 @@ xx(DYNCAST_K,	dyncast,RPRPKP),
 
 // Control flow.
 xx(TEST,	test,	RII16),		// if (dA != BC) then pc++
+xx(TESTN,	testn,	RII16),		// if (dA != -BC) then pc++
 xx(JMP,		jmp,	I24),		// pc += ABC		-- The ABC fields contain a signed 24-bit offset.
 xx(IJMP,	ijmp,	RII16),		// pc += dA + BC	-- BC is a signed offset. The target instruction must be a JMP.
 xx(PARAM,	param,	__BCP),		// push parameter encoded in BC for function call (B=regtype, C=regnum)
diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp
index 5d309bedc..ed85ab6b3 100644
--- a/src/scripting/zscript/zcc_compile.cpp
+++ b/src/scripting/zscript/zcc_compile.cpp
@@ -2677,9 +2677,26 @@ FxExpression *ZCCCompiler::ConvertNode(ZCC_TreeNode *ast)
 
 	// not yet done
 	case AST_SwitchStmt:
-	case AST_CaseStmt:
-		break;
+	{
+		auto swtch = static_cast<ZCC_SwitchStmt *>(ast);
+		if (swtch->Content->NodeType != AST_CompoundStmt)
+		{
+			Error(ast, "Expecting { after 'switch'");
+			return new FxNop(*ast);	// allow compiler to continue looking for errors.
+		}
+		else
+		{
+			// The switch content is wrapped into a compound statement which needs to be unraveled here.
+			auto cmpnd = static_cast<ZCC_CompoundStmt *>(swtch->Content);
+			return new FxSwitchStatement(ConvertNode(swtch->Condition), ConvertNodeList(cmpnd->Content), *ast);
+		}
+	}
 
+	case AST_CaseStmt:
+	{
+		auto cases = static_cast<ZCC_CaseStmt *>(ast);
+		return new FxCaseStatement(ConvertNode(cases->Condition), *ast); 
+	}
 
 	case AST_CompoundStmt:
 	{
diff --git a/wadsrc/static/zscript.txt b/wadsrc/static/zscript.txt
index 6de64d07e..3ab33bfd9 100644
--- a/wadsrc/static/zscript.txt
+++ b/wadsrc/static/zscript.txt
@@ -177,3 +177,5 @@ zscript/chex/chexweapons.txt
 zscript/chex/chexitems.txt
 zscript/chex/chexdecorations.txt
 zscript/chex/chexplayer.txt
+
+zscript/test2.txt