2016-03-01 15:47:10 +00:00
|
|
|
/*
|
|
|
|
** p_saveg.cpp
|
|
|
|
** Code for serializing the world state in an archive
|
|
|
|
**
|
|
|
|
**---------------------------------------------------------------------------
|
2016-09-19 08:34:54 +00:00
|
|
|
** Copyright 1998-2016 Randy Heit
|
|
|
|
** Copyright 2005-2016 Christoph Oelckers
|
2016-03-01 15:47:10 +00:00
|
|
|
** All rights reserved.
|
|
|
|
**
|
|
|
|
** Redistribution and use in source and binary forms, with or without
|
|
|
|
** modification, are permitted provided that the following conditions
|
|
|
|
** are met:
|
|
|
|
**
|
|
|
|
** 1. Redistributions of source code must retain the above copyright
|
|
|
|
** notice, this list of conditions and the following disclaimer.
|
|
|
|
** 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
** notice, this list of conditions and the following disclaimer in the
|
|
|
|
** documentation and/or other materials provided with the distribution.
|
|
|
|
** 3. The name of the author may not be used to endorse or promote products
|
|
|
|
** derived from this software without specific prior written permission.
|
|
|
|
**
|
|
|
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
|
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
|
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
|
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
|
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
|
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
|
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
**---------------------------------------------------------------------------
|
|
|
|
**
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "i_system.h"
|
|
|
|
#include "p_local.h"
|
|
|
|
#include "p_spec.h"
|
|
|
|
|
|
|
|
// State.
|
|
|
|
#include "d_player.h"
|
|
|
|
#include "dobject.h"
|
|
|
|
#include "doomstat.h"
|
|
|
|
#include "r_state.h"
|
|
|
|
#include "m_random.h"
|
|
|
|
#include "p_saveg.h"
|
|
|
|
#include "s_sndseq.h"
|
|
|
|
#include "v_palette.h"
|
|
|
|
#include "a_sharedglobal.h"
|
|
|
|
#include "r_data/r_interpolate.h"
|
|
|
|
#include "g_level.h"
|
|
|
|
#include "po_man.h"
|
|
|
|
#include "p_setup.h"
|
|
|
|
#include "r_data/colormaps.h"
|
|
|
|
#include "p_lnspec.h"
|
|
|
|
#include "p_acs.h"
|
|
|
|
#include "p_terrain.h"
|
2016-09-19 08:34:54 +00:00
|
|
|
#include "am_map.h"
|
|
|
|
#include "r_data/r_translate.h"
|
|
|
|
#include "sbar.h"
|
|
|
|
#include "r_utility.h"
|
|
|
|
#include "r_sky.h"
|
|
|
|
#include "r_renderer.h"
|
|
|
|
#include "serializer.h"
|
2017-01-08 17:45:30 +00:00
|
|
|
#include "g_levellocals.h"
|
2017-01-23 21:05:51 +00:00
|
|
|
#include "events.h"
|
2016-09-19 08:34:54 +00:00
|
|
|
|
2017-01-08 11:11:31 +00:00
|
|
|
static TStaticArray<sector_t> loadsectors;
|
2017-01-08 13:39:16 +00:00
|
|
|
static TStaticArray<line_t> loadlines;
|
2017-01-08 17:45:30 +00:00
|
|
|
static TStaticArray<side_t> loadsides;
|
2017-01-08 11:11:31 +00:00
|
|
|
|
2016-09-19 08:34:54 +00:00
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
FSerializer &Serialize(FSerializer &arc, const char *key, line_t &line, line_t *def)
|
|
|
|
{
|
|
|
|
if (arc.BeginObject(key))
|
|
|
|
{
|
|
|
|
arc("flags", line.flags, def->flags)
|
|
|
|
("activation", line.activation, def->activation)
|
|
|
|
("special", line.special, def->special)
|
|
|
|
("alpha", line.alpha, def->alpha)
|
|
|
|
.Args("args", line.args, def->args, line.special)
|
|
|
|
("portalindex", line.portalindex, def->portalindex)
|
|
|
|
// Unless the map loader is changed the sidedef references will not change between map loads so there's no need to save them.
|
|
|
|
//.Array("sides", line.sidedef, 2)
|
|
|
|
.EndObject();
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
2016-09-19 08:34:54 +00:00
|
|
|
return arc;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
2016-03-01 15:47:10 +00:00
|
|
|
|
2016-09-19 08:34:54 +00:00
|
|
|
FSerializer &Serialize(FSerializer &arc, const char *key, side_t::part &part, side_t::part *def)
|
|
|
|
{
|
|
|
|
if (arc.canSkip() && def != nullptr && !memcmp(&part, def, sizeof(part)))
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2016-09-19 08:34:54 +00:00
|
|
|
return arc;
|
|
|
|
}
|
2016-03-01 15:47:10 +00:00
|
|
|
|
2016-09-19 08:34:54 +00:00
|
|
|
if (arc.BeginObject(key))
|
|
|
|
{
|
|
|
|
arc("xoffset", part.xOffset, def->xOffset)
|
|
|
|
("yoffset", part.yOffset, def->yOffset)
|
|
|
|
("xscale", part.xScale, def->xScale)
|
|
|
|
("yscale", part.yScale, def->yScale)
|
|
|
|
("texture", part.texture, def->texture)
|
|
|
|
("interpolation", part.interpolation)
|
|
|
|
.EndObject();
|
|
|
|
}
|
|
|
|
return arc;
|
|
|
|
}
|
2016-03-01 15:47:10 +00:00
|
|
|
|
2016-09-19 08:34:54 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
FSerializer &Serialize(FSerializer &arc, const char *key, side_t &side, side_t *def)
|
|
|
|
{
|
|
|
|
if (arc.BeginObject(key))
|
|
|
|
{
|
|
|
|
arc.Array("textures", side.textures, def->textures, 3, true)
|
|
|
|
("light", side.Light, def->Light)
|
|
|
|
("flags", side.Flags, def->Flags)
|
|
|
|
// These also remain identical across map loads
|
|
|
|
//("leftside", side.LeftSide)
|
|
|
|
//("rightside", side.RightSide)
|
|
|
|
//("index", side.Index)
|
|
|
|
("attacheddecals", side.AttachedDecals)
|
|
|
|
.EndObject();
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
2016-09-19 08:34:54 +00:00
|
|
|
return arc;
|
|
|
|
}
|
2016-03-01 15:47:10 +00:00
|
|
|
|
2016-09-19 08:34:54 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
2016-03-01 15:47:10 +00:00
|
|
|
|
2016-09-19 08:34:54 +00:00
|
|
|
FSerializer &Serialize(FSerializer &arc, const char *key, FLinkedSector &ls, FLinkedSector *def)
|
|
|
|
{
|
|
|
|
if (arc.BeginObject(key))
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2016-09-19 08:34:54 +00:00
|
|
|
arc("sector", ls.Sector)
|
|
|
|
("type", ls.Type)
|
|
|
|
.EndObject();
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
2016-09-19 08:34:54 +00:00
|
|
|
return arc;
|
|
|
|
}
|
2016-03-01 15:47:10 +00:00
|
|
|
|
2016-09-19 08:34:54 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
FSerializer &Serialize(FSerializer &arc, const char *key, sector_t::splane &p, sector_t::splane *def)
|
|
|
|
{
|
|
|
|
if (arc.canSkip() && def != nullptr && !memcmp(&p, def, sizeof(p)))
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2016-09-19 08:34:54 +00:00
|
|
|
return arc;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
|
2016-09-19 08:34:54 +00:00
|
|
|
if (arc.BeginObject(key))
|
|
|
|
{
|
|
|
|
arc("xoffs", p.xform.xOffs, def->xform.xOffs)
|
|
|
|
("yoffs", p.xform.yOffs, def->xform.yOffs)
|
|
|
|
("xscale", p.xform.xScale, def->xform.xScale)
|
|
|
|
("yscale", p.xform.yScale, def->xform.yScale)
|
|
|
|
("angle", p.xform.Angle, def->xform.Angle)
|
|
|
|
("baseyoffs", p.xform.baseyOffs, def->xform.baseyOffs)
|
|
|
|
("baseangle", p.xform.baseAngle, def->xform.baseAngle)
|
|
|
|
("flags", p.Flags, def->Flags)
|
|
|
|
("light", p.Light, def->Light)
|
|
|
|
("texture", p.Texture, def->Texture)
|
|
|
|
("texz", p.TexZ, def->TexZ)
|
|
|
|
("alpha", p.alpha, def->alpha)
|
2016-12-28 20:35:42 +00:00
|
|
|
("glowcolor", p.GlowColor, def->GlowColor)
|
|
|
|
("glowheight", p.GlowHeight, def->GlowHeight)
|
2016-09-19 08:34:54 +00:00
|
|
|
.EndObject();
|
|
|
|
}
|
|
|
|
return arc;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
|
2016-09-19 08:34:54 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
FSerializer &Serialize(FSerializer &arc, const char *key, secplane_t &p, secplane_t *def)
|
2016-04-20 17:42:00 +00:00
|
|
|
{
|
2016-09-19 08:34:54 +00:00
|
|
|
if (arc.canSkip() && def != nullptr && !memcmp(&p, def, sizeof(p)))
|
|
|
|
{
|
|
|
|
return arc;
|
|
|
|
}
|
2016-04-20 17:42:00 +00:00
|
|
|
|
2016-09-19 08:34:54 +00:00
|
|
|
if (arc.BeginObject(key))
|
2016-04-20 17:42:00 +00:00
|
|
|
{
|
2016-09-23 21:47:25 +00:00
|
|
|
Serialize(arc, "normal", p.normal, def ? &def->normal : nullptr);
|
|
|
|
Serialize(arc, "d", p.D, def ? &def->D : nullptr);
|
|
|
|
arc.EndObject();
|
2016-09-19 08:34:54 +00:00
|
|
|
|
|
|
|
if (arc.isReading() && p.normal.Z != 0)
|
|
|
|
{
|
2016-09-23 21:47:25 +00:00
|
|
|
p.negiC = -1 / p.normal.Z;
|
2016-09-19 08:34:54 +00:00
|
|
|
}
|
2016-04-20 17:42:00 +00:00
|
|
|
}
|
2016-09-19 08:34:54 +00:00
|
|
|
return arc;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
FSerializer &Serialize(FSerializer &arc, const char *key, sector_t &p, sector_t *def)
|
|
|
|
{
|
2016-09-20 11:21:41 +00:00
|
|
|
// save the Scroll data here because it's a lot easier to handle a default.
|
|
|
|
// Just writing out the full array can massively inflate the archive for no gain.
|
|
|
|
DVector2 scroll = { 0,0 }, nul = { 0,0 };
|
|
|
|
if (arc.isWriting() && level.Scrolls.Size() > 0) scroll = level.Scrolls[p.sectornum];
|
|
|
|
|
2016-09-19 08:34:54 +00:00
|
|
|
if (arc.BeginObject(key))
|
2016-04-20 17:42:00 +00:00
|
|
|
{
|
2016-09-19 08:34:54 +00:00
|
|
|
arc("floorplane", p.floorplane, def->floorplane)
|
|
|
|
("ceilingplane", p.ceilingplane, def->ceilingplane)
|
|
|
|
("lightlevel", p.lightlevel, def->lightlevel)
|
|
|
|
("special", p.special, def->special)
|
|
|
|
("seqtype", p.seqType, def->seqType)
|
|
|
|
("seqname", p.SeqName, def->SeqName)
|
|
|
|
("friction", p.friction, def->friction)
|
|
|
|
("movefactor", p.movefactor, def->movefactor)
|
|
|
|
("stairlock", p.stairlock, def->stairlock)
|
|
|
|
("prevsec", p.prevsec, def->prevsec)
|
|
|
|
("nextsec", p.nextsec, def->nextsec)
|
|
|
|
.Array("planes", p.planes, def->planes, 2, true)
|
|
|
|
// These cannot change during play.
|
|
|
|
//("heightsec", p.heightsec)
|
|
|
|
//("bottommap", p.bottommap)
|
|
|
|
//("midmap", p.midmap)
|
|
|
|
//("topmap", p.topmap)
|
|
|
|
("damageamount", p.damageamount, def->damageamount)
|
|
|
|
("damageinterval", p.damageinterval, def->damageinterval)
|
|
|
|
("leakydamage", p.leakydamage, def->leakydamage)
|
|
|
|
("damagetype", p.damagetype, def->damagetype)
|
|
|
|
("sky", p.sky, def->sky)
|
|
|
|
("moreflags", p.MoreFlags, def->MoreFlags)
|
|
|
|
("flags", p.Flags, def->Flags)
|
|
|
|
.Array("portals", p.Portals, def->Portals, 2, true)
|
|
|
|
("zonenumber", p.ZoneNumber, def->ZoneNumber)
|
|
|
|
.Array("interpolations", p.interpolations, 4, true)
|
|
|
|
("soundtarget", p.SoundTarget)
|
|
|
|
("secacttarget", p.SecActTarget)
|
|
|
|
("floordata", p.floordata)
|
|
|
|
("ceilingdata", p.ceilingdata)
|
|
|
|
("lightingdata", p.lightingdata)
|
|
|
|
("fakefloor_sectors", p.e->FakeFloor.Sectors)
|
|
|
|
("midtexf_lines", p.e->Midtex.Floor.AttachedLines)
|
|
|
|
("midtexf_sectors", p.e->Midtex.Floor.AttachedSectors)
|
|
|
|
("midtexc_lines", p.e->Midtex.Ceiling.AttachedLines)
|
|
|
|
("midtexc_sectors", p.e->Midtex.Ceiling.AttachedSectors)
|
|
|
|
("linked_floor", p.e->Linked.Floor.Sectors)
|
|
|
|
("linked_ceiling", p.e->Linked.Ceiling.Sectors)
|
|
|
|
("colormap", p.ColorMap, def->ColorMap)
|
2017-01-28 18:05:39 +00:00
|
|
|
.Array("specialcolors", p.SpecialColors, def->SpecialColors, 5, true)
|
2017-01-13 22:29:52 +00:00
|
|
|
("gravity", p.gravity, def->gravity)
|
2016-09-19 08:34:54 +00:00
|
|
|
.Terrain("floorterrain", p.terrainnum[0], &def->terrainnum[0])
|
|
|
|
.Terrain("ceilingterrain", p.terrainnum[1], &def->terrainnum[1])
|
2016-09-20 11:21:41 +00:00
|
|
|
("scrolls", scroll, nul)
|
2016-09-23 23:47:44 +00:00
|
|
|
// GZDoom exclusive:
|
|
|
|
.Array("reflect", p.reflect, def->reflect, 2)
|
2016-09-19 08:34:54 +00:00
|
|
|
.EndObject();
|
2016-09-20 11:21:41 +00:00
|
|
|
|
2016-09-22 09:22:15 +00:00
|
|
|
if (arc.isReading() && !scroll.isZero())
|
2016-09-20 11:21:41 +00:00
|
|
|
{
|
|
|
|
if (level.Scrolls.Size() == 0)
|
|
|
|
{
|
2017-01-07 18:32:24 +00:00
|
|
|
level.Scrolls.Resize(level.sectors.Size());
|
2016-09-20 11:21:41 +00:00
|
|
|
memset(&level.Scrolls[0], 0, sizeof(level.Scrolls[0])*level.Scrolls.Size());
|
2016-09-22 09:22:15 +00:00
|
|
|
level.Scrolls[p.sectornum] = scroll;
|
2016-09-20 11:21:41 +00:00
|
|
|
}
|
|
|
|
}
|
2016-04-20 17:42:00 +00:00
|
|
|
}
|
2016-09-19 08:34:54 +00:00
|
|
|
return arc;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// RecalculateDrawnSubsectors
|
|
|
|
//
|
|
|
|
// In case the subsector data is unusable this function tries to reconstruct
|
|
|
|
// if from the linedefs' ML_MAPPED info.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void RecalculateDrawnSubsectors()
|
|
|
|
{
|
|
|
|
for (int i = 0; i<numsubsectors; i++)
|
2016-04-29 09:44:17 +00:00
|
|
|
{
|
2016-09-19 08:34:54 +00:00
|
|
|
subsector_t *sub = &subsectors[i];
|
|
|
|
for (unsigned int j = 0; j<sub->numlines; j++)
|
2016-04-29 09:44:17 +00:00
|
|
|
{
|
2016-09-19 08:34:54 +00:00
|
|
|
if (sub->firstline[j].linedef != NULL &&
|
|
|
|
(sub->firstline[j].linedef->flags & ML_MAPPED))
|
2016-04-29 09:44:17 +00:00
|
|
|
{
|
2016-09-19 08:34:54 +00:00
|
|
|
sub->flags |= SSECF_DRAWN;
|
2016-04-29 09:44:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-04-20 17:42:00 +00:00
|
|
|
}
|
|
|
|
|
2016-09-19 08:34:54 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
2016-03-01 15:47:10 +00:00
|
|
|
|
2016-09-19 08:34:54 +00:00
|
|
|
FSerializer &Serialize(FSerializer &arc, const char *key, subsector_t *&ss, subsector_t **)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2016-09-19 08:34:54 +00:00
|
|
|
BYTE by;
|
|
|
|
const char *str;
|
|
|
|
|
|
|
|
if (arc.isWriting())
|
|
|
|
{
|
|
|
|
if (hasglnodes)
|
|
|
|
{
|
|
|
|
TArray<char> encoded(1 + (numsubsectors + 5) / 6);
|
|
|
|
int p = 0;
|
|
|
|
for (int i = 0; i < numsubsectors; i += 6)
|
|
|
|
{
|
|
|
|
by = 0;
|
|
|
|
for (int j = 0; j < 6; j++)
|
|
|
|
{
|
|
|
|
if (i + j < numsubsectors && (subsectors[i + j].flags & SSECF_DRAWN))
|
|
|
|
{
|
|
|
|
by |= (1 << j);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (by < 10) by += '0';
|
|
|
|
else if (by < 36) by += 'A' - 10;
|
|
|
|
else if (by < 62) by += 'a' - 36;
|
|
|
|
else if (by == 62) by = '-';
|
|
|
|
else if (by == 63) by = '+';
|
|
|
|
encoded[p++] = by;
|
|
|
|
}
|
|
|
|
encoded[p] = 0;
|
|
|
|
str = &encoded[0];
|
|
|
|
if (arc.BeginArray(key))
|
|
|
|
{
|
2017-01-08 23:46:16 +00:00
|
|
|
auto numvertexes = level.vertexes.Size();
|
2016-09-19 08:34:54 +00:00
|
|
|
arc(nullptr, numvertexes)
|
|
|
|
(nullptr, numsubsectors)
|
|
|
|
.StringPtr(nullptr, str)
|
|
|
|
.EndArray();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int num_verts, num_subs;
|
|
|
|
bool success = false;
|
|
|
|
if (arc.BeginArray(key))
|
|
|
|
{
|
|
|
|
arc(nullptr, num_verts)
|
|
|
|
(nullptr, num_subs)
|
|
|
|
.StringPtr(nullptr, str)
|
|
|
|
.EndArray();
|
|
|
|
|
2017-01-14 11:35:23 +00:00
|
|
|
if (num_verts == (int)level.vertexes.Size() && num_subs == numsubsectors && hasglnodes)
|
2016-09-19 08:34:54 +00:00
|
|
|
{
|
|
|
|
success = true;
|
2016-09-20 11:30:31 +00:00
|
|
|
int sub = 0;
|
2016-09-19 08:34:54 +00:00
|
|
|
for (int i = 0; str[i] != 0; i++)
|
|
|
|
{
|
|
|
|
by = str[i];
|
|
|
|
if (by >= '0' && by <= '9') by -= '0';
|
|
|
|
else if (by >= 'A' && by <= 'Z') by -= 'A' - 10;
|
|
|
|
else if (by >= 'a' && by <= 'z') by -= 'a' - 36;
|
|
|
|
else if (by == '-') by = 62;
|
|
|
|
else if (by == '+') by = 63;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
success = false;
|
|
|
|
break;
|
|
|
|
}
|
2016-09-20 11:30:31 +00:00
|
|
|
for (int s = 0; s < 6; s++)
|
|
|
|
{
|
|
|
|
if (sub + s < numsubsectors && (by & (1 << s)))
|
|
|
|
{
|
|
|
|
subsectors[sub + s].flags |= SSECF_DRAWN;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sub += 6;
|
2016-09-19 08:34:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (hasglnodes && !success)
|
|
|
|
{
|
|
|
|
RecalculateDrawnSubsectors();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2016-03-01 15:47:10 +00:00
|
|
|
return arc;
|
|
|
|
}
|
|
|
|
|
2016-09-19 08:34:54 +00:00
|
|
|
//============================================================================
|
|
|
|
//
|
|
|
|
// Save a line portal for savegames.
|
|
|
|
//
|
|
|
|
//============================================================================
|
|
|
|
|
|
|
|
FSerializer &Serialize(FSerializer &arc, const char *key, FLinePortal &port, FLinePortal *def)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2016-09-19 08:34:54 +00:00
|
|
|
if (arc.BeginObject(key))
|
|
|
|
{
|
|
|
|
arc("origin", port.mOrigin)
|
|
|
|
("destination", port.mDestination)
|
|
|
|
("displacement", port.mDisplacement)
|
|
|
|
("type", port.mType)
|
|
|
|
("flags", port.mFlags)
|
|
|
|
("defflags", port.mDefFlags)
|
|
|
|
("align", port.mAlign)
|
|
|
|
.EndObject();
|
|
|
|
}
|
2016-03-01 15:47:10 +00:00
|
|
|
return arc;
|
|
|
|
}
|
|
|
|
|
2016-09-19 08:34:54 +00:00
|
|
|
//============================================================================
|
2016-03-01 15:47:10 +00:00
|
|
|
//
|
2016-09-19 08:34:54 +00:00
|
|
|
// Save a sector portal for savegames.
|
2016-03-01 15:47:10 +00:00
|
|
|
//
|
2016-09-19 08:34:54 +00:00
|
|
|
//============================================================================
|
2016-03-01 15:47:10 +00:00
|
|
|
|
2016-09-19 08:34:54 +00:00
|
|
|
FSerializer &Serialize(FSerializer &arc, const char *key, FSectorPortal &port, FSectorPortal *def)
|
|
|
|
{
|
|
|
|
if (arc.BeginObject(key))
|
|
|
|
{
|
|
|
|
arc("type", port.mType)
|
|
|
|
("flags", port.mFlags)
|
|
|
|
("partner", port.mPartner)
|
|
|
|
("plane", port.mPlane)
|
|
|
|
("origin", port.mOrigin)
|
|
|
|
("destination", port.mDestination)
|
|
|
|
("displacement", port.mDisplacement)
|
|
|
|
("planez", port.mPlaneZ)
|
|
|
|
("skybox", port.mSkybox)
|
|
|
|
.EndObject();
|
|
|
|
}
|
|
|
|
return arc;
|
|
|
|
}
|
|
|
|
|
2016-09-20 07:11:13 +00:00
|
|
|
//============================================================================
|
|
|
|
//
|
|
|
|
// one polyobject.
|
|
|
|
//
|
|
|
|
//============================================================================
|
|
|
|
|
|
|
|
FSerializer &Serialize(FSerializer &arc, const char *key, FPolyObj &poly, FPolyObj *def)
|
|
|
|
{
|
|
|
|
if (arc.BeginObject(key))
|
|
|
|
{
|
|
|
|
DAngle angle = poly.Angle;
|
|
|
|
DVector2 delta = poly.StartSpot.pos;
|
|
|
|
arc("angle", angle)
|
|
|
|
("pos", delta)
|
|
|
|
("interpolation", poly.interpolation)
|
|
|
|
("blocked", poly.bBlocked)
|
|
|
|
("hasportals", poly.bHasPortals)
|
|
|
|
("specialdata", poly.specialdata)
|
|
|
|
.EndObject();
|
|
|
|
|
|
|
|
if (arc.isReading())
|
|
|
|
{
|
|
|
|
poly.RotatePolyobj(angle, true);
|
|
|
|
delta -= poly.StartSpot.pos;
|
|
|
|
poly.MovePolyobj(delta, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return arc;
|
|
|
|
}
|
|
|
|
|
2016-09-19 08:34:54 +00:00
|
|
|
//==========================================================================
|
2016-03-01 15:47:10 +00:00
|
|
|
//
|
|
|
|
//
|
2016-09-19 08:34:54 +00:00
|
|
|
//
|
|
|
|
//==========================================================================
|
2016-03-01 15:47:10 +00:00
|
|
|
|
2016-09-19 08:34:54 +00:00
|
|
|
FSerializer &Serialize(FSerializer &arc, const char *key, ReverbContainer *&c, ReverbContainer **def)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2016-09-19 08:34:54 +00:00
|
|
|
int id = (arc.isReading() || c == nullptr) ? 0 : c->ID;
|
|
|
|
Serialize(arc, key, id, nullptr);
|
|
|
|
if (arc.isReading())
|
|
|
|
{
|
|
|
|
c = S_FindEnvironment(id);
|
|
|
|
}
|
|
|
|
return arc;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
|
2016-09-19 08:34:54 +00:00
|
|
|
FSerializer &Serialize(FSerializer &arc, const char *key, zone_t &z, zone_t *def)
|
2016-06-01 09:43:56 +00:00
|
|
|
{
|
2016-09-19 08:34:54 +00:00
|
|
|
return Serialize(arc, key, z.Environment, nullptr);
|
|
|
|
}
|
|
|
|
|
2016-09-20 21:13:12 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// ArchiveSounds
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void P_SerializeSounds(FSerializer &arc)
|
|
|
|
{
|
|
|
|
S_SerializeSounds(arc);
|
|
|
|
DSeqNode::SerializeSequences (arc);
|
2016-09-23 18:05:12 +00:00
|
|
|
const char *name = NULL;
|
2016-09-20 21:13:12 +00:00
|
|
|
BYTE order;
|
|
|
|
|
|
|
|
if (arc.isWriting())
|
|
|
|
{
|
2016-09-23 19:24:56 +00:00
|
|
|
order = S_GetMusic(&name);
|
2016-09-20 21:13:12 +00:00
|
|
|
}
|
2016-09-23 18:05:12 +00:00
|
|
|
arc.StringPtr("musicname", name)
|
2016-09-20 21:13:12 +00:00
|
|
|
("musicorder", order);
|
|
|
|
|
|
|
|
if (arc.isReading())
|
|
|
|
{
|
|
|
|
if (!S_ChangeMusic(name, order))
|
|
|
|
if (level.cdtrack == 0 || !S_ChangeCDMusic(level.cdtrack, level.cdid))
|
|
|
|
S_ChangeMusic(level.Music, level.musicorder);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void CopyPlayer(player_t *dst, player_t *src, const char *name);
|
|
|
|
static void ReadOnePlayer(FSerializer &arc, bool skipload);
|
|
|
|
static void ReadMultiplePlayers(FSerializer &arc, int numPlayers, int numPlayersNow, bool skipload);
|
|
|
|
static void SpawnExtraPlayers();
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// P_ArchivePlayers
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void P_SerializePlayers(FSerializer &arc, bool skipload)
|
|
|
|
{
|
2016-09-20 23:18:29 +00:00
|
|
|
int numPlayers, numPlayersNow;
|
2016-09-20 21:13:12 +00:00
|
|
|
int i;
|
|
|
|
|
|
|
|
// Count the number of players present right now.
|
|
|
|
for (numPlayersNow = 0, i = 0; i < MAXPLAYERS; ++i)
|
|
|
|
{
|
|
|
|
if (playeringame[i])
|
|
|
|
{
|
|
|
|
++numPlayersNow;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (arc.isWriting())
|
|
|
|
{
|
|
|
|
// Record the number of players in this save.
|
2016-09-20 23:18:29 +00:00
|
|
|
arc("numplayers", numPlayersNow);
|
|
|
|
if (arc.BeginArray("players"))
|
2016-09-20 21:13:12 +00:00
|
|
|
{
|
2016-09-20 23:18:29 +00:00
|
|
|
// Record each player's name, followed by their data.
|
|
|
|
for (i = 0; i < MAXPLAYERS; ++i)
|
2016-09-20 21:13:12 +00:00
|
|
|
{
|
2016-09-20 23:18:29 +00:00
|
|
|
if (playeringame[i])
|
|
|
|
{
|
|
|
|
if (arc.BeginObject(nullptr))
|
|
|
|
{
|
|
|
|
const char *n = players[i].userinfo.GetName();
|
|
|
|
arc.StringPtr("playername", n);
|
|
|
|
players[i].Serialize(arc);
|
|
|
|
arc.EndObject();
|
|
|
|
}
|
|
|
|
}
|
2016-09-20 21:13:12 +00:00
|
|
|
}
|
2016-09-20 23:18:29 +00:00
|
|
|
arc.EndArray();
|
2016-09-20 21:13:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-09-20 23:18:29 +00:00
|
|
|
arc("numplayers", numPlayers);
|
2016-09-20 21:13:12 +00:00
|
|
|
|
2016-09-20 23:18:29 +00:00
|
|
|
if (arc.BeginArray("players"))
|
2016-09-20 21:13:12 +00:00
|
|
|
{
|
2016-09-20 23:18:29 +00:00
|
|
|
// If there is only one player in the game, they go to the
|
|
|
|
// first player present, no matter what their name.
|
|
|
|
if (numPlayers == 1)
|
|
|
|
{
|
|
|
|
ReadOnePlayer(arc, skipload);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ReadMultiplePlayers(arc, numPlayers, numPlayersNow, skipload);
|
|
|
|
}
|
2016-09-23 18:05:12 +00:00
|
|
|
arc.EndArray();
|
2016-09-20 21:13:12 +00:00
|
|
|
}
|
|
|
|
if (!skipload && numPlayersNow > numPlayers)
|
|
|
|
{
|
|
|
|
SpawnExtraPlayers();
|
|
|
|
}
|
|
|
|
// Redo pitch limits, since the spawned player has them at 0.
|
|
|
|
players[consoleplayer].SendPitchLimits();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
static void ReadOnePlayer(FSerializer &arc, bool skipload)
|
|
|
|
{
|
|
|
|
int i;
|
2016-09-20 23:18:29 +00:00
|
|
|
const char *name = NULL;
|
2016-09-20 21:13:12 +00:00
|
|
|
bool didIt = false;
|
|
|
|
|
2016-09-20 23:18:29 +00:00
|
|
|
if (arc.BeginObject(nullptr))
|
2016-09-20 21:13:12 +00:00
|
|
|
{
|
2016-09-20 23:18:29 +00:00
|
|
|
arc.StringPtr("playername", name);
|
|
|
|
|
|
|
|
for (i = 0; i < MAXPLAYERS; ++i)
|
2016-09-20 21:13:12 +00:00
|
|
|
{
|
2016-09-20 23:18:29 +00:00
|
|
|
if (playeringame[i])
|
2016-09-20 21:13:12 +00:00
|
|
|
{
|
2016-09-20 23:18:29 +00:00
|
|
|
if (!didIt)
|
2016-09-20 21:13:12 +00:00
|
|
|
{
|
2016-09-20 23:18:29 +00:00
|
|
|
didIt = true;
|
|
|
|
player_t playerTemp;
|
|
|
|
playerTemp.Serialize(arc);
|
|
|
|
if (!skipload)
|
|
|
|
{
|
2016-10-23 01:56:31 +00:00
|
|
|
// This temp player has undefined pitch limits, so set them to something
|
|
|
|
// that should leave the pitch stored in the savegame intact when
|
|
|
|
// rendering. The real pitch limits will be set by P_SerializePlayers()
|
|
|
|
// via a net command, but that won't be processed in time for a screen
|
|
|
|
// wipe, so we need something here.
|
|
|
|
playerTemp.MaxPitch = playerTemp.MinPitch = playerTemp.mo->Angles.Pitch;
|
2016-09-20 23:18:29 +00:00
|
|
|
CopyPlayer(&players[i], &playerTemp, name);
|
|
|
|
}
|
2016-09-25 07:23:44 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
// we need the player actor, so that G_FinishTravel can destroy it later.
|
|
|
|
players[i].mo = playerTemp.mo;
|
|
|
|
}
|
2016-09-20 21:13:12 +00:00
|
|
|
}
|
2016-09-20 23:18:29 +00:00
|
|
|
else
|
2016-09-20 21:13:12 +00:00
|
|
|
{
|
2016-09-20 23:18:29 +00:00
|
|
|
if (players[i].mo != NULL)
|
|
|
|
{
|
|
|
|
players[i].mo->Destroy();
|
|
|
|
players[i].mo = NULL;
|
|
|
|
}
|
2016-09-20 21:13:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-09-23 18:05:12 +00:00
|
|
|
arc.EndObject();
|
2016-09-20 21:13:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
static void ReadMultiplePlayers(FSerializer &arc, int numPlayers, int numPlayersNow, bool skipload)
|
|
|
|
{
|
|
|
|
// For two or more players, read each player into a temporary array.
|
|
|
|
int i, j;
|
2016-09-20 23:18:29 +00:00
|
|
|
const char **nametemp = new const char *[numPlayers];
|
2016-09-20 21:13:12 +00:00
|
|
|
player_t *playertemp = new player_t[numPlayers];
|
|
|
|
BYTE *tempPlayerUsed = new BYTE[numPlayers];
|
|
|
|
BYTE playerUsed[MAXPLAYERS];
|
|
|
|
|
|
|
|
for (i = 0; i < numPlayers; ++i)
|
|
|
|
{
|
|
|
|
nametemp[i] = NULL;
|
2016-09-20 23:18:29 +00:00
|
|
|
if (arc.BeginObject(nullptr))
|
|
|
|
{
|
|
|
|
arc.StringPtr("playername", nametemp[i]);
|
|
|
|
playertemp[i].Serialize(arc);
|
2016-09-23 18:05:12 +00:00
|
|
|
arc.EndObject();
|
2016-09-20 23:18:29 +00:00
|
|
|
}
|
2016-09-20 21:13:12 +00:00
|
|
|
tempPlayerUsed[i] = 0;
|
|
|
|
}
|
|
|
|
for (i = 0; i < MAXPLAYERS; ++i)
|
|
|
|
{
|
|
|
|
playerUsed[i] = playeringame[i] ? 0 : 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!skipload)
|
|
|
|
{
|
|
|
|
// Now try to match players from the savegame with players present
|
|
|
|
// based on their names. If two players in the savegame have the
|
|
|
|
// same name, then they are assigned to players in the current game
|
|
|
|
// on a first-come, first-served basis.
|
|
|
|
for (i = 0; i < numPlayers; ++i)
|
|
|
|
{
|
|
|
|
for (j = 0; j < MAXPLAYERS; ++j)
|
|
|
|
{
|
|
|
|
if (playerUsed[j] == 0 && stricmp(players[j].userinfo.GetName(), nametemp[i]) == 0)
|
|
|
|
{ // Found a match, so copy our temp player to the real player
|
|
|
|
Printf("Found player %d (%s) at %d\n", i, nametemp[i], j);
|
|
|
|
CopyPlayer(&players[j], &playertemp[i], nametemp[i]);
|
|
|
|
playerUsed[j] = 1;
|
|
|
|
tempPlayerUsed[i] = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Any players that didn't have matching names are assigned to existing
|
|
|
|
// players on a first-come, first-served basis.
|
|
|
|
for (i = 0; i < numPlayers; ++i)
|
|
|
|
{
|
|
|
|
if (tempPlayerUsed[i] == 0)
|
|
|
|
{
|
|
|
|
for (j = 0; j < MAXPLAYERS; ++j)
|
|
|
|
{
|
|
|
|
if (playerUsed[j] == 0)
|
|
|
|
{
|
|
|
|
Printf("Assigned player %d (%s) to %d (%s)\n", i, nametemp[i], j, players[j].userinfo.GetName());
|
|
|
|
CopyPlayer(&players[j], &playertemp[i], nametemp[i]);
|
|
|
|
playerUsed[j] = 1;
|
|
|
|
tempPlayerUsed[i] = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure any extra players don't have actors spawned yet. Happens if the players
|
|
|
|
// present now got the same slots as they had in the save, but there are not as many
|
|
|
|
// as there were in the save.
|
|
|
|
for (j = 0; j < MAXPLAYERS; ++j)
|
|
|
|
{
|
|
|
|
if (playerUsed[j] == 0)
|
|
|
|
{
|
|
|
|
if (players[j].mo != NULL)
|
|
|
|
{
|
|
|
|
players[j].mo->Destroy();
|
|
|
|
players[j].mo = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove any temp players that were not used. Happens if there are fewer players
|
|
|
|
// than there were in the save, and they got shuffled.
|
|
|
|
for (i = 0; i < numPlayers; ++i)
|
|
|
|
{
|
|
|
|
if (tempPlayerUsed[i] == 0)
|
|
|
|
{
|
|
|
|
playertemp[i].mo->Destroy();
|
|
|
|
playertemp[i].mo = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-09-25 07:23:44 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
for (i = 0; i < MAXPLAYERS; ++i)
|
|
|
|
{
|
|
|
|
players[i].mo = playertemp[i].mo;
|
|
|
|
}
|
|
|
|
}
|
2016-09-20 21:13:12 +00:00
|
|
|
|
|
|
|
delete[] tempPlayerUsed;
|
|
|
|
delete[] playertemp;
|
|
|
|
delete[] nametemp;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void CopyPlayer(player_t *dst, player_t *src, const char *name)
|
|
|
|
{
|
|
|
|
// The userinfo needs to be saved for real players, but it
|
|
|
|
// needs to come from the save for bots.
|
|
|
|
userinfo_t uibackup;
|
|
|
|
userinfo_t uibackup2;
|
|
|
|
|
|
|
|
uibackup.TransferFrom(dst->userinfo);
|
|
|
|
uibackup2.TransferFrom(src->userinfo);
|
|
|
|
|
|
|
|
int chasecam = dst->cheats & CF_CHASECAM; // Remember the chasecam setting
|
|
|
|
bool attackdown = dst->attackdown;
|
|
|
|
bool usedown = dst->usedown;
|
|
|
|
|
|
|
|
|
|
|
|
*dst = *src; // To avoid memory leaks at this point the userinfo in src must be empty which is taken care of by the TransferFrom call above.
|
|
|
|
|
|
|
|
dst->cheats |= chasecam;
|
|
|
|
|
|
|
|
if (dst->Bot != nullptr)
|
|
|
|
{
|
|
|
|
botinfo_t *thebot = bglobal.botinfo;
|
|
|
|
while (thebot && stricmp(name, thebot->name))
|
|
|
|
{
|
|
|
|
thebot = thebot->next;
|
|
|
|
}
|
|
|
|
if (thebot)
|
|
|
|
{
|
|
|
|
thebot->inuse = BOTINUSE_Yes;
|
|
|
|
}
|
|
|
|
bglobal.botnum++;
|
|
|
|
dst->userinfo.TransferFrom(uibackup2);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
dst->userinfo.TransferFrom(uibackup);
|
2017-02-15 10:55:08 +00:00
|
|
|
// The player class must come from the save, so that the menu reflects the currently playing one.
|
|
|
|
dst->userinfo.PlayerClassChanged(src->mo->GetClass()->DisplayName);
|
2016-09-20 21:13:12 +00:00
|
|
|
}
|
2017-02-15 10:55:08 +00:00
|
|
|
|
2016-09-20 21:13:12 +00:00
|
|
|
// Validate the skin
|
2017-02-17 20:51:23 +00:00
|
|
|
dst->userinfo.SkinNumChanged(R_FindSkin(Skins[dst->userinfo.GetSkin()].Name, dst->CurrentPlayerClass));
|
2016-09-20 21:13:12 +00:00
|
|
|
|
|
|
|
// Make sure the player pawn points to the proper player struct.
|
|
|
|
if (dst->mo != nullptr)
|
|
|
|
{
|
|
|
|
dst->mo->player = dst;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Same for the psprites.
|
|
|
|
DPSprite *pspr = dst->psprites;
|
|
|
|
while (pspr)
|
|
|
|
{
|
|
|
|
pspr->Owner = dst;
|
|
|
|
|
|
|
|
pspr = pspr->Next;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't let the psprites be destroyed when src is destroyed.
|
|
|
|
src->psprites = nullptr;
|
|
|
|
|
|
|
|
// These 2 variables may not be overwritten.
|
|
|
|
dst->attackdown = attackdown;
|
|
|
|
dst->usedown = usedown;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
static void SpawnExtraPlayers()
|
|
|
|
{
|
|
|
|
// If there are more players now than there were in the savegame,
|
|
|
|
// be sure to spawn the extra players.
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (deathmatch)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < MAXPLAYERS; ++i)
|
|
|
|
{
|
|
|
|
if (playeringame[i] && players[i].mo == NULL)
|
|
|
|
{
|
|
|
|
players[i].playerstate = PST_ENTER;
|
|
|
|
P_SpawnPlayer(&playerstarts[i], i, (level.flags2 & LEVEL2_PRERAISEWEAPON) ? SPF_WEAPONFULLYUP : 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//============================================================================
|
2016-09-19 08:34:54 +00:00
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//============================================================================
|
|
|
|
|
2016-09-20 07:11:13 +00:00
|
|
|
void G_SerializeLevel(FSerializer &arc, bool hubload)
|
2016-09-19 08:34:54 +00:00
|
|
|
{
|
2016-09-20 07:11:13 +00:00
|
|
|
int i = level.totaltime;
|
|
|
|
|
|
|
|
if (arc.isWriting())
|
|
|
|
{
|
|
|
|
arc.Array("checksum", level.md5, 16);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// prevent bad things from happening by doing a check on the size of level arrays and the map's entire checksum.
|
|
|
|
// The old code happily tried to load savegames with any mismatch here, often causing meaningless errors
|
|
|
|
// deep down in the deserializer or just a crash if the few insufficient safeguards were not triggered.
|
|
|
|
BYTE chk[16] = { 0 };
|
|
|
|
arc.Array("checksum", chk, 16);
|
2017-01-08 13:39:16 +00:00
|
|
|
if (arc.GetSize("linedefs") != level.lines.Size() ||
|
2017-01-08 17:45:30 +00:00
|
|
|
arc.GetSize("sidedefs") != level.sides.Size() ||
|
2017-01-07 18:32:24 +00:00
|
|
|
arc.GetSize("sectors") != level.sectors.Size() ||
|
2016-10-17 04:19:08 +00:00
|
|
|
arc.GetSize("polyobjs") != (unsigned)po_NumPolyobjs ||
|
2016-09-20 07:11:13 +00:00
|
|
|
memcmp(chk, level.md5, 16))
|
|
|
|
{
|
|
|
|
I_Error("Savegame is from a different level");
|
|
|
|
}
|
|
|
|
}
|
2016-09-21 15:37:56 +00:00
|
|
|
arc("saveversion", SaveVersion);
|
2016-09-20 07:11:13 +00:00
|
|
|
|
2016-09-20 21:13:12 +00:00
|
|
|
Renderer->StartSerialize(arc);
|
2016-09-20 07:11:13 +00:00
|
|
|
if (arc.isReading())
|
|
|
|
{
|
2016-09-23 21:03:11 +00:00
|
|
|
DThinker::DestroyAllThinkers();
|
- removed the sequential processing of JSON objects because the benefit is too small.
After testing with a savegame on ZDCMP2 which is probably the largest map in existence, timing both methods resulted in a speed difference of less than 40 ms (70 vs 110 ms for reading all sectory, linedefs, sidedefs and objects).
This compares to an overall restoration time, including reloading the level, precaching all textures and setting everything up, of approx. 1.2 s, meaning an increase of 3% of the entire reloading time.
That's simply not worth all the negative side effects that may happen with a method that highly depends on proper code construction.
On the other hand, using random access means that a savegame version change is only needed now when the semantics of a field change, but not if some get added or deleted.
- do not I_Error out in the serializer unless caused by a programming error.
It is better to let the serializer finish, collect all the errors and I_Error out when the game is known to be in a stable enough state to allow unwinding.
2016-09-23 12:04:05 +00:00
|
|
|
interpolator.ClearInterpolations();
|
2016-09-23 15:49:33 +00:00
|
|
|
arc.ReadObjects(hubload);
|
2016-09-20 07:11:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
arc("level.flags", level.flags)
|
|
|
|
("level.flags2", level.flags2)
|
|
|
|
("level.fadeto", level.fadeto)
|
|
|
|
("level.found_secrets", level.found_secrets)
|
|
|
|
("level.found_items", level.found_items)
|
|
|
|
("level.killed_monsters", level.killed_monsters)
|
|
|
|
("level.total_secrets", level.total_secrets)
|
|
|
|
("level.total_items", level.total_items)
|
|
|
|
("level.total_monsters", level.total_monsters)
|
|
|
|
("level.gravity", level.gravity)
|
|
|
|
("level.aircontrol", level.aircontrol)
|
|
|
|
("level.teamdamage", level.teamdamage)
|
|
|
|
("level.maptime", level.maptime)
|
|
|
|
("level.totaltime", i)
|
|
|
|
("level.skytexture1", level.skytexture1)
|
2016-09-20 11:21:41 +00:00
|
|
|
("level.skytexture2", level.skytexture2);
|
2016-09-20 07:11:13 +00:00
|
|
|
|
|
|
|
// Hub transitions must keep the current total time
|
|
|
|
if (!hubload)
|
|
|
|
level.totaltime = i;
|
|
|
|
|
|
|
|
if (arc.isReading())
|
|
|
|
{
|
|
|
|
sky1texture = level.skytexture1;
|
|
|
|
sky2texture = level.skytexture2;
|
|
|
|
R_InitSkyMap();
|
- removed the sequential processing of JSON objects because the benefit is too small.
After testing with a savegame on ZDCMP2 which is probably the largest map in existence, timing both methods resulted in a speed difference of less than 40 ms (70 vs 110 ms for reading all sectory, linedefs, sidedefs and objects).
This compares to an overall restoration time, including reloading the level, precaching all textures and setting everything up, of approx. 1.2 s, meaning an increase of 3% of the entire reloading time.
That's simply not worth all the negative side effects that may happen with a method that highly depends on proper code construction.
On the other hand, using random access means that a savegame version change is only needed now when the semantics of a field change, but not if some get added or deleted.
- do not I_Error out in the serializer unless caused by a programming error.
It is better to let the serializer finish, collect all the errors and I_Error out when the game is known to be in a stable enough state to allow unwinding.
2016-09-23 12:04:05 +00:00
|
|
|
G_AirControlChanged();
|
2016-09-20 07:11:13 +00:00
|
|
|
}
|
|
|
|
|
2016-09-20 11:21:41 +00:00
|
|
|
|
2016-09-20 07:11:13 +00:00
|
|
|
|
2016-09-19 08:34:54 +00:00
|
|
|
// fixme: This needs to ensure it reads from the correct place. Should be one once there's enough of this code converted to JSON
|
2016-09-20 07:11:13 +00:00
|
|
|
|
2016-09-23 12:06:51 +00:00
|
|
|
FBehavior::StaticSerializeModuleStates(arc);
|
2016-09-20 11:21:41 +00:00
|
|
|
// The order here is important: First world state, then portal state, then thinkers, and last polyobjects.
|
2017-01-08 13:39:16 +00:00
|
|
|
arc.Array("linedefs", &level.lines[0], &loadlines[0], level.lines.Size());
|
2017-01-08 17:45:30 +00:00
|
|
|
arc.Array("sidedefs", &level.sides[0], &loadsides[0], level.sides.Size());
|
2017-01-07 18:32:24 +00:00
|
|
|
arc.Array("sectors", &level.sectors[0], &loadsectors[0], level.sectors.Size());
|
2016-09-20 07:11:13 +00:00
|
|
|
arc("zones", Zones);
|
|
|
|
arc("lineportals", linePortals);
|
2017-01-14 15:05:40 +00:00
|
|
|
arc("sectorportals", level.sectorPortals);
|
2016-09-19 08:34:54 +00:00
|
|
|
if (arc.isReading()) P_CollectLinkedPortals();
|
- removed the sequential processing of JSON objects because the benefit is too small.
After testing with a savegame on ZDCMP2 which is probably the largest map in existence, timing both methods resulted in a speed difference of less than 40 ms (70 vs 110 ms for reading all sectory, linedefs, sidedefs and objects).
This compares to an overall restoration time, including reloading the level, precaching all textures and setting everything up, of approx. 1.2 s, meaning an increase of 3% of the entire reloading time.
That's simply not worth all the negative side effects that may happen with a method that highly depends on proper code construction.
On the other hand, using random access means that a savegame version change is only needed now when the semantics of a field change, but not if some get added or deleted.
- do not I_Error out in the serializer unless caused by a programming error.
It is better to let the serializer finish, collect all the errors and I_Error out when the game is known to be in a stable enough state to allow unwinding.
2016-09-23 12:04:05 +00:00
|
|
|
|
2017-01-23 21:05:51 +00:00
|
|
|
// [ZZ] serialize events
|
|
|
|
E_SerializeEvents(arc);
|
2016-09-23 12:06:51 +00:00
|
|
|
DThinker::SerializeThinkers(arc, !hubload);
|
2016-09-20 11:21:41 +00:00
|
|
|
arc.Array("polyobjs", polyobjs, po_NumPolyobjs);
|
|
|
|
arc("subsectors", subsectors);
|
|
|
|
StatusBar->SerializeMessages(arc);
|
|
|
|
AM_SerializeMarkers(arc);
|
2016-09-20 17:45:32 +00:00
|
|
|
FRemapTable::StaticSerializeTranslations(arc);
|
2016-09-19 08:34:54 +00:00
|
|
|
FCanvasTextureInfo::Serialize(arc);
|
2016-09-20 23:18:29 +00:00
|
|
|
P_SerializePlayers(arc, hubload);
|
2016-09-19 08:34:54 +00:00
|
|
|
P_SerializeSounds(arc);
|
- removed the sequential processing of JSON objects because the benefit is too small.
After testing with a savegame on ZDCMP2 which is probably the largest map in existence, timing both methods resulted in a speed difference of less than 40 ms (70 vs 110 ms for reading all sectory, linedefs, sidedefs and objects).
This compares to an overall restoration time, including reloading the level, precaching all textures and setting everything up, of approx. 1.2 s, meaning an increase of 3% of the entire reloading time.
That's simply not worth all the negative side effects that may happen with a method that highly depends on proper code construction.
On the other hand, using random access means that a savegame version change is only needed now when the semantics of a field change, but not if some get added or deleted.
- do not I_Error out in the serializer unless caused by a programming error.
It is better to let the serializer finish, collect all the errors and I_Error out when the game is known to be in a stable enough state to allow unwinding.
2016-09-23 12:04:05 +00:00
|
|
|
|
2016-09-20 21:13:12 +00:00
|
|
|
if (arc.isReading())
|
2016-09-19 08:34:54 +00:00
|
|
|
{
|
2017-01-07 18:32:24 +00:00
|
|
|
for (auto &sec : level.sectors)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2017-01-07 18:32:24 +00:00
|
|
|
P_Recalculate3DFloors(&sec);
|
2016-09-19 08:34:54 +00:00
|
|
|
}
|
2016-09-20 07:11:13 +00:00
|
|
|
for (int i = 0; i < MAXPLAYERS; ++i)
|
2016-09-19 08:34:54 +00:00
|
|
|
{
|
|
|
|
if (playeringame[i] && players[i].mo != NULL)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2016-09-19 08:34:54 +00:00
|
|
|
players[i].mo->SetupWeaponSlots();
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-09-19 08:34:54 +00:00
|
|
|
Renderer->EndSerialize(arc);
|
2016-09-20 21:13:12 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-01-08 11:11:31 +00:00
|
|
|
// Create a backup of the map data so the savegame code can toss out all fields that haven't changed in order to reduce processing time and file size.
|
2016-09-20 21:13:12 +00:00
|
|
|
|
2017-01-08 11:11:31 +00:00
|
|
|
void P_BackupMapData()
|
|
|
|
{
|
|
|
|
loadsectors = level.sectors;
|
2017-01-08 13:39:16 +00:00
|
|
|
loadlines = level.lines;
|
2017-01-08 17:45:30 +00:00
|
|
|
loadsides = level.sides;
|
2017-01-08 11:11:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void P_FreeMapDataBackup()
|
|
|
|
{
|
|
|
|
loadsectors.Clear();
|
|
|
|
loadlines.Clear();
|
|
|
|
loadsides.Clear();
|
2017-01-14 11:35:23 +00:00
|
|
|
}
|