From 884de51b705be9c3e1ba71c8a826037d5c95ac18 Mon Sep 17 00:00:00 2001 From: Kevin Caccamo Date: Wed, 22 Aug 2018 03:52:11 -0400 Subject: [PATCH 01/16] Add the "RIndexOf" method to FString, which works like String.lastIndexOf from JavaScript RIndexOf returns the index where the substring starts, instead of the index where the substring ends - 1. Deprecate the LastIndexOf method of StringStruct --- src/scripting/thingdef_data.cpp | 12 ++++++++-- src/zstring.cpp | 39 ++++++++++++++++++++++++++++++++- src/zstring.h | 7 +++++- wadsrc/static/zscript/base.txt | 3 ++- 4 files changed, 56 insertions(+), 5 deletions(-) diff --git a/src/scripting/thingdef_data.cpp b/src/scripting/thingdef_data.cpp index ca6bee2a44..8f4a209d02 100644 --- a/src/scripting/thingdef_data.cpp +++ b/src/scripting/thingdef_data.cpp @@ -327,7 +327,7 @@ static FFlagDef ActorFlagDefs[]= DEFINE_FLAG(RF, INVISIBLE, AActor, renderflags), DEFINE_FLAG(RF, FORCEYBILLBOARD, AActor, renderflags), DEFINE_FLAG(RF, FORCEXYBILLBOARD, AActor, renderflags), - DEFINE_FLAG(RF, ROLLSPRITE, AActor, renderflags), // [marrub] roll the sprite billboard + DEFINE_FLAG(RF, ROLLSPRITE, AActor, renderflags), // [marrub] roll the sprite billboard // [fgsfds] Flat sprites DEFINE_FLAG(RF, FLATSPRITE, AActor, renderflags), DEFINE_FLAG(RF, WALLSPRITE, AActor, renderflags), @@ -593,7 +593,7 @@ FFlagDef *FindFlag (const PClass *type, const char *part1, const char *part2, bo //========================================================================== // -// Gets the name of an actor flag +// Gets the name of an actor flag // //========================================================================== @@ -1273,6 +1273,14 @@ DEFINE_ACTION_FUNCTION(FStringStruct, LastIndexOf) ACTION_RETURN_INT(self->LastIndexOf(substr, endIndex)); } +DEFINE_ACTION_FUNCTION(FStringStruct, RIndexOf) +{ + PARAM_SELF_STRUCT_PROLOGUE(FString); + PARAM_STRING(substr); + PARAM_INT_DEF(endIndex); + ACTION_RETURN_INT(self->RIndexOf(substr, endIndex)); +} + DEFINE_ACTION_FUNCTION(FStringStruct, ToUpper) { PARAM_SELF_STRUCT_PROLOGUE(FString); diff --git a/src/zstring.cpp b/src/zstring.cpp index ea9c249bb2..e207cdb6b3 100644 --- a/src/zstring.cpp +++ b/src/zstring.cpp @@ -596,6 +596,43 @@ long FString::LastIndexOfAny (const char *charset, long endIndex) const return -1; } +long FString::RIndexOf (const FString &substr) const +{ + return RIndexOf(substr.Chars, Len() - substr.Len(), substr.Len()); +} + +long FString::RIndexOf (const FString &substr, long endIndex) const +{ + return RIndexOf(substr.Chars, endIndex, substr.Len()); +} + +long FString::RIndexOf (const char *substr) const +{ + return RIndexOf(substr, Len() - strlen(substr), strlen(substr)); +} + +long FString::RIndexOf (const char *substr, long endIndex) const +{ + return RIndexOf(substr, endIndex, strlen(substr)); +} + +long FString::RIndexOf (const char *substr, long endIndex, size_t substrlen) const +{ + if ((size_t)endIndex + substrlen > Len()) + { + endIndex = Len() - substrlen; + } + while (endIndex >= 0) + { + if (strncmp (substr, Chars + endIndex, substrlen) == 0) + { + return endIndex; + } + endIndex--; + } + return -1; +} + void FString::ToUpper () { LockBuffer(); @@ -1003,7 +1040,7 @@ void FString::Substitute (const char *oldstr, const char *newstr, size_t oldstrl bool FString::IsInt () const { - // String must match: [whitespace] [{+ | –}] [0 [{ x | X }]] [digits] [whitespace] + // String must match: [whitespace] [{+ | �}] [0 [{ x | X }]] [digits] [whitespace] /* This state machine is based on a simplification of re2c's output for this input: digits = [0-9]; diff --git a/src/zstring.h b/src/zstring.h index 5ab2674142..d61408dc77 100644 --- a/src/zstring.h +++ b/src/zstring.h @@ -214,6 +214,12 @@ public: long LastIndexOfAny (const FString &charset, long endIndex) const; long LastIndexOfAny (const char *charset, long endIndex) const; + long RIndexOf (const FString &substr) const; + long RIndexOf (const FString &substr, long endIndex) const; + long RIndexOf (const char *substr) const; + long RIndexOf (const char *substr, long endIndex) const; + long RIndexOf (const char *substr, long endIndex, size_t substrlen) const; + void ToUpper (); void ToLower (); void SwapCase (); @@ -463,4 +469,3 @@ template<> struct THashTraits // Compares two keys, returning zero if they are the same. int Compare(const FString &left, const FString &right) { return left.Compare(right); } }; - diff --git a/wadsrc/static/zscript/base.txt b/wadsrc/static/zscript/base.txt index 498f6ae717..90c847a232 100644 --- a/wadsrc/static/zscript/base.txt +++ b/wadsrc/static/zscript/base.txt @@ -815,7 +815,8 @@ struct StringStruct native native int CharCodeAt(int pos) const; native String Filter(); native int IndexOf(String substr, int startIndex = 0) const; - native int LastIndexOf(String substr, int endIndex = 2147483647) const; + deprecated("3.5") native int LastIndexOf(String substr, int endIndex = 2147483647) const; + native int RIndexOf(String substr, int endIndex = 2147483647) const; native void ToUpper(); native void ToLower(); native int ToInt(int base = 0) const; From c04c48d157ca709324b09463b16bd52e14bb144e Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 25 Aug 2018 20:39:58 +0200 Subject: [PATCH 02/16] - renamed RIndexOf to RightIndexOf --- src/scripting/thingdef_data.cpp | 2 +- wadsrc/static/zscript/base.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scripting/thingdef_data.cpp b/src/scripting/thingdef_data.cpp index 8f4a209d02..35d2733f5c 100644 --- a/src/scripting/thingdef_data.cpp +++ b/src/scripting/thingdef_data.cpp @@ -1273,7 +1273,7 @@ DEFINE_ACTION_FUNCTION(FStringStruct, LastIndexOf) ACTION_RETURN_INT(self->LastIndexOf(substr, endIndex)); } -DEFINE_ACTION_FUNCTION(FStringStruct, RIndexOf) +DEFINE_ACTION_FUNCTION(FStringStruct, RightIndexOf) { PARAM_SELF_STRUCT_PROLOGUE(FString); PARAM_STRING(substr); diff --git a/wadsrc/static/zscript/base.txt b/wadsrc/static/zscript/base.txt index 90c847a232..23ebafaf8c 100644 --- a/wadsrc/static/zscript/base.txt +++ b/wadsrc/static/zscript/base.txt @@ -816,7 +816,7 @@ struct StringStruct native native String Filter(); native int IndexOf(String substr, int startIndex = 0) const; deprecated("3.5") native int LastIndexOf(String substr, int endIndex = 2147483647) const; - native int RIndexOf(String substr, int endIndex = 2147483647) const; + native int RightIndexOf(String substr, int endIndex = 2147483647) const; native void ToUpper(); native void ToLower(); native int ToInt(int base = 0) const; From d263f7bcc8fefa5b5173b52109749fd0ddd006a3 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 25 Aug 2018 20:54:16 +0200 Subject: [PATCH 03/16] - removed most of the old LastIndexOf methods in FString, only leaving one for ZScript and clearly giving it a name that says it all. RIndexOf has been made the proper version of LastIndexOf internally now. --- src/scripting/thingdef_data.cpp | 4 ++-- src/zstring.cpp | 42 ++++++++++----------------------- src/zstring.h | 17 ++++++------- wadsrc/static/zscript/base.txt | 2 +- 4 files changed, 22 insertions(+), 43 deletions(-) diff --git a/src/scripting/thingdef_data.cpp b/src/scripting/thingdef_data.cpp index 35d2733f5c..a10d05710d 100644 --- a/src/scripting/thingdef_data.cpp +++ b/src/scripting/thingdef_data.cpp @@ -1270,7 +1270,7 @@ DEFINE_ACTION_FUNCTION(FStringStruct, LastIndexOf) PARAM_SELF_STRUCT_PROLOGUE(FString); PARAM_STRING(substr); PARAM_INT_DEF(endIndex); - ACTION_RETURN_INT(self->LastIndexOf(substr, endIndex)); + ACTION_RETURN_INT(self->LastIndexOfBroken(substr, endIndex)); } DEFINE_ACTION_FUNCTION(FStringStruct, RightIndexOf) @@ -1278,7 +1278,7 @@ DEFINE_ACTION_FUNCTION(FStringStruct, RightIndexOf) PARAM_SELF_STRUCT_PROLOGUE(FString); PARAM_STRING(substr); PARAM_INT_DEF(endIndex); - ACTION_RETURN_INT(self->RIndexOf(substr, endIndex)); + ACTION_RETURN_INT(self->LastIndexOf(substr, endIndex)); } DEFINE_ACTION_FUNCTION(FStringStruct, ToUpper) diff --git a/src/zstring.cpp b/src/zstring.cpp index e207cdb6b3..5e57df45bd 100644 --- a/src/zstring.cpp +++ b/src/zstring.cpp @@ -507,31 +507,11 @@ long FString::IndexOfAny (const char *charset, long startIndex) const return long(brk - Chars); } -long FString::LastIndexOf (const FString &substr) const -{ - return LastIndexOf (substr.Chars, long(Len()), substr.Len()); -} - -long FString::LastIndexOf (const char *substr) const -{ - return LastIndexOf (substr, long(Len()), strlen(substr)); -} - long FString::LastIndexOf (char subchar) const { return LastIndexOf (subchar, long(Len())); } -long FString::LastIndexOf (const FString &substr, long endIndex) const -{ - return LastIndexOf (substr.Chars, endIndex, substr.Len()); -} - -long FString::LastIndexOf (const char *substr, long endIndex) const -{ - return LastIndexOf (substr, endIndex, strlen(substr)); -} - long FString::LastIndexOf (char subchar, long endIndex) const { if ((size_t)endIndex > Len()) @@ -548,8 +528,10 @@ long FString::LastIndexOf (char subchar, long endIndex) const return -1; } -long FString::LastIndexOf (const char *substr, long endIndex, size_t substrlen) const +long FString::LastIndexOfBroken (const FString &_substr, long endIndex) const { + const char *substr = _substr.GetChars(); + size_t substrlen = _substr.Len(); if ((size_t)endIndex > Len()) { endIndex = long(Len()); @@ -596,27 +578,27 @@ long FString::LastIndexOfAny (const char *charset, long endIndex) const return -1; } -long FString::RIndexOf (const FString &substr) const +long FString::LastIndexOf (const FString &substr) const { - return RIndexOf(substr.Chars, Len() - substr.Len(), substr.Len()); + return LastIndexOf(substr.Chars, Len() - substr.Len(), substr.Len()); } -long FString::RIndexOf (const FString &substr, long endIndex) const +long FString::LastIndexOf (const FString &substr, long endIndex) const { - return RIndexOf(substr.Chars, endIndex, substr.Len()); + return LastIndexOf(substr.Chars, endIndex, substr.Len()); } -long FString::RIndexOf (const char *substr) const +long FString::LastIndexOf (const char *substr) const { - return RIndexOf(substr, Len() - strlen(substr), strlen(substr)); + return LastIndexOf(substr, Len() - strlen(substr), strlen(substr)); } -long FString::RIndexOf (const char *substr, long endIndex) const +long FString::LastIndexOf (const char *substr, long endIndex) const { - return RIndexOf(substr, endIndex, strlen(substr)); + return LastIndexOf(substr, endIndex, strlen(substr)); } -long FString::RIndexOf (const char *substr, long endIndex, size_t substrlen) const +long FString::LastIndexOf (const char *substr, long endIndex, size_t substrlen) const { if ((size_t)endIndex + substrlen > Len()) { diff --git a/src/zstring.h b/src/zstring.h index d61408dc77..67fef4a181 100644 --- a/src/zstring.h +++ b/src/zstring.h @@ -201,24 +201,21 @@ public: long IndexOfAny (const FString &charset, long startIndex=0) const; long IndexOfAny (const char *charset, long startIndex=0) const; - long LastIndexOf (const FString &substr) const; - long LastIndexOf (const char *substr) const; + // This is only kept for backwards compatibility with old ZScript versions that used this function and depend on its bug. long LastIndexOf (char subchar) const; - long LastIndexOf (const FString &substr, long endIndex) const; - long LastIndexOf (const char *substr, long endIndex) const; + long LastIndexOfBroken (const FString &substr, long endIndex) const; long LastIndexOf (char subchar, long endIndex) const; - long LastIndexOf (const char *substr, long endIndex, size_t substrlen) const; long LastIndexOfAny (const FString &charset) const; long LastIndexOfAny (const char *charset) const; long LastIndexOfAny (const FString &charset, long endIndex) const; long LastIndexOfAny (const char *charset, long endIndex) const; - long RIndexOf (const FString &substr) const; - long RIndexOf (const FString &substr, long endIndex) const; - long RIndexOf (const char *substr) const; - long RIndexOf (const char *substr, long endIndex) const; - long RIndexOf (const char *substr, long endIndex, size_t substrlen) const; + long LastIndexOf (const FString &substr) const; + long LastIndexOf (const FString &substr, long endIndex) const; + long LastIndexOf (const char *substr) const; + long LastIndexOf (const char *substr, long endIndex) const; + long LastIndexOf (const char *substr, long endIndex, size_t substrlen) const; void ToUpper (); void ToLower (); diff --git a/wadsrc/static/zscript/base.txt b/wadsrc/static/zscript/base.txt index 23ebafaf8c..f310e29007 100644 --- a/wadsrc/static/zscript/base.txt +++ b/wadsrc/static/zscript/base.txt @@ -815,7 +815,7 @@ struct StringStruct native native int CharCodeAt(int pos) const; native String Filter(); native int IndexOf(String substr, int startIndex = 0) const; - deprecated("3.5") native int LastIndexOf(String substr, int endIndex = 2147483647) const; + deprecated("3.5.1") native int LastIndexOf(String substr, int endIndex = 2147483647) const; native int RightIndexOf(String substr, int endIndex = 2147483647) const; native void ToUpper(); native void ToLower(); From 0c4e6f88a6feb95b22da28a6460c0099942ebb34 Mon Sep 17 00:00:00 2001 From: Rachael Alexanderson Date: Sat, 25 Aug 2018 16:46:09 -0400 Subject: [PATCH 04/16] - bump ZScript version number to 3.6.0, since GZDoom 3.5.1 updated it and that's not yet reflected in the master. --- src/version.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/version.h b/src/version.h index 9e36f91fe2..e014d54ed9 100644 --- a/src/version.h +++ b/src/version.h @@ -55,9 +55,9 @@ const char *GetVersionString(); #define RC_FILEVERSION 3,5,9999,0 #define RC_PRODUCTVERSION 3,5,9999,0 #define RC_PRODUCTVERSION2 VERSIONSTR -// These are for content versioning. The current state is '3.5'. +// These are for content versioning. #define VER_MAJOR 3 -#define VER_MINOR 5 +#define VER_MINOR 6 #define VER_REVISION 0 // Version identifier for network games. From 5b7d3c91f93cb82216ab971d46ad047487d2b535 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 25 Aug 2018 23:51:07 +0200 Subject: [PATCH 05/16] - defaulted constructors and assignment operators of several trivial types. --- src/d_player.h | 2 +- src/dobjgc.h | 17 ++-- src/doomtype.h | 5 +- src/gl/scene/gl_portal.cpp | 1 - src/hwrenderer/dynlights/hw_aabbtree.cpp | 1 - src/hwrenderer/textures/hw_material.cpp | 1 - src/name.h | 4 +- src/p_mobj.cpp | 1 - src/p_slopes.cpp | 2 +- src/s_sound.h | 25 +----- src/stats.h | 12 --- src/textures/texture.cpp | 2 - src/vectors.h | 110 +++++------------------ src/zstring.cpp | 6 +- 14 files changed, 40 insertions(+), 149 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index 1ae74de329..ee1418f57b 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -468,7 +468,7 @@ public: TObjPtr MUSINFOactor = nullptr; // For MUSINFO purposes int8_t MUSINFOtics = 0; - bool settings_controller = false; // Player can control game settings. + bool settings_controller = true; // Player can control game settings. int8_t crouching = 0; int8_t crouchdir = 0; diff --git a/src/dobjgc.h b/src/dobjgc.h index 269dffc155..41ae9ba238 100644 --- a/src/dobjgc.h +++ b/src/dobjgc.h @@ -167,22 +167,19 @@ class TObjPtr DObject *o; }; public: - TObjPtr() throw() - { - } + TObjPtr() = default; + TObjPtr(const TObjPtr &q) = default; + TObjPtr(T q) throw() : pp(q) { } - TObjPtr(const TObjPtr &q) throw() - : pp(q.pp) + T operator=(T q) { + pp = q; + return *this; } - T operator=(T q) throw() - { - return pp = q; - // The caller must now perform a write barrier. - } + operator T() throw() { return GC::ReadBarrier(pp); diff --git a/src/doomtype.h b/src/doomtype.h index 6e0ad770e5..04fae16ffd 100644 --- a/src/doomtype.h +++ b/src/doomtype.h @@ -103,7 +103,7 @@ enum struct PalEntry { - PalEntry () {} + PalEntry() = default; PalEntry (uint32_t argb) { d = argb; } operator uint32_t () const { return d; } void SetRGB(PalEntry other) @@ -146,6 +146,7 @@ struct PalEntry { return (d & 0xffffff) == 0xffffff; } + PalEntry &operator= (const PalEntry &other) = default; PalEntry &operator= (uint32_t other) { d = other; return *this; } PalEntry InverseColor() const { PalEntry nc; nc.a = a; nc.r = 255 - r; nc.g = 255 - g; nc.b = 255 - b; return nc; } #ifdef __BIG_ENDIAN__ @@ -205,7 +206,7 @@ class FTextureID friend void R_InitSpriteDefs(); public: - FTextureID() throw() {} + FTextureID() = default; bool isNull() const { return texnum == 0; } bool isValid() const { return texnum > 0; } bool Exists() const { return texnum >= 0; } diff --git a/src/gl/scene/gl_portal.cpp b/src/gl/scene/gl_portal.cpp index 62c2d21c7b..6f721a7dc8 100644 --- a/src/gl/scene/gl_portal.cpp +++ b/src/gl/scene/gl_portal.cpp @@ -399,7 +399,6 @@ void GLHorizonPortal::DrawContents(HWDrawInfo *hwdi) Clocker c(PortalAll); FMaterial * gltexture; - PalEntry color; player_t * player=&players[consoleplayer]; GLSectorPlane * sp = &origin->plane; auto &vp = di->Viewpoint; diff --git a/src/hwrenderer/dynlights/hw_aabbtree.cpp b/src/hwrenderer/dynlights/hw_aabbtree.cpp index 5df3c4ac78..3d2365bf94 100644 --- a/src/hwrenderer/dynlights/hw_aabbtree.cpp +++ b/src/hwrenderer/dynlights/hw_aabbtree.cpp @@ -228,7 +228,6 @@ int LevelAABBTree::GenerateTreeNode(int *lines, int num_lines, const FVector2 *c // Try sort at longest axis, then if that fails then the other one. // We place the sorted lines into work_buffer and then move the result back to the lines list when done. int left_count, right_count; - FVector2 axis; for (int attempt = 0; attempt < 2; attempt++) { // Find the sort plane for axis diff --git a/src/hwrenderer/textures/hw_material.cpp b/src/hwrenderer/textures/hw_material.cpp index 26276c7cfe..9058602040 100644 --- a/src/hwrenderer/textures/hw_material.cpp +++ b/src/hwrenderer/textures/hw_material.cpp @@ -319,7 +319,6 @@ void FMaterial::SetSpriteRect() bool FMaterial::TrimBorders(uint16_t *rect) { - PalEntry col; int w; int h; diff --git a/src/name.h b/src/name.h index 575e42d065..8dbb634446 100644 --- a/src/name.h +++ b/src/name.h @@ -46,7 +46,7 @@ class FString; class FName { public: - FName() = default;// : Index(0) {} + FName() = default; FName (const char *text) { Index = NameData.FindName (text, false); } FName (const char *text, bool noCreate) { Index = NameData.FindName (text, noCreate); } FName (const char *text, size_t textlen, bool noCreate) { Index = NameData.FindName (text, textlen, noCreate); } @@ -63,7 +63,7 @@ public: FName &operator = (const char *text) { Index = NameData.FindName (text, false); return *this; } FName &operator = (const FString &text); - FName &operator = (const FName &other) { Index = other.Index; return *this; } + FName &operator = (const FName &other) = default; FName &operator = (ENamedName index) { Index = index; return *this; } int SetName (const char *text, bool noCreate=false) { return Index = NameData.FindName (text, noCreate); } diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index 16396f29c0..070745df41 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -2345,7 +2345,6 @@ double P_XYMovement (AActor *mo, DVector2 scroll) { static int pushtime = 0; bool bForceSlide = !scroll.isZero(); - DAngle Angle; DVector2 ptry; player_t *player; DVector2 move; diff --git a/src/p_slopes.cpp b/src/p_slopes.cpp index cec49003d2..de5eeab7f6 100644 --- a/src/p_slopes.cpp +++ b/src/p_slopes.cpp @@ -306,7 +306,7 @@ static void P_SetSlopesFromVertexHeights(FMapThing *firstmt, FMapThing *lastmt, { if (sec.Lines.Size() != 3) continue; // only works with triangular sectors - DVector3 vt1, vt2, vt3, cross; + DVector3 vt1, vt2, vt3; DVector3 vec1, vec2; int vi1, vi2, vi3; diff --git a/src/s_sound.h b/src/s_sound.h index 3a64ba76f3..1d42cb2aed 100644 --- a/src/s_sound.h +++ b/src/s_sound.h @@ -114,15 +114,8 @@ public: { ID = S_FindSound(name.GetChars()); } - FSoundID(const FSoundID &other) - { - ID = other.ID; - } - FSoundID &operator=(const FSoundID &other) - { - ID = other.ID; - return *this; - } + FSoundID(const FSoundID &other) = default; + FSoundID &operator=(const FSoundID &other) = default; FSoundID &operator=(const char *name) { ID = S_FindSound(name); @@ -168,19 +161,7 @@ class FSoundIDNoInit : public FSoundID { public: FSoundIDNoInit() : FSoundID(NoInit) {} - - FSoundID &operator=(const FSoundID &other) - { - return FSoundID::operator=(other); - } - FSoundID &operator=(const char *name) - { - return FSoundID::operator=(name); - } - FSoundID &operator=(const FString &name) - { - return FSoundID::operator=(name); - } + using FSoundID::operator=; }; extern FRolloffInfo S_Rolloff; diff --git a/src/stats.h b/src/stats.h index 4eefdc9a9f..ec963f5342 100644 --- a/src/stats.h +++ b/src/stats.h @@ -57,12 +57,6 @@ public: class cycle_t { public: - cycle_t &operator= (const cycle_t &o) - { - Sec = o.Sec; - return *this; - } - void Reset() { Sec = 0; @@ -153,12 +147,6 @@ inline uint64_t rdtsc() class cycle_t { public: - cycle_t &operator= (const cycle_t &o) - { - Counter = o.Counter; - return *this; - } - void Reset() { Counter = 0; diff --git a/src/textures/texture.cpp b/src/textures/texture.cpp index bd259b8af5..88ae5b1763 100644 --- a/src/textures/texture.cpp +++ b/src/textures/texture.cpp @@ -961,8 +961,6 @@ PalEntry FTexture::averageColor(const uint32_t *data, int size, int maxout) PalEntry FTexture::GetSkyCapColor(bool bottom) { - PalEntry col; - if (!bSWSkyColorDone) { bSWSkyColorDone = true; diff --git a/src/vectors.h b/src/vectors.h index 65a30345fe..002062f8c6 100644 --- a/src/vectors.h +++ b/src/vectors.h @@ -66,19 +66,14 @@ struct TVector2 { vec_t X, Y; - TVector2 () - { - } + TVector2() = default; TVector2 (vec_t a, vec_t b) : X(a), Y(b) { } - TVector2 (const TVector2 &other) - : X(other.X), Y(other.Y) - { - } + TVector2(const TVector2 &other) = default; TVector2 (const TVector3 &other) // Copy the X and Y from the 3D vector and discard the Z : X(other.X), Y(other.Y) @@ -95,18 +90,7 @@ struct TVector2 return X == 0 && Y == 0; } - TVector2 &operator= (const TVector2 &other) - { - // This might seem backwards, but this helps produce smaller code when a newly - // created vector is assigned, because the components can just be popped off - // the FPU stack in order without the need for fxch. For platforms with a - // more sensible registered-based FPU, of course, the order doesn't matter. - // (And, yes, I know fxch can improve performance in the right circumstances, - // but this isn't one of those times. Here, it's little more than a no-op that - // makes the exe 2 bytes larger whenever you assign one vector to another.) - Y = other.Y, X = other.X; - return *this; - } + TVector2 &operator= (const TVector2 &other) = default; // Access X and Y as an array vec_t &operator[] (int index) @@ -317,9 +301,7 @@ struct TVector3 vec_t X, Y, Z; - TVector3 () - { - } + TVector3() = default; TVector3 (vec_t a, vec_t b, vec_t c) : X(a), Y(b), Z(c) @@ -331,10 +313,7 @@ struct TVector3 { } - TVector3 (const TVector3 &other) - : X(other.X), Y(other.Y), Z(other.Z) - { - } + TVector3(const TVector3 &other) = default; TVector3 (const Vector2 &xy, vec_t z) : X(xy.X), Y(xy.Y), Z(z) @@ -353,11 +332,7 @@ struct TVector3 return X == 0 && Y == 0 && Z == 0; } - TVector3 &operator= (const TVector3 &other) - { - Z = other.Z, Y = other.Y, X = other.X; - return *this; - } + TVector3 &operator= (const TVector3 &other) = default; // Access X and Y and Z as an array vec_t &operator[] (int index) @@ -546,13 +521,12 @@ struct TVector3 { right = { 0.f, 0.f, 1.f }; } - - if (major == 1 || (major == 2 && n[2] > 0.f)) + else if (major == 1 || (major == 2 && n[2] > 0.f)) { right = { 1.f, 0.f, 0.f }; } - - if (major == 2 && n[2] < 0.0f) + // Unconditional to ease static analysis + else // major == 2 && n[2] <= 0.0f { right = { -1.f, 0.f, 0.f }; } @@ -662,9 +636,7 @@ struct TVector4 vec_t X, Y, Z, W; - TVector4() - { - } + TVector4() = default; TVector4(vec_t a, vec_t b, vec_t c, vec_t d) : X(a), Y(b), Z(c), W(d) @@ -676,10 +648,7 @@ struct TVector4 { } - TVector4(const TVector4 &other) - : X(other.X), Y(other.Y), Z(other.Z), W(other.W) - { - } + TVector4(const TVector4 &other) = default; TVector4(const Vector3 &xyz, vec_t w) : X(xyz.X), Y(xyz.Y), Z(xyz.Z), W(w) @@ -696,11 +665,7 @@ struct TVector4 return X == 0 && Y == 0 && Z == 0 && W == 0; } - TVector4 &operator= (const TVector4 &other) - { - W = other.W, Z = other.Z, Y = other.Y, X = other.X; - return *this; - } + TVector4 &operator= (const TVector4 &other) = default; // Access X and Y and Z as an array vec_t &operator[] (int index) @@ -939,16 +904,8 @@ struct TMatrix3x3 vec_t Cells[3][3]; - TMatrix3x3() - { - } - - TMatrix3x3(const TMatrix3x3 &other) - { - (*this)[0] = other[0]; - (*this)[1] = other[1]; - (*this)[2] = other[2]; - } + TMatrix3x3() = default; + TMatrix3x3(const TMatrix3x3 &other) = default; TMatrix3x3(const Vector3 &row1, const Vector3 &row2, const Vector3 &row3) { @@ -1151,32 +1108,15 @@ struct TAngle TAngle &operator= (long other) = delete; TAngle &operator= (unsigned long other) = delete; - TAngle () - { - } + TAngle() = default; TAngle (vec_t amt) : Degrees(amt) { } - /* - TAngle (int amt) - : Degrees(vec_t(amt)) - { - } - */ - - TAngle (const TAngle &other) - : Degrees(other.Degrees) - { - } - - TAngle &operator= (const TAngle &other) - { - Degrees = other.Degrees; - return *this; - } + TAngle(const TAngle &other) = default; + TAngle &operator= (const TAngle &other) = default; TAngle &operator= (double other) { @@ -1491,25 +1431,15 @@ struct TRotator Angle Roll; // rotation about the forward axis. Angle CamRoll; // Roll specific to actor cameras. Used by quakes. - TRotator () - { - } + TRotator() = default; TRotator (const Angle &p, const Angle &y, const Angle &r) : Pitch(p), Yaw(y), Roll(r) { } - TRotator (const TRotator &other) - : Pitch(other.Pitch), Yaw(other.Yaw), Roll(other.Roll) - { - } - - TRotator &operator= (const TRotator &other) - { - Roll = other.Roll, Yaw = other.Yaw, Pitch = other.Pitch; - return *this; - } + TRotator(const TRotator &other) = default; + TRotator &operator= (const TRotator &other) = default; // Access angles as an array Angle &operator[] (int index) diff --git a/src/zstring.cpp b/src/zstring.cpp index 5e57df45bd..ff17fd607e 100644 --- a/src/zstring.cpp +++ b/src/zstring.cpp @@ -580,7 +580,7 @@ long FString::LastIndexOfAny (const char *charset, long endIndex) const long FString::LastIndexOf (const FString &substr) const { - return LastIndexOf(substr.Chars, Len() - substr.Len(), substr.Len()); + return LastIndexOf(substr.Chars, long(Len() - substr.Len()), substr.Len()); } long FString::LastIndexOf (const FString &substr, long endIndex) const @@ -590,7 +590,7 @@ long FString::LastIndexOf (const FString &substr, long endIndex) const long FString::LastIndexOf (const char *substr) const { - return LastIndexOf(substr, Len() - strlen(substr), strlen(substr)); + return LastIndexOf(substr, long(Len() - strlen(substr)), strlen(substr)); } long FString::LastIndexOf (const char *substr, long endIndex) const @@ -602,7 +602,7 @@ long FString::LastIndexOf (const char *substr, long endIndex, size_t substrlen) { if ((size_t)endIndex + substrlen > Len()) { - endIndex = Len() - substrlen; + endIndex = long(Len() - substrlen); } while (endIndex >= 0) { From 4d3249496a65ddf74ff68aa483e9452b6b8263b9 Mon Sep 17 00:00:00 2001 From: Rachael Alexanderson Date: Sat, 25 Aug 2018 18:23:49 -0400 Subject: [PATCH 06/16] - fixed: sound from poly objects through portals will now propegate properly --- src/s_sound.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/s_sound.cpp b/src/s_sound.cpp index 6b6e4b74f4..30e6f918e7 100644 --- a/src/s_sound.cpp +++ b/src/s_sound.cpp @@ -776,8 +776,8 @@ static void CalcPosVel(int type, const AActor *actor, const sector_t *sector, { DVector2 disp = level.Displacements.getOffset(pgroup, poly->CenterSubsector->sector->PortalGroup); CalcPolyobjSoundOrg(listenpos + disp, poly, *pos); - pos->X += (float)disp.X; - pos->Z += (float)disp.Y; + pos->X -= (float)disp.X; + pos->Z -= (float)disp.Y; } break; } From aaee655ebaab2e3604c7ff57ad7ccc4982684d25 Mon Sep 17 00:00:00 2001 From: Braden Obrzut Date: Tue, 28 Aug 2018 20:07:55 -0400 Subject: [PATCH 07/16] - Added paths for all games on Steam for Linux since they now offer the ability to download all games for Proton/Wine. - Check ~/.steam on Linux for the config since either they moved it at some point or my setup changed and that's where it is for me now. --- src/posix/i_steam.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/posix/i_steam.cpp b/src/posix/i_steam.cpp index 5764a0daca..29dc9b845e 100644 --- a/src/posix/i_steam.cpp +++ b/src/posix/i_steam.cpp @@ -153,13 +153,13 @@ static struct SteamAppInfo const int AppID; } AppInfo[] = { - /*{"doom 2/base", 2300}, - {"final doom/base", 2290}, - {"heretic shadow of the serpent riders/base", 2390}, - {"hexen/base", 2360}, - {"hexen deathkings of the dark citadel/base", 2370}, - {"ultimate doom/base", 2280}, - {"DOOM 3 BFG Edition/base/wads", 208200},*/ + {"Doom 2/base", 2300}, + {"Final Doom/base", 2290}, + {"Heretic Shadow of the Serpent Riders/base", 2390}, + {"Hexen/base", 2360}, + {"Hexen Deathkings of the Dark Citadel/base", 2370}, + {"Ultimate Doom/base", 2280}, + {"DOOM 3 BFG Edition/base/wads", 208200}, {"Strife", 317040} }; @@ -190,7 +190,13 @@ TArray I_GetSteamPath() if(home != NULL && *home != '\0') { FString regPath; - regPath.Format("%s/.local/share/Steam/config/config.vdf", home); + regPath.Format("%s/.steam/config/config.vdf", home); + // [BL] The config seems to have moved from the more modern .local to + // .steam at some point. Not sure if it's just my setup so I guess we + // can fall back on it? + if(!FileExists(regPath)) + regPath.Format("%s/.local/share/Steam/config/config.vdf", home); + try { SteamInstallFolders = ParseSteamRegistry(regPath); From e428f6948ec85d627293e550d27bbc526faa2b01 Mon Sep 17 00:00:00 2001 From: Rachael Alexanderson Date: Wed, 29 Aug 2018 19:13:44 -0400 Subject: [PATCH 08/16] - add menu sliders for `vr_ipd` and `vr_screendist` --- wadsrc/static/language.enu | 2 ++ wadsrc/static/menudef.txt | 2 ++ 2 files changed, 4 insertions(+) diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu index c843f5fcdc..3e20e1e034 100644 --- a/wadsrc/static/language.enu +++ b/wadsrc/static/language.enu @@ -2820,6 +2820,8 @@ GLPREFMNU_DITHER = "Dither output"; GLPREFMNU_PALTONEMAPORDER = "Tonemap Palette Order"; GLPREFMNU_PALTONEMAPPOWER = "Tonemap Palette Exponent"; GLPREFMNU_SWLMBANDED = "Banded SW Lightmode"; +GLPREFMNU_VRIPD = "Distance Between Your Eyes"; +GLPREFMNU_VRSCREENDIST = "Distance From Your Screen"; // Option Values OPTVAL_SMART = "Smart"; diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt index 218182c07e..d8d3c9a4c3 100644 --- a/wadsrc/static/menudef.txt +++ b/wadsrc/static/menudef.txt @@ -2290,6 +2290,8 @@ OptionMenu "OpenGLOptions" protected { Option "$GLPREFMNU_VRQUADSTEREO", vr_enable_quadbuffered, "OnOff" } + Slider "$GLPREFMNU_VRIPD", vr_ipd, 0.041, 0.073, 0.001, 3 + Slider "$GLPREFMNU_VRSCREENDIST", vr_screendist, 0.2, 10.0, 0.1, 1 StaticText " " Option "$GLPREFMNU_MULTISAMPLE", gl_multisample, "Multisample" Option "$GLPREFMNU_TONEMAP", gl_tonemap, "TonemapModes" From ea81ab4097ec74624b4b5ca1aed9665deef677e8 Mon Sep 17 00:00:00 2001 From: Jonathan Russell Date: Thu, 30 Aug 2018 16:49:10 +0100 Subject: [PATCH 09/16] - added Screen.DrawThickLine for drawing lines with thickness --- src/v_2ddrawer.cpp | 40 ++++++++++++++++++++++++++++++++++ src/v_2ddrawer.h | 1 + src/v_draw.cpp | 18 +++++++++++++++ src/v_video.h | 3 +++ wadsrc/static/zscript/base.txt | 1 + 5 files changed, 63 insertions(+) diff --git a/src/v_2ddrawer.cpp b/src/v_2ddrawer.cpp index ad5effb30a..a09ecd5fd8 100644 --- a/src/v_2ddrawer.cpp +++ b/src/v_2ddrawer.cpp @@ -594,6 +594,46 @@ void F2DDrawer::AddLine(int x1, int y1, int x2, int y2, int palcolor, uint32_t c // //========================================================================== +void F2DDrawer::AddThickLine(int x1, int y1, int x2, int y2, double thickness, uint32_t color) +{ + PalEntry p = (PalEntry)color; + + DVector2 point0(x1, y1); + DVector2 point1(x2, y2); + + DVector2 delta = point1 - point0; + DVector2 perp(-delta.Y, delta.X); + perp.MakeUnit(); + perp *= thickness / 2; + + DVector2 corner0 = point0 + perp; + DVector2 corner1 = point0 - perp; + DVector2 corner2 = point1 + perp; + DVector2 corner3 = point1 - perp; + + RenderCommand dg; + + dg.mType = DrawTypeTriangles; + dg.mVertCount = 4; + dg.mVertIndex = (int)mVertices.Reserve(4); + dg.mRenderStyle = LegacyRenderStyles[STYLE_Translucent]; + auto ptr = &mVertices[dg.mVertIndex]; + ptr->Set(corner0.X, corner0.Y, 0, 0, 0, p); ptr++; + ptr->Set(corner1.X, corner1.Y, 0, 0, 0, p); ptr++; + ptr->Set(corner2.X, corner2.Y, 0, 0, 0, p); ptr++; + ptr->Set(corner3.X, corner3.Y, 0, 0, 0, p); ptr++; + dg.mIndexIndex = mIndices.Size(); + dg.mIndexCount += 6; + AddIndices(dg.mVertIndex, 6, 0, 1, 2, 1, 3, 2); + AddCommand(&dg); +} + +//========================================================================== +// +// +// +//========================================================================== + void F2DDrawer::AddPixel(int x1, int y1, int palcolor, uint32_t color) { PalEntry p = color ? (PalEntry)color : GPalette.BaseColors[palcolor]; diff --git a/src/v_2ddrawer.h b/src/v_2ddrawer.h index ec88dc9083..f60665e9e6 100644 --- a/src/v_2ddrawer.h +++ b/src/v_2ddrawer.h @@ -149,6 +149,7 @@ public: void AddLine(int x1, int y1, int x2, int y2, int palcolor, uint32_t color); + void AddThickLine(int x1, int y1, int x2, int y2, double thickness, uint32_t color); void AddPixel(int x1, int y1, int palcolor, uint32_t color); void Clear(); diff --git a/src/v_draw.cpp b/src/v_draw.cpp index e3ebe63bcd..765b200039 100644 --- a/src/v_draw.cpp +++ b/src/v_draw.cpp @@ -1088,6 +1088,24 @@ DEFINE_ACTION_FUNCTION(_Screen, DrawLine) return 0; } +void DFrameBuffer::DrawThickLine(int x0, int y0, int x1, int y1, double thickness, uint32_t realcolor) { + m2DDrawer.AddThickLine(x0, y0, x1, y1, thickness, realcolor); +} + +DEFINE_ACTION_FUNCTION(_Screen, DrawThickLine) +{ + PARAM_PROLOGUE; + PARAM_INT(x0); + PARAM_INT(y0); + PARAM_INT(x1); + PARAM_INT(y1); + PARAM_FLOAT(thickness); + PARAM_INT(color); + if (!screen->HasBegun2D()) ThrowAbortException(X_OTHER, "Attempt to draw to screen outside a draw function"); + screen->DrawThickLine(x0, y0, x1, y1, thickness, color); + return 0; +} + //========================================================================== // // Draw a single pixel diff --git a/src/v_video.h b/src/v_video.h index 1e456e6b8a..ec0e43f3d3 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -498,6 +498,9 @@ public: // Draws a line void DrawLine(int x0, int y0, int x1, int y1, int palColor, uint32_t realcolor); + // Draws a line with thickness + void DrawThickLine(int x0, int y0, int x1, int y1, double thickness, uint32_t realcolor); + // Draws a single pixel void DrawPixel(int x, int y, int palcolor, uint32_t rgbcolor); diff --git a/wadsrc/static/zscript/base.txt b/wadsrc/static/zscript/base.txt index f310e29007..e648febd11 100644 --- a/wadsrc/static/zscript/base.txt +++ b/wadsrc/static/zscript/base.txt @@ -194,6 +194,7 @@ struct Screen native native static vararg void DrawChar(Font font, int normalcolor, double x, double y, int character, ...); native static vararg void DrawText(Font font, int normalcolor, double x, double y, String text, ...); native static void DrawLine(int x0, int y0, int x1, int y1, Color color); + native static void DrawThickLine(int x0, int y0, int x1, int y1, double thickness, Color color); native static void DrawFrame(int x, int y, int w, int h); native static Vector2, Vector2 VirtualToRealCoords(Vector2 pos, Vector2 size, Vector2 vsize, bool vbottom=false, bool handleaspect=true); native static double GetAspectRatio(); From 050d72724f049bed04b74a1c8f2ee7014248ecc7 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Fri, 31 Aug 2018 10:42:53 +0300 Subject: [PATCH 10/16] - fixed: always initialize active colors in special font https://forum.zdoom.org/viewtopic.php?t=61859 --- src/v_font.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/v_font.cpp b/src/v_font.cpp index d9e624270e..0a7d742151 100644 --- a/src/v_font.cpp +++ b/src/v_font.cpp @@ -1995,7 +1995,14 @@ FSpecialFont::FSpecialFont (const char *name, int first, int count, FTexture **l FixXMoves(); - if (!noTranslate) LoadTranslations(); + if (noTranslate) + { + ActiveColors = 0; + } + else + { + LoadTranslations(); + } delete[] charlumps; } From c8e55fed4628eeb0bf0010ce0cc9496a2b18adee Mon Sep 17 00:00:00 2001 From: ZippeyKeys12 Date: Thu, 30 Aug 2018 16:39:11 -0500 Subject: [PATCH 11/16] Moved FConfigFile over to FileReader/FileWriter Don't use new operator, use value instead --- src/configfile.cpp | 26 +++++++++++++------------- src/configfile.h | 3 ++- src/gameconfigfile.cpp | 4 ++-- src/gameconfigfile.h | 2 +- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/configfile.cpp b/src/configfile.cpp index 2319f28226..b841e8d3da 100644 --- a/src/configfile.cpp +++ b/src/configfile.cpp @@ -38,6 +38,7 @@ #include "doomtype.h" #include "configfile.h" +#include "files.h" #include "m_random.h" #define READBUFFERSIZE 256 @@ -601,17 +602,16 @@ FConfigFile::FConfigEntry *FConfigFile::NewConfigEntry ( void FConfigFile::LoadConfigFile () { - FILE *file = fopen (PathName, "r"); + FileReader file; bool succ; FileExisted = false; - if (file == NULL) + if (!file.OpenFile (PathName)) { return; } - succ = ReadConfig (file); - fclose (file); + succ = ReadConfig (&file); FileExisted = succ; } @@ -787,7 +787,7 @@ FConfigFile::FConfigEntry *FConfigFile::ReadMultiLineValue(void *file, FConfigSe char *FConfigFile::ReadLine (char *string, int n, void *file) const { - return fgets (string, n, (FILE *)file); + return ((FileReader *)file)->Gets (string, n); } //==================================================================== @@ -805,7 +805,7 @@ bool FConfigFile::WriteConfigFile () const return true; } - FILE *file = fopen (PathName, "w"); + FileWriter *file = FileWriter::Open (PathName); FConfigSection *section; FConfigEntry *entry; @@ -820,27 +820,27 @@ bool FConfigFile::WriteConfigFile () const entry = section->RootEntry; if (section->Note.IsNotEmpty()) { - fputs (section->Note.GetChars(), file); + file->Write (section->Note.GetChars(), section->Note.Len()); } - fprintf (file, "[%s]\n", section->SectionName.GetChars()); + file->Printf ("[%s]\n", section->SectionName.GetChars()); while (entry != NULL) { if (strpbrk(entry->Value, "\r\n") == NULL) { // Single-line value - fprintf (file, "%s=%s\n", entry->Key, entry->Value); + file->Printf ("%s=%s\n", entry->Key, entry->Value); } else { // Multi-line value const char *endtag = GenerateEndTag(entry->Value); - fprintf (file, "%s=<<<%s\n%s\n>>>%s\n", entry->Key, + file->Printf ("%s=<<<%s\n%s\n>>>%s\n", entry->Key, endtag, entry->Value, endtag); } entry = entry->Next; } section = section->Next; - fputs ("\n", file); + file->Write ("\n", 1); } - fclose (file); + delete file; return true; } @@ -890,7 +890,7 @@ const char *FConfigFile::GenerateEndTag(const char *value) // //==================================================================== -void FConfigFile::WriteCommentHeader (FILE *file) const +void FConfigFile::WriteCommentHeader (FileWriter *file) const { } diff --git a/src/configfile.h b/src/configfile.h index a48ddfad61..ab68664306 100644 --- a/src/configfile.h +++ b/src/configfile.h @@ -35,6 +35,7 @@ #define __CONFIGFILE_H__ #include +#include "files.h" #include "zstring.h" class FConfigFile @@ -73,7 +74,7 @@ public: bool WriteConfigFile () const; protected: - virtual void WriteCommentHeader (FILE *file) const; + virtual void WriteCommentHeader (FileWriter *file) const; virtual char *ReadLine (char *string, int n, void *file) const; bool ReadConfig (void *file); diff --git a/src/gameconfigfile.cpp b/src/gameconfigfile.cpp index cb47c3597f..37abce9d7f 100644 --- a/src/gameconfigfile.cpp +++ b/src/gameconfigfile.cpp @@ -167,9 +167,9 @@ FGameConfigFile::~FGameConfigFile () { } -void FGameConfigFile::WriteCommentHeader (FILE *file) const +void FGameConfigFile::WriteCommentHeader (FileWriter *file) const { - fprintf (file, "# This file was generated by " GAMENAME " %s on %s\n", GetVersionString(), myasctime()); + file->Printf ("# This file was generated by " GAMENAME " %s on %s\n", GetVersionString(), myasctime()); } void FGameConfigFile::DoAutoloadSetup (FIWadManager *iwad_man) diff --git a/src/gameconfigfile.h b/src/gameconfigfile.h index 1ba066d058..9ef4684898 100644 --- a/src/gameconfigfile.h +++ b/src/gameconfigfile.h @@ -58,7 +58,7 @@ public: void ReadNetVars (); protected: - void WriteCommentHeader (FILE *file) const; + void WriteCommentHeader (FileWriter *file) const; void CreateStandardAutoExec (const char *section, bool start); private: From 7c4dd9408da7df674ee6b26d25c203bd3e901599 Mon Sep 17 00:00:00 2001 From: Rachael Alexanderson Date: Fri, 31 Aug 2018 18:01:51 -0400 Subject: [PATCH 12/16] - update french language translation from Tapwave --- wadsrc/static/language.fr | 608 +++++++++++++++++++++----------------- 1 file changed, 335 insertions(+), 273 deletions(-) diff --git a/wadsrc/static/language.fr b/wadsrc/static/language.fr index fa49cc07eb..181c9e0dbc 100644 --- a/wadsrc/static/language.fr +++ b/wadsrc/static/language.fr @@ -34,7 +34,7 @@ QUITMSG10 = "Tirez vous de là et retournez\nà vos programmes ennuyeux."; QUITMSG11 = "Si j'étais votre patron, je vous\n collerai un Deathmatch dans la minute!"; QUITMSG12 = "Regardez l'ami. Vous partez maintenant\net vous pouvez oublier votre score!"; QUITMSG13 = "Allez, partez. Quand vous reviendrez\nje vous attendrai avec une batte."; -QUITMSG14 = "Vous êtes chanceux que je ne vous \ncolle pas une parce que vous pensez partir."; +QUITMSG14 = "Vous êtes chanceux que je ne vous en \ncolle pas une parce que vous pensez partir."; // Quit Strife messages QUITMSG15 = "Où allez vous?\nQue va devenir la rébellion?"; @@ -206,7 +206,7 @@ HUSTR_23 = "NIVEAU 23: Une Tonne-eau de plaisir"; HUSTR_24 = "NIVEAU 24: Le Gouffre"; HUSTR_25 = "NIVEAU 25: Chutes de Sang"; HUSTR_26 = "NIVEAU 26: Les Mines Abandonnées"; -HUSTR_27 = "NIVEAU 27: Monstrueuse Résiden,ce"; +HUSTR_27 = "NIVEAU 27: Monstrueuse Résidence"; HUSTR_28 = "NIVEAU 28: Le Monde Spirituel"; HUSTR_29 = "NIVEAU 29: La Limite"; HUSTR_30 = "NIVEAU 30: L'Icône du Péché"; @@ -257,7 +257,7 @@ PHUSTR_26 = "NIVEAU 26: Bunker"; PHUSTR_27 = "NIVEAU 27: Anti-Christ"; PHUSTR_28 = "NIVEAU 28: Les Egouts"; PHUSTR_29 = "NIVEAU 29: Odysée de Bruits"; -PHUSTR_30 = "NIVEAU 30: La Porte des Enferts"; +PHUSTR_30 = "NIVEAU 30: La Porte des Enfers"; PHUSTR_31 = "NIVEAU 31: Cyber-Antre"; PHUSTR_32 = "NIVEAU 32: GO 2 IT"; @@ -348,10 +348,10 @@ TXT_DEFAULTPICKUPMSG = "Quelque chose récupéré."; // F_Finale.C // E1TEXT = - "APRES AVOIR VAINCU LES GROS MECHANTS\n" + "APRES AVOIR VAINCU CES SALES PESTES\n" "ET NETTOYE LA BASE LUNAIRE, VOUS AVEZ\n" "GAGNE, NON? PAS VRAI? OU EST DONC VOTRE\n" - " RECOMPENSE ET VOTRE BILLET DE\n" + "RECOMPENSE ET VOTRE BILLET DE\n" "RETOUR? QU'EST-QUE CA VEUT DIRE? CE" "N'EST PAS LA FIN ESPEREE!\n" "\n" @@ -381,7 +381,7 @@ E2TEXT = "LA SURFACE DE L'ENFER.\n" "\n" "VOICI MAINTENANT LE CHAPITRE FINAL DE\n" - "DOOM! -- L'ENFER."; + "DOOM! -- INFERNO."; E3TEXT = "LE DEMON ARACHNEEN ET REPUGNANT\n" @@ -399,28 +399,28 @@ E3TEXT = "VOUS VOUS DEMANDEZ CE QUI S'EST PASSE\n" "SUR TERRE PENDANT QUE VOUS AVEZ\n" "COMBATTU LE DEMON. HEUREUSEMENT,\n" - "AUCUN GERME DU MAL N'A FRANCHI\n" + "QU'AUCUN GERME DU MAL N'A FRANCHI\n" "CETTE PORTE AVEC VOUS..."; E4TEXT = - "l'araignee cerveau doit avoir envoye\n" - "ses legions de creatures de l' enfer \n" + "l'araignee cerveau doit avoir envoyé\n" + "ses légions de créatures de l'enfer \n" "avant votre confrontation finale avec\n" - "cette terrible bete de l'enfer.\n" + "cette terrible bête infernale.\n" "Mais vous avancez et mettez de suite\n" "la damnation eternelle et souffrances\n" - "a la meute comme un hero le fairait\n" - "face de quelque chose de si malefique.\n" - "D'ailleurs,quelqu'un va devoir payer \n" - "pour ce qui est arrive a margherite, \n" + "a la meute comme un héros le fairait\n" + "face à quelque chose de si maléfique.\n" + "D'ailleurs, quelqu'un va devoir payer \n" + "pour ce qui est arrivé a Daisy, \n" "votre lapin domestique.\n" "\n" "mais maintenant, vous voyez devant vous \n" "encore plus de nouvelles souffrances \n" - "potencielles et de chairs broyees\n" - "comme une horde de demons devunus \n" - "vindicatifs parmi nos villes.\n\n" - "prochain arret,l'enfer sur terre!"; + "potentielles et de chairs broyees\n" + "comme une horde de demons déversant \n" + "la mort parmi nos villes.\n\n" + "prochain arrêt, l'enfer sur terre!"; // after level 6, put this: @@ -479,7 +479,7 @@ C4TEXT = "TAILLE INCROYABLE S'EFFONDRE DEVANT\n" "VOUS LORSQUE VOUS TIREZ UNE SALVE DE\n" "ROQUETTES DANS SON CERVEAU. LE MONSTRE\n" - "SE RATATINE, SES MEMBRES DECHIQUETES\n" + "S'ECRASE, SES MEMBRES DECHIQUETES\n" "SE REPANDANT SUR DES CENTAINES DE\n" "KILOMETRES A LA SURFACE DE L'ENFER.\n" "\n" @@ -508,151 +508,170 @@ C6TEXT = "MIEUX DE FONCER DANS CELUI-LA!\n"; P1TEXT = - "Vous jubilez sur la carcass brulante \n" - "du Guardien. Avec sa mort, vous avez \n" - "arrache l'accelerateur des griffes \n" - "fetides de l'enfer. Vous vous reposez \n" - "et regardez aux alentours de l'endroit. \n" - "Mince! Il etait suppose qu'il y ait\n" - "au mois un prototype fonctionnant, \n" - "mais vous ne le voyez pas. les demons \n" - "l'on surement pris. \n\n" - "Vous devez trouver le prototype ,ou \n" - "tous vos combats auront ete vains. \n" - "continez a avancer, vous battre, tuer. \n" - "Oh oui ,a vivre ,aussi."; + "Vous jubilez sur la carcasse brûlante \n" + "du Gardien. Avec sa mort, vous avez \n" + "arraché l'accélerateur des griffes \n" + "fétides de l'enfer. Vous vous reposez \n" + "et regardez aux alentours du lieu. \n" + "Mince! Il était supposé qu'il y ait\n" + "au mois un prototype en fonctionnement, \n" + "mais vous ne le trouvez pas. les démons \n" + "l'on sûrement pris avec eux. \n\n" + "Vous devez trouver le prototype, ou \n" + "tous vos combats auront été vains. \n" + "continez à avancer, à vous battre, à tuer. \n" + "Oh oui, à vivre, aussi."; P2TEXT = - "Meme le labyrinthe mortifere des \n" - "archi-infames ne vous arreta pas,\n" - "et vous arrivez au prototype \n" - "d'accelerateur qui est proche \n" - "desactive efficacement et pour toujours.\n" + "Même le labyrinthe mortifère des \n" + "arche-viles ne vous a pas arrêté,\n" + "et vous obtenez le prototype \n" + "d'accelerateur et parvenez à le \n" + "désactiver efficacement et pour toujours.\n" "\n" - "Vous etes doue pour ces choses la."; + "Vous êtes doué pour ces choses là."; P3TEXT = - "Vous vous etes fraye votre chemin a \n" + "Vous vous êtes frayé votre chemin à \n" "travers le coeur d'une ruche de \n" "diables. Il est temps pour une mission\n" "de recherche et destruction visant le \n" - "guardien de la porte ,avec sa \n" - "progeniture repugnante descendant en \n" + "gardien de la porte, avec sa \n" + "progéniture répugnante descendant en \n" "cascade sur terre. Oui ,il est cruel. \n" "Mais vous savez qui est pire!\n\n" - "Sourriant malefiquement, vous verifiez \n" - "l'equipement et vous vous preparez a\n" + "Souriant malicieusement, vous vérifiez \n" + "l'equipement et vous vous préparez a\n" "faire voir a ce fumier un petit enfer\n" - "fait de votre propre main!"; + "fait de vos propres mains!"; P4TEXT = - "La face malefique du gardien de la porte \n" - "est eparpillee aux alentours. Comme son \n" - "corps en lambeaux vacille ,un portail \n" - "inverse se forme et aspire les eclat du \n" - "dernier prototype d'accelerateur, sans \n" + "La face maléfique du gardien de la porte \n" + "est éparpillee aux alentours. Comme son \n" + "corps en lambeaux vacille, un portail \n" + "inverse se forme et aspire les éclats du \n" + "dernier prototype d'accélerateur, sans \n" "parler des quelques demons restants.\n" - "Vous avez termine. L'enfer a fini de\n" - "liberer des mort-vivants au lieu de \n" + "Vous avez gagné. L'enfer a fini de\n" + "libérer des mort-vivants au lieu de \n" "gens bien vivants.\n" "Souvenez vous de dire a vos enfants de\n" "mettre un lance-roquette dans votre\n" "cercueil. Si vous allez en enfer,\n" "quand vous serez mort, vous en aurez \n" - "besoin ,pour un nettoyage definitif..."; + "besoin, pour un nettoyage définitif..."; P5TEXT = - "Vous avez trouver notre second niveau\n" + "Vous avez trouvé le deuxième niveau\n" "le plus difficile que nous avons.\n" - "J'espere que vous avez sauvegarde\n" - "au deux precendants.\n" - "Sinon, soyez prets a mourrir souvent.\n" - "Pour maitre marine seulement."; + "J'espère que vous avez sauvegardé\n" + "lors du précédant...\n" + "Sinon, soyez prêt a mourir souvent.\n" + "Pour les pros seulement."; P6TEXT = - "peut etre, vous demandiez vous quel\n" - "ETAIT le niveau le plus dure, que nous\n" - "avions prepare pour vous? Maintenant,\n" + "Alors, vous vous demandiez quel\n" + "était le niveau le plus dur que nous\n" + "avions préparé pour vous? Maintenant,\n" "vous savez. Nul n'en sortira vivant.\n"; T1TEXT = - "Vous vous etes fraye un chemin vers la\n" - "sortie des labos experimentaux infestes.\n" - "Il semble que l'UAC les a encore vides. \n" - "Avec leur chiffres d'affaires eleve,\n" - "ca doit etre dur pour la pauvre vieille\n" - "UAC d' acheter des societes \n" - "d'assurance vie ,de nos jours...\n" + "Vous vous êtes frayé un chemin vers la\n" + "sortie des labos expérimentaux infestés.\n" + "Il semble que l'UAC les a encore vidés. \n" + "Avec leur chiffres d'affaires élevé,\n" + "ca doit etre dûr pour la pauvre vieille\n" + "UAC d'acheter des societes \n" + "d'assurance vie de nos jours...\n" "\n" "Vous avez devant vous le complexe \n" - "militaire ,cela grouille d'horreurs \n" - "morbides pres a vous enfoncer leur dents.\n" - "Avec un peu de chance ,le complexe a \n" - "encore quelques munitions de guerre,\n" + "militaire, cela grouille d'horreurs \n" + "morbides prètes a vous estropier.\n" + "Avec un peu de chance, le complexe a \n" + "encore quelques munitions de guerre\n" "reposant autour."; T2TEXT = "Vous entendez le grincement d'une grosse\n" - "machinerie devant.Vous esperez surement\n" + "machinerie devant. Vous esperez surement\n" "qu'ils ne sont pas en train de fabriquer\n" "de nouvelles creatures de l'enfer,\n" - "mais vous etes pret a balayer tout\n" + "mais vous etes prêt a balayer tout\n" "un troupeau si vous le deviez.\n" - "Ils pourraient etre en train de preparer\n" - "un festin de sang ,mais vous vous sentez\n" + "Ils pourraient etre en train de préparer\n" + "un festin de sang, mais vous vous sentez\n" "pareil a deux milliers de fous dans le \n" - "corps d'un seul et meme tueur\n" + "corps d'un seul et même tueur.\n" "\n" - "Vous ne pensez pas tomber si facilement."; + "Vous ne pensez pas mourir si facilement."; T3TEXT = "La vue s'ouvrant devant vous semble \n" - "sacrement ,diablement ,familiere.\n" - "Les odeurs familieres, trop -- comme\n" - "des excrements frits. Vous n'aimiez \n" + "sacrément, diablement familière.\n" + "Les odeurs familières, trop -- comme\n" + "des excrèments frits. Vous n'aimiez \n" "pas cet endroit avant, et vous etes \n" - "terriblement sur de ne pas envisager\n" + "terriblement sûr de ne pas envisager\n" "l'aimer maintenant.\n" - "Plus vous pensiez a cette idee ,\n" - "plus vous sombriez dans la folie.\n" - "Saisissant votre arme ,un sourrire\n" - "malefique apparait sur votre visage.\n" - "Il est temps de prendre quelques noms."; + "Plus vous pensiez a cette idée,\n" + "plus vous sombrez dans la folie.\n" + "Saisissant votre arme, un sourrire\n" + "maléfique apparait sur votre visage.\n" + "Il est l'heure de buter du démon...."; T4TEXT = "Tout a coup, tout est silencieux ,\n" "d'un horizon a l'autre.\n" - "L'echo agonisant de l'enfer s'estompe,\n" + "L'écho agonisant de l'enfer s'estompe,\n" "le ciel cauchemardeque tourne au bleu,\n" "les tas de cadavres de monstres \n" - "commencent a s'evaporer avec une puanteur\n" - "nauseabonde qui remplissait l'air.\n" - "Doux jesus ,peut etre l'avez vous fait.\n" - "Avez vous reellement gagne?\n\n" + "commencent a s'évaporer avec une puanteur\n" + "nauséabonde qui remplit l'air.\n" + "Doux jésus, peut être l'avez vous fait.\n" + "Avez vous réellement gagné?\n\n" "quelque chose gronde au loin.\n" - "une lumiere bleue a commence a luire\n" - "dans le crane defonce du demon\n" + "une lumière bleue a commence a luire\n" + "dans le crâne défonce du démon\n" "cracheur."; T5TEXT = "Maintenant quoi? Cela semble \n" - "completement different.\n" - "Une Sorte de lottissement du\n" - "roi Tut.\n" - "Bon, rien ne peut-etre pire\n" + "complètement different.\n" + "Une sorte de lotissement au\n" + "bon vieux Toutankhamon.\n" + "Bon, rien ne peut être pire\n" "que d'habitude. N'est-ce pas?\n" - "Ou peut etre est ce mieux\n" + "Ou peut être est ce mieux\n" "de laisser dormir les dieux,\n" - "la ou ils reposent\n" + "la ou ils reposent..\n" T6TEXT = - "Vous avez besoin de vacances.\n" - "Vous avez eclates les entrailles\n" - "de l'enfer et pour sur vous etes \n" - "pres pour une pause.\n" - "Vous marmonner a vous meme:\n" - "peut-etre quelqu'un pourrait botter\n" + "Vous aviez besoin de vacances.\n" + "Vous avez éclaté les entrailles\n" + "de l'enfer et pour sûr vous êtes \n" + "prét pour une pause.\n" + "Vous marmonnez à vous même:\n" + "Peut-etre quelqu'un pourrait botter\n" "le cul de l'enfer a votre place,\n" "la prochaine fois.\n" - "Une ville tranquille reside devant, \n" + "Une ville tranquille réside devant, \n" "avec le flot paisible de l'eau,\n" "les batiments pittoresques, et\n" - "probablement plus de creatures\n" + "probablement plus de créatures\n" "de l'enfer.\n" "Quand vous descendez au dehors du \n" "transport, vous entendez le bruit \n" "du sabot d'acier d'un \n" - "demon-cybernetique."; + "Cyberdémon."; + +NERVETEXT = + "Quelqu'un avait décidé de foutre le bordel\n" + "sur votre lieu de vacances préféré: L'Enfer.\n" + "Un cyberdémon à la noix avait décidé d'en\n" + "faire son parc d'attraction, et la terre\n" + "son petit guichet.\n" + "\n" + "Cet abruti méchanique ne s'attendait pas\n" + "à ce que vous visitiez son chantier,\n" + "rien de mieux qu'un stand de tir plein de\n" + "démons pour faire chauffer l'ambiance..\n" + "\n" + "Maintenant que le cadavre croulant du démon\n" + "résonne en s'écrasant au sol, ses gémissements\n" + "d'agonie se déversent de son visage\n" + "que vous avez laissé en charpie.\n" + "\n" + "Le manège est fermé!\n"; // @@ -709,7 +728,7 @@ TXT_FRAGLIMIT = "Limite de frags atteinte."; TXT_TIMELIMIT = "Limite de temps atteinte."; // Spree messages -SPREEKILLSELF = "%o était en pleine folie meurtrière que %g ne se bute!"; +SPREEKILLSELF = "%o était en pleine folie meurtrière avant que %g ne se bute!"; SPREEOVER = "La folie meurtrière de %o à été terminée par %!k"; SPREE5 = "%k est en folie meurtrière!"; SPREE10 = "%k est en plein massacre!"; @@ -732,7 +751,7 @@ OB_EXIT = "%o a tenté de partir."; OB_WATER = "%o ne sait pas nager."; OB_SLIME = "%o a muté."; OB_LAVA = "%o a fondu."; -OB_BARREL = "%o a sauté."; +OB_BARREL = "%o s'est pété la face."; OB_SPLASH = "%o s'est tenu au mauvais endroit."; OB_R_SPLASH = "%o aurait dû garder ses distances."; OB_ROCKET = "%o aurait dû garder ses distances."; @@ -788,7 +807,7 @@ OB_IRONLICH = "%o a été dévasté par une Liche de Fer."; OB_IRONLICHHIT = "%o a fait ami-ami avec une Liche de Fer."; OB_BONEKNIGHT = "%o s'est pris la hache d'un guerrier mort-vivant."; OB_BONEKNIGHTHIT = "%o s'est fait pourfendre par un guerrier mort-vivant."; -OB_MINOTAUR = "%o was blasted into cinders by a Maulotaur."; +OB_MINOTAUR = "%o s'est fait incinérer par un Massetaure."; OB_MINOTAURHIT = "%o s'est fait éclater par un Massetaure."; OB_MUMMY = "%o a été défoncé par un golem."; OB_MUMMYLEADER = "%o s'est fait percer les tympans par un nitrogolem."; @@ -796,7 +815,7 @@ OB_SNAKE = "%o s est fait secouer par un ophidien."; OB_WIZARD = "%o a été maudit par un sorcier."; OB_WIZARDHIT = "%o a été palpé par un sorcier."; -OB_FIREDEMON = "%o a gouté au feu d'un Afrit."; +OB_FIREDEMON = "%o a goûté au feu d'un Afrit."; OB_DEMON1 = "%o a été brûlé par un Serpent."; OB_DEMON2 = "%o a été empoisonné par un Serpent."; OB_ETTIN = "%o a été réduit en purée par un Ettin."; @@ -818,10 +837,10 @@ OB_HERESIARCH = "%o s'est fait rouler les os par l'Hérésiarche."; OB_ACOLYTE = "%o a souffert d'une bavure policière."; OB_MACIL = "%o n'aurait jamais du se rebeller contre Macil."; -OB_REBEL = "%o a été abattu par un Rebel."; +OB_REBEL = "%o a été abattu par un rebelle."; OB_BEGGAR = "%o a été battu a mort par un pauvre."; OB_PEASANT = "%o n'aurait jamais du chercher des noises a un civil."; -OB_ALIENSPECTRE = "%o a été terrasse par le Spectre."; +OB_ALIENSPECTRE = "%o a été terrassé par le Spectre."; OB_ENTITY = "%o a senti le courroux du Seul Dieu."; OB_LOREMASTER = "%o n'a pu échapper a l'emprise du Maître des Traditions."; OB_PROGRAMMER = "%o a été effacé par le Programmeur."; @@ -882,14 +901,14 @@ OB_MPCWEAPWRAITHVERGE = "%o wa été purifié par la Verge Phantasmale de %k."; OB_MPMWEAPWAND = "%o s'est pris un rayon saphirique en trop de la part de %k."; OB_MPMWEAPFROST = "%o s'est fait transformer en glaçon par %k."; OB_MPMWEAPLIGHTNING = "%o a reçu un sacré coup de jus de la part de %k."; -OB_MPMWEAPBLOODSCOURGE = "%o s'est fait effacer de l'univers par la Menace Sanglante de %k."; +OB_MPMWEAPBLOODSCOURGE = "%o s'est fait effacer de l'univers par le Fléau Sanglant de %k."; OB_MPPUNCHDAGGER = "%o s'est fait planter un lame dans le dos de la part de %k."; OB_MPELECTRICBOLT = "%o s'est fait clouer au mur par %k."; OB_MPPOISONBOLT = "%o a recu une dose létale de la colère de %k."; OB_MPASSAULTGUN = "%o s'est fait couvrir de trous par le fusil d'assaut de %k."; OB_MPMINIMISSILELAUNCHER = "%o a avalé le missile de %k."; -OB_MPSTRIFEGRENADE = "%o a été mis sens dessus dessous pa la grenade explosive de %k."; +OB_MPSTRIFEGRENADE = "%o a été mis sens dessus dessous par la grenade explosive de %k."; OB_MPPHOSPHOROUSGRENADE = "%o s'est permis une pyroclave dans les flammes phosphoriques de %k."; OB_MPFLAMETHROWER = "%o est passé au barbecue de %k."; OB_MPMAULER1 = "%o s'est fait électrocuter par %k."; @@ -897,7 +916,7 @@ OB_MPMAULER = "%o à été vicieusement vaporisé par %k."; OB_MPSIGIL = "%o s'est prosterné face à la toute puissance du Sigil de %k."; // Same as OB_MPTELEFRAG, but shown when a monster telefrags you -OB_MONTELEFRAG = "%o a été telefrague."; +OB_MONTELEFRAG = "%o a été telefragué."; OB_DEFAULT = "%o est mort."; OB_FRIENDLY1 = "%k a tué un de ses équipiers."; @@ -905,21 +924,12 @@ OB_FRIENDLY2 = "%k vérifie ses lunettes."; OB_FRIENDLY3 = "%k marque un point pour l'autre équipe."; OB_FRIENDLY4 = "%k a perdu un autre ami."; -SAVEGAMENAME = "zdoomsv"; -STARTUP1 = ""; -STARTUP2 = ""; -STARTUP3 = ""; -STARTUP4 = ""; -STARTUP5 = ""; SCORE_ITEMS = "OBJETS"; -SCORE_BONUS = "BONUS"; SCORE_COLOR = "COULEUR"; -SCORE_SECRET = "SECRET"; SCORE_NAME = "NOM"; SCORE_DELAY = "DELAI(ms)"; SCORE_KILLS = "VICTIMES"; -SCORE_FRAGS = "FRAGS"; SCORE_DEATHS = "MORTS"; SCORE_MISSED = "RATES"; SCORE_TOTAL = "TOTAL"; @@ -971,7 +981,7 @@ TAG_ARTITORCH = "Torche"; TAG_CWEAPMACE = "Masse de Pénitence"; TAG_CWEAPSTAFF = "Sceptre du Serpent"; TAG_CWEAPFLAME = "Tempête de Feu"; -TAG_CWEAPWRAITHVERGE = "Bâton du Courroux"; +TAG_CWEAPWRAITHVERGE = "Verge Phantasmale"; TAG_FWEAPFIST = "Gantelets à Pointes"; TAG_FWEAPAXE = "Hache de Timon"; TAG_FWEAPHAMMER = "Marteau de la Rétribution"; @@ -1033,7 +1043,7 @@ TAG_SIGIL = "SIGIL"; TAG_COIN = "Pièce"; TAG_MEDPATCH = "Pansement"; TAG_MEDICALKIT = "Kit Médical"; -TAG_SURGERYKIT = "Kit de Chirurgie"; // "full_health" in the Teaser +TAG_SURGERYKIT = "Kit de Chirurgie"; TAG_BELDINSRING = "Anneau"; TAG_OFFERINGCHALICE = "Calice d'Obole"; TAG_EAR = "Oreille"; @@ -1059,20 +1069,20 @@ TAG_TELEPORTERBEACON = "Balise de téléportation"; TAG_METALARMOR = "Armure en Métal"; TAG_LEATHER = "Armure en Cuir"; TAG_HEGRENADES = "Grenades Explosives"; -TAG_PHGRENADES = "Grenades Incendiaires"; // "Fire-Grenade_Rounds" in the Teaser -TAG_CLIPOFBULLETS = "Chargeur d'Assaut"; // "bullets" in the Teaser +TAG_PHGRENADES = "Grenades Incendiaires"; +TAG_CLIPOFBULLETS = "Chargeur d'Assaut"; TAG_BOXOFBULLETS = "Bôite de Munitions"; -TAG_MINIMISSILES = "Mini-Missiles"; //"rocket" in the Teaser -TAG_CRATEOFMISSILES = "Caisse de Mini-Missiles"; //"box_of_rockets" in the Teaser +TAG_MINIMISSILES = "Mini-Missiles"; +TAG_CRATEOFMISSILES = "Caisse de Mini-Missiles"; TAG_ENERGYPOD = "Cellule Energétique"; TAG_ENERGYPACK = "Pack Energétique"; -TAG_POISONBOLTS = "Carreaux Empoisonnés"; // "poison_arrows" in the Teaser -TAG_ELECTRICBOLTS = "Carreaux Electriques"; // "electric_arrows" in the Teaser -TAG_AMMOSATCHEL = "Sacoche à munitions"; // "Back_pack" in the Teaser +TAG_POISONBOLTS = "Carreaux Empoisonnés"; +TAG_ELECTRICBOLTS = "Carreaux Electriques"; +TAG_AMMOSATCHEL = "Sacoche à munitions"; + -// Item tags: Strife keys TAG_BASEKEY = "Clé de la base"; -TAG_GOVSKEY = "Clé du Gouverneur"; // "Rebel_Key" in the Teaser +TAG_GOVSKEY = "Clé du Gouverneur"; TAG_PASSCARD = "Passe"; TAG_IDBADGE = "Badge d'Identification"; TAG_PRISONKEY = "Clé de la Prison"; @@ -1091,13 +1101,12 @@ TAG_BRASSKEY = "Clé en Bronze"; TAG_REDCRYSTALKEY = "Clé de Cristal Rouge"; TAG_BLUECRYSTALKEY = "Clé de Crisal Bleu"; TAG_CHAPELKEY = "Clé de la Chapelle"; -TAG_CATACOMBKEY = "Clé des Catacombes"; // "Tunnel_Key" in the Teaser +TAG_CATACOMBKEY = "Clé des Catacombes"; TAG_SECURITYKEY = "Clé de la Sécurité"; -TAG_COREKEY = "Clé du Réacteur"; // "New_Key1" in the Teaser -TAG_MAULERKEY = "Clé du Broyeur"; // "New_Key2" in the Teaser -TAG_FACTORYKEY = "Clé de l'Usine"; // "New_Key3" in the Teaser -TAG_MINEKEY = "Clé de la Mine"; // "New_Key4" in the Teaser -TAG_NEWKEY5 = "New Key5"; +TAG_COREKEY = "Clé du Réacteur"; +TAG_MAULERKEY = "Clé du Broyeur"; +TAG_FACTORYKEY = "Clé de l'Usine"; +TAG_MINEKEY = "Clé de la Mine"; TAG_ORACLEPASS = "Passe de l'Oracle"; // Item tags: misc Strife stuff @@ -1105,9 +1114,6 @@ TAG_10GOLD = "10 Pièces"; TAG_25GOLD = "25 Pièces"; TAG_50GOLD = "50 Pièces"; TAG_300GOLD = "300 Pièces"; -TAG_QUEST4 = "quest4"; -TAG_QUEST5 = "quest5"; -TAG_QUEST6 = "quest4"; // Item tags: Strife NPCs TAG_ACOLYTE = "Acolyte"; @@ -1130,7 +1136,7 @@ TAG_SPORK = "Super Fourchette"; TAG_MINIZORCHER = "Mini Zorcheur"; TAG_LARGEZORCHER = "Zorcheur Large"; TAG_SUPERLARGEZORCHER = "Zorcheur Extra-large"; -TAG_RAPIDZORCHER = "Zorcheur"; +TAG_RAPIDZORCHER = "Zorcheur Rapide"; TAG_ZORCHPROPULSOR = "Propulseur de Zorch"; TAG_PHASINGZORCHER = "Zorcheur à Phase"; TAG_LAZDEVICE = "ZZL"; @@ -1138,113 +1144,113 @@ TAG_LAZDEVICE = "ZZL"; // Heretic strings HE1TEXT = - "avec la destruction des sangsues\n" - "de fer et leur seides, les derniers\n" - "des mort-vivants furent nettoye de\n" - "cette face de l'existence.\n\n" - "ces creatures durent venir de \n" - "quelquepart, bien que,vous eutes le\n" - "soupcon sournois que l ardent\n" - "portail de l'enfer put conduire\n" + "Avec la destruction des liches\n" + "de fer et leur serviteurs, les derniers\n" + "mort-vivants furent eliminés de\n" + "cette plaine de l'existence.\n\n" + "ces créatures doivent venir de \n" + "quelque part, bien que vous ayez\n" + "un soupçon que ce portail\n" + "ardent de l'enfer puisse conduire\n" "dans leur dimension.\n\n" - "pour etre sur que beaucoup de\n" - "mort-vivants ou pire ne viennent\n" - "par son biais, vous dutes scelle le\n" - "portail de l enfer de l'autre cote.\n" - "bien sur cela veut dire que vous\n" - "eutes pu rester coince dans un monde\n" - "tres hostile, mais personne n'a\n" - "jamais dis qu'etre un heretic eut\n" - "ete facile!"; + "pour etre sûr qu'aucun des\n" + "mort-vivants ou pire ne suivent\n" + "ce chemin, vous scemllez dûment le\n" + "portail de l'autre côté.\n" + "Bien sur cela veut dire que vous\n" + "auriez pû rester coincé dans un monde\n" + "très hostile, mais personne n'a\n" + "jamais dit qu'être un hérétique\n" + "soit facile!"; HE2TEXT = - "les puissants massotaures eurent \n" - "prouve ne pas vous correspondre,\n" - "et leurs cadavres vaporeux glissant sur\n" - "le sol, vous sentites l effroyable\n" - "satisfaction qu ils furent\n" - "detruit.\n\n" - "les passerelles qu'ils garderent\n" - "furent ouvertes, revelant ce que vous\n" - "esperates etre le chemin du retour.\n" - "mais au fur et a mesure que vous \n" - "avancates ,un rire moqueur sonna a\n" - "vos oreilles.\n\n" - "etait ce quelqu'autre force controlant\n" - "les massotaures? eusse t'elles pu etre\n" - "plus atroces les creatures dans\n" - "cette porte? le balayage d'un dome\n" - "de crystal\n" - "depasse ou le ciel aurait du etre\n" - "ce ne fut pas de bon augure...."; + "les puissants massetaures ont fini par\n" + "prouver qu'il n'étaient pas votre égal,\n" + "et leurs cadavres fumants étalés sur\n" + "le sol, vous sentez l'infernale\n" + "satisfaction de les avoir complètement\n" + "détruits.\n\n" + "la passage qu'ils gardaient\n" + "s'ouvrit, révélant ce que vous\n" + "espérez être le chemin du retour.\n" + "Mais au fur et a mesure que vous \n" + "avancez, un rire moqueur sonne\n" + "à vos oreilles.\n\n" + "était-ce quelque autre force contrôlant\n" + "les massetaures? Ait elle pû être\n" + "plus atroce que les créatures de\n" + "ce portail? La forme d'un dôme\n" + "de cristal dépasse de\n" + "l'horizon, couvrant le ciel,\n" + "ce n'est pas de bon augure...."; HE3TEXT = - "la mort de d'sparil a liberes\n" + "la mort de d'sparil a liberé\n" "les liens magiques liant\n" - "les creatures de cette face,\n" - "leurs cri en mourrant surpasserent\n" - "ses propres cris d agonie.\n\n" + "les créatures de ce monde,\n" + "leurs cris d'agonie surpassant\n" + "ses propres hurlements.\n\n" "votre serment de vengeance rempli,\n" - "vous entrate dans le portail pour\n" - "votre monde, moments insignifiants\n" - "avant que la coupole ne volasse en \neclats.\n" - "mais si le pouvoir de d'sparil\n" - "fut rompu a jamais, pourquoi ne pas\n" - "ne pas vous sentir en securite?\n" - "fut ce son dernier cri\n" - "celui qui resonna comme une\n" - "malediction? une invoquation? vous\n" - "ne putes en etre sur vraiment, mais\n" - "il s'eut put n'etre qu un cri.\n\n" - "puis de nouveau, que devenirent\n" - "les autres serpents coureurs?"; + "vous pénétrez dans le portail vers\n" + "votre monde, un court moment\n" + "avant que la coupole vole en éclats.\n" + "Mais avec le pouvoir de d'sparil\n" + "rompu a jamais, pourquoi vous ne\n" + "pouvez pas vous sentir en sécurite?\n" + "Serai-çe à cause de son dernier cri\n" + "celui qui résonna comme une\n" + "malédiction? une invocation? vous\n" + "ne pouvez pas en être sûr, mais\n" + "ce n'est peut être qu un cri.\n\n" + "De plus, qu'adviendrat-il\n" + "des autres chevaucheurs de serpent?"; HE4TEXT = - "vous crutes pouvoir retourner dans\n" + "vous aviez cru pouvoir retourner dans\n" "votre monde apres que d'sparil\n" - "trepassa , mais son acte\n" + "ne trépasse, mais son acte\n" "final fut de vous bannir dans sa\n" - "propre dimension. ici vous entrates\n" - " dans des restes effondres de terres\n" - "conquisent par d'sparil. vous eutes\n" + "propre dimension. Ici vous errez\n" + "dans les territoires ruinés des\n" + "conquêtes de d'sparil. vous êtes\n" "le dernier gardien de ces terres,\n" - "mais a ce moment vous vous tenites\n" + "mais a ce moment vous vous tenez\n" "devant le bastion de d'sparil." - "jusqua ce moment vous n'eutes \n" + "jusqu'à ce moment vous n'aviez\n" "aucun doute sur votre capacite\n" - "d affronter tout ce que vous pouviez\n" - "rencontrer,mais derriere ce portail\n" - "git le coeur profond du mal\n" + "à affronter tout ce que vous pouviez\n" + "rencontrer, mais derrière ce portail\n" + "gît le coeur du mal profond\n" "qui envahit votre monde. d'sparil\n" - "eut pu etre mort,mais le puit ou\n" - "il fut ne demeura. maintenant vous\n" - "dutes entrer dans ce puit dans l'espoir\n" - "de trouver une sortie.et quelque part,\n" + "peut être mort, mais le puis d'où\n" + "il est sorti ne l'est pas. Maintenant\n" + "vous devez y entrer dans l'espoir\n" + "de trouver une sortie, quelque part,\n" "dans la plus sombre partie du\n" - "royaume de d'sparil,\n" - "sa garde personnelle attendit votre\n" - "arrivee ..."; + "royaume de d'sparil,\n" + "Sa garde personnelle attend votre\n" + "arrivée ..."; HE5TEXT = - "des que le massotaure final eu mugit\n" - "son agonie mortelle, vous realisates que\n" - "vous n'eutes jamais ete aussi proche de\n" - "votre propre destruction.meme le \n" + "dès que le massetaure final eu mugit\n" + "son râle d'agonie, vous réalisez que\n" + "vous n'avez jamais été aussi proche de\n" + "votre propre mort. Même le \n" "combat contre d'sparil et ses\n" "disciples n'eut ete a ce point \n" - "desespere. souriant, quand vous \n" - "regardates fixement le portail s'ouvrir \n" - "devant vous ,vous demandant s'il menait \n" - "chez vous,ou si il ouvrait sur une\n" - "innimaginable horreur. vous vous \n" - "demandates si vous eutes la force d'y aller,\n" + "éreintant. Souriant, vous \n" + "regardez fixement le portail s'ouvrir \n" + "devant vous, vous demandant s'il mêne\n" + "chez vous, où si il ouvre sur une\n" + "horreur inimaginable. Vous n'êtes \n" + "pas sûr d'avoir la force d'y aller,\n" "si rien d'autre que souffrance\n" - "et mort vous eurent attendu.\n" - "mais que putes vous faire d'autre, si\n" - "la volonte de vous battre s'eut enfuie?\n" - "putes vous,vous meme vous forcer a \n" - "continuer face a un tel desespoir? \n" - "eutes vous le courage? vous trouvates,\n" - "a la fin ,ce ne fut pas dans votre nature\n" - "de vous rendre sans un combat. les yeux\n" - "larges ,vous partates rencontrer votre\n" - "destinee."; + "et mort ne vous y attendent.\n" + "mais que faire d'autre, si la\n" + "volonté de vous battre disparaît?\n" + "Comment voulez-vous vous forcer à\n" + "continuer face a un tel désespoir? \n" + "En avez vous le courage? Vous pensez,\n" + "a la fin que ce n'est pas dans votre nature\n" + "de vous rendre sans combattre. les yeux\n" + "larges, vous partez rencontrer votre\n" + "destinée."; // EPISODE 1 - LA CITE DES DAMNES HHUSTR_E1M1 = "LES DOCKS"; @@ -1352,7 +1358,7 @@ TXT_WPNSKULLROD = "BATON INFERNAL"; TXT_WPNPHOENIXROD = "BATON INFERNAL"; TXT_WPNGAUNTLETS = "GANTELETS DU NECROMANCIEN"; -TXT_NEEDBLUEKEY = "CETTE PORTE NESCESSITE UNE CLE BLEU POUR S'OUVRIR"; +TXT_NEEDBLUEKEY = "CETTE PORTE NESCESSITE UNE CLE BLEUE POUR S'OUVRIR"; TXT_NEEDGREENKEY = "CETTE PORTE NESCESSITE UNE CLE VERTE POUR S'OUVRIR"; TXT_NEEDYELLOWKEY = "CETTE PORTE NESCESSITE UNE CLE JAUNE POUR S'OUVRIR"; @@ -1367,7 +1373,6 @@ TXT_CHEATIDKFA = "TRICHEUR - TU NE MERITE PAS D'ARMES!"; TXT_CHEATTICKERON = "COMPTEUR ALLUME"; TXT_CHEATTICKEROFF = "COMPTER ETEINT"; TXT_CHEATARTIFACTS3 = "VOUS L'AVEZ"; -TXT_MIDASTOUCH = "YOU GOT THE MIDAS TOUCH, BABY!"; TXT_GOTSTUFF = "Voilà ton équipement!"; TXT_FREEZEON = "Temps arrêté."; TXT_FREEZEOFF = "Le temps reprend son cours.."; @@ -1385,6 +1390,19 @@ TXT_IMTIME = "TEMPS"; RAVENQUITMSG = "ETES VOUS SUR DE VOULOIR QUITTER?"; +FN_CHICKEN = "Poulet"; +FN_BEAST = "Dragon-garou"; +FN_CLINK = "Sabregriffe"; +FN_DSPARIL = "D'Sparil"; +FN_HERETICIMP = "Gargouille"; +FN_IRONLICH = "Liche de Fer"; +FN_BONEKNIGHT = "Guerrier Mort-Vivant"; +FN_MINOTAUR = "Massetaure"; +FN_MUMMY = "Golem"; +FN_MUMMYLEADER = "Nitrogolem"; +FN_SNAKE = "Ophidien"; +FN_WIZARD = "Magicien"; + // Hexen strings // Mana @@ -1400,7 +1418,7 @@ TXT_KEY_CAVE = "CLE DE LA CAVE"; TXT_KEY_AXE = "CLE DE LA HACHE"; TXT_KEY_FIRE = "CLE DU FEU"; TXT_KEY_EMERALD = "CLE D'EMERAUDE"; -TXT_KEY_DUNGEON = "CLE DU DONGEON"; +TXT_KEY_DUNGEON = "CLE DU DONJON"; TXT_KEY_SILVER = "CLE D'ARGENT"; TXT_KEY_RUSTED = "CLE ROUILLEE"; TXT_KEY_HORN = "CLE CORNE"; @@ -1412,10 +1430,10 @@ TXT_NEED_KEY_CAVE = "Vous avez besoin de la CLE DE LA CAVE"; TXT_NEED_KEY_AXE = "Vous avez besoin de la CLE DE LA HACHE"; TXT_NEED_KEY_FIRE = "Vous avez besoin de la CLE DU FEU"; TXT_NEED_KEY_EMERALD = "Vous avez besoin de la CLE D'EMERAUDE"; -TXT_NEED_KEY_DUNGEON = "Vous avez besoin de la CLE DU DONGEON"; +TXT_NEED_KEY_DUNGEON = "Vous avez besoin de la CLE DU DONJON"; TXT_NEED_KEY_SILVER = "Vous avez besoin de la CLE D'ARGENT"; TXT_NEED_KEY_RUSTED = "Vous avez besoin de la CLE ROUILLEE"; -TXT_NEED_KEY_HORN = "Vous avez besoin de la CLE ROUILLEE"; +TXT_NEED_KEY_HORN = "Vous avez besoin de la CLE CORNE"; TXT_NEED_KEY_SWAMP = "Vous avez besoin de la CLE DES MARECAGES"; TXT_NEED_KEY_CASTLE = "Vous avez besoin de la CLE DU CHATEAU"; @@ -1464,13 +1482,13 @@ TXT_WEAPON_F3 = "MARTEAU DE LA RETRIBUTION"; TXT_WEAPON_F4 = "QUIETUS ASSEMBLE"; TXT_WEAPON_C2 = "SCEPTRE DU SERPENT"; TXT_WEAPON_C3 = "TEMPETE DE FEU"; -TXT_WEAPON_C4 = "BATON DU COURROUX ASSEMBLE"; +TXT_WEAPON_C4 = "VERGE PHANTASMALE ASSEMBLE"; TXT_WEAPON_M2 = "ECLATS DE GIVRE"; TXT_WEAPON_M3 = "FOUDRE MORTELLE"; TXT_WEAPON_M4 = "FLEAU SANGLANT ASSEMBLE"; TXT_WEAPONPIECE = "Une piece d'arme! C'est votre jour de chance!"; TXT_QUIETUS_PIECE = "SEGMENT DE QUIETUS"; -TXT_WRAITHVERGE_PIECE = "SEGMENT DU BATON DU COURROUX"; +TXT_WRAITHVERGE_PIECE = "SEGMENT DE LA VERGE PHANTASMALE"; TXT_BLOODSCOURGE_PIECE = "SEGMENT DU FLEAU SANGLANT"; // Strife locks @@ -1488,6 +1506,8 @@ TXT_NEED_SILVERKEY = "Vous avez besoin de la clé en argent."; TXT_NEED_BRASSKEY = "Vous avez besoin de la clé en bronze."; TXT_NEED_REDCRYSTAL = "Vous avez besoin du cristal rouge."; TXT_NEED_BLUECRYSTAL = "Vous avez besoin du cristal bleu."; +TXT_RETAIL_ONLY = "CETTE ZONE N'EST ACCESSIBLE QUE DANS LA VERSION COMPLETE DE STRIFE"; +TXT_DOES_NOT_WORK = "Cela ne semble pas fonctionner."; // Strife Quest messages @@ -1599,8 +1619,8 @@ TXT_RANDOM_REBEL_03 = "On couvre tes arrières, t'inquiète pas."; TXT_RANDOM_REBEL_04 = "Ne vous approchez pas trop des gros robots. Ils brûleront jusqu'à l'os!"; TXT_RANDOM_REBEL_05 = "Le jour de gloire arrivera bientôt, et ceux qui s'opposeront a nous seront écrasés!"; TXT_RANDOM_REBEL_06 = "Ne vous reposez pas sur vos lauriers. Du travail nous attend encore."; -TXT_RANDOM_REBEL_07 = "Macil nous dis que tu es notre nouvel espoir. Garde ca à l'esprit."; -TXT_RANDOM_REBEL_08 = "Une fois que nous nous serons débarassés de ces charlatans, nous serons capable de rebatir le monde tel qu'il devrait être."; +TXT_RANDOM_REBEL_07 = "Macil nous dit que tu es notre nouvel espoir. Garde ca à l'esprit."; +TXT_RANDOM_REBEL_08 = "Une fois que nous nous serons débarassés de ces charlatans, nous serons capable de rebâtir le monde tel qu'il devrait être."; TXT_RANDOM_REBEL_09 = "Souviens-toi, tu ne te bas pas seulement pour toi, mais pour tous le monde ici et ailleurs."; TXT_RANDOM_REBEL_10 = "Aussi longtemps qu'un de nous est vivant, nous vaincrons."; @@ -1619,14 +1639,14 @@ TXT_RANDOM_BEGGAR_01 = "L'aumône pour les pauvres?"; TXT_RANDOM_BEGGAR_02 = "Tu regarde qui, habitant de la surface?"; TXT_RANDOM_BEGGAR_03 = "Aurais-tu de la nourriture en trop sur toi?"; TXT_RANDOM_BEGGAR_04 = "Vous, les gens de la surface, vous ne comprendrez jamais."; -TXT_RANDOM_BEGGAR_05 = "Ha, les gardes ne nous trouveront jamaiss, ces idios ne savent même pas que l'on existe!"; +TXT_RANDOM_BEGGAR_05 = "Ha, les gardes ne nous trouveront jamais, ces idiots ne savent même pas qu'on existe!"; TXT_RANDOM_BEGGAR_06 = "Un jour, tous sauf les membres de l'ordre seront forcés de nous rejoindre."; -TXT_RANDOM_BEGGAR_07 = "Amuse toi à nous regarder autant que tu veus, mais tu sais que ceci sera aussi ton sort un jour."; -TXT_RANDOM_BEGGAR_08 = "Il n'y rien de plus barbant qu'un type de la surface et son attitude!"; +TXT_RANDOM_BEGGAR_07 = "Moque nous si tu veux, mais tu sais que ceci sera aussi ton sort un jour."; +TXT_RANDOM_BEGGAR_08 = "Il n'y rien de plus barbant qu'un type de la surface et son orgeuil!"; TXT_RANDOM_BEGGAR_09 = "L'ordre se débarrassera de vos soldats pathétiques."; TXT_RANDOM_BEGGAR_10 = "Prend guarde a toi, habitant de la surface. Nous connaîssons nos ennemis!"; -TXT_RANDOM_PGUARD_01 = "Nous sommes les mains du destin. Apprendre notre colere est l'égal de trouver l'oubli!"; +TXT_RANDOM_PGUARD_01 = "Nous sommes les mains du destin. Apprendre notre colère est l'égal de trouver l'oubli!"; TXT_RANDOM_PGUARD_02 = "L'ordre nettoira le monde des faibles et corrompus!"; TXT_RANDOM_PGUARD_03 = "Obéissez à la volonté de vos maîtres!"; TXT_RANDOM_PGUARD_04 = "Longue vie aux membres de l'Ordre!"; @@ -1687,7 +1707,7 @@ MNU_DEMESNE = "LE DOMAINE STAGNANT"; MNU_WETNURSE = "VOUS AVEZ BESOIN D'UNE NOURRICE"; MNU_YELLOWBELLIES = "UN TROUILLARD-TU-EST"; -MNU_BRINGEST = "AMMENEZ LES MOI!"; +MNU_BRINGEST = "AMENEZ LES MOI!"; MNU_SMITE = "TU EST UN MAITRE-MEURTRIER"; MNU_BLACKPLAGUE = "LA PESTE NOIRE ME POSSEDE"; @@ -1721,7 +1741,6 @@ MNU_WARRIOR = "GUERRIER"; MNU_ALTARBOY = "ENFANT DE CHOEUR"; MNU_PRIEST = "PRETRE"; -MNU_CARDINAL = "CARDINAL"; MNU_POPE = "PAPE"; MNU_APPRENTICE = "APPRENTI"; @@ -1754,7 +1773,7 @@ OPTMNU_CHANGERENDER = "Changer de Moteur de Rendu"; OPTMNU_DEFAULTS = "Réinitialiser les paramètres"; OPTMNU_RESETTOSAVED = "Recharger dernière config."; OPTMNU_CONSOLE = "Ouvrir la console"; - +OPTMNU_REVERB = "Editeur environement de révérb."; // Controls Menu CNTRLMNU_TITLE = "MODIFIER CONTROLES"; @@ -1821,7 +1840,7 @@ CNTRLMNU_OTHER = "Autres"; CNTRLMNU_AUTOMAP = "Activer Carte"; CNTRLMNU_CHASECAM = "Caméra 3ième personne"; CNTRLMNU_COOPSPY = "Espionner Coéquiper"; -CNTRLMNU_SCREENSHOT = "Capture d'Ecran"; +CNTRLMNU_SCREENSHOT = "Capture d'écran"; CNTRLMNU_CONSOLE = "Ouvrir Console"; CNTRLMNU_POPUPS = "Popups de Strife"; CNTRLMNU_MISSION = "Objectifs de Mission"; @@ -1836,7 +1855,7 @@ MOUSEMNU_MOUSEINMENU = "Activer Souris dans les Menus"; MOUSEMNU_SHOWBACKBUTTON = "Afficher le bouton retour"; MOUSEMNU_CURSOR = "Curseur"; MOUSEMNU_SENSITIVITY = "Sensibilité générale"; -MOUSEMNU_NOPRESCALE = "Prescaling movement souris"; +MOUSEMNU_NOPRESCALE = "Prescaling mouvement souris"; MOUSEMNU_SMOOTHMOUSE = "Lissage Souris"; MOUSEMNU_TURNSPEED = "Vitesse pour tourner"; MOUSEMNU_MOUSELOOKSPEED = "Vitesse Vue Souris"; @@ -1985,7 +2004,7 @@ MISCMNU_QUERYIWAD = "Afficher la séléction d'IWAD"; MISCMNU_ALLCHEATS = "Activer cheats de tous les jeux"; MISCMNU_ENABLEAUTOSAVES = "Activer Sauvegardes auto"; MISCMNU_AUTOSAVECOUNT = "Total de sauvegardes auto"; -MISCMNU_SAVELOADCONFIRMATION = "Confirmation C/S"; +MISCMNU_SAVELOADCONFIRMATION = "Confirmation C/S"; MISCMNU_DEHLOAD = "Charger fichiers *.deh/*.bex"; MISCMNU_CACHENODES = "Mise en cache des nodes"; MISCMNU_CACHETIME = "Limite cache des nodes"; @@ -2119,7 +2138,7 @@ GMPLYMNU_ALLOWSPYING = "Autoriser espionnage"; GMPLYMNU_CHASECAM = "Cheat caméra 3ième personne"; GMPLYMNU_DONTCHECKAMMO = "Vérifier munitions pour changement arme"; GMPLYMNU_KILLBOSSSPAWNS = "Tuer L'Icône tue tous ses monstres"; -GMPLYMNU_NOCOUNTENDMONSTER = "Ennemis dans secteur de sortie comptent"; +GMPLYMNU_NOCOUNTENDMONSTER = "Ennemis du secteur sortie comptés"; GMPLYMNU_DEATHMATCH = "Options Deathmatch"; GMPLYMNU_WEAPONSSTAY = "Armes restent au sol quand récupérées"; GMPLYMNU_ALLOWPOWERUPS = "Autoriser powerups"; @@ -2149,21 +2168,21 @@ GMPLYMNU_SPAWNWHEREDIED = "Réapparaitre sur lieu de mort"; CMPTMNU_TITLE = "OPTIONS COMPATIBILITE"; CMPTMNU_MODE = "Mode de compatibilité"; CMPTMNU_ACTORBEHAVIOR = "Comportement des Acteurs"; -CMPTMNU_CORPSEGIBS = "Monstres écrasés peuvent être ressucités"; -CMPTMNU_NOBLOCKFRIENDS = "Monstres amicaux ne sont pas bloqués"; -CMPTMNU_LIMITPAIN = "Limite sur les âmes des élémentaires"; -CMPTMNU_MBFMONSTERMOVE = "Mouvement monstre affecté par les effets"; +CMPTMNU_CORPSEGIBS = "Monstres écrasés résucitables"; +CMPTMNU_NOBLOCKFRIENDS = "Monstres amicaux non bloqués"; +CMPTMNU_LIMITPAIN = "Limiter âmes des élémentaires"; +CMPTMNU_MBFMONSTERMOVE = "Mouvement monstre affecté par effets"; CMPTMNU_CROSSDROPOFF = "Monstres ne passent pas les corniches"; CMPTMNU_DROPOFF = "Monstres bloqués par les corniches"; CMPTMNU_INVISIBILITY = "Monstres voient joueurs invisibles"; CMPTMNU_MINOTAUR = "Pas de feu de Massetaur sur l'eau"; -CMPTMNU_NOTOSSDROPS = "OBjets lâchés sont direct au sol"; +CMPTMNU_NOTOSSDROPS = "Objets lâchés direct au sol"; CMPTMNU_DEHACKEDBEHAVIOR = "Comportement DeHackEd"; CMPTMNU_DEHHEALTH = "Niveaux de santé DEH comme Doom2.EXE"; CMPTMNU_MUSHROOM = "Vitesse A_Mushroom originale pour DEH"; CMPTMNU_MAPACTIONBEHAVIOR = "Comportement Niveau/Actions"; CMPTMNU_USEBLOCKING = "Toute ligne d'action bloque "; -CMPTMNU_ANYBOSSDEATH = "N'importe quel boss peut activer actions boss"; +CMPTMNU_ANYBOSSDEATH = "N'importe quel boss active actions boss"; CMPTMNU_NODOORLIGHT = "Pas d'effet de lumière BOOM sur portes"; CMPTMNU_LIGHT = "Trouver prochaine texture comme DOOM"; CMPTMNU_SHORTTEX = "Trouver plus petite texture comme Doom"; @@ -2200,7 +2219,7 @@ SNDMNU_SFXVOLUME = "Volume des Sons"; SNDMNU_MENUVOLUME = "Volume du Menu"; SNDMNU_MUSICVOLUME = "Volume Musique"; SNDMNU_MIDIDEVICE = "Sortie MIDI"; -SNDMNU_BACKGROUND = "Sons activé en arrière plan"; +SNDMNU_BACKGROUND = "Son activé en arrière plan"; SNDMNU_UNDERWATERREVERB = "Reverbération sous l'eau"; SNDMNU_RANDOMIZEPITCHES = "Tons sonores aléatoires"; SNDMNU_CHANNELS = "Canaux sonores"; @@ -2218,7 +2237,7 @@ OPENALMNU_RESAMPLER = "Resampler"; // Advanced Sound Options ADVSNDMNU_TITLE = "OPTIONS SONORES AVANCEES"; -ADVSNDMNU_SAMPLERATE = "Sample rate"; +ADVSNDMNU_SAMPLERATE = "Cadence de Sampling"; ADVSNDMNU_HRTF = "HRTF"; ADVSNDMNU_OPLSYNTHESIS = "Synthèse OPL"; ADVSNDMNU_OPLNUMCHIPS = "Puces OPL émulées"; @@ -2229,18 +2248,24 @@ ADVSNDMNU_GUSCONFIG = "Fichier Config. GUS"; ADVSNDMNU_MIDIVOICES = "Voix MIDI"; ADVSNDMNU_DMXGUS = "Lire fichiers DMXGUS"; ADVSNDMNU_GUSMEMSIZE = "Taille mémoire GUS"; -ADVSNDMNU_FLUIDSYNTH = "FluidSynth"; -ADVSNDMNU_FLUIDPATCHSET = "Patchset"; -ADVSNDMNU_FLUIDGAIN = "Gain"; -ADVSNDMNU_REVERB = "Reverb"; -ADVSNDMNU_CHORUS = "Chorus"; -ADVSNDMNU_TIMIDITY = "Timidity++"; ADVSNDMNU_TIMIDITYEXE = "Chemin exécutable"; ADVSNDMNU_TIMIDITYCONFIG = "Fichier de config. TiMidity"; ADVSNDMNU_TIMIDITYVOLUME = "Volume Relatif"; -ADVSNDMNU_WILDMIDI = "WildMidi"; ADVSNDMNU_WILDMIDICONFIG = "Fichier config. WildMidi"; ADVSNDMNU_SELCONFIG = "Sélectionner configuration"; +ADVSNDMNU_ADVRESAMPLING = "Resampling Avancé"; +ADVSNDMNU_OPLBANK = "Banque OPL"; +ADVSNDMNU_ADLOPLCORES = "Coeur Emulateur OPL"; +ADVSNDMNU_RUNPCMRATE = "Emulateur utilise cadence PCM"; +ADVSNDMNU_ADLNUMCHIPS = "Puces OPL émulées"; +ADVSNDMNU_VLMODEL = "Modèle de Volume"; +ADVSNDMNU_OPNNUMCHIPS = "Puces OPN émulées"; + +// ADLMIDI's volume models +ADLVLMODEL_AUTO = "Auto (Utiliser paramètre banque)"; +ADLVLMODEL_GENERIC = "Générique"; +ADLVLMODEL_NATIVE = "OPL Natif"; + // Module Replayer Options MODMNU_TITLE = "OPTIONS LECTEUR MODULES"; @@ -2248,7 +2273,6 @@ MODMNU_REPLAYERENGINE = "Moteur de lecture"; MODMNU_MASTERVOLUME = "Volume maître"; MODMNU_QUALITY = "Qualité"; MODMNU_VOLUMERAMPING = "Rampe du volume"; -MODMNU_CHIPOMATIC = "Chip-o-matic"; // Renderer Options RNDMNU_TITLE = "SELECTION MOTEUR RENDU"; @@ -2259,6 +2283,7 @@ RNDMNU_CANVAS = "Canvas Software"; // Video Options VIDMNU_TITLE = "MODE VIDEO"; +IDMNU_RENDERMODE = "Mode de Rendu"; VIDMNU_FULLSCREEN = "Plein écran"; VIDMNU_HIDPI = "Support Retina/HiDPI "; VIDMNU_BRDLSS = "Mode fenêtré sans bordures"; @@ -2272,6 +2297,16 @@ VIDMNU_ENTERTEXT = "Appuyez sur ENTREE pour choisir un mode"; VIDMNU_TESTTEXT1 = "Appuyez sur T pour tester ce mode pendant 5 secondes."; VIDMNU_TESTTEXT2 = "Veuillez attendre 5 secondes..."; +VIDMNU_USELINEAR = "Mise à l'échelle Linéaire (Plein écran)"; +VIDMNU_CUSTOMRES = "Résolution Personalisée"; +VIDMNU_CUSTOMX = "Largeur Personalisée"; +VIDMNU_CUSTOMY = "Hauteur Personalisée"; +VIDMNU_APPLYW = "Appliquer Changements (Fenêtre)"; +VIDMNU_APPLYFS = "Appliquer Changements (Plein écran)"; +VIDMNU_RESPRESET = "Choisir paramètre personalisé"; +VIDMNU_RESPRESETTTL = "Résolutions Personalisée"; +VIDMNU_RESPRESETHEAD = "Choisir mode de Résolution"; + // Network Options NETMNU_TITLE = "OPTIONS RESEAU"; NETMNU_LOCALOPTIONS = "Options Locales"; @@ -2405,6 +2440,33 @@ OPTVAL_VTAZDOOM = "Auto (ZDoom Préféré)"; OPTVAL_VTAVANILLA = "Auto (Vanilla Préféré)"; OPTVAL_SCALENEAREST = "Mise à l'échelle (Proche Voisin)"; OPTVAL_SCALELINEAR = "Mise à l'échelle (Linéaire)"; +OPTVAL_LINEAR = "Linéaire"; +OPTVAL_CUBIC = "Cubique"; +OPTVAL_BLEP = "Step limité par bande"; +OPTVAL_LINEARSLOW = "Linéaire (Lent)"; +OPTVAL_BLAM = "Linéaire limité par bande"; +OPTVAL_CUBICSLOW = "Cubique (Lent)"; +OPTVAL_NOTEONOFFONLY = "Note on/off seulement"; +OPTVAL_FULLRAMPING = "Rampe complète"; +OPTVAL_ALLUNACKNOWLEDGED = "Tout non-acknowledged"; +OPTVAL_ERRORS = "Erreurs"; +OPTVAL_WARNINGS = "Avertissements"; +OPTVAL_EVERYTHING = "Tout"; +OPTVAL_FULLSCREENONLY = "Plein écran seulement"; +OPTVAL_HWPOLY = "Accéléré par OpenGL"; +OPTVAL_SWDOOM = "Rendu Software Doom"; +OPTVAL_SWDOOMTC = "Rendu Software Couleurs Réeles"; +OPTVAL_SWPOLY = "Rendu Softpoly"; +OPTVAL_SWPOLYTC = "Softpoly Couleurs Réeles"; +OPTVAL_DEDICATED = "Hautes Performances"; +OPTVAL_INTEGRATED = "Economie d'Energie"; +OPTVAL_VTFZDOOM = "ZDoom (Forcé)"; +OPTVAL_VTFVANILLA = "Vanilla (Forcé)"; +OPTVAL_VTAZDOOM = "Auto (ZDoom Préféré)"; +OPTVAL_VTAVANILLA = "Auto (Vanilla Préféré)"; +OPTVAL_SCALENEAREST = "Mis à l'échelle (Proche Voisin)"; +OPTVAL_SCALELINEAR = "Mis à l'échelle (Linéaire)"; + // Colors C_BRICK = "\cabrique"; From 1c15fb240876a8b8538aa631f42f9af14c0d87fd Mon Sep 17 00:00:00 2001 From: Kevin Caccamo Date: Sun, 3 Jun 2018 03:26:42 -0400 Subject: [PATCH 13/16] Initial work on OBJ model support What works: - Parsing the model - Constructing geometry (surfaces) for triangulated models What doesn't: - Rendering the model - Building the vertex buffer - Triangulating quads --- src/CMakeLists.txt | 1 + src/r_data/models/models.cpp | 6 +- src/r_data/models/models_obj.cpp | 344 +++++++++++++++++++++++++++++++ src/r_data/models/models_obj.h | 94 +++++++++ 4 files changed, 444 insertions(+), 1 deletion(-) create mode 100644 src/r_data/models/models_obj.cpp create mode 100644 src/r_data/models/models_obj.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 021661d312..0c880459d2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1154,6 +1154,7 @@ set (PCH_SOURCES r_data/models/models_md2.cpp r_data/models/models_voxel.cpp r_data/models/models_ue1.cpp + r_data/models/models_obj.cpp scripting/symbols.cpp scripting/types.cpp scripting/thingdef.cpp diff --git a/src/r_data/models/models.cpp b/src/r_data/models/models.cpp index 44d70b493c..fefbb88a06 100644 --- a/src/r_data/models/models.cpp +++ b/src/r_data/models/models.cpp @@ -40,6 +40,7 @@ #include "r_utility.h" #include "r_data/models/models.h" #include "r_data/models/models_ue1.h" +#include "r_data/models/models_obj.h" #include "i_time.h" #ifdef _MSC_VER @@ -437,6 +438,10 @@ static unsigned FindModel(const char * path, const char * modelfile) model = new FUE1Model; } } + else if ( (size_t)fullname.LastIndexOf(".obj") == fullname.Len() - 1 ) + { // LastIndexOf works differently than it does in JavaScript + model = new FOBJModel; + } else if (!memcmp(buffer, "DMDM", 4)) { model = new FDMDModel; @@ -937,4 +942,3 @@ bool IsHUDModelForPlayerAvailable (player_t * player) FSpriteModelFrame *smf = FindModelFrame(player->ReadyWeapon->GetClass(), state->sprite, state->GetFrame(), false); return ( smf != nullptr ); } - diff --git a/src/r_data/models/models_obj.cpp b/src/r_data/models/models_obj.cpp new file mode 100644 index 0000000000..e754d2daa6 --- /dev/null +++ b/src/r_data/models/models_obj.cpp @@ -0,0 +1,344 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2018 Kevin Caccamo +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- + +#include "w_wad.h" +#include "cmdlib.h" +#include "r_data/models/models_obj.h" + +bool FOBJModel::Load(const char* fn, int lumpnum, const char* buffer, int length) +{ + FString objName = Wads.GetLumpFullPath(lumpnum); + sc.OpenMem(objName, buffer, length); + Printf("Parsing %s\n", objName.GetChars()); + while(sc.GetString()) + { + if (sc.Compare("#")) // Line comment + { + sc.Line += 1; // I don't think this does anything, though... + } + else if (sc.Compare("v")) // Vertex + { + ParseVector3(this->verts); + } + else if (sc.Compare("vn")) // Vertex normal + { + ParseVector3(this->norms); + } + else if (sc.Compare("vt")) // UV Coordinates + { + ParseVector2(this->uvs); + } + else if (sc.Compare("usemtl")) + { + // Get material name and try to load it + sc.MustGetString(); + + curMtl = LoadSkin("", sc.String); + if (!curMtl.isValid()) + { + // Relative to model file path? + curMtl = LoadSkin(fn, sc.String); + } + + if (!curMtl.isValid()) + { + // Don't use materials that don't exist + continue; + } + // Most OBJs are sorted by material, but just in case the one we're loading isn't... + // Build surface... + if (curSurface == nullptr) + { + // First surface + curSurface = new OBJSurface(curMtl); + } + else + { + if (curSurfFaceCount > 0) + { + // Search for existing surface with current material + /* + for(size_t i = 0; i < surfaces.Size(); i++) + { + if (surfaces[i].skin == curMtl) + { + curSurface = &surfaces[i]; + surfExists = true; + } + } + */ + // Add previous surface + curSurface->numFaces = curSurfFaceCount; + curSurface->faceStart = aggSurfFaceCount; + surfaces.Push(*curSurface); + delete curSurface; + // Go to next surface + curSurface = new OBJSurface(curMtl); + aggSurfFaceCount += curSurfFaceCount; + } + else + { + curSurface->skin = curMtl; + } + } + curSurfFaceCount = 0; + } + else if (sc.Compare("f")) + { + FString sides[4]; + OBJFace face; + for (int i = 0; i < 3; i++) + { + // A face must have at least 3 sides + sc.MustGetString(); + sides[i] = sc.String; + ParseFaceSide(sides[i], face, i); + } + face.sideCount = 3; + if (sc.GetString()) + { + if (!sc.Compare("f")) + { + sides[3] = sc.String; + face.sideCount += 1; + ParseFaceSide(sides[3], face, 3); + } + else + { + sc.UnGet(); // No 4th side, move back + } + } + faces.Push(face); + curSurfFaceCount += 1; + } + } + sc.Close(); + + // No valid materials detected + if (curSurface == nullptr) + { + FTextureID dummyMtl = LoadSkin("", "-NOFLAT-"); // Built-in to GZDoom + curSurface = new OBJSurface(dummyMtl); + } + curSurface->numFaces = curSurfFaceCount; + curSurface->faceStart = aggSurfFaceCount; + surfaces.Push(*curSurface); + delete curSurface; + + /* + Printf("%d vertices\n", verts.Size()); + Printf("%d normals\n", norms.Size()); + Printf("%d UVs\n", uvs.Size()); + Printf("%d faces\n", faces.Size()); + Printf("%d surfaces\n", surfaces.Size()); + */ + + mLumpNum = lumpnum; + for (size_t i = 0; i < surfaces.Size(); i++) + { + ConstructSurfaceTris(&surfaces[i]); + } + return true; +} + +/** + * Parse a 2D vector + * + * @param start The buffer to parse from + * @param array The array to append the parsed vector to + */ +void FOBJModel::ParseVector2(TArray &array) +{ + float coord[2]; + for (int axis = 0; axis < 2; axis++) + { + sc.MustGetFloat(); + coord[axis] = (float)sc.Float; + } + FVector2 vec(coord); + array.Push(vec); +} + +/** + * Parse a 3D vector + * + * @param start The buffer to parse from + * @param array The array to append the parsed vector to + */ +void FOBJModel::ParseVector3(TArray &array) +{ + float coord[3]; + for (int axis = 0; axis < 3; axis++) + { + sc.MustGetFloat(); + coord[axis] = (float)sc.Float; + } + FVector3 vec(coord); + array.Push(vec); +} + +void FOBJModel::ParseFaceSide(const FString &sideStr, OBJFace &face, int sidx) +{ + OBJFaceSide side; + int origIdx; + if (sideStr.IndexOf("/") >= 0) + { + TArray sides = sideStr.Split("/"); + if (sides[0].Len() > 0) + { + origIdx = atoi(sides[0].GetChars()); + side.vertref = ResolveIndex(origIdx, FaceElement::VertexIndex); + } + if (sides[1].Len() > 0) + { + origIdx = atoi(sides[1].GetChars()); + side.uvref = ResolveIndex(origIdx, FaceElement::UVIndex); + } + if (sides.Size() > 2) + { + if (sides[2].Len() > 0) + { + origIdx = atoi(sides[2].GetChars()); + side.normref = ResolveIndex(origIdx, FaceElement::VNormalIndex); + } + } + } + else + { + origIdx = atoi(sideStr.GetChars()); + side.vertref = ResolveIndex(origIdx, FaceElement::VertexIndex); + side.normref = 0; + side.uvref = 0; + } + face.sides[sidx] = side; +} + +int FOBJModel::ResolveIndex(int origIndex, FaceElement el) +{ + if (origIndex > 0) + { + return origIndex - 1; // OBJ indices start at 1 + } + else if (origIndex < 0) + { + if (el == FaceElement::VertexIndex) + { + return this->verts.Size() + origIndex; // origIndex is negative + } + else if (el == FaceElement::UVIndex) + { + return this->uvs.Size() + origIndex; + } + else if (el == FaceElement::VNormalIndex) + { + return this->norms.Size() + origIndex; + } + } + return 0; +} + +void FOBJModel::TriangulateQuad(const OBJFace &quad, OBJFace *tris) +{ + // if (quad.sides < 3 || quad.sides > 4) exception + // if (quad.sides == 3) return &quad; + tris[0].sideCount = 3; + tris[1].sideCount = 3; + + for (int i = 0; i < 3; i++) + { + tris[0].sides[i].vertref = quad.sides[i].vertref; + tris[1].sides[i].vertref = quad.sides[i+1].vertref; + tris[0].sides[i].uvref = quad.sides[i].uvref; + tris[1].sides[i].uvref = quad.sides[i+1].uvref; + tris[0].sides[i].normref = quad.sides[i].normref; + tris[1].sides[i].normref = quad.sides[i+1].normref; + } +} + +void FOBJModel::ConstructSurfaceTris(OBJSurface *surf) +{ + int triCount = 0; + + size_t start = surf->faceStart; + size_t end = start + surf->numFaces; + for (size_t i = start; i < end; i++) + { + triCount += (faces[i].sideCount > 3) ? 2 : 1; + } + + surf->numTris = triCount; + surf->tris = new OBJFace[triCount]; + + int quadCount = 0; + for (size_t i = start; i < end; i++) + { + surf->tris[i+quadCount].sideCount = 3; + if (faces[i].sideCount == 3) + { + memcpy(surf->tris[i+quadCount].sides, faces[i].sides, sizeof(OBJFaceSide) * 3); + } + else if (faces[i].sideCount == 4) + { + OBJFace *triangulated = new OBJFace[2]; + TriangulateQuad(faces[i], triangulated); + memcpy(surf->tris[i+quadCount].sides, triangulated->sides, sizeof(OBJFaceSide) * 3); + memcpy(surf->tris[i+quadCount+1].sides, (triangulated+1)->sides, sizeof(OBJFaceSide) * 3); + delete[] triangulated; + quadCount += 1; + } + } +} + +int FOBJModel::FindFrame(const char* name) +{ + return 0; // OBJs are not animated. +} + +// Stubs - I don't know what these do exactly +void FOBJModel::RenderFrame(FModelRenderer *renderer, FTexture * skin, int frameno, int frameno2, double inter, int translation) +{ + /* + for (int i = 0; i < numMtls; i++) + { + renderer->SetMaterial(skin, false, translation); + GetVertexBuffer(renderer)->SetupFrame(renderer, surf->vindex + frameno * surf->numVertices, surf->vindex + frameno2 * surf->numVertices, surf->numVertices); + renderer->DrawElements(surf->numTriangles * 3, surf->iindex * sizeof(unsigned int)); + } + */ +} + +void FOBJModel::BuildVertexBuffer(FModelRenderer *renderer) +{ + +} + +void FOBJModel::AddSkins(uint8_t* hitlist) +{ + +} + +FOBJModel::~FOBJModel() +{ + for (size_t i = 0; i < surfaces.Size(); i++) + { + delete[] surfaces[i].tris; + } +} diff --git a/src/r_data/models/models_obj.h b/src/r_data/models/models_obj.h new file mode 100644 index 0000000000..6fa2034c2d --- /dev/null +++ b/src/r_data/models/models_obj.h @@ -0,0 +1,94 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2018 Kevin Caccamo +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// + +#ifndef __GL_MODELS_OBJ_H__ +#define __GL_MODELS_OBJ_H__ + +#include "models.h" +#include "sc_man.h" + +class FOBJModel : public FModel +{ +private: + int mLumpNum; + int numMtls; + + enum class FaceElement + { + VertexIndex, + UVIndex, + VNormalIndex + }; + + struct OBJFaceSide + { + int vertref; + int normref; + int uvref; + }; + struct OBJFace + { + int sideCount; + OBJFaceSide sides[4]; + }; + struct OBJTriangle + { + int vertref[3]; + }; + struct OBJSurface // 1 surface per 'usemtl' + { + int numTris; // Number of triangulated faces + int numFaces; // Number of faces + int faceStart; // Index of first face in faces array + OBJFace* tris; // Triangles + FTextureID skin; + OBJSurface(FTextureID skin): numTris(0), numFaces(0), faceStart(0), tris(nullptr), skin(skin) {} + }; + + TArray verts; + TArray norms; + TArray uvs; + TArray faces; + TArray surfaces; + FTextureID curMtl; + OBJSurface* curSurface; + int aggSurfFaceCount; + int curSurfFaceCount; + FScanner sc; + + void ParseVector2(TArray &array); + void ParseVector3(TArray &array); + void ParseFaceSide(const FString &side, OBJFace &face, int sidx); + void ConstructSurfaceTris(OBJSurface *surf); + int ResolveIndex(int origIndex, FaceElement el); + void TriangulateQuad(const OBJFace &quad, OBJFace *tris); +public: + FOBJModel(): curSurface(nullptr), aggSurfFaceCount(0), curSurfFaceCount(0) {} + ~FOBJModel(); + bool Load(const char* fn, int lumpnum, const char* buffer, int length) override; + int FindFrame(const char* name) override; + void RenderFrame(FModelRenderer* renderer, FTexture* skin, int frame, int frame2, double inter, int translation=0) override; + void BuildVertexBuffer(FModelRenderer* renderer) override; + void AddSkins(uint8_t* hitlist) override; +}; + +#endif From bb8c66b3a0a0d68a667e2779b6307fe791a80dfd Mon Sep 17 00:00:00 2001 From: Kevin Caccamo Date: Sun, 3 Jun 2018 04:11:38 -0400 Subject: [PATCH 14/16] Fix rendering and parsing of OBJ models Create a new surface for each 'usemtl' statement in the OBJ file, and fix memory errors caused by TriangulateQuad. Calculate missing normals, and fix incorrect UV coordinates Fix construction of vertex buffer for objects with multiple surfaces Localize curMtl, curSurface, aggSurfFaceCount, and curSurfFaceCount to FOBJModel::Load(), since they are not used anywhere else. Fix parsing of OBJs without UV references Internally, I replaced hashtag line comments with C-style line comments, and I replaced each forward slash with newSideSep. If no UV coordinates are available, add a default vector of (0,0). Also, remove "this->" from ResolveIndex to make the code a bit cleaner, and fix a minor garbage issue I failed to notice earlier (normref would pick up garbage if there was no normal reference). Ensure usemtl statements remain intact It may be a bit inefficient, but I tried modifying the buffer directly, and I got memory corruption errors. In this case, it's a lot better to be safe than sorry. --- src/r_data/models/models_obj.cpp | 721 +++++++++++++++++++------------ src/r_data/models/models_obj.h | 103 ++--- 2 files changed, 490 insertions(+), 334 deletions(-) diff --git a/src/r_data/models/models_obj.cpp b/src/r_data/models/models_obj.cpp index e754d2daa6..65abfd7c2d 100644 --- a/src/r_data/models/models_obj.cpp +++ b/src/r_data/models/models_obj.cpp @@ -23,322 +23,485 @@ #include "cmdlib.h" #include "r_data/models/models_obj.h" +/** + * Load an OBJ model + * + * @param fn The path to the model file + * @param lumpnum The lump index in the wad collection + * @param buffer The contents of the model file + * @param length The size of the model file + */ bool FOBJModel::Load(const char* fn, int lumpnum, const char* buffer, int length) { - FString objName = Wads.GetLumpFullPath(lumpnum); - sc.OpenMem(objName, buffer, length); - Printf("Parsing %s\n", objName.GetChars()); - while(sc.GetString()) - { - if (sc.Compare("#")) // Line comment - { - sc.Line += 1; // I don't think this does anything, though... - } - else if (sc.Compare("v")) // Vertex - { - ParseVector3(this->verts); - } - else if (sc.Compare("vn")) // Vertex normal - { - ParseVector3(this->norms); - } - else if (sc.Compare("vt")) // UV Coordinates - { - ParseVector2(this->uvs); - } - else if (sc.Compare("usemtl")) - { - // Get material name and try to load it - sc.MustGetString(); + FString objName = Wads.GetLumpFullPath(lumpnum); + FString objBuf(buffer, length); - curMtl = LoadSkin("", sc.String); - if (!curMtl.isValid()) - { - // Relative to model file path? - curMtl = LoadSkin(fn, sc.String); - } + // Do some replacements before we parse the OBJ string + { + // Ensure usemtl statements remain intact + TArray mtlUsages; + TArray mtlUsageIdxs; + long bpos = 0, nlpos = 0, slashpos = 0; + while (1) + { + bpos = objBuf.IndexOf("\nusemtl", bpos); + if (bpos == -1) break; + slashpos = objBuf.IndexOf('/', bpos); + nlpos = objBuf.IndexOf('\n', ++bpos); + if (slashpos > nlpos || slashpos == -1) + { + continue; + } + if (nlpos == -1) + { + nlpos = objBuf.Len(); + } + FString lineStr(objBuf.GetChars() + bpos, nlpos - bpos); + mtlUsages.Push(lineStr); + mtlUsageIdxs.Push(bpos); + } - if (!curMtl.isValid()) - { - // Don't use materials that don't exist - continue; - } - // Most OBJs are sorted by material, but just in case the one we're loading isn't... - // Build surface... - if (curSurface == nullptr) - { - // First surface - curSurface = new OBJSurface(curMtl); - } - else - { - if (curSurfFaceCount > 0) - { - // Search for existing surface with current material - /* - for(size_t i = 0; i < surfaces.Size(); i++) - { - if (surfaces[i].skin == curMtl) - { - curSurface = &surfaces[i]; - surfExists = true; - } - } - */ - // Add previous surface - curSurface->numFaces = curSurfFaceCount; - curSurface->faceStart = aggSurfFaceCount; - surfaces.Push(*curSurface); - delete curSurface; - // Go to next surface - curSurface = new OBJSurface(curMtl); - aggSurfFaceCount += curSurfFaceCount; - } - else - { - curSurface->skin = curMtl; - } - } - curSurfFaceCount = 0; - } - else if (sc.Compare("f")) - { - FString sides[4]; - OBJFace face; - for (int i = 0; i < 3; i++) - { - // A face must have at least 3 sides - sc.MustGetString(); - sides[i] = sc.String; - ParseFaceSide(sides[i], face, i); - } - face.sideCount = 3; - if (sc.GetString()) - { - if (!sc.Compare("f")) - { - sides[3] = sc.String; - face.sideCount += 1; - ParseFaceSide(sides[3], face, 3); - } - else - { - sc.UnGet(); // No 4th side, move back - } - } - faces.Push(face); - curSurfFaceCount += 1; - } - } - sc.Close(); + // Replace forward slashes with percent signs so they aren't parsed as line comments + objBuf.ReplaceChars('/', *newSideSep); - // No valid materials detected - if (curSurface == nullptr) - { - FTextureID dummyMtl = LoadSkin("", "-NOFLAT-"); // Built-in to GZDoom - curSurface = new OBJSurface(dummyMtl); - } - curSurface->numFaces = curSurfFaceCount; - curSurface->faceStart = aggSurfFaceCount; - surfaces.Push(*curSurface); - delete curSurface; + // Substitute broken usemtl statements with old ones + bpos = 0, nlpos = 0; + for (size_t i = 0; i < mtlUsages.Size(); i++) + { + bpos = mtlUsageIdxs[i]; + nlpos = objBuf.IndexOf('\n', bpos); + if (nlpos == -1) + { + nlpos = objBuf.Len(); + } + FString lineStr(objBuf.GetChars() + bpos, nlpos - bpos); + objBuf.Substitute(lineStr, mtlUsages[i]); + } - /* - Printf("%d vertices\n", verts.Size()); - Printf("%d normals\n", norms.Size()); - Printf("%d UVs\n", uvs.Size()); - Printf("%d faces\n", faces.Size()); - Printf("%d surfaces\n", surfaces.Size()); - */ + // Find each OBJ line comment, and convert each to a C-style line comment + while (1) + { + bpos = objBuf.IndexOf('#'); + if (bpos == -1) break; + objBuf.Remove(bpos, 1); + objBuf.Insert(bpos, "//", 2); + } + } + sc.OpenString(objName, objBuf); + //Printf("Parsing %s\n", objName.GetChars()); - mLumpNum = lumpnum; - for (size_t i = 0; i < surfaces.Size(); i++) - { - ConstructSurfaceTris(&surfaces[i]); - } - return true; + FTextureID curMtl = FNullTextureID(); + OBJSurface *curSurface = nullptr; + int aggSurfFaceCount = 0; + int curSurfFaceCount = 0; + + while(sc.GetString()) + { + if /*(sc.Compare("#")) // Line comment + { + sc.Line += 1; // I don't think this does anything, though... + } + else if*/ (sc.Compare("v")) // Vertex + { + ParseVector3(this->verts); + } + else if (sc.Compare("vn")) // Vertex normal + { + ParseVector3(this->norms); + } + else if (sc.Compare("vt")) // UV Coordinates + { + ParseVector2(this->uvs); + } + else if (sc.Compare("usemtl")) + { + // Get material name and try to load it + sc.MustGetString(); + + curMtl = LoadSkin("", sc.String); + if (!curMtl.isValid()) + { + // Relative to model file path? + curMtl = LoadSkin(fn, sc.String); + } + + // Build surface... + if (curSurface == nullptr) + { + // First surface + curSurface = new OBJSurface(curMtl); + } + else + { + if (curSurfFaceCount > 0) + { + // Add previous surface + curSurface->numFaces = curSurfFaceCount; + curSurface->faceStart = aggSurfFaceCount; + surfaces.Push(*curSurface); + delete curSurface; + // Go to next surface + curSurface = new OBJSurface(curMtl); + aggSurfFaceCount += curSurfFaceCount; + } + else + { + curSurface->skin = curMtl; + } + } + curSurfFaceCount = 0; + } + else if (sc.Compare("f")) + { + FString sides[4]; + OBJFace face; + for (int i = 0; i < 3; i++) + { + // A face must have at least 3 sides + sc.MustGetString(); + sides[i] = sc.String; + ParseFaceSide(sides[i], face, i); + } + face.sideCount = 3; + if (sc.GetString()) + { + if (!sc.Compare("f") && FString(sc.String).IndexOfAny("-0123456789") == 0) + { + sides[3] = sc.String; + face.sideCount += 1; + ParseFaceSide(sides[3], face, 3); + } + else + { + sc.UnGet(); // No 4th side, move back + } + } + faces.Push(face); + curSurfFaceCount += 1; + } + } + sc.Close(); + + if (curSurface == nullptr) + { // No valid materials detected + FTextureID dummyMtl = LoadSkin("", "-NOFLAT-"); // Built-in to GZDoom + curSurface = new OBJSurface(dummyMtl); + } + curSurface->numFaces = curSurfFaceCount; + curSurface->faceStart = aggSurfFaceCount; + surfaces.Push(*curSurface); + delete curSurface; + + if (uvs.Size() == 0) + { // Needed so that OBJs without UVs can work + uvs.Push(FVector2(0.0, 0.0)); + } + + /* + Printf("%d vertices\n", verts.Size()); + Printf("%d normals\n", norms.Size()); + Printf("%d UVs\n", uvs.Size()); + Printf("%d faces\n", faces.Size()); + Printf("%d surfaces\n", surfaces.Size()); + */ + + mLumpNum = lumpnum; + return true; } /** - * Parse a 2D vector - * - * @param start The buffer to parse from - * @param array The array to append the parsed vector to - */ + * Parse a 2D vector + * + * @param start The buffer to parse from + * @param array The array to append the parsed vector to + */ void FOBJModel::ParseVector2(TArray &array) { - float coord[2]; - for (int axis = 0; axis < 2; axis++) - { - sc.MustGetFloat(); - coord[axis] = (float)sc.Float; - } - FVector2 vec(coord); - array.Push(vec); + float coord[2]; + for (int axis = 0; axis < 2; axis++) + { + sc.MustGetFloat(); + coord[axis] = (float)sc.Float; + } + FVector2 vec(coord); + array.Push(vec); } /** - * Parse a 3D vector - * - * @param start The buffer to parse from - * @param array The array to append the parsed vector to - */ + * Parse a 3D vector + * + * @param start The buffer to parse from + * @param array The array to append the parsed vector to + */ void FOBJModel::ParseVector3(TArray &array) { - float coord[3]; - for (int axis = 0; axis < 3; axis++) - { - sc.MustGetFloat(); - coord[axis] = (float)sc.Float; - } - FVector3 vec(coord); - array.Push(vec); + float coord[3]; + for (int axis = 0; axis < 3; axis++) + { + sc.MustGetFloat(); + coord[axis] = (float)sc.Float; + } + FVector3 vec(coord); + array.Push(vec); } void FOBJModel::ParseFaceSide(const FString &sideStr, OBJFace &face, int sidx) { - OBJFaceSide side; - int origIdx; - if (sideStr.IndexOf("/") >= 0) - { - TArray sides = sideStr.Split("/"); - if (sides[0].Len() > 0) - { - origIdx = atoi(sides[0].GetChars()); - side.vertref = ResolveIndex(origIdx, FaceElement::VertexIndex); - } - if (sides[1].Len() > 0) - { - origIdx = atoi(sides[1].GetChars()); - side.uvref = ResolveIndex(origIdx, FaceElement::UVIndex); - } - if (sides.Size() > 2) - { - if (sides[2].Len() > 0) - { - origIdx = atoi(sides[2].GetChars()); - side.normref = ResolveIndex(origIdx, FaceElement::VNormalIndex); - } - } - } - else - { - origIdx = atoi(sideStr.GetChars()); - side.vertref = ResolveIndex(origIdx, FaceElement::VertexIndex); - side.normref = 0; - side.uvref = 0; - } - face.sides[sidx] = side; + OBJFaceSide side; + int origIdx; + if (sideStr.IndexOf(newSideSep) >= 0) + { + TArray sides = sideStr.Split(newSideSep, FString::TOK_KEEPEMPTY); + + if (sides[0].Len() > 0) + { + origIdx = atoi(sides[0].GetChars()); + side.vertref = ResolveIndex(origIdx, FaceElement::VertexIndex); + } + else + { + sc.ScriptError("Vertex reference is not optional!"); + } + + if (sides[1].Len() > 0) + { + origIdx = atoi(sides[1].GetChars()); + side.uvref = ResolveIndex(origIdx, FaceElement::UVIndex); + } + else + { + side.uvref = -1; + } + + if (sides.Size() > 2) + { + if (sides[2].Len() > 0) + { + origIdx = atoi(sides[2].GetChars()); + side.normref = ResolveIndex(origIdx, FaceElement::VNormalIndex); + } + else + { + side.normref = -1; + } + } + else + { + side.normref = -1; + } + } + else + { + origIdx = atoi(sideStr.GetChars()); + side.vertref = ResolveIndex(origIdx, FaceElement::VertexIndex); + side.normref = -1; + side.uvref = -1; + } + face.sides[sidx] = side; } int FOBJModel::ResolveIndex(int origIndex, FaceElement el) { - if (origIndex > 0) - { - return origIndex - 1; // OBJ indices start at 1 - } - else if (origIndex < 0) - { - if (el == FaceElement::VertexIndex) - { - return this->verts.Size() + origIndex; // origIndex is negative - } - else if (el == FaceElement::UVIndex) - { - return this->uvs.Size() + origIndex; - } - else if (el == FaceElement::VNormalIndex) - { - return this->norms.Size() + origIndex; - } - } - return 0; -} - -void FOBJModel::TriangulateQuad(const OBJFace &quad, OBJFace *tris) -{ - // if (quad.sides < 3 || quad.sides > 4) exception - // if (quad.sides == 3) return &quad; - tris[0].sideCount = 3; - tris[1].sideCount = 3; - - for (int i = 0; i < 3; i++) - { - tris[0].sides[i].vertref = quad.sides[i].vertref; - tris[1].sides[i].vertref = quad.sides[i+1].vertref; - tris[0].sides[i].uvref = quad.sides[i].uvref; - tris[1].sides[i].uvref = quad.sides[i+1].uvref; - tris[0].sides[i].normref = quad.sides[i].normref; - tris[1].sides[i].normref = quad.sides[i+1].normref; - } -} - -void FOBJModel::ConstructSurfaceTris(OBJSurface *surf) -{ - int triCount = 0; - - size_t start = surf->faceStart; - size_t end = start + surf->numFaces; - for (size_t i = start; i < end; i++) - { - triCount += (faces[i].sideCount > 3) ? 2 : 1; - } - - surf->numTris = triCount; - surf->tris = new OBJFace[triCount]; - - int quadCount = 0; - for (size_t i = start; i < end; i++) - { - surf->tris[i+quadCount].sideCount = 3; - if (faces[i].sideCount == 3) - { - memcpy(surf->tris[i+quadCount].sides, faces[i].sides, sizeof(OBJFaceSide) * 3); - } - else if (faces[i].sideCount == 4) - { - OBJFace *triangulated = new OBJFace[2]; - TriangulateQuad(faces[i], triangulated); - memcpy(surf->tris[i+quadCount].sides, triangulated->sides, sizeof(OBJFaceSide) * 3); - memcpy(surf->tris[i+quadCount+1].sides, (triangulated+1)->sides, sizeof(OBJFaceSide) * 3); - delete[] triangulated; - quadCount += 1; - } - } -} - -int FOBJModel::FindFrame(const char* name) -{ - return 0; // OBJs are not animated. -} - -// Stubs - I don't know what these do exactly -void FOBJModel::RenderFrame(FModelRenderer *renderer, FTexture * skin, int frameno, int frameno2, double inter, int translation) -{ - /* - for (int i = 0; i < numMtls; i++) - { - renderer->SetMaterial(skin, false, translation); - GetVertexBuffer(renderer)->SetupFrame(renderer, surf->vindex + frameno * surf->numVertices, surf->vindex + frameno2 * surf->numVertices, surf->numVertices); - renderer->DrawElements(surf->numTriangles * 3, surf->iindex * sizeof(unsigned int)); - } - */ + if (origIndex > 0) + { + return origIndex - 1; // OBJ indices start at 1 + } + else if (origIndex < 0) + { + if (el == FaceElement::VertexIndex) + { + return verts.Size() + origIndex; // origIndex is negative + } + else if (el == FaceElement::UVIndex) + { + return uvs.Size() + origIndex; + } + else if (el == FaceElement::VNormalIndex) + { + return norms.Size() + origIndex; + } + } + return -1; } void FOBJModel::BuildVertexBuffer(FModelRenderer *renderer) { + if (GetVertexBuffer(renderer)) + { + return; + } + unsigned int vbufsize = 0; + + for (size_t i = 0; i < surfaces.Size(); i++) + { + ConstructSurfaceTris(surfaces[i]); + surfaces[i].vbStart = vbufsize; + vbufsize += surfaces[i].numTris * 3; + } + + auto vbuf = renderer->CreateVertexBuffer(false,true); + SetVertexBuffer(renderer, vbuf); + + FModelVertex *vertptr = vbuf->LockVertexBuffer(vbufsize); + + for (size_t i = 0; i < surfaces.Size(); i++) + { + for (size_t j = 0; j < surfaces[i].numTris; j++) + { + for (size_t side = 0; side < 3; side++) + { + FModelVertex *mdv = vertptr + + side + j * 3 + // Current surface and previous triangles + surfaces[i].vbStart; // Previous surfaces + + OBJFaceSide &curSide = surfaces[i].tris[j].sides[side]; + + int vidx = curSide.vertref; + int uvidx = (curSide.uvref >= 0 && curSide.uvref < uvs.Size()) ? curSide.uvref : 0; + int nidx = curSide.normref; + + mdv->Set(verts[vidx].X, verts[vidx].Y, verts[vidx].Z, uvs[uvidx].X, uvs[uvidx].Y * -1); + + if (nidx >= 0 && nidx < norms.Size()) + { + mdv->SetNormal(norms[nidx].X, norms[nidx].Y, norms[nidx].Z); + } + else + { + // https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal + // Find other sides of triangle + int nextSidx = side + 1; + if (nextSidx >= 3) nextSidx -= 3; + + int lastSidx = side + 2; + if (lastSidx >= 3) lastSidx -= 3; + + OBJFaceSide &nextSide = surfaces[i].tris[j].sides[nextSidx]; + OBJFaceSide &lastSide = surfaces[i].tris[j].sides[lastSidx]; + + // Cross-multiply the U-vector and V-vector + FVector3 uvec = verts[nextSide.vertref] - verts[curSide.vertref]; + FVector3 vvec = verts[lastSide.vertref] - verts[curSide.vertref]; + + FVector3 nvec = uvec ^ vvec; + mdv->SetNormal(nvec.X, nvec.Y, nvec.Z); + } + } + } + } + + vbuf->UnlockVertexBuffer(); +} + +void FOBJModel::ConstructSurfaceTris(OBJSurface &surf) +{ + int triCount = 0; + + size_t start = surf.faceStart; + size_t end = start + surf.numFaces; + for (size_t i = start; i < end; i++) + { + triCount += faces[i].sideCount - 2; + } + + surf.numTris = triCount; + surf.tris = new OBJFace[triCount]; + + for (size_t i = start, triIdx = 0; i < end; i++, triIdx++) + { + surf.tris[triIdx].sideCount = 3; + if (faces[i].sideCount == 3) + { + memcpy(surf.tris[triIdx].sides, faces[i].sides, sizeof(OBJFaceSide) * 3); + } + else if (faces[i].sideCount == 4) // Triangulate face + { + OBJFace *triangulated = new OBJFace[2]; + TriangulateQuad(faces[i], triangulated); + memcpy(surf.tris[triIdx].sides, triangulated[0].sides, sizeof(OBJFaceSide) * 3); + memcpy(surf.tris[triIdx+1].sides, triangulated[1].sides, sizeof(OBJFaceSide) * 3); + delete[] triangulated; + triIdx += 1; // Filling out two faces + } + } +} + +void FOBJModel::TriangulateQuad(const OBJFace &quad, OBJFace *tris) +{ + tris[0].sideCount = 3; + tris[1].sideCount = 3; + + int tsidx[2][3] = {{0, 1, 3}, {1, 2, 3}}; + + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 2; j++) + { + tris[j].sides[i].vertref = quad.sides[tsidx[j][i]].vertref; + tris[j].sides[i].uvref = quad.sides[tsidx[j][i]].uvref; + tris[j].sides[i].normref = quad.sides[tsidx[j][i]].normref; + } + } +} + +int FOBJModel::FindFrame(const char* name) +{ + return 0; // OBJs are not animated. +} + +void FOBJModel::RenderFrame(FModelRenderer *renderer, FTexture * skin, int frameno, int frameno2, double inter, int translation) +{ + for (unsigned int i = 0; i < surfaces.Size(); i++) + { + OBJSurface *surf = &surfaces[i]; + + FTexture *userSkin = skin; + if (!userSkin) + { + if (curSpriteMDLFrame->surfaceskinIDs[curMDLIndex][i].isValid()) + { + userSkin = TexMan(curSpriteMDLFrame->surfaceskinIDs[curMDLIndex][i]); + } + else if (surf->skin.isValid()) + { + userSkin = TexMan(surf->skin); + } + } + if (!userSkin) return; + + renderer->SetMaterial(userSkin, false, translation); + GetVertexBuffer(renderer)->SetupFrame(renderer, 0, 0, surf->numTris * 3); + renderer->DrawArrays(surf->vbStart, surf->numTris * 3); + } } void FOBJModel::AddSkins(uint8_t* hitlist) { + for (size_t i = 0; i < surfaces.Size(); i++) + { + if (curSpriteMDLFrame->surfaceskinIDs[curMDLIndex][i].isValid()) + { + hitlist[curSpriteMDLFrame->surfaceskinIDs[curMDLIndex][i].GetIndex()] |= FTextureManager::HIT_Flat; + } + OBJSurface * surf = &surfaces[i]; + if (surf->skin.isValid()) + { + hitlist[surf->skin.GetIndex()] |= FTextureManager::HIT_Flat; + } + } } FOBJModel::~FOBJModel() { - for (size_t i = 0; i < surfaces.Size(); i++) - { - delete[] surfaces[i].tris; - } + verts.Clear(); + norms.Clear(); + uvs.Clear(); + faces.Clear(); + for (size_t i = 0; i < surfaces.Size(); i++) + { + delete[] surfaces[i].tris; + } + surfaces.Clear(); } diff --git a/src/r_data/models/models_obj.h b/src/r_data/models/models_obj.h index 6fa2034c2d..76af2e649f 100644 --- a/src/r_data/models/models_obj.h +++ b/src/r_data/models/models_obj.h @@ -29,66 +29,59 @@ class FOBJModel : public FModel { private: - int mLumpNum; - int numMtls; + int mLumpNum; + const char *newSideSep = "$"; // OBJ side separator is /, which is parsed as a line comment by FScanner if two of them are next to each other. - enum class FaceElement - { - VertexIndex, - UVIndex, - VNormalIndex - }; + enum class FaceElement + { + VertexIndex, + UVIndex, + VNormalIndex + }; - struct OBJFaceSide - { - int vertref; - int normref; - int uvref; - }; - struct OBJFace - { - int sideCount; - OBJFaceSide sides[4]; - }; - struct OBJTriangle - { - int vertref[3]; - }; - struct OBJSurface // 1 surface per 'usemtl' - { - int numTris; // Number of triangulated faces - int numFaces; // Number of faces - int faceStart; // Index of first face in faces array - OBJFace* tris; // Triangles - FTextureID skin; - OBJSurface(FTextureID skin): numTris(0), numFaces(0), faceStart(0), tris(nullptr), skin(skin) {} - }; + struct OBJFaceSide + { + int vertref; + int normref; + int uvref; + }; + struct OBJFace + { + int sideCount; + OBJFaceSide sides[4]; + }; + struct OBJSurface // 1 surface per 'usemtl' + { + unsigned int numTris; // Number of triangulated faces + unsigned int numFaces; // Number of faces + unsigned int vbStart; // First index in vertex buffer + unsigned int faceStart; // Index of first face in faces array + OBJFace* tris; // Triangles + FTextureID skin; + OBJSurface(FTextureID skin): numTris(0), numFaces(0), vbStart(0), faceStart(0), tris(nullptr), skin(skin) {} + }; - TArray verts; - TArray norms; - TArray uvs; - TArray faces; - TArray surfaces; - FTextureID curMtl; - OBJSurface* curSurface; - int aggSurfFaceCount; - int curSurfFaceCount; - FScanner sc; + TArray verts; + TArray norms; + TArray uvs; + TArray faces; + TArray surfaces; + FScanner sc; - void ParseVector2(TArray &array); - void ParseVector3(TArray &array); - void ParseFaceSide(const FString &side, OBJFace &face, int sidx); - void ConstructSurfaceTris(OBJSurface *surf); - int ResolveIndex(int origIndex, FaceElement el); - void TriangulateQuad(const OBJFace &quad, OBJFace *tris); + void ParseVector2(TArray &array); + void ParseVector3(TArray &array); + void ParseFaceSide(const FString &side, OBJFace &face, int sidx); + void ConstructSurfaceTris(OBJSurface &surf); + int ResolveIndex(int origIndex, FaceElement el); + void TriangulateQuad(const OBJFace &quad, OBJFace *tris); public: - FOBJModel(): curSurface(nullptr), aggSurfFaceCount(0), curSurfFaceCount(0) {} - ~FOBJModel(); - bool Load(const char* fn, int lumpnum, const char* buffer, int length) override; - int FindFrame(const char* name) override; - void RenderFrame(FModelRenderer* renderer, FTexture* skin, int frame, int frame2, double inter, int translation=0) override; - void BuildVertexBuffer(FModelRenderer* renderer) override; - void AddSkins(uint8_t* hitlist) override; + FOBJModel() {} + ~FOBJModel(); + bool Load(const char* fn, int lumpnum, const char* buffer, int length) override; + int FindFrame(const char* name) override; + void RenderFrame(FModelRenderer* renderer, FTexture* skin, int frame, int frame2, double inter, int translation=0) override; + void BuildVertexBuffer(FModelRenderer* renderer) override; + void AddSkins(uint8_t* hitlist) override; }; #endif From a38b0813cf0b2c52ccf93da86b59ee311f875843 Mon Sep 17 00:00:00 2001 From: Kevin Caccamo Date: Wed, 6 Jun 2018 01:08:05 -0400 Subject: [PATCH 15/16] Fix OBJ rendering in software renderers, and other things Pass surf->vbStart to SetupFrame instead of 0, and pass 0 to DrawArrays instead of surf->vbStart. Use a potentially faster method of modifying the OBJ file text buffer by modifying it directly. Add RealignVector and FixUV methods to begin work on re-aligning OBJ models to the same orientation as MD3 models. Re-align OBJ models to match MD3 models Fix normal calculation for re-aligned OBJs Ensure AddSkins does not go out of bounds of surfaceskinIDs Do not precache skins that were replaced by the user. Fix OBJs with a large number of materials not being fully rendered Print a warning message if a material referenced by the OBJ could not be found. Free surface triangles once they are no longer needed Also, use continue instead of return so that surfaces after those with missing materials are still rendered. Fail if a face side has no vertex reference. Vertex references are required for a valid OBJ. Clean up OBJ model code Remove commented code, mainly Printf's that aren't used any more. Add more documentation comments, and tweak existing documentation comments Replace ParseVector2 and ParseVector3 with a template ParseVector function --- src/r_data/models/models.cpp | 1 + src/r_data/models/models_obj.cpp | 231 +++++++++++++++++++++---------- src/r_data/models/models_obj.h | 10 +- 3 files changed, 162 insertions(+), 80 deletions(-) diff --git a/src/r_data/models/models.cpp b/src/r_data/models/models.cpp index fefbb88a06..690d7c75f7 100644 --- a/src/r_data/models/models.cpp +++ b/src/r_data/models/models.cpp @@ -942,3 +942,4 @@ bool IsHUDModelForPlayerAvailable (player_t * player) FSpriteModelFrame *smf = FindModelFrame(player->ReadyWeapon->GetClass(), state->sprite, state->GetFrame(), false); return ( smf != nullptr ); } + diff --git a/src/r_data/models/models_obj.cpp b/src/r_data/models/models_obj.cpp index 65abfd7c2d..a3124539b0 100644 --- a/src/r_data/models/models_obj.cpp +++ b/src/r_data/models/models_obj.cpp @@ -20,7 +20,6 @@ //-------------------------------------------------------------------------- #include "w_wad.h" -#include "cmdlib.h" #include "r_data/models/models_obj.h" /** @@ -30,6 +29,7 @@ * @param lumpnum The lump index in the wad collection * @param buffer The contents of the model file * @param length The size of the model file + * @return Whether or not the model was parsed successfully */ bool FOBJModel::Load(const char* fn, int lumpnum, const char* buffer, int length) { @@ -63,9 +63,9 @@ bool FOBJModel::Load(const char* fn, int lumpnum, const char* buffer, int length // Replace forward slashes with percent signs so they aren't parsed as line comments objBuf.ReplaceChars('/', *newSideSep); + char* wObjBuf = objBuf.LockBuffer(); // Substitute broken usemtl statements with old ones - bpos = 0, nlpos = 0; for (size_t i = 0; i < mtlUsages.Size(); i++) { bpos = mtlUsageIdxs[i]; @@ -74,21 +74,30 @@ bool FOBJModel::Load(const char* fn, int lumpnum, const char* buffer, int length { nlpos = objBuf.Len(); } - FString lineStr(objBuf.GetChars() + bpos, nlpos - bpos); - objBuf.Substitute(lineStr, mtlUsages[i]); + memcpy(wObjBuf + bpos, mtlUsages[i].GetChars(), nlpos - bpos); } + bpos = 0; // Find each OBJ line comment, and convert each to a C-style line comment while (1) { - bpos = objBuf.IndexOf('#'); + bpos = objBuf.IndexOf('#', bpos); if (bpos == -1) break; - objBuf.Remove(bpos, 1); - objBuf.Insert(bpos, "//", 2); + if (objBuf[(unsigned int)bpos + 1] == '\n') + { + wObjBuf[bpos] = ' '; + } + else + { + wObjBuf[bpos] = '/'; + wObjBuf[bpos+1] = '/'; + } + bpos += 1; } + wObjBuf = nullptr; + objBuf.UnlockBuffer(); } sc.OpenString(objName, objBuf); - //Printf("Parsing %s\n", objName.GetChars()); FTextureID curMtl = FNullTextureID(); OBJSurface *curSurface = nullptr; @@ -97,21 +106,17 @@ bool FOBJModel::Load(const char* fn, int lumpnum, const char* buffer, int length while(sc.GetString()) { - if /*(sc.Compare("#")) // Line comment + if (sc.Compare("v")) // Vertex { - sc.Line += 1; // I don't think this does anything, though... - } - else if*/ (sc.Compare("v")) // Vertex - { - ParseVector3(this->verts); + ParseVector(this->verts); } else if (sc.Compare("vn")) // Vertex normal { - ParseVector3(this->norms); + ParseVector(this->norms); } else if (sc.Compare("vt")) // UV Coordinates { - ParseVector2(this->uvs); + ParseVector(this->uvs); } else if (sc.Compare("usemtl")) { @@ -125,6 +130,11 @@ bool FOBJModel::Load(const char* fn, int lumpnum, const char* buffer, int length curMtl = LoadSkin(fn, sc.String); } + if (!curMtl.isValid()) + { + sc.ScriptMessage("Material %s (#%u) not found.", sc.String, surfaces.Size()); + } + // Build surface... if (curSurface == nullptr) { @@ -160,7 +170,7 @@ bool FOBJModel::Load(const char* fn, int lumpnum, const char* buffer, int length // A face must have at least 3 sides sc.MustGetString(); sides[i] = sc.String; - ParseFaceSide(sides[i], face, i); + if (!ParseFaceSide(sides[i], face, i)) return false; } face.sideCount = 3; if (sc.GetString()) @@ -169,7 +179,7 @@ bool FOBJModel::Load(const char* fn, int lumpnum, const char* buffer, int length { sides[3] = sc.String; face.sideCount += 1; - ParseFaceSide(sides[3], face, 3); + if (!ParseFaceSide(sides[3], face, 3)) return false; } else { @@ -197,55 +207,38 @@ bool FOBJModel::Load(const char* fn, int lumpnum, const char* buffer, int length uvs.Push(FVector2(0.0, 0.0)); } - /* - Printf("%d vertices\n", verts.Size()); - Printf("%d normals\n", norms.Size()); - Printf("%d UVs\n", uvs.Size()); - Printf("%d faces\n", faces.Size()); - Printf("%d surfaces\n", surfaces.Size()); - */ - - mLumpNum = lumpnum; return true; } /** - * Parse a 2D vector - * - * @param start The buffer to parse from - * @param array The array to append the parsed vector to - */ -void FOBJModel::ParseVector2(TArray &array) + * Parse an x-Dimensional vector + * + * @tparam T A subclass of TVector2 to be used + * @tparam L The length of the vector to parse + * @param[out] array The array to append the parsed vector to + */ +template void FOBJModel::ParseVector(TArray &array) { - float coord[2]; - for (int axis = 0; axis < 2; axis++) + float *coord = new float[L]; + for (size_t axis = 0; axis < L; axis++) { sc.MustGetFloat(); coord[axis] = (float)sc.Float; } - FVector2 vec(coord); + T vec(coord); array.Push(vec); + delete[] coord; } /** - * Parse a 3D vector - * - * @param start The buffer to parse from - * @param array The array to append the parsed vector to - */ -void FOBJModel::ParseVector3(TArray &array) -{ - float coord[3]; - for (int axis = 0; axis < 3; axis++) - { - sc.MustGetFloat(); - coord[axis] = (float)sc.Float; - } - FVector3 vec(coord); - array.Push(vec); -} - -void FOBJModel::ParseFaceSide(const FString &sideStr, OBJFace &face, int sidx) + * Parse a side of a face + * + * @param[in] sideStr The side definition string + * @param[out] face The face to assign the parsed side data to + * @param sidx The 0-based index of the side + * @return Whether or not the face side was parsed successfully + */ +bool FOBJModel::ParseFaceSide(const FString &sideStr, OBJFace &face, int sidx) { OBJFaceSide side; int origIdx; @@ -261,6 +254,7 @@ void FOBJModel::ParseFaceSide(const FString &sideStr, OBJFace &face, int sidx) else { sc.ScriptError("Vertex reference is not optional!"); + return false; } if (sides[1].Len() > 0) @@ -298,8 +292,18 @@ void FOBJModel::ParseFaceSide(const FString &sideStr, OBJFace &face, int sidx) side.uvref = -1; } face.sides[sidx] = side; + return true; } +/** + * Resolve an OBJ index to an absolute index + * + * OBJ indices are 1-based, and can also be negative + * + * @param origIndex The original OBJ index to resolve + * @param el What type of element the index references + * @return The absolute index of the element + */ int FOBJModel::ResolveIndex(int origIndex, FaceElement el) { if (origIndex > 0) @@ -324,6 +328,11 @@ int FOBJModel::ResolveIndex(int origIndex, FaceElement el) return -1; } +/** + * Construct the vertex buffer for this model + * + * @param renderer A pointer to the model renderer. Used to allocate the vertex buffer. + */ void FOBJModel::BuildVertexBuffer(FModelRenderer *renderer) { if (GetVertexBuffer(renderer)) @@ -358,45 +367,55 @@ void FOBJModel::BuildVertexBuffer(FModelRenderer *renderer) OBJFaceSide &curSide = surfaces[i].tris[j].sides[side]; int vidx = curSide.vertref; - int uvidx = (curSide.uvref >= 0 && curSide.uvref < uvs.Size()) ? curSide.uvref : 0; + int uvidx = (curSide.uvref >= 0 && (unsigned int)curSide.uvref < uvs.Size()) ? curSide.uvref : 0; int nidx = curSide.normref; - mdv->Set(verts[vidx].X, verts[vidx].Y, verts[vidx].Z, uvs[uvidx].X, uvs[uvidx].Y * -1); + FVector3 curVvec = RealignVector(verts[vidx]); + FVector2 curUvec = FixUV(uvs[uvidx]); + FVector3 *nvec = nullptr; - if (nidx >= 0 && nidx < norms.Size()) + mdv->Set(curVvec.X, curVvec.Y, curVvec.Z, curUvec.X, curUvec.Y); + + if (nidx >= 0 && (unsigned int)nidx < norms.Size()) { - mdv->SetNormal(norms[nidx].X, norms[nidx].Y, norms[nidx].Z); + nvec = new FVector3(RealignVector(norms[nidx])); } else { // https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal // Find other sides of triangle - int nextSidx = side + 1; + int nextSidx = side + 2; if (nextSidx >= 3) nextSidx -= 3; - int lastSidx = side + 2; + int lastSidx = side + 1; if (lastSidx >= 3) lastSidx -= 3; OBJFaceSide &nextSide = surfaces[i].tris[j].sides[nextSidx]; OBJFaceSide &lastSide = surfaces[i].tris[j].sides[lastSidx]; // Cross-multiply the U-vector and V-vector - FVector3 uvec = verts[nextSide.vertref] - verts[curSide.vertref]; - FVector3 vvec = verts[lastSide.vertref] - verts[curSide.vertref]; + FVector3 uvec = RealignVector(verts[nextSide.vertref]) - curVvec; + FVector3 vvec = RealignVector(verts[lastSide.vertref]) - curVvec; - FVector3 nvec = uvec ^ vvec; - mdv->SetNormal(nvec.X, nvec.Y, nvec.Z); + nvec = new FVector3(uvec ^ vvec); } + mdv->SetNormal(nvec->X, nvec->Y, nvec->Z); + delete nvec; } } + delete[] surfaces[i].tris; } - vbuf->UnlockVertexBuffer(); } +/** + * Fill in the triangle data for a surface + * + * @param[in,out] surf The surface to fill in the triangle data for + */ void FOBJModel::ConstructSurfaceTris(OBJSurface &surf) { - int triCount = 0; + unsigned int triCount = 0; size_t start = surf.faceStart; size_t end = start + surf.numFaces; @@ -427,6 +446,12 @@ void FOBJModel::ConstructSurfaceTris(OBJSurface &surf) } } +/** + * Triangulate a 4-sided face + * + * @param[in] quad The 4-sided face to triangulate + * @param[out] tris The resultant triangle data + */ void FOBJModel::TriangulateQuad(const OBJFace &quad, OBJFace *tris) { tris[0].sideCount = 3; @@ -445,11 +470,53 @@ void FOBJModel::TriangulateQuad(const OBJFace &quad, OBJFace *tris) } } +/** + * Re-align a vector to match MD3 alignment + * + * @param vecToRealign The vector to re-align + * @return The re-aligned vector + */ +inline FVector3 FOBJModel::RealignVector(FVector3 vecToRealign) +{ + vecToRealign.Z *= -1; + return vecToRealign; +} + +/** + * Fix UV coordinates of a UV vector + * + * @param vecToRealign The vector to fix + * @return The fixed UV coordinate vector + */ +inline FVector2 FOBJModel::FixUV(FVector2 vecToRealign) +{ + vecToRealign.Y *= -1; + return vecToRealign; +} + +/** + * Find the index of the frame with the given name + * + * OBJ models are not animated, so this always returns 0 + * + * @param name The name of the frame + * @return The index of the frame + */ int FOBJModel::FindFrame(const char* name) { return 0; // OBJs are not animated. } +/** + * Render the model + * + * @param renderer The model renderer + * @param skin The loaded skin for the surface + * @param frameno Unused + * @param frameno2 Unused + * @param inter Unused + * @param translation The translation for the skin + */ void FOBJModel::RenderFrame(FModelRenderer *renderer, FTexture * skin, int frameno, int frameno2, double inter, int translation) { for (unsigned int i = 0; i < surfaces.Size(); i++) @@ -459,7 +526,7 @@ void FOBJModel::RenderFrame(FModelRenderer *renderer, FTexture * skin, int frame FTexture *userSkin = skin; if (!userSkin) { - if (curSpriteMDLFrame->surfaceskinIDs[curMDLIndex][i].isValid()) + if (i < MD3_MAX_SURFACES && curSpriteMDLFrame->surfaceskinIDs[curMDLIndex][i].isValid()) { userSkin = TexMan(curSpriteMDLFrame->surfaceskinIDs[curMDLIndex][i]); } @@ -468,21 +535,36 @@ void FOBJModel::RenderFrame(FModelRenderer *renderer, FTexture * skin, int frame userSkin = TexMan(surf->skin); } } - if (!userSkin) return; + + // Still no skin after checking for one? + if (!userSkin) + { + continue; + } renderer->SetMaterial(userSkin, false, translation); - GetVertexBuffer(renderer)->SetupFrame(renderer, 0, 0, surf->numTris * 3); - renderer->DrawArrays(surf->vbStart, surf->numTris * 3); + GetVertexBuffer(renderer)->SetupFrame(renderer, surf->vbStart, surf->vbStart, surf->numTris * 3); + renderer->DrawArrays(0, surf->numTris * 3); } } +/** + * Pre-cache skins for the model + * + * @param hitlist The list of textures + */ void FOBJModel::AddSkins(uint8_t* hitlist) { for (size_t i = 0; i < surfaces.Size(); i++) { - if (curSpriteMDLFrame->surfaceskinIDs[curMDLIndex][i].isValid()) + if (i < MD3_MAX_SURFACES && curSpriteMDLFrame->surfaceskinIDs[curMDLIndex][i].isValid()) { + // Precache skins manually reassigned by the user. + // On OBJs with lots of skins, such as Doom map OBJs exported from GZDB, + // there may be too many skins for the user to manually change, unless + // the limit is bumped or surfaceskinIDs is changed to a TArray. hitlist[curSpriteMDLFrame->surfaceskinIDs[curMDLIndex][i].GetIndex()] |= FTextureManager::HIT_Flat; + return; // No need to precache skin that was replaced } OBJSurface * surf = &surfaces[i]; @@ -493,15 +575,14 @@ void FOBJModel::AddSkins(uint8_t* hitlist) } } +/** + * Remove the data that was loaded + */ FOBJModel::~FOBJModel() { verts.Clear(); norms.Clear(); uvs.Clear(); faces.Clear(); - for (size_t i = 0; i < surfaces.Size(); i++) - { - delete[] surfaces[i].tris; - } surfaces.Clear(); } diff --git a/src/r_data/models/models_obj.h b/src/r_data/models/models_obj.h index 76af2e649f..d6d68a58eb 100644 --- a/src/r_data/models/models_obj.h +++ b/src/r_data/models/models_obj.h @@ -29,7 +29,6 @@ class FOBJModel : public FModel { private: - int mLumpNum; const char *newSideSep = "$"; // OBJ side separator is /, which is parsed as a line comment by FScanner if two of them are next to each other. enum class FaceElement @@ -47,7 +46,7 @@ private: }; struct OBJFace { - int sideCount; + unsigned int sideCount; OBJFaceSide sides[4]; }; struct OBJSurface // 1 surface per 'usemtl' @@ -68,12 +67,13 @@ private: TArray surfaces; FScanner sc; - void ParseVector2(TArray &array); - void ParseVector3(TArray &array); - void ParseFaceSide(const FString &side, OBJFace &face, int sidx); + template void ParseVector(TArray &array); + bool ParseFaceSide(const FString &side, OBJFace &face, int sidx); void ConstructSurfaceTris(OBJSurface &surf); int ResolveIndex(int origIndex, FaceElement el); void TriangulateQuad(const OBJFace &quad, OBJFace *tris); + FVector3 RealignVector(FVector3 vecToRealign); + FVector2 FixUV(FVector2 vecToRealign); public: FOBJModel() {} ~FOBJModel(); From 55d1613ea4ff5146bc065fdda0ce61318844d6ce Mon Sep 17 00:00:00 2001 From: Kevin Caccamo Date: Tue, 28 Aug 2018 12:04:42 -0400 Subject: [PATCH 16/16] Update usage of LastIndexOf Now that LastIndexOf works properly, update the code that uses it to find OBJ models Also, make UE1 model finder use LastIndexOf --- src/r_data/models/models.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/r_data/models/models.cpp b/src/r_data/models/models.cpp index 690d7c75f7..178aabb6c0 100644 --- a/src/r_data/models/models.cpp +++ b/src/r_data/models/models.cpp @@ -420,7 +420,7 @@ static unsigned FindModel(const char * path, const char * modelfile) FMemLump lumpd = Wads.ReadLump(lump); char * buffer = (char*)lumpd.GetMem(); - if ( (size_t)fullname.IndexOf("_d.3d") == fullname.Len()-5 ) + if ( (size_t)fullname.LastIndexOf("_d.3d") == fullname.Len()-5 ) { FString anivfile = fullname.GetChars(); anivfile.Substitute("_d.3d","_a.3d"); @@ -429,7 +429,7 @@ static unsigned FindModel(const char * path, const char * modelfile) model = new FUE1Model; } } - else if ( (size_t)fullname.IndexOf("_a.3d") == fullname.Len()-5 ) + else if ( (size_t)fullname.LastIndexOf("_a.3d") == fullname.Len()-5 ) { FString datafile = fullname.GetChars(); datafile.Substitute("_a.3d","_d.3d"); @@ -438,8 +438,8 @@ static unsigned FindModel(const char * path, const char * modelfile) model = new FUE1Model; } } - else if ( (size_t)fullname.LastIndexOf(".obj") == fullname.Len() - 1 ) - { // LastIndexOf works differently than it does in JavaScript + else if ( (size_t)fullname.LastIndexOf(".obj") == fullname.Len() - 4 ) + { model = new FOBJModel; } else if (!memcmp(buffer, "DMDM", 4))