diff --git a/src/dobjtype.cpp b/src/dobjtype.cpp
index b023a26932..3d42059ca2 100644
--- a/src/dobjtype.cpp
+++ b/src/dobjtype.cpp
@@ -199,6 +199,7 @@ END_POINTERS
 PType::PType()
 : Size(0), Align(1), HashNext(NULL)
 {
+	mDescriptiveName = "Type";
 }
 
 //==========================================================================
@@ -210,6 +211,7 @@ PType::PType()
 PType::PType(unsigned int size, unsigned int align)
 : Size(size), Align(align), HashNext(NULL)
 {
+	mDescriptiveName = "Type";
 }
 
 //==========================================================================
@@ -533,6 +535,17 @@ void PType::GetTypeIDs(intptr_t &id1, intptr_t &id2) const
 	id2 = 0;
 }
 
+//==========================================================================
+//
+// PType :: GetTypeIDs
+//
+//==========================================================================
+
+const char *PType::DescriptiveName() const
+{
+	return mDescriptiveName.GetChars();
+}
+
 //==========================================================================
 //
 // PType :: StaticInit												STATIC
@@ -649,6 +662,7 @@ PBasicType::PBasicType()
 PBasicType::PBasicType(unsigned int size, unsigned int align)
 : PType(size, align)
 {
+	mDescriptiveName = "BasicType";
 }
 
 /* PCompoundType **********************************************************/
@@ -714,6 +728,7 @@ IMPLEMENT_CLASS(PInt)
 PInt::PInt()
 : PBasicType(4, 4), Unsigned(false)
 {
+	mDescriptiveName = "SInt32";
 	Symbols.AddSymbol(new PSymbolConstNumeric(NAME_Min, this, -0x7FFFFFFF - 1));
 	Symbols.AddSymbol(new PSymbolConstNumeric(NAME_Max, this,  0x7FFFFFFF));
 }
@@ -727,6 +742,8 @@ PInt::PInt()
 PInt::PInt(unsigned int size, bool unsign)
 : PBasicType(size, size), Unsigned(unsign)
 {
+	mDescriptiveName.Format("%cInt%d", unsign? 'U':'S', size);
+
 	MemberOnly = (size < 4);
 	if (!unsign)
 	{
@@ -981,6 +998,8 @@ IMPLEMENT_CLASS(PBool)
 PBool::PBool()
 : PInt(sizeof(bool), true)
 {
+	mDescriptiveName = "Bool";
+	MemberOnly = false;
 	// Override the default max set by PInt's constructor
 	PSymbolConstNumeric *maxsym = static_cast<PSymbolConstNumeric *>(Symbols.FindSymbol(NAME_Max, false));
 	assert(maxsym != NULL && maxsym->IsKindOf(RUNTIME_CLASS(PSymbolConstNumeric)));
@@ -1000,6 +1019,7 @@ IMPLEMENT_CLASS(PFloat)
 PFloat::PFloat()
 : PBasicType(8, 8)
 {
+	mDescriptiveName = "Float";
 	SetDoubleSymbols();
 }
 
@@ -1012,6 +1032,7 @@ PFloat::PFloat()
 PFloat::PFloat(unsigned int size)
 : PBasicType(size, size)
 {
+	mDescriptiveName.Format("Float%d", size);
 	if (size == 8)
 	{
 		SetDoubleSymbols();
@@ -1274,6 +1295,7 @@ IMPLEMENT_CLASS(PString)
 PString::PString()
 : PBasicType(sizeof(FString), __alignof(FString))
 {
+	mDescriptiveName = "String";
 }
 
 //==========================================================================
@@ -1376,6 +1398,7 @@ IMPLEMENT_CLASS(PName)
 PName::PName()
 : PInt(sizeof(FName), true)
 {
+	mDescriptiveName = "Name";
 	assert(sizeof(FName) == __alignof(FName));
 }
 
@@ -1425,6 +1448,7 @@ IMPLEMENT_CLASS(PSound)
 PSound::PSound()
 : PInt(sizeof(FSoundID), true)
 {
+	mDescriptiveName = "Sound";
 	assert(sizeof(FSoundID) == __alignof(FSoundID));
 }
 
@@ -1474,6 +1498,7 @@ IMPLEMENT_CLASS(PColor)
 PColor::PColor()
 : PInt(sizeof(PalEntry), true)
 {
+	mDescriptiveName = "Color";
 	assert(sizeof(PalEntry) == __alignof(PalEntry));
 }
 
@@ -1490,6 +1515,7 @@ IMPLEMENT_CLASS(PStatePointer)
 PStatePointer::PStatePointer()
 : PBasicType(sizeof(FState *), __alignof(FState *))
 {
+	mDescriptiveName = "State";
 }
 
 //==========================================================================
@@ -1564,6 +1590,7 @@ END_POINTERS
 PPointer::PPointer()
 : PBasicType(sizeof(void *), __alignof(void *)), PointedType(NULL)
 {
+	mDescriptiveName = "Pointer";
 }
 
 //==========================================================================
@@ -1575,6 +1602,7 @@ PPointer::PPointer()
 PPointer::PPointer(PType *pointsat)
 : PBasicType(sizeof(void *), __alignof(void *)), PointedType(pointsat)
 {
+	mDescriptiveName.Format("Pointer<%s>", pointsat->DescriptiveName());
 }
 
 //==========================================================================
@@ -1708,6 +1736,7 @@ END_POINTERS
 PClassPointer::PClassPointer()
 : PPointer(RUNTIME_CLASS(PClass)), ClassRestriction(NULL)
 {
+	mDescriptiveName = "ClassPointer";
 }
 
 //==========================================================================
@@ -1719,6 +1748,7 @@ PClassPointer::PClassPointer()
 PClassPointer::PClassPointer(PClass *restrict)
 : PPointer(RUNTIME_CLASS(PClass)), ClassRestriction(restrict)
 {
+	mDescriptiveName.Format("ClassPointer<%s>", restrict->TypeName.GetChars());
 }
 
 //==========================================================================
@@ -1784,6 +1814,7 @@ END_POINTERS
 PEnum::PEnum()
 : ValueType(NULL)
 {
+	mDescriptiveName = "Enum";
 }
 
 //==========================================================================
@@ -1795,6 +1826,7 @@ PEnum::PEnum()
 PEnum::PEnum(FName name, PTypeBase *outer)
 : PNamedType(name, outer), ValueType(NULL)
 {
+	mDescriptiveName.Format("Enum<%s>", name.GetChars());
 }
 
 //==========================================================================
@@ -1833,6 +1865,7 @@ END_POINTERS
 PArray::PArray()
 : ElementType(NULL), ElementCount(0)
 {
+	mDescriptiveName = "Array";
 }
 
 //==========================================================================
@@ -1844,6 +1877,8 @@ PArray::PArray()
 PArray::PArray(PType *etype, unsigned int ecount)
 : ElementType(etype), ElementCount(ecount)
 {
+	mDescriptiveName.Format("Array<%s>[%d]", etype->DescriptiveName(), ecount);
+
 	Align = etype->Align;
 	// Since we are concatenating elements together, the element size should
 	// also be padded to the nearest alignment.
@@ -1975,6 +2010,7 @@ IMPLEMENT_CLASS(PVector)
 PVector::PVector()
 : PArray(TypeFloat32, 3)
 {
+	mDescriptiveName = "Vector";
 }
 
 //==========================================================================
@@ -1986,6 +2022,7 @@ PVector::PVector()
 PVector::PVector(unsigned int size)
 : PArray(TypeFloat32, size)
 {
+	mDescriptiveName.Format("Vector<%d>", size);
 	assert(size >= 2 && size <= 4);
 }
 
@@ -2025,6 +2062,7 @@ END_POINTERS
 PDynArray::PDynArray()
 : ElementType(NULL)
 {
+	mDescriptiveName = "DynArray";
 	Size = sizeof(FArray);
 	Align = __alignof(FArray);
 }
@@ -2038,6 +2076,7 @@ PDynArray::PDynArray()
 PDynArray::PDynArray(PType *etype)
 : ElementType(etype)
 {
+	mDescriptiveName.Format("DynArray<%s>", etype->DescriptiveName());
 	Size = sizeof(FArray);
 	Align = __alignof(FArray);
 }
@@ -2105,6 +2144,7 @@ END_POINTERS
 PMap::PMap()
 : KeyType(NULL), ValueType(NULL)
 {
+	mDescriptiveName = "Map";
 	Size = sizeof(FMap);
 	Align = __alignof(FMap);
 }
@@ -2118,6 +2158,7 @@ PMap::PMap()
 PMap::PMap(PType *keytype, PType *valtype)
 : KeyType(keytype), ValueType(valtype)
 {
+	mDescriptiveName.Format("Map<%s, %s>", keytype->DescriptiveName(), valtype->DescriptiveName());
 	Size = sizeof(FMap);
 	Align = __alignof(FMap);
 }
@@ -2181,6 +2222,7 @@ IMPLEMENT_CLASS(PStruct)
 
 PStruct::PStruct()
 {
+	mDescriptiveName = "Struct";
 }
 
 //==========================================================================
@@ -2192,6 +2234,7 @@ PStruct::PStruct()
 PStruct::PStruct(FName name, PTypeBase *outer)
 : PNamedType(name, outer)
 {
+	mDescriptiveName.Format("Struct<%s>", name.GetChars());
 }
 
 //==========================================================================
@@ -2804,6 +2847,7 @@ PClass::PClass()
 	Defaults = NULL;
 	bRuntimeClass = false;
 	ConstructNative = NULL;
+	mDescriptiveName = "Class";
 
 	PClass::AllClasses.Push(this);
 }
@@ -2896,6 +2940,7 @@ void ClassReg::SetupClass(PClass *cls)
 	cls->Size = SizeOf;
 	cls->Pointers = Pointers;
 	cls->ConstructNative = ConstructNative;
+	cls->mDescriptiveName.Format("Class<%s>", cls->TypeName.GetChars());
 }
 
 //==========================================================================
@@ -3108,6 +3153,7 @@ PClass *PClass::CreateDerivedClass(FName name, unsigned int size)
 	type->TypeName = name;
 	type->Size = size;
 	type->bRuntimeClass = true;
+	type->mDescriptiveName.Format("Class<%s>", name.GetChars());
 	Derive(type);
 	DeriveData(type);
 	if (!notnew)
@@ -3185,6 +3231,7 @@ PClass *PClass::FindClassTentative(FName name, bool fatal)
 	type->ConstructNative = ConstructNative;
 	type->Size = TentativeClass;
 	type->bRuntimeClass = true;
+	type->mDescriptiveName.Format("Class<%s>", name.GetChars());
 	type->Symbols.SetParentTable(&Symbols);
 	TypeTable.AddType(type, RUNTIME_CLASS(PClass), (intptr_t)type->Outer, name, bucket);
 	return type;
diff --git a/src/dobjtype.h b/src/dobjtype.h
index 885e1ea0ed..fd6e078eab 100644
--- a/src/dobjtype.h
+++ b/src/dobjtype.h
@@ -222,6 +222,7 @@ public:
 	PType			*HashNext;		// next type in this type table
 	PSymbolTable	Symbols;
 	bool			MemberOnly = false;		// type may only be used as a struct/class member but not as a local variable or function argument.
+	FString			mDescriptiveName;
 
 	PType();
 	PType(unsigned int size, unsigned int align);
@@ -285,6 +286,8 @@ public:
 	// Get the type IDs used by IsMatch
 	virtual void GetTypeIDs(intptr_t &id1, intptr_t &id2) const;
 
+	const char *DescriptiveName() const;
+
 	size_t PropagateMark();
 
 	static void StaticInit();
@@ -377,8 +380,12 @@ public:
 	PTypeBase		*Outer;			// object this type is contained within
 	FName			TypeName;		// this type's name
 
-	PNamedType() : Outer(NULL) {}
-	PNamedType(FName name, PTypeBase *outer) : Outer(outer), TypeName(name) {}
+	PNamedType() : Outer(NULL) {
+		mDescriptiveName = "NamedType";
+	}
+	PNamedType(FName name, PTypeBase *outer) : Outer(outer), TypeName(name) {
+		mDescriptiveName = name.GetChars();
+	}
 
 	virtual bool IsMatch(intptr_t id1, intptr_t id2) const;
 	virtual void GetTypeIDs(intptr_t &id1, intptr_t &id2) const;
diff --git a/src/scripting/codegeneration/codegen.cpp b/src/scripting/codegeneration/codegen.cpp
index 91153f3acf..9b8a87eedb 100644
--- a/src/scripting/codegeneration/codegen.cpp
+++ b/src/scripting/codegeneration/codegen.cpp
@@ -595,9 +595,9 @@ ExpEmit FxIntCast::Emit(VMFunctionBuilder *build)
 //==========================================================================
 
 FxFloatCast::FxFloatCast(FxExpression *x)
-: FxExpression(x->ScriptPosition)
+	: FxExpression(x->ScriptPosition)
 {
-	basex=x;
+	basex = x;
 	ValueType = TypeFloat64;
 }
 
@@ -685,6 +685,519 @@ ExpEmit FxFloatCast::Emit(VMFunctionBuilder *build)
 //
 //==========================================================================
 
+FxNameCast::FxNameCast(FxExpression *x)
+	: FxExpression(x->ScriptPosition)
+{
+	basex = x;
+	ValueType = TypeName;
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+FxNameCast::~FxNameCast()
+{
+	SAFE_DELETE(basex);
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+FxExpression *FxNameCast::Resolve(FCompileContext &ctx)
+{
+	CHECKRESOLVED();
+	SAFE_RESOLVE(basex, ctx);
+
+	if (basex->ValueType == TypeName)
+	{
+		FxExpression *x = basex;
+		basex = NULL;
+		delete this;
+		return x;
+	}
+	else if (basex->ValueType == TypeString)
+	{
+		if (basex->isConstant())
+		{
+			ExpVal constval = static_cast<FxConstant *>(basex)->GetValue();
+			FxExpression *x = new FxConstant(constval.GetName(), ScriptPosition);
+			delete this;
+			return x;
+		}
+		return this;
+	}
+	else
+	{
+		ScriptPosition.Message(MSG_ERROR, "Cannot convert to name");
+		delete this;
+		return NULL;
+	}
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+ExpEmit FxNameCast::Emit(VMFunctionBuilder *build)
+{
+	ExpEmit from = basex->Emit(build);
+	assert(!from.Konst);
+	assert(basex->ValueType == TypeString);
+	from.Free(build);
+	ExpEmit to(build, REGT_INT);
+	build->Emit(OP_CAST, to.RegNum, from.RegNum, CAST_S2N);
+	return to;
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+FxStringCast::FxStringCast(FxExpression *x)
+	: FxExpression(x->ScriptPosition)
+{
+	basex = x;
+	ValueType = TypeString;
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+FxStringCast::~FxStringCast()
+{
+	SAFE_DELETE(basex);
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+FxExpression *FxStringCast::Resolve(FCompileContext &ctx)
+{
+	CHECKRESOLVED();
+	SAFE_RESOLVE(basex, ctx);
+
+	if (basex->ValueType == TypeString)
+	{
+		FxExpression *x = basex;
+		basex = NULL;
+		delete this;
+		return x;
+	}
+	else if (basex->ValueType == TypeName)
+	{
+		if (basex->isConstant())
+		{
+			ExpVal constval = static_cast<FxConstant *>(basex)->GetValue();
+			FxExpression *x = new FxConstant(constval.GetString(), ScriptPosition);
+			delete this;
+			return x;
+		}
+		return this;
+	}
+	else if (basex->ValueType == TypeSound)
+	{
+		if (basex->isConstant())
+		{
+			ExpVal constval = static_cast<FxConstant *>(basex)->GetValue();
+			FxExpression *x = new FxConstant(S_sfx[constval.GetInt()].name, ScriptPosition);
+			delete this;
+			return x;
+		}
+		return this;
+	}
+	// although it could be done, let's not convert colors back to strings.
+	else
+	{
+		ScriptPosition.Message(MSG_ERROR, "Cannot convert to string");
+		delete this;
+		return NULL;
+	}
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+ExpEmit FxStringCast::Emit(VMFunctionBuilder *build)
+{
+	ExpEmit from = basex->Emit(build);
+	assert(!from.Konst);
+
+	from.Free(build);
+	ExpEmit to(build, REGT_STRING);
+	if (ValueType == TypeName)
+	{
+		build->Emit(OP_CAST, to.RegNum, from.RegNum, CAST_N2S);
+	}
+	else if (ValueType == TypeSound)
+	{
+		build->Emit(OP_CAST, to.RegNum, from.RegNum, CAST_So2S);
+	}
+	return to;
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+FxColorCast::FxColorCast(FxExpression *x)
+	: FxExpression(x->ScriptPosition)
+{
+	basex = x;
+	ValueType = TypeColor;
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+FxColorCast::~FxColorCast()
+{
+	SAFE_DELETE(basex);
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+FxExpression *FxColorCast::Resolve(FCompileContext &ctx)
+{
+	CHECKRESOLVED();
+	SAFE_RESOLVE(basex, ctx);
+
+	if (basex->ValueType == TypeColor || basex->ValueType->GetClass() == RUNTIME_CLASS(PInt))
+	{
+		FxExpression *x = basex;
+		x->ValueType = TypeColor;
+		basex = NULL;
+		delete this;
+		return x;
+	}
+	else if (basex->ValueType == TypeString)
+	{
+		if (basex->isConstant())
+		{
+			ExpVal constval = static_cast<FxConstant *>(basex)->GetValue();
+			FxExpression *x = new FxConstant(V_GetColor(nullptr, constval.GetString()), ScriptPosition);
+			delete this;
+			return x;
+		}
+		return this;
+	}
+	else
+	{
+		ScriptPosition.Message(MSG_ERROR, "Cannot convert to color");
+		delete this;
+		return NULL;
+	}
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+ExpEmit FxColorCast::Emit(VMFunctionBuilder *build)
+{
+	ExpEmit from = basex->Emit(build);
+	assert(!from.Konst);
+	assert(basex->ValueType == TypeString);
+	from.Free(build);
+	ExpEmit to(build, REGT_INT);
+	build->Emit(OP_CAST, to.RegNum, from.RegNum, CAST_S2Co);
+	return to;
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+FxSoundCast::FxSoundCast(FxExpression *x)
+	: FxExpression(x->ScriptPosition)
+{
+	basex = x;
+	ValueType = TypeSound;
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+FxSoundCast::~FxSoundCast()
+{
+	SAFE_DELETE(basex);
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+FxExpression *FxSoundCast::Resolve(FCompileContext &ctx)
+{
+	CHECKRESOLVED();
+	SAFE_RESOLVE(basex, ctx);
+
+	if (basex->ValueType == TypeSound || basex->ValueType->GetClass() == RUNTIME_CLASS(PInt))
+	{
+		FxExpression *x = basex;
+		x->ValueType = TypeSound;
+		basex = NULL;
+		delete this;
+		return x;
+	}
+	else if (basex->ValueType == TypeString)
+	{
+		if (basex->isConstant())
+		{
+			ExpVal constval = static_cast<FxConstant *>(basex)->GetValue();
+			FxExpression *x = new FxConstant(FSoundID(constval.GetString()), ScriptPosition);
+			delete this;
+			return x;
+		}
+		return this;
+	}
+	else
+	{
+		ScriptPosition.Message(MSG_ERROR, "Cannot convert to sound");
+		delete this;
+		return NULL;
+	}
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+ExpEmit FxSoundCast::Emit(VMFunctionBuilder *build)
+{
+	ExpEmit from = basex->Emit(build);
+	assert(!from.Konst);
+	assert(basex->ValueType == TypeString);
+	from.Free(build);
+	ExpEmit to(build, REGT_INT);
+	build->Emit(OP_CAST, to.RegNum, from.RegNum, CAST_S2So);
+	return to;
+}
+
+//==========================================================================
+//
+// generic type cast operator
+//
+//==========================================================================
+
+FxTypeCast::FxTypeCast(FxExpression *x, PType *type, bool nowarn)
+	: FxExpression(x->ScriptPosition)
+{
+	basex = x;
+	ValueType = type;
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+FxTypeCast::~FxTypeCast()
+{
+	SAFE_DELETE(basex);
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+FxExpression *FxTypeCast::Resolve(FCompileContext &ctx)
+{
+	CHECKRESOLVED();
+	SAFE_RESOLVE(basex, ctx);
+
+	// first deal with the simple types
+	if (ValueType == TypeError || basex->ValueType == TypeError)
+	{
+		delete this;
+		return nullptr;
+	}
+	else if (ValueType == TypeVoid)	// this should never happen
+	{
+		goto errormsg;
+	}
+	else if (basex->ValueType == TypeVoid)
+	{
+		goto errormsg;
+	}
+	else if (basex->ValueType == ValueType)
+	{
+		// don't go through the entire list if the types are the same.
+		goto basereturn;
+	}
+	else if (ValueType->GetRegType() == REGT_FLOAT)
+	{
+		FxExpression *x = new FxFloatCast(basex);
+		x = x->Resolve(ctx);
+		basex = nullptr;
+		delete this;
+		return x;
+	}
+	else if (ValueType->IsA(RUNTIME_CLASS(PInt)))
+	{
+		// This is only for casting to actual ints. Subtypes representing an int will be handled elsewhere.
+		FxExpression *x = new FxIntCast(basex, NoWarn);
+		x = x->Resolve(ctx);
+		basex = nullptr;
+		delete this;
+		return x;
+	}
+	else if (ValueType == TypeBool)
+	{
+		FxExpression *x = new FxBoolCast(basex);
+		x = x->Resolve(ctx);
+		basex = nullptr;
+		delete this;
+		return x;
+	}
+	else if (ValueType == TypeString)
+	{
+		FxExpression *x = new FxStringCast(basex);
+		x = x->Resolve(ctx);
+		basex = nullptr;
+		delete this;
+		return x;
+	}
+	else if (ValueType == TypeName)
+	{
+		FxExpression *x = new FxNameCast(basex);
+		x = x->Resolve(ctx);
+		basex = nullptr;
+		delete this;
+		return x;
+	}
+	else if (ValueType == TypeSound)
+	{
+		FxExpression *x = new FxSoundCast(basex);
+		x = x->Resolve(ctx);
+		basex = nullptr;
+		delete this;
+		return x;
+	}
+	else if (ValueType == TypeColor)
+	{
+		FxExpression *x = new FxColorCast(basex);
+		x = x->Resolve(ctx);
+		basex = nullptr;
+		delete this;
+		return x;
+	}
+	else if (ValueType->IsKindOf(RUNTIME_CLASS(PClassPointer)))
+	{
+		FxExpression *x = new FxClassTypeCast(static_cast<PClassPointer*>(ValueType), basex);
+		x = x->Resolve(ctx);
+		basex = nullptr;
+		delete this;
+		return x;
+	}
+	/* else if (ValueType->IsKindOf(RUNTIME_CLASS(PEnum)))
+	{
+	// this is not yet ready and does not get assigned to actual values.
+	}
+	*/
+	else if (ValueType->IsKindOf(RUNTIME_CLASS(PClass)))	// this should never happen because the VM doesn't handle plain class types - just pointers
+	{
+		if (basex->ValueType->IsKindOf(RUNTIME_CLASS(PClass)))
+		{
+			// class types are only compatible if the base type is a descendant of the result type.
+			auto fromtype = static_cast<PClass *>(basex->ValueType);
+			auto totype = static_cast<PClass *>(ValueType);
+			if (fromtype->IsDescendantOf(totype)) goto basereturn;
+		}
+	}
+	else if (ValueType->IsKindOf(RUNTIME_CLASS(PPointer)))
+	{
+		// Pointers to different types are only compatible if both point to an object and the source type is a child of the destination type.
+		if (basex->ValueType->IsKindOf(RUNTIME_CLASS(PPointer)))
+		{
+			auto fromtype = static_cast<PPointer *>(basex->ValueType);
+			auto totype = static_cast<PPointer *>(ValueType);
+			if (fromtype->PointedType->IsKindOf(RUNTIME_CLASS(PClass)) && totype->PointedType->IsKindOf(RUNTIME_CLASS(PClass)))
+			{
+
+				auto fromcls = static_cast<PClass *>(fromtype->PointedType);
+				auto tocls = static_cast<PClass *>(totype->PointedType);
+				if (fromcls->IsDescendantOf(tocls)) goto basereturn;
+			}
+		}
+	}
+	// todo: pointers to class objects. 
+	// All other types are only compatible to themselves and have already been handled above by the equality check.
+	// Anything that falls through here is not compatible and must print an error.
+
+errormsg:
+	ScriptPosition.Message(MSG_ERROR, "Cannot convert %s to %s", basex->ValueType->DescriptiveName(), ValueType->DescriptiveName());
+	delete this;
+	return nullptr;
+
+basereturn:
+	auto x = basex;
+	basex = nullptr;
+	delete this;
+	return x;
+
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+ExpEmit FxTypeCast::Emit(VMFunctionBuilder *build)
+{
+	assert(false);
+	// This should never be reached
+	return ExpEmit();
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
 FxPlusSign::FxPlusSign(FxExpression *operand)
 : FxExpression(operand->ScriptPosition)
 {
@@ -5030,10 +5543,11 @@ VMFunction *FxReturnStatement::GetDirectFunction()
 //
 //==========================================================================
 
-FxClassTypeCast::FxClassTypeCast(PClass *dtype, FxExpression *x)
+FxClassTypeCast::FxClassTypeCast(PClassPointer *dtype, FxExpression *x)
 : FxExpression(x->ScriptPosition)
 {
-	desttype = dtype;
+	ValueType = dtype;
+	desttype = dtype->ClassRestriction;
 	basex=x;
 }
 
@@ -5058,8 +5572,25 @@ FxExpression *FxClassTypeCast::Resolve(FCompileContext &ctx)
 {
 	CHECKRESOLVED();
 	SAFE_RESOLVE(basex, ctx);
+
+	if (basex->ValueType->GetClass() == RUNTIME_CLASS(PClassPointer))
+	{
+		auto to = static_cast<PClassPointer *>(ValueType);
+		auto from = static_cast<PClassPointer *>(basex->ValueType);
+		if (from->ClassRestriction->IsDescendantOf(to->ClassRestriction))
+		{
+			basex->ValueType = to;
+			auto x = basex;
+			basex = nullptr;
+			delete this;
+			return x;
+		}
+		ScriptPosition.Message(MSG_ERROR, "Cannot convert from %s to %s: Incompatible class types", from->ClassRestriction->TypeName.GetChars(), to->ClassRestriction->TypeName.GetChars());
+		delete this;
+		return nullptr;
+	}
 	
-	if (basex->ValueType != TypeName)
+	if (basex->ValueType != TypeName && basex->ValueType != TypeString)
 	{
 		ScriptPosition.Message(MSG_ERROR, "Cannot convert to class type");
 		delete this;
@@ -5097,6 +5628,10 @@ FxExpression *FxClassTypeCast::Resolve(FCompileContext &ctx)
 		delete this;
 		return x;
 	}
+	if (basex->ValueType == TypeString)
+	{
+		basex = new FxNameCast(basex);
+	}
 	return this;
 }
 
diff --git a/src/scripting/codegeneration/codegen.h b/src/scripting/codegeneration/codegen.h
index 213fe388a7..7d6465e75d 100644
--- a/src/scripting/codegeneration/codegen.h
+++ b/src/scripting/codegeneration/codegen.h
@@ -411,6 +411,78 @@ public:
 	ExpEmit Emit(VMFunctionBuilder *build);
 };
 
+class FxNameCast : public FxExpression
+{
+	FxExpression *basex;
+
+public:
+
+	FxNameCast(FxExpression *x);
+	~FxNameCast();
+	FxExpression *Resolve(FCompileContext&);
+
+	ExpEmit Emit(VMFunctionBuilder *build);
+};
+
+class FxStringCast : public FxExpression
+{
+	FxExpression *basex;
+
+public:
+
+	FxStringCast(FxExpression *x);
+	~FxStringCast();
+	FxExpression *Resolve(FCompileContext&);
+
+	ExpEmit Emit(VMFunctionBuilder *build);
+};
+
+class FxColorCast : public FxExpression
+{
+	FxExpression *basex;
+
+public:
+
+	FxColorCast(FxExpression *x);
+	~FxColorCast();
+	FxExpression *Resolve(FCompileContext&);
+
+	ExpEmit Emit(VMFunctionBuilder *build);
+};
+
+class FxSoundCast : public FxExpression
+{
+	FxExpression *basex;
+
+public:
+
+	FxSoundCast(FxExpression *x);
+	~FxSoundCast();
+	FxExpression *Resolve(FCompileContext&);
+
+	ExpEmit Emit(VMFunctionBuilder *build);
+};
+
+//==========================================================================
+//
+//	FxTypeCast
+//
+//==========================================================================
+
+class FxTypeCast : public FxExpression
+{
+	FxExpression *basex;
+	bool NoWarn;
+
+public:
+
+	FxTypeCast(FxExpression *x, PType *type, bool nowarn);
+	~FxTypeCast();
+	FxExpression *Resolve(FCompileContext&);
+
+	ExpEmit Emit(VMFunctionBuilder *build);
+};
+
 //==========================================================================
 //
 //	FxSign
@@ -1130,7 +1202,7 @@ class FxClassTypeCast : public FxExpression
 
 public:
 
-	FxClassTypeCast(PClass *dtype, FxExpression *x);
+	FxClassTypeCast(PClassPointer *dtype, FxExpression *x);
 	~FxClassTypeCast();
 	FxExpression *Resolve(FCompileContext&);
 	ExpEmit Emit(VMFunctionBuilder *build);
diff --git a/src/scripting/decorate/thingdef_parse.cpp b/src/scripting/decorate/thingdef_parse.cpp
index 534c74d18e..f416d6103b 100644
--- a/src/scripting/decorate/thingdef_parse.cpp
+++ b/src/scripting/decorate/thingdef_parse.cpp
@@ -207,7 +207,7 @@ FxExpression *ParseParameter(FScanner &sc, PClassActor *cls, PType *type, bool c
 		sc.SetEscape(true);
 		sc.MustGetString();
 		sc.SetEscape(false);
-		x = new FxClassTypeCast(static_cast<PClassPointer *>(type)->ClassRestriction, new FxConstant(FName(sc.String), sc));
+		x = new FxClassTypeCast(static_cast<PClassPointer *>(type), new FxConstant(FName(sc.String), sc));
 	}
 	else
 	{
diff --git a/src/scripting/vm/vm.h b/src/scripting/vm/vm.h
index 69e5b81b31..425eba531f 100644
--- a/src/scripting/vm/vm.h
+++ b/src/scripting/vm/vm.h
@@ -116,6 +116,12 @@ enum
 	CAST_P2S,
 	CAST_S2I,
 	CAST_S2F,
+	CAST_S2N,
+	CAST_N2S,
+	CAST_S2Co,
+	CAST_S2So,
+	CAST_Co2S,
+	CAST_So2S,
 };
 
 // Register types for VMParam
diff --git a/src/scripting/vm/vmdisasm.cpp b/src/scripting/vm/vmdisasm.cpp
index 0b84cf8c09..563f89f280 100644
--- a/src/scripting/vm/vmdisasm.cpp
+++ b/src/scripting/vm/vmdisasm.cpp
@@ -379,6 +379,9 @@ void VMDisasm(FILE *out, const VMOP *code, int codesize, const VMScriptFunction
 				case CAST_I2F:
 					mode = MODE_AF | MODE_BI | MODE_CUNUSED;
 					break;
+				case CAST_Co2S:
+				case CAST_So2S:
+				case CAST_N2S:
 				case CAST_I2S:
 					mode = MODE_AS | MODE_BI | MODE_CUNUSED;
 					break;
@@ -391,6 +394,9 @@ void VMDisasm(FILE *out, const VMOP *code, int codesize, const VMScriptFunction
 				case CAST_P2S:
 					mode = MODE_AS | MODE_BP | MODE_CUNUSED;
 					break;
+				case CAST_S2Co:
+				case CAST_S2So:
+				case CAST_S2N:
 				case CAST_S2I:
 					mode = MODE_AI | MODE_BS | MODE_CUNUSED;
 					break;
diff --git a/src/scripting/vm/vmexec.cpp b/src/scripting/vm/vmexec.cpp
index 651090c6ba..b1393382a2 100644
--- a/src/scripting/vm/vmexec.cpp
+++ b/src/scripting/vm/vmexec.cpp
@@ -32,6 +32,8 @@
 */
 
 #include <math.h>
+#include <v_video.h>
+#include <s_sound.h>
 #include "vm.h"
 #include "xs_Float.h"
 #include "math/cmath.h"
diff --git a/src/scripting/vm/vmexec.h b/src/scripting/vm/vmexec.h
index adf2986c50..1d1764c6ea 100644
--- a/src/scripting/vm/vmexec.h
+++ b/src/scripting/vm/vmexec.h
@@ -1466,6 +1466,39 @@ static void DoCast(const VMRegisters &reg, const VMFrame *f, int a, int b, int c
 		reg.f[a] = reg.s[b].ToDouble();
 		break;
 
+	case CAST_S2N:
+		ASSERTD(a); ASSERTS(b);
+		reg.d[a] = FName(reg.s[b]);
+		break;
+
+	case CAST_N2S:
+	{
+		ASSERTS(a); ASSERTD(b);
+		FName name = FName(ENamedName(reg.d[b]));
+		reg.s[a] = name.IsValidName() ? name.GetChars() : "";
+		break; 
+	}
+
+	case CAST_S2Co:
+		ASSERTD(a); ASSERTS(b);
+		reg.d[a] = V_GetColor(NULL, reg.s[b]);
+		break;
+
+	case CAST_Co2S:
+		ASSERTS(a); ASSERTD(b);
+		reg.s[a].Format("%02x %02x %02x", PalEntry(reg.d[b]).r, PalEntry(reg.d[b]).g, PalEntry(reg.d[b]).b);
+		break;
+
+	case CAST_S2So:
+		ASSERTD(a); ASSERTS(b);
+		reg.d[a] = FSoundID(reg.s[b]);
+		break;
+
+	case CAST_So2S:
+		ASSERTS(a); ASSERTD(b);
+		reg.s[a] = S_sfx[reg.d[b]].name;
+		break;
+
 	default:
 		assert(0);
 	}
diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp
index f6d23a99b3..1992587c8f 100644
--- a/src/scripting/zscript/zcc_compile.cpp
+++ b/src/scripting/zscript/zcc_compile.cpp
@@ -885,7 +885,7 @@ ZCC_Expression *ZCCCompiler::SimplifyFunctionCall(ZCC_ExprFuncCall *callop, PSym
 			if (routelen < 0)
 			{
 				///FIXME: Need real type names
-				Error(callop, "Cannot convert type 1 to type 2");
+				Error(callop, "Cannot convert %s to %s", parm->Value->Type->DescriptiveName(), dest->DescriptiveName());
 				callop->ToErrorNode();
 			}
 			else
@@ -1341,7 +1341,7 @@ PType *ZCCCompiler::DetermineType(PType *outertype, ZCC_TreeNode *field, FName n
 			//return TypeFloat32;
 		case ZCC_Float64:
 			retval = TypeFloat64;
-			return;
+			break;
 
 		case ZCC_String:
 			retval = TypeString;
@@ -1433,7 +1433,7 @@ PType *ZCCCompiler::DetermineType(PType *outertype, ZCC_TreeNode *field, FName n
 	}
 	if (retval != TypeError && retval->MemberOnly && !formember)
 	{
-		Error(field, "Invalid type");	// fixme: Types need a descriptive name that can be output here.
+		Error(field, "Invalid type %s", retval->DescriptiveName());	// fixme: Types need a descriptive name that can be output here.
 		return TypeError;
 	}
 	return retval;
diff --git a/wadsrc/static/zscript.txt b/wadsrc/static/zscript.txt
index 73bb467d82..a349d7ed5b 100644
--- a/wadsrc/static/zscript.txt
+++ b/wadsrc/static/zscript.txt
@@ -7,9 +7,7 @@ zscript/shared/morph.txt
 zscript/shared/botstuff.txt
 zscript/shared/sharedmisc.txt
 zscript/shared/blood.txt
-zscript/shared/ice.txt
 zscript/shared/debris.txt
-zscript/shared/specialspot.txt
 zscript/shared/decal.txt
 zscript/shared/splashes.txt
 zscript/shared/pickups.txt
@@ -18,6 +16,7 @@ zscript/shared/spark.txt
 zscript/shared/soundsequence.txt
 zscript/shared/soundenvironment.txt
 zscript/shared/bridge.txt
+zscript/shared/specialspot.txt
 zscript/shared/teleport.txt
 zscript/shared/camera.txt
 zscript/shared/movingcamera.txt
@@ -28,6 +27,7 @@ zscript/shared/hatetarget.txt
 zscript/shared/secrettrigger.txt
 zscript/shared/setcolor.txt
 zscript/shared/sectoraction.txt
+zscript/shared/ice.txt
 zscript/shared/dog.txt
 
 zscript/doom/doomplayer.txt
@@ -59,5 +59,5 @@ zscript/doom/doomweapons.txt
 zscript/doom/stealthmonsters.txt
 zscript/doom/scriptedmarine.txt
 
-//zscript/test1.txt
+zscript/heretic/beast.txt