diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c08895bd9..765114b38 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -13,7 +13,6 @@ include( CheckIncludeFile ) include( CheckIncludeFiles ) include( CheckLibraryExists ) include( FindPkgConfig ) -include( FindOpenGL ) if( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) option( NO_STRIP "Do not strip Release or MinSizeRel builds" ) @@ -162,6 +161,7 @@ if( WIN32 ) endif() set( ZDOOM_LIBS + opengl32 wsock32 winmm "${DX_dinput8_LIBRARY}" @@ -255,15 +255,6 @@ else() endif() endif() -# Check if we have OpenGL - -if( NOT OPENGL_FOUND ) - message( FATAL_ERROR "OpenGL is required for building." ) -endif( NOT OPENGL_FOUND ) - -set( ZDOOM_LIBS ${ZDOOM_LIBS} ${OPENGL_LIBRARIES} ) -include_directories( ${OPENGL_INCLUDE_DIR} ) - if( NOT NO_OPENAL ) find_package( OpenAL ) mark_as_advanced(CLEAR OPENAL_INCLUDE_DIR) diff --git a/src/dobjtype.h b/src/dobjtype.h index 316a2bccc..d38c42c58 100644 --- a/src/dobjtype.h +++ b/src/dobjtype.h @@ -35,6 +35,7 @@ enum VARF_Ref = (1<<16), // argument is passed by reference. VARF_Transient = (1<<17), // don't auto serialize field. VARF_Meta = (1<<18), // static class data (by necessity read only.) + VARF_VarArg = (1<<19), // [ZZ] vararg: don't typecheck values after ... in function signature }; // Symbol information ------------------------------------------------------- @@ -597,8 +598,6 @@ public: class PClass *ClassRestriction; - // this is only here to block PPointer's implementation - void SetPointer(void *base, unsigned offset, TArray *special = NULL) const override {} bool isCompatible(PType *type); virtual bool IsMatch(intptr_t id1, intptr_t id2) const; diff --git a/src/g_level.cpp b/src/g_level.cpp index d557efbf5..0367e7d91 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -432,10 +432,16 @@ void G_InitNew (const char *mapname, bool bTitleLevel) StatusBar->Destroy(); StatusBar = NULL; } + auto cls = PClass::FindClass("DoomStatusBar"); + if (bTitleLevel) { StatusBar = new DBaseStatusBar (0); } + else if (cls && gameinfo.gametype == GAME_Doom) + { + StatusBar = (DBaseStatusBar*)cls->CreateNew(); + } else if (SBarInfoScript[SCRIPT_CUSTOM] != NULL) { int cstype = SBarInfoScript[SCRIPT_CUSTOM]->GetGameType(); diff --git a/src/gl/scene/gl_sprite.cpp b/src/gl/scene/gl_sprite.cpp index 92416f88b..adbc626ed 100644 --- a/src/gl/scene/gl_sprite.cpp +++ b/src/gl/scene/gl_sprite.cpp @@ -646,8 +646,14 @@ void GLSprite::Process(AActor* thing, sector_t * sector, int thruportal) sector_t rs; sector_t * rendersector; + if (thing == nullptr) + return; + + // [ZZ] allow CustomSprite-style direct picnum specification + bool isPicnumOverride = thing->picnum.isValid(); + // Don't waste time projecting sprites that are definitely not visible. - if (thing == nullptr || thing->sprite == 0 || !thing->IsVisibleToPlayer() || !thing->IsInsideVisibleAngles()) + if ((thing->sprite == 0 && !isPicnumOverride) || !thing->IsVisibleToPlayer() || !thing->IsInsideVisibleAngles()) { return; } @@ -752,13 +758,19 @@ void GLSprite::Process(AActor* thing, sector_t * sector, int thruportal) z += fz; } - modelframe = gl_FindModelFrame(thing->GetClass(), spritenum, thing->frame, !!(thing->flags & MF_DROPPED)); + modelframe = isPicnumOverride ? nullptr : gl_FindModelFrame(thing->GetClass(), spritenum, thing->frame, !!(thing->flags & MF_DROPPED)); if (!modelframe) { bool mirror; DAngle ang = (thingpos - ViewPos).Angle(); FTextureID patch; - if (thing->flags7 & MF7_SPRITEANGLE) + // [ZZ] add direct picnum override + if (isPicnumOverride) + { + patch = thing->picnum; + mirror = false; + } + else if (thing->flags7 & MF7_SPRITEANGLE) { patch = gl_GetSpriteFrame(spritenum, thing->frame, -1, (thing->SpriteAngle).BAMs(), &mirror); } @@ -775,7 +787,8 @@ void GLSprite::Process(AActor* thing, sector_t * sector, int thruportal) if (!patch.isValid()) return; int type = thing->renderflags & RF_SPRITETYPEMASK; gltexture = FMaterial::ValidateTexture(patch, (type == RF_FACESPRITE), false); - if (!gltexture) return; + if (!gltexture) + return; vt = gltexture->GetSpriteVT(); vb = gltexture->GetSpriteVB(); diff --git a/src/gl/system/gl_load.c b/src/gl/system/gl_load.c index f390b5cc9..643b79013 100644 --- a/src/gl/system/gl_load.c +++ b/src/gl/system/gl_load.c @@ -17,11 +17,12 @@ static void* AppleGLGetProcAddress (const char *name) } #endif /* __APPLE__ */ -#if defined(__sgi) || defined (__sun) +/* BEGINNING OF MANUAL CHANGES, DO NOT REMOVE! */ +#if defined(__sgi) || defined (__sun) || defined(__unix__) #include #include -static void* SunGetProcAddress (const GLubyte* name) +static void* PosixGetProcAddress (const GLubyte* name) { static void* h = NULL; static void* gpa; @@ -37,7 +38,7 @@ static void* SunGetProcAddress (const GLubyte* name) else return dlsym(h, (const char*)name); } -#endif /* __sgi || __sun */ +#endif /* __sgi || __sun || __unix__ */ #if defined(_WIN32) @@ -77,8 +78,9 @@ static PROC WinGetProcAddress(const char *name) #if defined(__APPLE__) #define IntGetProcAddress(name) AppleGLGetProcAddress(name) #else - #if defined(__sgi) || defined(__sun) - #define IntGetProcAddress(name) SunGetProcAddress(name) + #if defined(__sgi) || defined(__sun) || defined(__unix__) + #define IntGetProcAddress(name) PosixGetProcAddress((const GLubyte*)name) +/* END OF MANUAL CHANGES, DO NOT REMOVE! */ #else /* GLX */ #include diff --git a/src/p_interaction.cpp b/src/p_interaction.cpp index 0320b0feb..c33cfb080 100644 --- a/src/p_interaction.cpp +++ b/src/p_interaction.cpp @@ -979,7 +979,7 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da if (target == NULL || !((target->flags & MF_SHOOTABLE) || (target->flags6 & MF6_VULNERABLE))) { // Shouldn't happen - return -1; + return 0; } //Rather than unnecessarily call the function over and over again, let's be a little more efficient. @@ -991,14 +991,14 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da { if (inflictor == NULL || !(inflictor->flags4 & MF4_SPECTRAL)) { - return -1; + return 0; } } if (target->health <= 0) { if (inflictor && mod == NAME_Ice && !(inflictor->flags7 & MF7_ICESHATTER)) { - return -1; + return 0; } else if (target->flags & MF_ICECORPSE) // frozen { @@ -1006,7 +1006,7 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da target->flags6 |= MF6_SHATTERING; target->Vel.Zero(); } - return -1; + return 0; } // [MC] Changed it to check rawdamage here for consistency, even though that doesn't actually do anything // different here. At any rate, invulnerable is being checked before type factoring, which is then being @@ -1027,7 +1027,7 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da goto fakepain; } else - return -1; + return 0; } } else @@ -1038,7 +1038,7 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da if (fakedPain) plrDontThrust = 1; else - return -1; + return 0; } } @@ -1068,7 +1068,7 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da if (target->flags2 & MF2_DORMANT) { // Invulnerable, and won't wake up - return -1; + return 0; } if (!telefragDamage || (target->flags7 & MF7_LAXTELEFRAGDMG)) // TELEFRAG_DAMAGE may only be reduced with LAXTELEFRAGDMG or it may not guarantee its effect. @@ -1086,19 +1086,19 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da if (player != NULL) { if (!deathmatch && inflictor->FriendPlayer > 0) - return -1; + return 0; } else if (target->flags4 & MF4_SPECTRAL) { if (inflictor->FriendPlayer == 0 && !target->IsHostile(inflictor)) - return -1; + return 0; } } damage = inflictor->CallDoSpecialDamage(target, damage, mod); if (damage < 0) { - return -1; + return 0; } } @@ -1141,7 +1141,7 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da { goto fakepain; } - return -1; + return 0; } } } @@ -1153,7 +1153,7 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da if (damage < 0) { // any negative value means that something in the above chain has cancelled out all damage and all damage effects, including pain. - return -1; + return 0; } // Push the target unless the source's weapon's kickback is 0. // (i.e. Gauntlets/Chainsaw) @@ -1244,7 +1244,7 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da damage = (int)(damage * level.teamdamage); if (damage < 0) { - return damage; + return 0; } else if (damage == 0) { @@ -1256,7 +1256,7 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da { goto fakepain; } - return -1; + return 0; } } } @@ -1294,14 +1294,14 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da //Make sure no godmodes and NOPAIN flags are found first. //Then, check to see if the player has NODAMAGE or ALLOWPAIN, or inflictor has CAUSEPAIN. if ((player->cheats & CF_GODMODE) || (player->cheats & CF_GODMODE2) || (player->mo->flags5 & MF5_NOPAIN)) - return -1; + return 0; else if ((((player->mo->flags7 & MF7_ALLOWPAIN) || (player->mo->flags5 & MF5_NODAMAGE)) || ((inflictor != NULL) && (inflictor->flags7 & MF7_CAUSEPAIN)))) { invulpain = true; goto fakepain; } else - return -1; + return 0; } // Armor for players. if (!(flags & DMG_NO_ARMOR) && player->mo->Inventory != NULL) @@ -1321,7 +1321,7 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da { // [MC] Godmode doesn't need checking here, it's already being handled above. if ((target->flags5 & MF5_NOPAIN) || (inflictor && (inflictor->flags5 & MF5_PAINLESS))) - return damage; + return 0; // If MF6_FORCEPAIN is set, make the player enter the pain state. if ((inflictor && (inflictor->flags6 & MF6_FORCEPAIN))) @@ -1332,7 +1332,7 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da invulpain = true; goto fakepain; } - return damage; + return 0; } } @@ -1396,7 +1396,7 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da if (fakedPain) goto fakepain; else - return damage; + return 0; } } @@ -1468,7 +1468,7 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da } } target->CallDie (source, inflictor, flags); - return damage; + return MAX(0, damage); } } @@ -1480,7 +1480,7 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da if (target->health <= woundhealth) { target->SetState (woundstate); - return damage; + return MAX(0, damage); } } @@ -1579,9 +1579,9 @@ dopain: if (invulpain) //Note that this takes into account all the cheats a player has, in terms of invulnerability. { - return -1; //NOW we return -1! + return 0; //NOW we return -1! } - return damage; + return MAX(0, damage); } DEFINE_ACTION_FUNCTION(AActor, DamageMobj) diff --git a/src/p_map.cpp b/src/p_map.cpp index ae3dfd0f2..ac5ca10a7 100644 --- a/src/p_map.cpp +++ b/src/p_map.cpp @@ -5011,7 +5011,7 @@ void P_RailAttack(FRailParams *p) DAngle angle = source->Angles.Yaw + p->angleoffset; DVector3 vec(DRotator(-pitch, angle, angle)); - double shootz = source->Center() - source->FloatSpeed + p->offset_z; + double shootz = source->Center() - source->FloatSpeed + p->offset_z - source->Floorclip; if (!(p->flags & RAF_CENTERZ)) { diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index 0cdd197a8..210c0cd5b 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -1041,7 +1041,7 @@ AInventory *AActor::DropInventory (AInventory *item) AInventory *drop = nullptr; IFVIRTUALPTR(item, AInventory, CreateTossable) { - VMValue params[1] = { (DObject*)this }; + VMValue params[1] = { (DObject*)item }; VMReturn ret((void**)&drop); GlobalVMStack.Call(func, params, 1, &ret, 1, nullptr); } diff --git a/src/resourcefiles/resourcefile.cpp b/src/resourcefiles/resourcefile.cpp index 603606341..c9f2f7446 100644 --- a/src/resourcefiles/resourcefile.cpp +++ b/src/resourcefiles/resourcefile.cpp @@ -291,11 +291,13 @@ static CheckFunc funcs[] = { CheckWad, CheckZip, Check7Z, CheckPak, CheckGRP, Ch FResourceFile *FResourceFile::OpenResourceFile(const char *filename, FileReader *file, bool quiet, bool containeronly) { + bool mustclose = false; if (file == NULL) { try { file = new FileReader(filename); + mustclose = true; } catch (CRecoverableError &) { @@ -307,6 +309,7 @@ FResourceFile *FResourceFile::OpenResourceFile(const char *filename, FileReader FResourceFile *resfile = funcs[i](filename, file, quiet); if (resfile != NULL) return resfile; } + if (mustclose) delete file; return NULL; } diff --git a/src/sc_man_scanner.re b/src/sc_man_scanner.re index 31a9c022f..ae35935f5 100644 --- a/src/sc_man_scanner.re +++ b/src/sc_man_scanner.re @@ -171,6 +171,7 @@ std2: 'export' { RET(TK_Export); } 'virtual' { RET(TK_Virtual); } 'override' { RET(TK_Override); } + 'vararg' { RET(TK_VarArg); } 'super' { RET(TK_Super); } 'global' { RET(TK_Global); } 'stop' { RET(TK_Stop); } diff --git a/src/sc_man_tokens.h b/src/sc_man_tokens.h index fb8f1c5ae..9f88e2242 100644 --- a/src/sc_man_tokens.h +++ b/src/sc_man_tokens.h @@ -112,6 +112,7 @@ xx(TK_Iterator, "'iterator'") xx(TK_Optional, "'optional'") xx(TK_Export, "'expert'") xx(TK_Virtual, "'virtual'") +xx(TK_VarArg, "'vararg'") xx(TK_Override, "'override'") xx(TK_Super, "'super'") xx(TK_Null, "'null'") diff --git a/src/scripting/codegeneration/codegen.cpp b/src/scripting/codegeneration/codegen.cpp index 4395d123e..cb5944ade 100644 --- a/src/scripting/codegeneration/codegen.cpp +++ b/src/scripting/codegeneration/codegen.cpp @@ -7188,14 +7188,6 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx) } } - // [ZZ] string formatting function - if (MethodName == NAME_Format) - { - FxExpression *x = new FxFormat(ArgList, ScriptPosition); - delete this; - return x->Resolve(ctx); - } - int min, max, special; if (MethodName == NAME_ACS_NamedExecuteWithResult || MethodName == NAME_CallACS) { @@ -7466,6 +7458,9 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) { if (ccls != nullptr) { + // [ZZ] substitute ccls for String internal type. + if (ccls->TypeName == NAME_String) + ccls = TypeStringStruct; if (!ccls->IsKindOf(RUNTIME_CLASS(PClass)) || static_cast(ccls)->bExported) { cls = ccls; @@ -8056,6 +8051,7 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx) for (unsigned i = 0; i < ArgList.Size(); i++) { // Varargs must all have the same type as the last typed argument. A_Jump is the only function using it. + // [ZZ] Varargs MAY have arbitrary types if the method is marked vararg. if (!foundvarargs) { if (argtypes[i + implicit] == nullptr) foundvarargs = true; @@ -8128,8 +8124,21 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx) flag = argflags[i + implicit]; } - FxExpression *x; - if (!(flag & (VARF_Ref|VARF_Out))) + FxExpression *x = nullptr; + if (foundvarargs && (Function->Variants[0].Flags & VARF_VarArg)) + { + // only cast implicit-string types for vararg, leave everything else as-is + // this was outright copypasted from FxFormat + ArgList[i] = ArgList[i]->Resolve(ctx); + if (ArgList[i]->ValueType == TypeName || + ArgList[i]->ValueType == TypeSound) + { + x = new FxStringCast(ArgList[i]); + x = x->Resolve(ctx); + } + else x = ArgList[i]; + } + else if (!(flag & (VARF_Ref|VARF_Out))) { x = new FxTypeCast(ArgList[i], type, false); x = x->Resolve(ctx); @@ -8475,280 +8484,6 @@ ExpEmit FxFlopFunctionCall::Emit(VMFunctionBuilder *build) return to; } - -//========================================================================== -// -// -// -//========================================================================== - -FxFormat::FxFormat(FArgumentList &args, const FScriptPosition &pos) - : FxExpression(EFX_Format, pos) -{ - EmitTail = false; - ArgList = std::move(args); -} - -//========================================================================== -// -// -// -//========================================================================== - -FxFormat::~FxFormat() -{ -} - -//========================================================================== -// -// -// -//========================================================================== - -PPrototype *FxFormat::ReturnProto() -{ - EmitTail = true; - return FxExpression::ReturnProto(); -} - -//========================================================================== -// -// -// -//========================================================================== - -FxExpression *FxFormat::Resolve(FCompileContext& ctx) -{ - CHECKRESOLVED(); - - for (unsigned i = 0; i < ArgList.Size(); i++) - { - ArgList[i] = ArgList[i]->Resolve(ctx); - if (ArgList[i] == nullptr) - { - delete this; - return nullptr; - } - - // first argument should be a string - if (!i && ArgList[i]->ValueType != TypeString) - { - ScriptPosition.Message(MSG_ERROR, "String was expected for format"); - delete this; - return nullptr; - } - - if (ArgList[i]->ValueType == TypeName || - ArgList[i]->ValueType == TypeSound) - { - FxExpression* x = new FxStringCast(ArgList[i]); - x = x->Resolve(ctx); - if (x == nullptr) - { - delete this; - return nullptr; - } - ArgList[i] = x; - } - } - - ValueType = TypeString; - return this; -} - -//========================================================================== -// -// -//========================================================================== - -static int BuiltinFormat(VMValue *args, TArray &defaultparam, int numparam, VMReturn *ret, int numret) -{ - assert(args[0].Type == REGT_STRING); - FString fmtstring = args[0].s().GetChars(); - - // note: we don't need a real printf format parser. - // enough to simply find the subtitution tokens and feed them to the real printf after checking types. - // https://en.wikipedia.org/wiki/Printf_format_string#Format_placeholder_specification - FString output; - bool in_fmt = false; - FString fmt_current; - int argnum = 1; - int argauto = 1; - // % = starts - // [0-9], -, +, \s, 0, #, . continue - // %, s, d, i, u, fF, eE, gG, xX, o, c, p, aA terminate - // various type flags are not supported. not like stuff like 'hh' modifier is to be used in the VM. - // the only combination that is parsed locally is %n$... - bool haveargnums = false; - for (size_t i = 0; i < fmtstring.Len(); i++) - { - char c = fmtstring[i]; - if (in_fmt) - { - if ((c >= '0' && c <= '9') || - c == '-' || c == '+' || (c == ' ' && fmt_current[fmt_current.Len() - 1] != ' ') || c == '#' || c == '.') - { - fmt_current += c; - } - else if (c == '$') // %number$format - { - if (!haveargnums && argauto > 1) - ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); - FString argnumstr = fmt_current.Mid(1); - if (!argnumstr.IsInt()) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for argument number, got '%s'.", argnumstr.GetChars()); - argnum = argnumstr.ToLong(); - if (argnum < 1 || argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format (tried to access argument %d, %d total).", argnum, numparam); - fmt_current = "%"; - haveargnums = true; - } - else - { - fmt_current += c; - - switch (c) - { - // string - case 's': - { - if (argnum < 0 && haveargnums) - ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); - in_fmt = false; - // fail if something was found, but it's not a string - if (argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format."); - if (args[argnum].Type != REGT_STRING) ThrowAbortException(X_FORMAT_ERROR, "Expected a string for format %s.", fmt_current.GetChars()); - // append - output.AppendFormat(fmt_current.GetChars(), args[argnum].s().GetChars()); - if (!haveargnums) argnum = ++argauto; - else argnum = -1; - break; - } - - // pointer - case 'p': - { - if (argnum < 0 && haveargnums) - ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); - in_fmt = false; - // fail if something was found, but it's not a string - if (argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format."); - if (args[argnum].Type != REGT_POINTER) ThrowAbortException(X_FORMAT_ERROR, "Expected a pointer for format %s.", fmt_current.GetChars()); - // append - output.AppendFormat(fmt_current.GetChars(), args[argnum].a); - if (!haveargnums) argnum = ++argauto; - else argnum = -1; - break; - } - - // int formats (including char) - case 'd': - case 'i': - case 'u': - case 'x': - case 'X': - case 'o': - case 'c': - { - if (argnum < 0 && haveargnums) - ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); - in_fmt = false; - // fail if something was found, but it's not an int - if (argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format."); - if (args[argnum].Type != REGT_INT && - args[argnum].Type != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars()); - // append - output.AppendFormat(fmt_current.GetChars(), args[argnum].ToInt()); - if (!haveargnums) argnum = ++argauto; - else argnum = -1; - break; - } - - // double formats - case 'f': - case 'F': - case 'e': - case 'E': - case 'g': - case 'G': - case 'a': - case 'A': - { - if (argnum < 0 && haveargnums) - ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); - in_fmt = false; - // fail if something was found, but it's not a float - if (argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format."); - if (args[argnum].Type != REGT_INT && - args[argnum].Type != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars()); - // append - output.AppendFormat(fmt_current.GetChars(), args[argnum].ToDouble()); - if (!haveargnums) argnum = ++argauto; - else argnum = -1; - break; - } - - default: - // invalid character - output += fmt_current; - in_fmt = false; - break; - } - } - } - else - { - if (c == '%') - { - if (i + 1 < fmtstring.Len() && fmtstring[i + 1] == '%') - { - output += '%'; - i++; - } - else - { - in_fmt = true; - fmt_current = "%"; - } - } - else - { - output += c; - } - } - } - - ACTION_RETURN_STRING(output); -} - -ExpEmit FxFormat::Emit(VMFunctionBuilder *build) -{ - // Call DecoRandom to generate a random number. - VMFunction *callfunc; - PSymbol *sym = FindBuiltinFunction(NAME_BuiltinFormat, BuiltinFormat); - - assert(sym->IsKindOf(RUNTIME_CLASS(PSymbolVMFunction))); - assert(((PSymbolVMFunction *)sym)->Function != nullptr); - callfunc = ((PSymbolVMFunction *)sym)->Function; - - if (build->FramePointer.Fixed) EmitTail = false; // do not tail call if the stack is in use - int opcode = (EmitTail ? OP_TAIL_K : OP_CALL_K); - - for (unsigned i = 0; i < ArgList.Size(); i++) - EmitParameter(build, ArgList[i], ScriptPosition); - build->Emit(opcode, build->GetConstantAddress(callfunc, ATAG_OBJECT), ArgList.Size(), 1); - - if (EmitTail) - { - ExpEmit call; - call.Final = true; - return call; - } - - ExpEmit out(build, REGT_STRING); - build->Emit(OP_RESULT, 0, REGT_STRING, out.RegNum); - return out; -} - - //========================================================================== // // diff --git a/src/scripting/codegeneration/codegen.h b/src/scripting/codegeneration/codegen.h index 8b43d28fa..3dac463eb 100644 --- a/src/scripting/codegeneration/codegen.h +++ b/src/scripting/codegeneration/codegen.h @@ -255,7 +255,6 @@ enum EFxType EFX_MemberFunctionCall, EFX_ActionSpecialCall, EFX_FlopFunctionCall, - EFX_Format, EFX_VMFunctionCall, EFX_Sequence, EFX_CompoundStatement, @@ -1534,26 +1533,6 @@ public: ExpEmit Emit(VMFunctionBuilder *build); }; -//========================================================================== -// -// FxFormat -// -//========================================================================== - -class FxFormat : public FxExpression -{ - FArgumentList ArgList; - bool EmitTail; - -public: - - FxFormat(FArgumentList &args, const FScriptPosition &pos); - ~FxFormat(); - FxExpression *Resolve(FCompileContext&); - PPrototype *ReturnProto(); - ExpEmit Emit(VMFunctionBuilder *build); -}; - //========================================================================== // // FxVectorBuiltin diff --git a/src/scripting/thingdef_data.cpp b/src/scripting/thingdef_data.cpp index 21e4f46d9..1dd8d6263 100644 --- a/src/scripting/thingdef_data.cpp +++ b/src/scripting/thingdef_data.cpp @@ -945,3 +945,176 @@ DEFINE_ACTION_FUNCTION(FString, Replace) return 0; } +static FString FStringFormat(VM_ARGS) +{ + assert(param[0].Type == REGT_STRING); + FString fmtstring = param[0].s().GetChars(); + + // note: we don't need a real printf format parser. + // enough to simply find the subtitution tokens and feed them to the real printf after checking types. + // https://en.wikipedia.org/wiki/Printf_format_string#Format_placeholder_specification + FString output; + bool in_fmt = false; + FString fmt_current; + int argnum = 1; + int argauto = 1; + // % = starts + // [0-9], -, +, \s, 0, #, . continue + // %, s, d, i, u, fF, eE, gG, xX, o, c, p, aA terminate + // various type flags are not supported. not like stuff like 'hh' modifier is to be used in the VM. + // the only combination that is parsed locally is %n$... + bool haveargnums = false; + for (size_t i = 0; i < fmtstring.Len(); i++) + { + char c = fmtstring[i]; + if (in_fmt) + { + if ((c >= '0' && c <= '9') || + c == '-' || c == '+' || (c == ' ' && fmt_current[fmt_current.Len() - 1] != ' ') || c == '#' || c == '.') + { + fmt_current += c; + } + else if (c == '$') // %number$format + { + if (!haveargnums && argauto > 1) + ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); + FString argnumstr = fmt_current.Mid(1); + if (!argnumstr.IsInt()) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for argument number, got '%s'.", argnumstr.GetChars()); + argnum = argnumstr.ToLong(); + if (argnum < 1 || argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format (tried to access argument %d, %d total).", argnum, numparam); + fmt_current = "%"; + haveargnums = true; + } + else + { + fmt_current += c; + + switch (c) + { + // string + case 's': + { + if (argnum < 0 && haveargnums) + ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); + in_fmt = false; + // fail if something was found, but it's not a string + if (argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format."); + if (param[argnum].Type != REGT_STRING) ThrowAbortException(X_FORMAT_ERROR, "Expected a string for format %s.", fmt_current.GetChars()); + // append + output.AppendFormat(fmt_current.GetChars(), param[argnum].s().GetChars()); + if (!haveargnums) argnum = ++argauto; + else argnum = -1; + break; + } + + // pointer + case 'p': + { + if (argnum < 0 && haveargnums) + ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); + in_fmt = false; + // fail if something was found, but it's not a string + if (argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format."); + if (param[argnum].Type != REGT_POINTER) ThrowAbortException(X_FORMAT_ERROR, "Expected a pointer for format %s.", fmt_current.GetChars()); + // append + output.AppendFormat(fmt_current.GetChars(), param[argnum].a); + if (!haveargnums) argnum = ++argauto; + else argnum = -1; + break; + } + + // int formats (including char) + case 'd': + case 'i': + case 'u': + case 'x': + case 'X': + case 'o': + case 'c': + { + if (argnum < 0 && haveargnums) + ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); + in_fmt = false; + // fail if something was found, but it's not an int + if (argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format."); + if (param[argnum].Type != REGT_INT && + param[argnum].Type != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars()); + // append + output.AppendFormat(fmt_current.GetChars(), param[argnum].ToInt()); + if (!haveargnums) argnum = ++argauto; + else argnum = -1; + break; + } + + // double formats + case 'f': + case 'F': + case 'e': + case 'E': + case 'g': + case 'G': + case 'a': + case 'A': + { + if (argnum < 0 && haveargnums) + ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); + in_fmt = false; + // fail if something was found, but it's not a float + if (argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format."); + if (param[argnum].Type != REGT_INT && + param[argnum].Type != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars()); + // append + output.AppendFormat(fmt_current.GetChars(), param[argnum].ToDouble()); + if (!haveargnums) argnum = ++argauto; + else argnum = -1; + break; + } + + default: + // invalid character + output += fmt_current; + in_fmt = false; + break; + } + } + } + else + { + if (c == '%') + { + if (i + 1 < fmtstring.Len() && fmtstring[i + 1] == '%') + { + output += '%'; + i++; + } + else + { + in_fmt = true; + fmt_current = "%"; + } + } + else + { + output += c; + } + } + } + + return output; +} + +DEFINE_ACTION_FUNCTION(FString, Format) +{ + PARAM_PROLOGUE; + FString s = FStringFormat(param, defaultparam, numparam, ret, numret); + ACTION_RETURN_STRING(s); +} + +DEFINE_ACTION_FUNCTION(FString, AppendFormat) +{ + PARAM_SELF_STRUCT_PROLOGUE(FString); + // first parameter is the self pointer + FString s = FStringFormat(param+1, defaultparam, numparam-1, ret, numret); + (*self) += s; + return 0; +} \ No newline at end of file diff --git a/src/scripting/zscript/zcc-parse.lemon b/src/scripting/zscript/zcc-parse.lemon index d0ff03625..bae828493 100644 --- a/src/scripting/zscript/zcc-parse.lemon +++ b/src/scripting/zscript/zcc-parse.lemon @@ -722,7 +722,7 @@ type_name1(X) ::= BOOL(T). { X.Int = ZCC_Bool; X.SourceLoc = T.SourceLoc; } type_name1(X) ::= int_type(X). type_name1(X) ::= FLOAT(T). { X.Int = ZCC_FloatAuto; X.SourceLoc = T.SourceLoc; } type_name1(X) ::= DOUBLE(T). { X.Int = ZCC_Float64; X.SourceLoc = T.SourceLoc; } -type_name1(X) ::= STRING(T). { X.Int = ZCC_String; X.SourceLoc = T.SourceLoc; } +//type_name1(X) ::= STRING(T). { X.Int = ZCC_String; X.SourceLoc = T.SourceLoc; } // [ZZ] it's handled elsewhere. this particular line only causes troubles in the form of String.Format being invalid. type_name1(X) ::= VECTOR2(T). { X.Int = ZCC_Vector2; X.SourceLoc = T.SourceLoc; } type_name1(X) ::= VECTOR3(T). { X.Int = ZCC_Vector3; X.SourceLoc = T.SourceLoc; } type_name1(X) ::= NAME(T). { X.Int = ZCC_Name; X.SourceLoc = T.SourceLoc; } @@ -994,6 +994,7 @@ decl_flag(X) ::= READONLY(T). { X.Int = ZCC_ReadOnly; X.SourceLoc = T.SourceLoc decl_flag(X) ::= DEPRECATED(T). { X.Int = ZCC_Deprecated; X.SourceLoc = T.SourceLoc; } decl_flag(X) ::= VIRTUAL(T). { X.Int = ZCC_Virtual; X.SourceLoc = T.SourceLoc; } decl_flag(X) ::= OVERRIDE(T). { X.Int = ZCC_Override; X.SourceLoc = T.SourceLoc; } +decl_flag(X) ::= VARARG(T). { X.Int = ZCC_VarArg; X.SourceLoc = T.SourceLoc; } func_const(X) ::= . { X.Int = 0; X.SourceLoc = stat->sc->GetMessageLine(); } func_const(X) ::= CONST(T). { X.Int = ZCC_FuncConst; X.SourceLoc = T.SourceLoc; } diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index ab96813bd..ddf08fbc4 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -1423,10 +1423,11 @@ bool ZCCCompiler::CompileProperties(PClass *type, TArray &Proper FString ZCCCompiler::FlagsToString(uint32_t flags) { - const char *flagnames[] = { "native", "static", "private", "protected", "latent", "final", "meta", "action", "deprecated", "readonly", "funcconst", "abstract" }; + + const char *flagnames[] = { "native", "static", "private", "protected", "latent", "final", "meta", "action", "deprecated", "readonly", "funcconst", "abstract", "extension", "virtual", "override", "transient", "vararg" }; FString build; - for (int i = 0; i < 12; i++) + for (size_t i = 0; i < countof(flagnames); i++) { if (flags & (1 << i)) { @@ -2287,6 +2288,11 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool if (f->Flags & ZCC_Deprecated) varflags |= VARF_Deprecated; if (f->Flags & ZCC_Virtual) varflags |= VARF_Virtual; if (f->Flags & ZCC_Override) varflags |= VARF_Override; + if (f->Flags & ZCC_VarArg) varflags |= VARF_VarArg; + if ((f->Flags & ZCC_VarArg) && !(f->Flags & ZCC_Native)) + { + Error(f, "'VarArg' can only be used with native methods"); + } if (f->Flags & ZCC_Action) { // Non-Actors cannot have action functions. diff --git a/src/scripting/zscript/zcc_parser.cpp b/src/scripting/zscript/zcc_parser.cpp index a0ecbe06c..95f75aea6 100644 --- a/src/scripting/zscript/zcc_parser.cpp +++ b/src/scripting/zscript/zcc_parser.cpp @@ -136,6 +136,7 @@ static void InitTokenMap() TOKENDEF (TK_Protected, ZCC_PROTECTED); TOKENDEF (TK_Latent, ZCC_LATENT); TOKENDEF (TK_Virtual, ZCC_VIRTUAL); + TOKENDEF (TK_VarArg, ZCC_VARARG); TOKENDEF (TK_Override, ZCC_OVERRIDE); TOKENDEF (TK_Final, ZCC_FINAL); TOKENDEF (TK_Meta, ZCC_META); diff --git a/src/scripting/zscript/zcc_parser.h b/src/scripting/zscript/zcc_parser.h index 19c327fc9..b80686bb0 100644 --- a/src/scripting/zscript/zcc_parser.h +++ b/src/scripting/zscript/zcc_parser.h @@ -36,6 +36,7 @@ enum ZCC_Virtual = 1 << 13, ZCC_Override = 1 << 14, ZCC_Transient = 1 << 15, + ZCC_VarArg = 1 << 16 }; // Function parameter modifiers diff --git a/wadsrc/static/zscript/base.txt b/wadsrc/static/zscript/base.txt index d5e58f653..eab046372 100644 --- a/wadsrc/static/zscript/base.txt +++ b/wadsrc/static/zscript/base.txt @@ -308,6 +308,8 @@ enum EPickStart struct String native { native void Replace(String pattern, String replacement); + native static vararg String Format(String fmt, ...); + native vararg void AppendFormat(String fmt, ...); } class Floor : Thinker native diff --git a/wadsrc/static/zscript/doom/lostsoul.txt b/wadsrc/static/zscript/doom/lostsoul.txt index 89cefc42a..7d918aa10 100644 --- a/wadsrc/static/zscript/doom/lostsoul.txt +++ b/wadsrc/static/zscript/doom/lostsoul.txt @@ -90,19 +90,19 @@ class BetaSkull : LostSoul extend class Actor { - const SKULLSPEED = 20; + const DEFSKULLSPEED = 20; - void A_SkullAttack(double skullspeed = SKULLSPEED) + void A_SkullAttack(double skullspeed = DEFSKULLSPEED) { if (target == null) return; - if (skullspeed <= 0) skullspeed = SKULLSPEED; + if (skullspeed <= 0) skullspeed = DEFSKULLSPEED; bSkullfly = true; A_PlaySound(AttackSound, CHAN_VOICE); A_FaceTarget(); VelFromAngle(skullspeed); - Vel.Z = (target.pos.Z + target.Height/2 - pos.Z) / DistanceBySpeed(target, speed); + Vel.Z = (target.pos.Z + target.Height/2 - pos.Z) / DistanceBySpeed(target, skullspeed); } void A_BetaSkullAttack() diff --git a/wadsrc/static/zscript/shared/player_cheat.txt b/wadsrc/static/zscript/shared/player_cheat.txt index b8c24b30f..3dba4ac2d 100644 --- a/wadsrc/static/zscript/shared/player_cheat.txt +++ b/wadsrc/static/zscript/shared/player_cheat.txt @@ -50,7 +50,7 @@ extend class PlayerPawn let player = self.player; if (PlayerNumber() != consoleplayer) - A_Log(format ("%s is a cheater: give %s\n", player.GetUserName(), name)); + A_Log(String.Format ("%s is a cheater: give %s\n", player.GetUserName(), name)); if (player.mo == NULL || player.health <= 0) { @@ -254,7 +254,7 @@ extend class PlayerPawn if (type == NULL) { if (PlayerNumber() == consoleplayer) - A_Log(format("Unknown item \"%s\"\n", name)); + A_Log(String.Format("Unknown item \"%s\"\n", name)); } else { @@ -391,7 +391,7 @@ extend class PlayerPawn if (type == NULL) { if (PlayerNumber() == consoleplayer) - A_Log(format("Unknown item \"%s\"\n", name)); + A_Log(String.Format("Unknown item \"%s\"\n", name)); } else { diff --git a/wadsrc/static/zscript/shared/sharedmisc.txt b/wadsrc/static/zscript/shared/sharedmisc.txt index bc410e3f0..c837f5f1e 100644 --- a/wadsrc/static/zscript/shared/sharedmisc.txt +++ b/wadsrc/static/zscript/shared/sharedmisc.txt @@ -156,10 +156,9 @@ class CustomSprite : Actor override void BeginPlay () { - String name; Super.BeginPlay (); - format(name, "BTIL%04d", args[0] & 0xffff); + String name = String.Format("BTIL%04d", args[0] & 0xffff); picnum = TexMan.CheckForTexture (name, TexMan.TYPE_Build); if (!picnum.Exists()) { diff --git a/wadsrc/static/zscript/shared/skies.txt b/wadsrc/static/zscript/shared/skies.txt index c817a3994..0bb0a78ef 100644 --- a/wadsrc/static/zscript/shared/skies.txt +++ b/wadsrc/static/zscript/shared/skies.txt @@ -115,7 +115,7 @@ class SkyPicker : Actor if (box == null && args[0] != 0) { - A_Log(format("Can't find SkyViewpoint %d for sector %d\n", args[0], CurSector.Index())); + A_Log(String.Format("Can't find SkyViewpoint %d for sector %d\n", args[0], CurSector.Index())); } else {