diff --git a/src/Inferno.Core/Inferno.Core.vcxproj b/src/Inferno.Core/Inferno.Core.vcxproj index 758990eb..ada04621 100644 --- a/src/Inferno.Core/Inferno.Core.vcxproj +++ b/src/Inferno.Core/Inferno.Core.vcxproj @@ -129,6 +129,7 @@ + @@ -151,6 +152,7 @@ + diff --git a/src/Inferno.Core/Inferno.Core.vcxproj.filters b/src/Inferno.Core/Inferno.Core.vcxproj.filters index 57b136aa..217649f5 100644 --- a/src/Inferno.Core/Inferno.Core.vcxproj.filters +++ b/src/Inferno.Core/Inferno.Core.vcxproj.filters @@ -92,6 +92,9 @@ Header Files + + Header Files + @@ -142,5 +145,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/src/Inferno.Core/Level.h b/src/Inferno.Core/Level.h index 6fd469f6..f0d69592 100644 --- a/src/Inferno.Core/Level.h +++ b/src/Inferno.Core/Level.h @@ -7,6 +7,7 @@ #include "Wall.h" #include "DataPool.h" #include "Segment.h" +#include "walls_container.h" namespace Inferno { struct Matcen { @@ -86,7 +87,7 @@ namespace Inferno { int Segments; // Note that source ports allow thousands of segments int Matcens = 20; int Vertices; - int Walls; + size_t Walls; int WallSwitches = 50; int WallLinks = 100; int FuelCenters = 70; @@ -103,6 +104,17 @@ namespace Inferno { constexpr auto MaxLightDeltas = 32000; // Rebirth limit. Original D2: 10000 struct Level { + // 1: Descent 1 + // 2 to 7: Descent 2 + // 8: Vertigo Enhanced + // >8: D2X-XL, unsupported + int Version; + // 22 to 25: Descent 1 + // 26 to 29: Descent 2 + // >32: D2X-XL, unsupported + int16 GameVersion; + + LevelLimits Limits; string Palette = "groupa.256"; SegID SecretExitReturn = SegID(0); Matrix3x3 SecretReturnOrientation; @@ -111,7 +123,7 @@ namespace Inferno { List Segments; List Pofs; List Objects; - List Walls; + WallsContainer Walls; List Triggers; List Matcens; List FlickeringLights; // Vertigo flickering lights @@ -128,17 +140,7 @@ namespace Inferno { List LightDeltaIndices; // Index into LightDeltas List LightDeltas; // For breakable or flickering lights - // 22 to 25: Descent 1 - // 26 to 29: Descent 2 - // >32: D2X-XL, unsupported - int16 GameVersion{}; - // 1: Descent 1 - // 2 to 7: Descent 2 - // 8: Vertigo Enhanced - // >8: D2X-XL, unsupported - int Version{}; - LevelLimits Limits = { 1 }; DataPool ActiveDoors{ ActiveDoor::IsAlive, 20 }; @@ -154,6 +156,13 @@ namespace Inferno { Vector3 CameraUp; #pragma endregion + Level(int version, WallsSerialization serialization) + : Version{ version } + , GameVersion{ version == 1 ? 25 : 32 } + , Limits{ Version } + , Walls{ Limits.Walls, serialization } + { } + bool IsDescent1() const { return Version == 1; } // Includes vertigo and non-vertigo bool IsDescent2() const { return Version > 1 && Version <= 8; } @@ -205,16 +214,12 @@ namespace Inferno { return count; } - constexpr const Wall& GetWall(WallID id) const { return Walls[(int)id]; } - constexpr Wall& GetWall(WallID id) { return Walls[(int)id]; } - constexpr Wall* TryGetWall(Tag tag) { if (tag.Segment == SegID::None) return nullptr; if (auto seg = TryGetSegment(tag)) { - auto id = (int)seg->GetSide(tag.Side).Wall; - if (Seq::inRange(Walls, id)) - return &Walls[id]; + auto id = seg->GetSide(tag.Side).Wall; + return Walls.TryGetWall(id); } return nullptr; @@ -228,29 +233,6 @@ namespace Inferno { return WallID::None; } - constexpr Wall* TryGetWall(TriggerID trigger) { - if (trigger == TriggerID::None) return nullptr; - - for (auto& wall : Walls) { - if (wall.Trigger == trigger) - return &wall; - } - - return nullptr; - } - - - const Wall* TryGetWall(WallID id) const { - if (id == WallID::None || (int)id >= Walls.size()) - return nullptr; - - // Check for invalid walls - if (Walls[(int)id].Tag.Segment == SegID::None) return nullptr; - return &Walls[(int)id]; - } - - Wall* TryGetWall(WallID id) { return (Wall*)std::as_const(*this).TryGetWall(id); } - // Tries to get the side connecting the two segments SideID GetConnectedSide(SegID src, SegID dst) const { if (!SegmentExists(src) || !SegmentExists(dst)) @@ -289,7 +271,7 @@ namespace Inferno { // Gets the wall connected to the other side of a wall (if present) WallID GetConnectedWall(WallID wallId) { - auto wall = TryGetWall(wallId); + auto wall = Walls.TryGetWall(wallId); if (!wall) return WallID::None; auto other = GetConnectedSide(wall->Tag); return TryGetWallID(other); @@ -371,7 +353,7 @@ namespace Inferno { } TriggerID GetTriggerID(WallID wid) const { - auto wall = TryGetWall(wid); + auto wall = Walls.TryGetWall(wid); if (!wall) return TriggerID::None; return wall->Trigger; } @@ -383,7 +365,7 @@ namespace Inferno { } Trigger* TryGetTrigger(WallID wid) { - auto wall = TryGetWall(wid); + auto wall = Walls.TryGetWall(wid); if (!wall) return nullptr; return TryGetTrigger(wall->Trigger); } @@ -444,6 +426,37 @@ namespace Inferno { bool CanAddMatcen() { return Matcens.size() < Limits.Matcens; } size_t Serialize(StreamWriter& writer); - static Level Deserialize(span); + static Level Deserialize(span, WallsSerialization); + + //creates closed walls from a single shared one + //to be called from the LevelReader + size_t CreateClosed(std::unordered_map> const& wallIdRefs) { + //check consistency + auto closedId = WallID::None; + for (auto&& [id, tags] : wallIdRefs) { + if (tags.size() == 1) + continue; + if (closedId != WallID::None) + throw Exception("CreateClosed: bad map, multiple shared walls"); + assert(tags.size() > 1); + Wall* wall = Walls.TryGetWall(id); + if (!wall || !wall->IsSimplyClosed()) + throw Exception("CreateClosed: bad map, shared wall is not closed with no trigger"); + closedId = id; + //don't break here, we want to check the whole map for consistency + } + if (closedId == WallID::None) + return 0; + + auto&& v = wallIdRefs.at(closedId); + for (auto it = v.begin() + 1; it != v.end(); ++it) { //skip the first tag + Wall w; + w.Type = WallType::Closed; + w.Tag = *it; + auto id = Walls.Append(std::move(w)); + GetSide(*it).Wall = id; //the side is guaranteed to exist by the way the wallIdRefs was filled + } + return v.size() - 1; + } }; } \ No newline at end of file diff --git a/src/Inferno.Core/LevelReader.cpp b/src/Inferno.Core/LevelReader.cpp index efd247cc..c9523025 100644 --- a/src/Inferno.Core/LevelReader.cpp +++ b/src/Inferno.Core/LevelReader.cpp @@ -3,6 +3,7 @@ #include "Streams.h" #include "Utility.h" #include "Pig.h" +#include "spdlog/spdlog.h" namespace Inferno { void ReadLevelInfo(StreamReader& reader, Level& level) { @@ -42,10 +43,18 @@ namespace Inferno { GameDataHeader _deltaLights{}, _deltaLightIndices{}; + // This map is filled while reading sides and maps wallId to all + // sides (Tags) that reference it. + // Normally this is one to one relation. Now we (may) + // allow all closed walls without a trigger to be one wall + // referenced by many sides to save WallID's that are limited by 255. + // While reading such a file we need to "unpack" shared walls. + std::unordered_map> wallToTag_; + public: LevelReader(span data) : _reader(data) {} - Level Read() { + Level Read(WallsSerialization wallsSerialization) { auto sig = (uint)_reader.ReadInt32(); if (sig != MakeFourCC("LVLP")) throw Exception("File is not a level (bad header)"); @@ -72,9 +81,8 @@ namespace Inferno { _reader.ReadInt32(); } - Level level; - level.Version = _levelVersion; - level.Limits = LevelLimits(_levelVersion); + Level level{ _levelVersion, wallsSerialization }; + ReadLevelInfo(_reader, level); ReadSegments(level); ReadGameData(level); @@ -150,14 +158,17 @@ namespace Inferno { seg.Connections[bit] = bitMask & (1 << bit) ? (SegID)_reader.ReadInt16() : SegID::None; } - void ReadSegmentWalls(Segment& seg) { + void ReadSegmentWalls(Segment& seg, SegID id) { auto mask = _reader.ReadByte(); for (int i = 0; i < MAX_SIDES; i++) { auto& side = seg.Sides[i]; - if (mask & (1 << i)) + if (mask & (1 << i)) { side.Wall = WallID(_reader.ReadByte()); + ////see the comment for wallToTag_ + wallToTag_[side.Wall].emplace_back(id, static_cast(i)); + } } } @@ -176,6 +187,7 @@ namespace Inferno { for (auto& v : level.Vertices) v = _reader.ReadVector(); + size_t segmentId = 0; for (auto& seg : level.Segments) { auto bitMask = _reader.ReadByte(); bool hasSpecialData = bitMask & (1 << MAX_SIDES); @@ -200,8 +212,9 @@ namespace Inferno { seg.VolumeLight = Color(light, light, light); } - ReadSegmentWalls(seg); + ReadSegmentWalls(seg, static_cast(segmentId)); ReadSegmentTextures(seg); + ++segmentId; } // D2 retail location for segment special data @@ -490,7 +503,7 @@ namespace Inferno { auto reactorTriggers = ReadHeader(); auto matcens = ReadHeader(); - level.Walls.resize(walls.Count); + //level.Walls.resize(walls.Count); level.Triggers.resize(triggers.Count); level.Objects.resize(objects.Count); level.Matcens.resize(matcens.Count); @@ -520,8 +533,20 @@ namespace Inferno { // Walls if (walls.Offset != -1) { _reader.Seek(walls.Offset); - for (auto& wall : level.Walls) - wall = ReadWall(); + for (size_t i=0; i= level.Triggers.size()) + w.ControllingTrigger = TriggerID::None; + } + //just in case: check all trigger targets + size_t counter = 0; + for (auto& t : level.Triggers) { + for (auto& tar : t.Targets) { + if (auto w = level.TryGetWall(tar)) + w->ControllingTrigger = (TriggerID)counter; + } + ++counter; + } + //end of temporary repair + + // Control center triggers if (reactorTriggers.Offset != -1) { _reader.Seek(reactorTriggers.Offset); @@ -542,8 +589,8 @@ namespace Inferno { } }; - Level Level::Deserialize(span data) { + Level Level::Deserialize(span data, WallsSerialization serialization) { LevelReader reader(data); - return reader.Read(); + return reader.Read(serialization); } } \ No newline at end of file diff --git a/src/Inferno.Core/LevelWriter.cpp b/src/Inferno.Core/LevelWriter.cpp index 2050634f..ecddc887 100644 --- a/src/Inferno.Core/LevelWriter.cpp +++ b/src/Inferno.Core/LevelWriter.cpp @@ -27,6 +27,8 @@ namespace Inferno { WriteVersionSpecificLevelInfo(writer, level); auto mineDataOffset = (int32)writer.Position(); + + auto guard = level.Walls.PrepareSerialization(); WriteMineData(writer, level); auto gameDataOffset = (int32)writer.Position(); @@ -173,7 +175,7 @@ namespace Inferno { } } - static void WriteWalls(StreamWriter& writer, const Segment& segment) { + static void WriteWalls(StreamWriter& writer, Level const& level, const Segment& segment) { ubyte mask = 0; for (short i = 0; i < MAX_SIDES; i++) { if (segment.Sides[i].Wall != WallID::None) @@ -183,9 +185,11 @@ namespace Inferno { writer.Write(mask); for (auto& side : segment.Sides) { - if (side.Wall == WallID::None) continue; - assert(side.Wall < WallID::Max); - writer.Write((ubyte)side.Wall); + if (side.Wall == WallID::None) + continue; + auto wall = level.Walls[side.Wall]; + assert(wall.SerializationId <= WallID::Max); + writer.Write((ubyte)wall.SerializationId); } } @@ -251,7 +255,7 @@ namespace Inferno { auto l = Desaturate(segment.VolumeLight); writer.Write((ushort)(FloatToFix(l * 2) >> 4)); } - WriteWalls(writer, segment); + WriteWalls(writer, level, segment); WriteSegmentTextures(writer, segment); } @@ -518,12 +522,14 @@ namespace Inferno { //AssertDataSize(writer, info.Objects); // object size varies based on type // Walls - info.Walls.Offset = (int32)(level.Walls.size() > 0 ? writer.Position() : -1); - info.Walls.Count = (int32)level.Walls.size(); + auto& walls = level.Walls.SeralizableWalls(); + info.Walls.Offset = (int32)(walls.size() > 0 ? writer.Position() : -1); + info.Walls.Count = (int32)walls.size(); info.Walls.ElementSize = 24; // Wall triggers are written before object triggers, so we have to filter - for (auto& wall : level.Walls) { + for (auto pwall : walls) { + auto&& wall = *pwall; writer.Write((int32)wall.Tag.Segment); writer.Write((int32)wall.Tag.Side); writer.WriteFix(wall.HitPoints); diff --git a/src/Inferno.Core/Segment.cpp b/src/Inferno.Core/Segment.cpp index d29865ca..b394dc7e 100644 --- a/src/Inferno.Core/Segment.cpp +++ b/src/Inferno.Core/Segment.cpp @@ -6,7 +6,7 @@ namespace Inferno { bool Segment::SideIsSolid(SideID side, Level& level) const { if (SideHasConnection(side)) { - if (auto wall = level.TryGetWall(Sides[(int)side].Wall)) + if (auto wall = level.Walls.TryGetWall(Sides[(int)side].Wall)) return wall->IsSolid(); // walls might be solid return false; // open side with no wall diff --git a/src/Inferno.Core/Types.h b/src/Inferno.Core/Types.h index f611289d..87e25708 100644 --- a/src/Inferno.Core/Types.h +++ b/src/Inferno.Core/Types.h @@ -197,6 +197,10 @@ namespace Inferno { enum class ModelID : int32 { None = -1 }; enum class MatcenID : uint8 { None = 255 }; enum class TriggerID : uint8 { None = 255 }; + enum class WallsSerialization { + STANDARD, + SHARED_SIMPLE_WALLS, + }; namespace Models { constexpr auto PlaceableMine = ModelID(159); // D2 editor placeable mine diff --git a/src/Inferno.Core/Wall.h b/src/Inferno.Core/Wall.h index 0b4cd86f..751ccc15 100644 --- a/src/Inferno.Core/Wall.h +++ b/src/Inferno.Core/Wall.h @@ -58,6 +58,7 @@ namespace Inferno { sbyte cloak_value = 0; // Fade percentage if this wall is cloaked Option BlocksLight; // Editor override + mutable WallID SerializationId{ WallID::None }; bool IsValid() const { return Tag.Segment != SegID::None; @@ -87,6 +88,11 @@ namespace Inferno { value = std::clamp(value, 0.0f, 1.0f); cloak_value = sbyte(value / CloakStep); } + + bool IsSimplyClosed() const { + return Type == WallType::Closed && ControllingTrigger == TriggerID::None; + } + }; struct ActiveDoor { diff --git a/src/Inferno.Core/walls_container.cpp b/src/Inferno.Core/walls_container.cpp new file mode 100644 index 00000000..5fad2017 --- /dev/null +++ b/src/Inferno.Core/walls_container.cpp @@ -0,0 +1,162 @@ +#include "pch.h" + +#include "walls_container.h" +#include "Types.h" + +namespace Inferno { + +WallsContainer::WallsContainer(size_t maxSize, WallsSerialization option) + : max_{ maxSize } + , option_{ option } +{ } + +WallsContainer::Iterator WallsContainer::begin() { + return walls_.begin(); +} +WallsContainer::Iterator WallsContainer::end() { + return walls_.end(); +} + +WallsContainer::ConstIterator WallsContainer::begin() const { + return walls_.begin(); +} +WallsContainer::ConstIterator WallsContainer::end() const { + return walls_.end(); +} + +WallsContainer::SerializationGuard WallsContainer::PrepareSerialization() const { + serializableWalls_.emplace(); + + switch (option_) { + case WallsSerialization::STANDARD: + for (auto& wall : walls_) { + auto id = static_cast(serializableWalls_->size()); + serializableWalls_->push_back(&wall); + wall.SerializationId = id; + } + break; + case WallsSerialization::SHARED_SIMPLE_WALLS: + { + WallID firstClosed = WallID::None; + for (auto& wall : walls_) { + auto id = static_cast(serializableWalls_->size()); + if (!wall.IsSimplyClosed() || firstClosed == WallID::None) + serializableWalls_->push_back(&wall); + + if (wall.IsSimplyClosed()) { + if (firstClosed == WallID::None) + firstClosed = id; + id = firstClosed; + } + + wall.SerializationId = id; + } + } + break; + } + + assert(serializableWalls_->size() <= max_); + + return SerializationGuard(&serializableWalls_, [this](auto) { + serializableWalls_.reset(); + for (auto& wall : walls_) + wall.SerializationId = WallID::None; + }); +} + +std::vector const& WallsContainer::SeralizableWalls() const { + //fails if no Prepare has been called or the guard is destroyed + return *serializableWalls_; +} + +Wall& WallsContainer::operator[](WallID id) { + return *TryGetWall(id); +} +Wall const& WallsContainer::operator[](WallID id) const { + return *TryGetWall(id); +} + +Wall* WallsContainer::TryGetWall(TriggerID trigger) { + if (trigger == TriggerID::None) + return nullptr; + + for (auto& wall : *this) { + if (wall.Trigger == trigger) + return &wall; + } + + return nullptr; +} + +Wall* WallsContainer::TryGetWall(WallID id) { + if (id == WallID::None) + return nullptr; + + auto iid = static_cast(id); + return iid < walls_.size() + ? (walls_[iid].IsValid() ? &walls_[iid] : nullptr) //the current implementation actually keeps walls always valid: this seems to be some ghost from the past + : nullptr; +} + +Wall const* WallsContainer::TryGetWall(WallID id) const { + return const_cast(*this).TryGetWall(id); +} + +WallID WallsContainer::Append(Wall wall) { + walls_.push_back(std::move(wall)); + return static_cast(walls_.size() - 1); +} + +void WallsContainer::Erase(WallID id) { + walls_.erase(walls_.begin() + static_cast(id)); +} + +bool WallsContainer::CanAdd(WallType type) const { + if (WallsSerialization::SHARED_SIMPLE_WALLS == option_ + && type == WallType::Closed) { + for (auto&& wall : walls_) + if (wall.IsSimplyClosed()) + return true; //can always add another simply closed + } + //not a closed or no closed + return ShrinkableSize() < max_; +} + +bool WallsContainer::Overfilled() const { + return ShrinkableSize() > max_; +} + +size_t WallsContainer::Size() const { + return walls_.size(); +} +size_t WallsContainer::ShrinkableSize() const { + if (WallsSerialization::STANDARD == option_) + return Size(); + + size_t count = 0; + size_t shared = 0; + for (auto&& w : walls_) { + if (w.IsSimplyClosed()) + shared = 1; + else + ++count; + } + return count + shared; +} + +WallsSerialization WallsContainer::SerializationKind() const { + return option_; +} + +void WallsContainer::SerializationKind(WallsSerialization option) { + if (option == option_) + return; + + if (WallsSerialization::SHARED_SIMPLE_WALLS == option_ + && Size() > max_) + throw Exception("WallsContainer: cannot switch serialization kind, too many walls"); + + option_ = option; +} + +}//namespace Inferno \ No newline at end of file diff --git a/src/Inferno.Core/walls_container.h b/src/Inferno.Core/walls_container.h new file mode 100644 index 00000000..9b956a63 --- /dev/null +++ b/src/Inferno.Core/walls_container.h @@ -0,0 +1,54 @@ +#pragma once + +#include "Wall.h" + +#include +#include + +namespace Inferno { + +class WallsContainer { + std::vector walls_; + mutable std::optional> serializableWalls_; + size_t max_; + WallsSerialization option_{ WallsSerialization::STANDARD }; + +public: + using Iterator = std::vector::iterator; + using ConstIterator = std::vector::const_iterator; + using SerializationGuard = std::shared_ptr; + + WallsContainer(size_t maxSize, WallsSerialization); + + Iterator begin(); + Iterator end(); + + ConstIterator begin() const; + ConstIterator end() const; + + size_t Size() const; + size_t ShrinkableSize() const; + + SerializationGuard PrepareSerialization() const; + std::vector const& SeralizableWalls() const; + + Wall& operator[](WallID); + Wall const& operator[](WallID) const; + + WallID Append(Wall w); + void Erase(WallID); + + Wall* TryGetWall(TriggerID); + Wall* TryGetWall(WallID); + Wall const* TryGetWall(WallID) const; + + bool CanAdd(WallType) const; + bool Overfilled() const; + + WallsSerialization SerializationKind() const; + void SerializationKind(WallsSerialization); +}; + + + +} //namespace Inferno \ No newline at end of file diff --git a/src/Inferno/Editor/Editor.Clipboard.cpp b/src/Inferno/Editor/Editor.Clipboard.cpp index aecc9576..3442bca0 100644 --- a/src/Inferno/Editor/Editor.Clipboard.cpp +++ b/src/Inferno/Editor/Editor.Clipboard.cpp @@ -66,7 +66,7 @@ namespace Inferno::Editor { for (auto& sid : SideIDs) { auto& side = seg.GetSide(sid); - if (auto wall = level.TryGetWall(side.Wall)) { + if (auto wall = level.Walls.TryGetWall(side.Wall)) { if (wall->Type != WallType::WallTrigger && seg.Connections[(int)sid] == SegID::None) { // Don't copy walls on the boundary of copied segments side.Wall = WallID::None; @@ -110,7 +110,6 @@ namespace Inferno::Editor { // Inserts segments into a level List InsertSegments(Level& level, SegmentClipboardData copy) { auto vertexOffset = (PointID)level.Vertices.size(); - auto wallOffset = level.Walls.size(); auto triggerOffset = level.Triggers.size(); auto segIdOffset = (SegID)level.Segments.size(); auto matcenOffset = level.Matcens.size(); @@ -127,17 +126,13 @@ namespace Inferno::Editor { conn += segIdOffset; } - newIds.push_back((SegID)level.Segments.size()); + auto segId = static_cast(level.Segments.size()); + newIds.push_back(segId); for (auto& side : seg.Sides) { if (Settings::Editor.PasteSegmentWalls) { if (side.Wall != WallID::None) { - auto wallId = (int)side.Wall + wallOffset; - if (wallId >= level.Limits.Walls) { - SPDLOG_WARN("Wall id is out of range!"); - break; - } - side.Wall = WallID(wallId); + copy.Walls[static_cast(side.Wall)].Tag.Segment = segId; } } else { @@ -174,12 +169,13 @@ namespace Inferno::Editor { if (Settings::Editor.PasteSegmentWalls) { for (auto& wall : copy.Walls) { - if (level.Walls.size() >= level.Limits.Walls) { + if (!level.Walls.CanAdd(wall.Type)) { SPDLOG_WARN("Ran out of space for walls!"); break; } - wall.Tag.Segment += segIdOffset; + //segment is already correct, but we need to update tag's wall id below + //wall.Tag.Segment += segIdOffset; if (wall.Trigger != TriggerID::None) { auto& trigger = copy.Triggers[(int)wall.Trigger]; @@ -198,7 +194,10 @@ namespace Inferno::Editor { } wall.Trigger = TriggerID(triggerId); } - level.Walls.push_back(std::move(wall)); + //updating the wall id of the tag + auto tag = wall.Tag; + auto id = level.Walls.Append(std::move(wall)); + level.GetSide(tag).Wall = id; } } diff --git a/src/Inferno/Editor/Editor.Diagnostics.cpp b/src/Inferno/Editor/Editor.Diagnostics.cpp index c8ca8897..7918fa0c 100644 --- a/src/Inferno/Editor/Editor.Diagnostics.cpp +++ b/src/Inferno/Editor/Editor.Diagnostics.cpp @@ -53,7 +53,7 @@ namespace Inferno::Editor { auto& side = level.GetSide(tag); if (side.Wall == WallID::None) continue; - if (auto wall = level.TryGetWall(side.Wall)) { + if (auto wall = level.Walls.TryGetWall(side.Wall)) { if (wall->Tag != tag) { SPDLOG_WARN("Fixing mismatched wall tag on segment {}:{}", (int)tag.Segment, (int)tag.Side); wall->Tag = tag; @@ -67,8 +67,7 @@ namespace Inferno::Editor { } // Fix VClip 2 - for (int id = 0; id < level.Walls.size(); id++) { - auto& wall = level.GetWall((WallID)id); + for (auto&& wall: level.Walls) { wall.LinkedWall = WallID::None; // Wall links are only valid during runtime if (wall.Clip == WClipID(2)) { // ID 2 is bad and has no animation @@ -444,7 +443,7 @@ namespace Inferno::Editor { usedWalls.insert(side.Wall); } - if (auto wall = level.TryGetWall(side.Wall)) { + if (auto wall = level.Walls.TryGetWall(side.Wall)) { if (usedTriggers.contains(wall->Trigger)) { auto msg = fmt::format("Trigger {} is already in use. Delete trigger on this side and insert a new one.", wall->Trigger); results.push_back({ 0, { segid, sideId }, msg }); diff --git a/src/Inferno/Editor/Editor.IO.cpp b/src/Inferno/Editor/Editor.IO.cpp index a40551c5..4adc7c06 100644 --- a/src/Inferno/Editor/Editor.IO.cpp +++ b/src/Inferno/Editor/Editor.IO.cpp @@ -15,8 +15,9 @@ namespace Inferno::Editor { constexpr auto METADATA_EXTENSION = "ied"; // inferno engine data size_t SaveLevel(Level& level, StreamWriter& writer) { - if (level.Walls.size() >= (int)WallID::Max) - throw Exception("Cannot save a level with more than 255 walls"); + if (level.Walls.ShrinkableSize() > level.Limits.Walls) + throw Exception(std::string("Cannot save a level with more than ") + + std::to_string(level.Limits.Walls) + " walls"); DisableFlickeringLights(level); ResetFlickeringLightTimers(level); @@ -100,7 +101,10 @@ namespace Inferno::Editor { if (!file.read((char*)buffer.data(), size)) throw Exception("Error reading file"); - auto level = Level::Deserialize(buffer); + auto level = Level::Deserialize(buffer, + Settings::Editor.UseSharedClosedWalls + ? WallsSerialization::SHARED_SIMPLE_WALLS + : WallsSerialization::STANDARD); level.FileName = path.filename().string(); level.Path = path; @@ -304,14 +308,16 @@ namespace Inferno::Editor { void OnSave(); - void NewLevel(string name, string fileName, int16 version, bool addToHog) { + void NewLevel(string name, string const& fileName, int16 version, bool addToHog) { if (!addToHog) Game::UnloadMission(); - Level level; - level.Name = name; - level.Version = version; - level.GameVersion = version == 1 ? 25 : 32; + Level level(version, + Settings::Editor.UseSharedClosedWalls + ? WallsSerialization::SHARED_SIMPLE_WALLS + : WallsSerialization::STANDARD);; + level.Name = std::move(name); //don't use name afterwards + auto ext = level.IsDescent1() ? ".rdl" : ".rl2"; level.FileName = fileName.substr(0, 8) + ext; diff --git a/src/Inferno/Editor/Editor.IO.h b/src/Inferno/Editor/Editor.IO.h index 679b761c..b0fa3857 100644 --- a/src/Inferno/Editor/Editor.IO.h +++ b/src/Inferno/Editor/Editor.IO.h @@ -8,7 +8,7 @@ namespace Inferno::Editor { // Creates a backup of a file using the provided extension void BackupFile(const filesystem::path& path, string_view ext = ".bak"); - void NewLevel(string name, string fileName, int16 version, bool addToHog); + void NewLevel(string name, string const& fileName, int16 version, bool addToHog); void CheckForAutosave(); void ResetAutosaveTimer(); void WritePlaytestLevel(filesystem::path missionFolder, Level& level, HogFile* mission); diff --git a/src/Inferno/Editor/Editor.Lighting.cpp b/src/Inferno/Editor/Editor.Lighting.cpp index fd38d7d3..bfc153b1 100644 --- a/src/Inferno/Editor/Editor.Lighting.cpp +++ b/src/Inferno/Editor/Editor.Lighting.cpp @@ -219,7 +219,7 @@ namespace Inferno::Editor { if (side.Wall == WallID::None) return true; // not a wall and this side is open - auto& wall = level.GetWall(side.Wall); + auto& wall = level.Walls[side.Wall]; if (wall.BlocksLight) return !(*wall.BlocksLight); // User defined switch (wall.Type) { @@ -260,7 +260,7 @@ namespace Inferno::Editor { auto& side = seg.GetSide(sideId); if (side.Wall == WallID::None) return false; // no wall - auto& wall = level.GetWall(side.Wall); + auto& wall = level.Walls[side.Wall]; switch (wall.Type) { case WallType::FlyThroughTrigger: case WallType::None: diff --git a/src/Inferno/Editor/Editor.Segment.cpp b/src/Inferno/Editor/Editor.Segment.cpp index f25208d1..9171d1c3 100644 --- a/src/Inferno/Editor/Editor.Segment.cpp +++ b/src/Inferno/Editor/Editor.Segment.cpp @@ -307,8 +307,8 @@ namespace Inferno::Editor { List walls; // Remove walls on this seg - for (int16 wallId = 0; wallId < level.Walls.size(); wallId++) { - if (level.Walls[wallId].Tag.Segment == segId) + for (int16 wallId = 0; wallId < level.Walls.Size(); wallId++) { + if (level.Walls[static_cast(wallId)].Tag.Segment == segId) walls.push_back((WallID)wallId); } diff --git a/src/Inferno/Editor/Editor.Selection.cpp b/src/Inferno/Editor/Editor.Selection.cpp index d5835a51..fb7b364d 100644 --- a/src/Inferno/Editor/Editor.Selection.cpp +++ b/src/Inferno/Editor/Editor.Selection.cpp @@ -25,7 +25,7 @@ namespace Inferno::Editor { for (auto& side : SideIDs) { if (!includeInvisible) { bool visibleWall = false; - if (auto wall = level.TryGetWall(seg.Sides[(int)side].Wall)) + if (auto wall = level.Walls.TryGetWall(seg.Sides[(int)side].Wall)) visibleWall = Settings::Editor.EnableWallMode || wall->Type != WallType::FlyThroughTrigger; if (seg.SideHasConnection(side) && !visibleWall) continue; @@ -402,7 +402,7 @@ namespace Inferno::Editor { } void EditorSelection::SelectByWall(WallID id) { - if (auto wall = Game::Level.TryGetWall(id)) { + if (auto wall = Game::Level.Walls.TryGetWall(id)) { Segment = wall->Tag.Segment; Side = wall->Tag.Side; } @@ -695,7 +695,7 @@ namespace Inferno::Editor { // check if any of the sides with these edges are walls bool EdgeHasWall(Level& level, Segment& seg, PointID v0, PointID v1) { for (auto& sid : SideIDs) { - auto wall = level.TryGetWall(seg.GetSide(sid).Wall); + auto wall = level.Walls.TryGetWall(seg.GetSide(sid).Wall); if (!wall) continue; if (wall->Type == WallType::FlyThroughTrigger) continue; @@ -788,7 +788,7 @@ namespace Inferno::Editor { if (!level.SegmentExists(tag)) return false; auto [seg, side] = level.GetSegmentAndSide(tag); - if (auto wall = level.TryGetWall(side.Wall)) { + if (auto wall = level.Walls.TryGetWall(side.Wall)) { return wall->Type != WallType::FlyThroughTrigger; } diff --git a/src/Inferno/Editor/Editor.Wall.cpp b/src/Inferno/Editor/Editor.Wall.cpp index 9916ab2b..89f4f5e5 100644 --- a/src/Inferno/Editor/Editor.Wall.cpp +++ b/src/Inferno/Editor/Editor.Wall.cpp @@ -3,13 +3,14 @@ #include "Editor.Wall.h" #include "Graphics/Render.h" #include "Editor.Texture.h" +#include "logging.h" namespace Inferno::Editor { bool FixWallClip(Level& level, Wall& wall) { - if (!level.SegmentExists(wall.Tag)) return false; - auto& side = level.GetSide(wall.Tag); - if (wall.Type == WallType::Door || wall.Type == WallType::Destroyable) { + if (!level.SegmentExists(wall.Tag)) return false; + auto& side = level.GetSide(wall.Tag); + // If a clip is selected assign it auto id1 = Resources::GetWallClipID(side.TMap); if (auto wc = Resources::TryGetWallClip(id1)) { @@ -51,31 +52,23 @@ namespace Inferno::Editor { return id; } - WallID AddWall(Level& level, Tag tag) { - if (!level.SegmentExists(tag.Segment)) return WallID::None; - auto [seg, side] = level.GetSegmentAndSide(tag); - - WallID wallId = [&] { - // Find an unused wall slot - for (int i = 0; i < level.Walls.size(); i++) { - auto& wall = level.GetWall(WallID(i)); - if (wall.Tag.Segment == SegID::None) - return WallID(i); - } + namespace { + WallID AddWall(Level& level, Tag tag) { + //!: assert(level.Walls.CanAddWall(type)); <- this should be checked in the calling code - // Allocate a new wall - level.Walls.emplace_back(); - return WallID(level.Walls.size() - 1); - }(); + if (!level.SegmentExists(tag.Segment)) return WallID::None; + auto [seg, side] = level.GetSegmentAndSide(tag); - auto& wall = level.GetWall(wallId); - wall.Tag = tag; - side.Wall = wallId; - return wallId; - } + Wall wall; + wall.Tag = tag; + auto id = level.Walls.Append(wall); + side.Wall = id; + return id; + } + }//namespace TriggerID AddTrigger(Level& level, WallID wallId, TriggerType type) { - if (auto wall = level.TryGetWall(wallId)) { + if (auto wall = level.Walls.TryGetWall(wallId)) { wall->Trigger = (TriggerID)level.Triggers.size(); auto& trigger = level.Triggers.emplace_back(); trigger.Type = type; @@ -86,7 +79,7 @@ namespace Inferno::Editor { } TriggerID AddTrigger(Level& level, WallID wallId, TriggerFlagD1 flags) { - if (auto wall = level.TryGetWall(wallId)) { + if (auto wall = level.Walls.TryGetWall(wallId)) { wall->Trigger = (TriggerID)level.Triggers.size(); auto& trigger = level.Triggers.emplace_back(); trigger.FlagsD1 = flags; @@ -104,14 +97,12 @@ namespace Inferno::Editor { if (wall.ControllingTrigger == id) wall.ControllingTrigger = TriggerID::None; - if (wall.ControllingTrigger > id) - wall.ControllingTrigger = TriggerID((int)wall.ControllingTrigger - 1); + if (wall.ControllingTrigger != TriggerID::None && wall.ControllingTrigger > id) + wall.ControllingTrigger--; if (wall.Trigger == id) wall.Trigger = TriggerID::None; - } - for (auto& wall : level.Walls) { if (wall.Trigger != TriggerID::None && wall.Trigger > id) wall.Trigger--; } @@ -133,22 +124,31 @@ namespace Inferno::Editor { } void AddTriggerTarget(Level& level, TriggerID id, Tag target) { + auto wall = level.TryGetWall(target); + + //if the wall was a Closed one and had no controlling trigger + //it probably did not count against the max wall count + //so we have to check if it is possible to add it back to the wall bookkeeping + if (wall && wall->IsSimplyClosed() + && !level.Walls.CanAdd(WallType::WallTrigger)) //WallTrigger is here just as something that is not Closed + { + SPDLOG_WARN("Can not add wall as target: it will cause the wall amount to exceed {}", level.Limits.Walls); + return; + } + auto trigger = level.TryGetTrigger(id); if (!trigger) return; trigger->Targets.Add(target); - // clear source trigger from wall it was targeting - if (auto wall = level.TryGetWall(target)) { - if (wall->ControllingTrigger == id) - wall->ControllingTrigger = TriggerID::None; - } + if (wall) + wall->ControllingTrigger = id; } bool RemoveWall(Level& level, WallID id) { if (id == WallID::None) return false; { - auto wall = level.TryGetWall(id); + auto wall = level.Walls.TryGetWall(id); if (!wall) return false; auto seg = level.TryGetSegment(wall->Tag.Segment); if (!seg) return false; @@ -175,13 +175,19 @@ namespace Inferno::Editor { } } - level.Walls.erase(level.Walls.begin() + (int)id); + level.Walls.Erase(id); Events::LevelChanged(); return true; } void InitWall(Level& level, Wall& wall, WallType type) { - if (wall.Type == type) return; + if (wall.Type == type) + return; + if (wall.IsSimplyClosed()) + if (!level.Walls.CanAdd(type)) { + SPDLOG_WARN("Can not change the wall type: it will increase walls count over {}", level.Limits.Walls); + return; + } if (type == WallType::Destroyable) wall.HitPoints = 100; @@ -206,7 +212,7 @@ namespace Inferno::Editor { } WallID AddWall(Level& level, Tag tag, WallType type, LevelTexID tmap1, LevelTexID tmap2, WallFlag flags) { - if (level.Walls.size() + 1 >= (int)WallID::Max) { + if (!level.Walls.CanAdd(type)) { SetStatusMessageWarn("Cannot have more than {} walls in a level", WallID::Max); return WallID::None; } @@ -238,8 +244,8 @@ namespace Inferno::Editor { return WallID::None; } - auto& wall = level.GetWall(wallId); - wall.Tag = tag; + auto& wall = level.Walls[wallId]; + //wall.Tag = tag; wall.Flags = flags; InitWall(level, wall, type); side.TMap = tmap1; diff --git a/src/Inferno/Editor/Editor.cpp b/src/Inferno/Editor/Editor.cpp index bc626520..cfa83473 100644 --- a/src/Inferno/Editor/Editor.cpp +++ b/src/Inferno/Editor/Editor.cpp @@ -665,7 +665,7 @@ namespace Inferno::Editor { void GoToExit() { if (auto tid = Seq::findIndex(Game::Level.Triggers, IsExit)) { - if (auto wall = Game::Level.TryGetWall((TriggerID)*tid)) { + if (auto wall = Game::Level.Walls.TryGetWall((TriggerID)*tid)) { Selection.SetSelection(wall->Tag); FocusSegment(); return; @@ -677,7 +677,7 @@ namespace Inferno::Editor { void GoToSecretExit() { if (auto tid = Seq::findIndex(Game::Level.Triggers, IsSecretExit)) { - if (auto wall = Game::Level.TryGetWall((TriggerID)*tid)) { + if (auto wall = Game::Level.Walls.TryGetWall((TriggerID)*tid)) { Selection.SetSelection(wall->Tag); FocusSegment(); return; diff --git a/src/Inferno/Editor/UI/DiagnosticWindow.h b/src/Inferno/Editor/UI/DiagnosticWindow.h index 296b6402..2103108d 100644 --- a/src/Inferno/Editor/UI/DiagnosticWindow.h +++ b/src/Inferno/Editor/UI/DiagnosticWindow.h @@ -192,7 +192,7 @@ namespace Inferno::Editor { ImGui::Text("%i", _robots); ImGui::TableRowLabel("Walls"); - ImGui::Text("%i", level.Walls.size()); + ImGui::Text("%i (%i)", level.Walls.Size(), level.Walls.ShrinkableSize()); ImGui::TableNextColumn(); ImGui::Text("%i", level.Limits.Walls); diff --git a/src/Inferno/Editor/UI/PropertyEditor.Segment.cpp b/src/Inferno/Editor/UI/PropertyEditor.Segment.cpp index c3aee96f..dfa1a6d6 100644 --- a/src/Inferno/Editor/UI/PropertyEditor.Segment.cpp +++ b/src/Inferno/Editor/UI/PropertyEditor.Segment.cpp @@ -82,7 +82,7 @@ namespace Inferno::Editor { bool TriggerPropertiesD1(Level& level, WallID wid) { bool snapshot = false; - auto wall = level.TryGetWall(wid); + auto wall = level.Walls.TryGetWall(wid); DisableControls disable(!wall); auto trigger = wall ? level.TryGetTrigger(wall->Trigger) : nullptr; @@ -135,7 +135,7 @@ namespace Inferno::Editor { bool TriggerPropertiesD2(Level& level, WallID wallId) { bool snapshot = false; - auto wall = level.TryGetWall(wallId); + auto wall = level.Walls.TryGetWall(wallId); auto tid = level.GetTriggerID(wallId); auto trigger = level.TryGetTrigger(wallId); DisableControls disable(!wall); @@ -741,9 +741,9 @@ namespace Inferno::Editor { // Returns true if any wall properties changed bool WallProperties(Level& level, WallID id) { - auto wall = level.TryGetWall(id); + auto wall = level.Walls.TryGetWall(id); auto tag = Editor::Selection.Tag(); - auto other = level.TryGetWall(level.GetConnectedWall(tag)); + auto other = level.Walls.TryGetWall(level.GetConnectedWall(tag)); bool open = ImGui::TableBeginTreeNode("Wall type"); auto wallType = wall ? wall->Type : WallType::None; @@ -768,11 +768,11 @@ namespace Inferno::Editor { } for (auto& markedId : GetSelectedWalls()) { - auto& markedWall = level.GetWall(markedId); + auto& markedWall = level.Walls[markedId]; markedWall.Clip = wall->Clip; OnChangeWallClip(level, markedWall); - auto markedOther = level.TryGetWall(level.GetConnectedWall(markedId)); + auto markedOther = level.Walls.TryGetWall(level.GetConnectedWall(markedId)); if (Settings::Editor.EditBothWallSides && markedOther && markedOther->Type == markedWall.Type) { markedOther->Clip = wall->Clip; OnChangeWallClip(level, *markedOther); @@ -797,10 +797,10 @@ namespace Inferno::Editor { other->SetFlag(flag, w->HasFlag(flag)); for (auto& markedId : GetSelectedWalls()) { - auto& markedWall = level.GetWall(markedId); + auto& markedWall = level.Walls[markedId]; markedWall.SetFlag(flag, w->HasFlag(flag)); - auto markedOther = level.TryGetWall(level.GetConnectedWall(markedId)); + auto markedOther = level.Walls.TryGetWall(level.GetConnectedWall(markedId)); if (Settings::Editor.EditBothWallSides && markedOther && markedOther->Type == markedWall.Type) markedOther->SetFlag(flag, w->HasFlag(flag)); } @@ -825,11 +825,11 @@ namespace Inferno::Editor { other->HitPoints = wall->HitPoints; for (auto& markedId : GetSelectedWalls()) { - auto markedWall = level.TryGetWall(markedId); + auto markedWall = level.Walls.TryGetWall(markedId); if (markedWall && markedWall->Type == WallType::Destroyable) markedWall->HitPoints = wall->HitPoints; - auto markedOther = level.TryGetWall(level.GetConnectedWall(markedId)); + auto markedOther = level.Walls.TryGetWall(level.GetConnectedWall(markedId)); if (Settings::Editor.EditBothWallSides && markedOther && markedOther->Type == WallType::Destroyable) markedOther->HitPoints = wall->HitPoints; } @@ -855,11 +855,11 @@ namespace Inferno::Editor { other->Keys = wall->Keys; for (auto& markedId : GetSelectedWalls()) { - auto markedWall = level.TryGetWall(markedId); + auto markedWall = level.Walls.TryGetWall(markedId); if (markedWall && markedWall->Type == WallType::Door) markedWall->Keys = wall->Keys; - auto markedOther = level.TryGetWall(level.GetConnectedWall(markedId)); + auto markedOther = level.Walls.TryGetWall(level.GetConnectedWall(markedId)); if (Settings::Editor.EditBothWallSides && markedOther && markedOther->Type == WallType::Door) markedOther->Keys = wall->Keys; } @@ -894,11 +894,11 @@ namespace Inferno::Editor { other->CloakValue(cloakValue / 100); for (auto& markedId : GetSelectedWalls()) { - auto markedWall = level.TryGetWall(markedId); + auto markedWall = level.Walls.TryGetWall(markedId); if (markedWall && markedWall->Type == WallType::Cloaked) markedWall->CloakValue(cloakValue / 100); - auto markedOther = level.TryGetWall(level.GetConnectedWall(markedId)); + auto markedOther = level.Walls.TryGetWall(level.GetConnectedWall(markedId)); if (Settings::Editor.EditBothWallSides && markedOther && markedOther->Type == WallType::Cloaked) markedOther->CloakValue(cloakValue / 100); } @@ -914,7 +914,7 @@ namespace Inferno::Editor { ImGui::TableRowLabel("Blocks Light"); if (WallLightDropdown(wall->BlocksLight)) { for (auto& wid : GetSelectedWalls()) { - if (auto w = level.TryGetWall(wid)) { + if (auto w = level.Walls.TryGetWall(wid)) { w->BlocksLight = wall->BlocksLight; auto cw = level.GetConnectedWall(*w); if (Settings::Editor.EditBothWallSides && cw) diff --git a/src/Inferno/Editor/UI/SettingsDialog.h b/src/Inferno/Editor/UI/SettingsDialog.h index 5c58d59f..fc84df55 100644 --- a/src/Inferno/Editor/UI/SettingsDialog.h +++ b/src/Inferno/Editor/UI/SettingsDialog.h @@ -247,7 +247,6 @@ namespace Inferno::Editor { ImGui::SliderInt("##Background", &_graphics.BackgroundFpsLimit, 1, 30); ImGui::NextColumn(); } - ImGui::Columns(1); ImGui::EndChild(); @@ -265,6 +264,11 @@ namespace Inferno::Editor { ImGui::SameLine(); ImGui::SetNextItemWidth(150 * Shell::DpiScale); ImGui::Combo("##texpreview", (int*)&_texturePreviewSize, "Small\0Medium\0Large"); + + ImGui::Checkbox("Use shared closed walls", &_editor.UseSharedClosedWalls); + ImGui::HelpMarker("All closed walls without trigger are shared as one wall.\nThis way a level can contain unlimited number of closed walls."); + ImGui::NextColumn(); + ImGui::EndTabItem(); } @@ -489,6 +493,19 @@ namespace Inferno::Editor { if (_graphics.MsaaSamples != Settings::Graphics.MsaaSamples) { resourcesChanged = true; } + if (_editor.UseSharedClosedWalls != Settings::Editor.UseSharedClosedWalls) { + try { + //throws if not possible + Inferno::Game::Level.Walls.SerializationKind(_editor.UseSharedClosedWalls + ? WallsSerialization::SHARED_SIMPLE_WALLS + : WallsSerialization::STANDARD); + } + catch (Exception const& e) { + _editor.UseSharedClosedWalls = Settings::Editor.UseSharedClosedWalls; + SPDLOG_ERROR(e.what()); + ShowErrorMessage(e); + } + } Settings::Inferno = _inferno; Settings::Editor = _editor; diff --git a/src/Inferno/Editor/UI/StatusBar.h b/src/Inferno/Editor/UI/StatusBar.h index 9f654ba0..f8bb583d 100644 --- a/src/Inferno/Editor/UI/StatusBar.h +++ b/src/Inferno/Editor/UI/StatusBar.h @@ -122,7 +122,7 @@ namespace Inferno::Editor { ImGui::TableNextColumn(); ImGui::Text("Walls"); ImGui::TableNextColumn(); - ImGui::Text("%i", level.Walls.size()); + ImGui::Text("%i (%i)", level.Walls.Size(), level.Walls.ShrinkableSize()); ImGui::EndTable(); } diff --git a/src/Inferno/Game.Wall.cpp b/src/Inferno/Game.Wall.cpp index 3ca30371..910fa1a0 100644 --- a/src/Inferno/Game.Wall.cpp +++ b/src/Inferno/Game.Wall.cpp @@ -78,11 +78,11 @@ namespace Inferno { } void DoOpenDoor(Level& level, ActiveDoor& door, float dt) { - auto& wall = level.GetWall(door.Front); + auto& wall = level.Walls[door.Front]; auto conn = level.GetConnectedSide(wall.Tag); auto& side = level.GetSide(wall.Tag); auto& cside = level.GetSide(conn); - auto& cwall = level.GetWall(cside.Wall); + auto& cwall = level.Walls[cside.Wall]; // todo: remove objects stuck on door @@ -117,10 +117,10 @@ namespace Inferno { } void DoCloseDoor(Level& level, ActiveDoor& door, float dt) { - auto& wall = level.GetWall(door.Front); + auto& wall = level.Walls[door.Front]; - auto front = level.TryGetWall(door.Front); - auto back = level.TryGetWall(door.Back); + auto front = level.Walls.TryGetWall(door.Front); + auto back = level.Walls.TryGetWall(door.Back); auto conn = level.GetConnectedSide(wall.Tag); auto& side = level.GetSide(wall.Tag); @@ -173,12 +173,12 @@ namespace Inferno { void OpenDoor(Level& level, Tag tag) { auto& seg = level.GetSegment(tag); auto& side = seg.GetSide(tag.Side); - auto wall = level.TryGetWall(side.Wall); + auto wall = level.Walls.TryGetWall(side.Wall); if (!wall) throw Exception("Tried to open door on side that has no wall"); auto conn = level.GetConnectedSide(tag); auto cwallId = level.TryGetWallID(conn); - auto cwall = level.TryGetWall(cwallId); + auto cwall = level.Walls.TryGetWall(cwallId); if (wall->State == WallState::DoorOpening || wall->State == WallState::DoorWaiting) @@ -236,7 +236,7 @@ namespace Inferno { void UpdateDoors(Level& level, float dt) { for (auto& door : level.ActiveDoors) { - auto wall = level.TryGetWall(door.Front); + auto wall = level.Walls.TryGetWall(door.Front); if (!wall) continue; if (wall->State == WallState::DoorOpening) { diff --git a/src/Inferno/Game.h b/src/Inferno/Game.h index bc0a3f48..93253367 100644 --- a/src/Inferno/Game.h +++ b/src/Inferno/Game.h @@ -6,7 +6,7 @@ namespace Inferno::Game { // The loaded level. Only one level can be active at a time. - inline Inferno::Level Level; + inline Inferno::Level Level{ 1, WallsSerialization::STANDARD }; // The loaded mission. Not always present. inline Option Mission; diff --git a/src/Inferno/Graphics/LevelMesh.cpp b/src/Inferno/Graphics/LevelMesh.cpp index a68a4d5d..95341ecf 100644 --- a/src/Inferno/Graphics/LevelMesh.cpp +++ b/src/Inferno/Graphics/LevelMesh.cpp @@ -231,7 +231,7 @@ namespace Inferno { if (seg.GetConnection(sideId) == SegID::Exit) continue; - auto wall = level.TryGetWall(side.Wall); + auto wall = level.Walls.TryGetWall(side.Wall); WallType wallType = wall ? wall->Type : WallType::None; // Do not render fly-through walls diff --git a/src/Inferno/Graphics/MaterialLibrary.cpp b/src/Inferno/Graphics/MaterialLibrary.cpp index 060a6478..fe840600 100644 --- a/src/Inferno/Graphics/MaterialLibrary.cpp +++ b/src/Inferno/Graphics/MaterialLibrary.cpp @@ -134,7 +134,7 @@ namespace Inferno::Render { } // Door clips - if (auto wall = level.TryGetWall(side.Wall)) { + if (auto wall = level.Walls.TryGetWall(side.Wall)) { auto& wclip = Resources::GetDoorClip(wall->Clip); auto wids = Seq::map(wclip.GetFrames(), Resources::LookupTexID); Seq::insert(ids, wids); diff --git a/src/Inferno/LevelSettings.cpp b/src/Inferno/LevelSettings.cpp index c76f212f..a5ce9412 100644 --- a/src/Inferno/LevelSettings.cpp +++ b/src/Inferno/LevelSettings.cpp @@ -138,8 +138,8 @@ namespace Inferno { void SaveWallInfo(ryml::NodeRef node, const Level& level) { node |= ryml::SEQ; - for (int id = 0; id < level.Walls.size(); id++) { - auto& wall = level.Walls[id]; + for (int id = 0; id < level.Walls.Size(); id++) { + auto& wall = level.Walls[static_cast(id)]; if (wall.BlocksLight) { auto child = node.append_child(); child |= ryml::MAP; @@ -156,7 +156,7 @@ namespace Inferno { auto id = WallID::None; ReadValue(child["ID"], (int16&)id); - if (auto wall = level.TryGetWall(id)) { + if (auto wall = level.Walls.TryGetWall(id)) { bool blocksLight = false; ReadValue(child["BlocksLight"], blocksLight); wall->BlocksLight = blocksLight; diff --git a/src/Inferno/Resources.cpp b/src/Inferno/Resources.cpp index 875fa248..639111ac 100644 --- a/src/Inferno/Resources.cpp +++ b/src/Inferno/Resources.cpp @@ -590,7 +590,10 @@ namespace Inferno::Resources { throw Exception("File not found"); } - auto level = Level::Deserialize(data); + auto level = Level::Deserialize(data, + Settings::Editor.UseSharedClosedWalls + ? WallsSerialization::SHARED_SIMPLE_WALLS + : WallsSerialization::STANDARD); level.FileName = name; return level; } diff --git a/src/Inferno/Settings.cpp b/src/Inferno/Settings.cpp index 264469db..20977453 100644 --- a/src/Inferno/Settings.cpp +++ b/src/Inferno/Settings.cpp @@ -250,6 +250,7 @@ namespace Inferno { node["TexturePreviewSize"] << (int)s.TexturePreviewSize; node["ShowLevelTitle"] << s.ShowLevelTitle; node["Descent3Mode"] << s.Descent3Mode; + node["UseSharedClosedWalls"] << s.UseSharedClosedWalls; SaveSelectionSettings(node["Selection"], s.Selection); SaveOpenWindows(node["Windows"], s.Windows); @@ -326,6 +327,7 @@ namespace Inferno { ReadValue(node["TexturePreviewSize"], (int&)s.TexturePreviewSize); ReadValue(node["ShowLevelTitle"], s.ShowLevelTitle); ReadValue(node["Descent3Mode"], s.Descent3Mode); + ReadValue(node["UseSharedClosedWalls"], s.UseSharedClosedWalls); s.Selection = LoadSelectionSettings(node["Selection"]); s.Windows = LoadOpenWindows(node["Windows"]); diff --git a/src/Inferno/Settings.h b/src/Inferno/Settings.h index ce2fb771..ec5e2020 100644 --- a/src/Inferno/Settings.h +++ b/src/Inferno/Settings.h @@ -54,6 +54,7 @@ namespace Inferno { float TranslationSnap = 5, RotationSnap = 0; Editor::CoordinateSystem CoordinateSystem{}; Editor::TexturePreviewSize TexturePreviewSize = Editor::TexturePreviewSize::Medium; + bool UseSharedClosedWalls = false; LightSettings Lighting; float MouselookSensitivity = 0.005f; // Editor mouselook