mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-12-15 06:51:36 +00:00
9bdb0f2e49
- precalculate if a sector's floor and ceiling plane overlap. This avoids rechecking this for each single call of hw_FakeFlat. - vertices must be marked dirty every time they change after map setup. That means that ChangePlaneTexZ must do this as well, because it cannot rely on interpolation taking care of it. - Having a 'dirty' argument for SetPlaneTexZ's ZScript version makes no sense. If the value changes from the script side the vertices must always be marked to be recalculated.
1019 lines
27 KiB
C++
1019 lines
27 KiB
C++
/*
|
|
** p_saveg.cpp
|
|
** Code for serializing the world state in an archive
|
|
**
|
|
**---------------------------------------------------------------------------
|
|
** Copyright 1998-2016 Randy Heit
|
|
** Copyright 2005-2016 Christoph Oelckers
|
|
** 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 "p_saveg.h"
|
|
#include "s_sndseq.h"
|
|
#include "a_sharedglobal.h"
|
|
#include "r_data/r_interpolate.h"
|
|
#include "po_man.h"
|
|
#include "p_setup.h"
|
|
#include "p_lnspec.h"
|
|
#include "p_acs.h"
|
|
#include "p_terrain.h"
|
|
#include "am_map.h"
|
|
#include "sbar.h"
|
|
#include "r_utility.h"
|
|
#include "r_sky.h"
|
|
#include "r_renderer.h"
|
|
#include "serializer.h"
|
|
#include "g_levellocals.h"
|
|
#include "events.h"
|
|
|
|
//==========================================================================
|
|
//
|
|
//
|
|
//
|
|
//==========================================================================
|
|
|
|
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();
|
|
}
|
|
return arc;
|
|
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
//
|
|
//
|
|
//==========================================================================
|
|
|
|
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)))
|
|
{
|
|
return arc;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
//
|
|
//
|
|
//==========================================================================
|
|
|
|
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();
|
|
}
|
|
return arc;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
//
|
|
//
|
|
//==========================================================================
|
|
|
|
FSerializer &Serialize(FSerializer &arc, const char *key, FLinkedSector &ls, FLinkedSector *def)
|
|
{
|
|
if (arc.BeginObject(key))
|
|
{
|
|
arc("sector", ls.Sector)
|
|
("type", ls.Type)
|
|
.EndObject();
|
|
}
|
|
return arc;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// Save a sector portal for savegames.
|
|
//
|
|
//============================================================================
|
|
|
|
FSerializer &Serialize(FSerializer &arc, const char *key, FColormap &port, FColormap *def)
|
|
{
|
|
if (arc.canSkip() && def != nullptr && port == *def)
|
|
{
|
|
return arc;
|
|
}
|
|
|
|
if (arc.BeginObject(key))
|
|
{
|
|
arc("lightcolor", port.LightColor)
|
|
("fadecolor", port.FadeColor)
|
|
("desaturation", port.Desaturation)
|
|
("fogdensity", port.FogDensity)
|
|
.EndObject();
|
|
}
|
|
return arc;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
//
|
|
//
|
|
//==========================================================================
|
|
|
|
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)))
|
|
{
|
|
return arc;
|
|
}
|
|
|
|
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)
|
|
("glowcolor", p.GlowColor, def->GlowColor)
|
|
("glowheight", p.GlowHeight, def->GlowHeight)
|
|
.EndObject();
|
|
}
|
|
return arc;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
//
|
|
//
|
|
//==========================================================================
|
|
|
|
FSerializer &Serialize(FSerializer &arc, const char *key, secplane_t &p, secplane_t *def)
|
|
{
|
|
if (arc.canSkip() && def != nullptr && !memcmp(&p, def, sizeof(p)))
|
|
{
|
|
return arc;
|
|
}
|
|
|
|
if (arc.BeginObject(key))
|
|
{
|
|
Serialize(arc, "normal", p.normal, def ? &def->normal : nullptr);
|
|
Serialize(arc, "d", p.D, def ? &def->D : nullptr);
|
|
arc.EndObject();
|
|
|
|
if (arc.isReading() && p.normal.Z != 0)
|
|
{
|
|
p.negiC = -1 / p.normal.Z;
|
|
}
|
|
}
|
|
return arc;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
//
|
|
//
|
|
//==========================================================================
|
|
|
|
FSerializer &Serialize(FSerializer &arc, const char *key, sector_t &p, sector_t *def)
|
|
{
|
|
// 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];
|
|
|
|
if (arc.BeginObject(key))
|
|
{
|
|
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)
|
|
.Array("specialcolors", p.SpecialColors, def->SpecialColors, 5, true)
|
|
("gravity", p.gravity, def->gravity)
|
|
.Terrain("floorterrain", p.terrainnum[0], &def->terrainnum[0])
|
|
.Terrain("ceilingterrain", p.terrainnum[1], &def->terrainnum[1])
|
|
("scrolls", scroll, nul)
|
|
// GZDoom exclusive:
|
|
.Array("reflect", p.reflect, def->reflect, 2, true)
|
|
.EndObject();
|
|
|
|
if (arc.isReading() && !scroll.isZero())
|
|
{
|
|
if (level.Scrolls.Size() == 0)
|
|
{
|
|
level.Scrolls.Resize(level.sectors.Size());
|
|
memset(&level.Scrolls[0], 0, sizeof(level.Scrolls[0])*level.Scrolls.Size());
|
|
level.Scrolls[p.sectornum] = scroll;
|
|
}
|
|
}
|
|
}
|
|
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 (auto &sub : level.subsectors)
|
|
{
|
|
for (unsigned int j = 0; j<sub.numlines; j++)
|
|
{
|
|
if (sub.firstline[j].linedef != NULL &&
|
|
(sub.firstline[j].linedef->flags & ML_MAPPED))
|
|
{
|
|
sub.flags |= SSECMF_DRAWN;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
//
|
|
//
|
|
//==========================================================================
|
|
|
|
FSerializer &SerializeSubsectors(FSerializer &arc, const char *key)
|
|
{
|
|
uint8_t by;
|
|
const char *str;
|
|
|
|
auto numsubsectors = level.subsectors.Size();
|
|
if (arc.isWriting())
|
|
{
|
|
if (hasglnodes)
|
|
{
|
|
TArray<char> encoded(1 + (numsubsectors + 5) / 6);
|
|
int p = 0;
|
|
for (unsigned i = 0; i < numsubsectors; i += 6)
|
|
{
|
|
by = 0;
|
|
for (unsigned j = 0; j < 6; j++)
|
|
{
|
|
if (i + j < numsubsectors && (level.subsectors[i + j].flags & SSECMF_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))
|
|
{
|
|
auto numvertexes = level.vertexes.Size();
|
|
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();
|
|
|
|
if (num_verts == (int)level.vertexes.Size() && num_subs == (int)numsubsectors && hasglnodes)
|
|
{
|
|
success = true;
|
|
int sub = 0;
|
|
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;
|
|
}
|
|
for (int s = 0; s < 6; s++)
|
|
{
|
|
if (sub + s < (int)numsubsectors && (by & (1 << s)))
|
|
{
|
|
level.subsectors[sub + s].flags |= SSECMF_DRAWN;
|
|
}
|
|
}
|
|
sub += 6;
|
|
}
|
|
}
|
|
if (hasglnodes && !success)
|
|
{
|
|
RecalculateDrawnSubsectors();
|
|
}
|
|
}
|
|
|
|
}
|
|
return arc;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// Save a line portal for savegames.
|
|
//
|
|
//============================================================================
|
|
|
|
FSerializer &Serialize(FSerializer &arc, const char *key, FLinePortal &port, FLinePortal *def)
|
|
{
|
|
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();
|
|
}
|
|
return arc;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// Save a sector portal for savegames.
|
|
//
|
|
//============================================================================
|
|
|
|
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;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
//
|
|
//
|
|
//==========================================================================
|
|
|
|
FSerializer &Serialize(FSerializer &arc, const char *key, ReverbContainer *&c, ReverbContainer **def)
|
|
{
|
|
int id = (arc.isReading() || c == nullptr) ? 0 : c->ID;
|
|
Serialize(arc, key, id, nullptr);
|
|
if (arc.isReading())
|
|
{
|
|
c = S_FindEnvironment(id);
|
|
}
|
|
return arc;
|
|
}
|
|
|
|
FSerializer &Serialize(FSerializer &arc, const char *key, zone_t &z, zone_t *def)
|
|
{
|
|
return Serialize(arc, key, z.Environment, nullptr);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// ArchiveSounds
|
|
//
|
|
//==========================================================================
|
|
|
|
void P_SerializeSounds(FSerializer &arc)
|
|
{
|
|
S_SerializeSounds(arc);
|
|
DSeqNode::SerializeSequences (arc);
|
|
const char *name = NULL;
|
|
uint8_t order;
|
|
float musvol = level.MusicVolume;
|
|
|
|
if (arc.isWriting())
|
|
{
|
|
order = S_GetMusic(&name);
|
|
}
|
|
arc.StringPtr("musicname", name)
|
|
("musicorder", order)
|
|
("musicvolume", musvol);
|
|
|
|
if (arc.isReading())
|
|
{
|
|
if (!S_ChangeMusic(name, order))
|
|
if (level.cdtrack == 0 || !S_ChangeCDMusic(level.cdtrack, level.cdid))
|
|
S_ChangeMusic(level.Music, level.musicorder);
|
|
level.SetMusicVolume(musvol);
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
//
|
|
//
|
|
//==========================================================================
|
|
|
|
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)
|
|
{
|
|
int numPlayers, numPlayersNow;
|
|
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.
|
|
arc("numplayers", numPlayersNow);
|
|
if (arc.BeginArray("players"))
|
|
{
|
|
// Record each player's name, followed by their data.
|
|
for (i = 0; i < MAXPLAYERS; ++i)
|
|
{
|
|
if (playeringame[i])
|
|
{
|
|
if (arc.BeginObject(nullptr))
|
|
{
|
|
const char *n = players[i].userinfo.GetName();
|
|
arc.StringPtr("playername", n);
|
|
players[i].Serialize(arc);
|
|
arc.EndObject();
|
|
}
|
|
}
|
|
}
|
|
arc.EndArray();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
arc("numplayers", numPlayers);
|
|
|
|
if (arc.BeginArray("players"))
|
|
{
|
|
// 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);
|
|
}
|
|
arc.EndArray();
|
|
}
|
|
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;
|
|
const char *name = NULL;
|
|
bool didIt = false;
|
|
|
|
if (arc.BeginObject(nullptr))
|
|
{
|
|
arc.StringPtr("playername", name);
|
|
|
|
for (i = 0; i < MAXPLAYERS; ++i)
|
|
{
|
|
if (playeringame[i])
|
|
{
|
|
if (!didIt)
|
|
{
|
|
didIt = true;
|
|
player_t playerTemp;
|
|
playerTemp.Serialize(arc);
|
|
if (!skipload)
|
|
{
|
|
// 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;
|
|
CopyPlayer(&players[i], &playerTemp, name);
|
|
}
|
|
else
|
|
{
|
|
// we need the player actor, so that G_FinishTravel can destroy it later.
|
|
players[i].mo = playerTemp.mo;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (players[i].mo != NULL)
|
|
{
|
|
players[i].mo->Destroy();
|
|
players[i].mo = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
arc.EndObject();
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
//
|
|
//
|
|
//==========================================================================
|
|
|
|
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;
|
|
const char **nametemp = new const char *[numPlayers];
|
|
player_t *playertemp = new player_t[numPlayers];
|
|
uint8_t *tempPlayerUsed = new uint8_t[numPlayers];
|
|
uint8_t playerUsed[MAXPLAYERS];
|
|
|
|
for (i = 0; i < numPlayers; ++i)
|
|
{
|
|
nametemp[i] = NULL;
|
|
if (arc.BeginObject(nullptr))
|
|
{
|
|
arc.StringPtr("playername", nametemp[i]);
|
|
playertemp[i].Serialize(arc);
|
|
arc.EndObject();
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < MAXPLAYERS; ++i)
|
|
{
|
|
players[i].mo = playertemp[i].mo;
|
|
}
|
|
}
|
|
|
|
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);
|
|
// The player class must come from the save, so that the menu reflects the currently playing one.
|
|
dst->userinfo.PlayerClassChanged(src->mo->GetInfo()->DisplayName);
|
|
}
|
|
|
|
// Validate the skin
|
|
dst->userinfo.SkinNumChanged(R_FindSkin(Skins[dst->userinfo.GetSkin()].Name, dst->CurrentPlayerClass));
|
|
|
|
// 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(&level.playerstarts[i], i, (level.flags2 & LEVEL2_PRERAISEWEAPON) ? SPF_WEAPONFULLYUP : 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
//
|
|
//
|
|
//============================================================================
|
|
|
|
void G_SerializeLevel(FSerializer &arc, bool hubload)
|
|
{
|
|
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.
|
|
uint8_t chk[16] = { 0 };
|
|
arc.Array("checksum", chk, 16);
|
|
if (arc.GetSize("linedefs") != level.lines.Size() ||
|
|
arc.GetSize("sidedefs") != level.sides.Size() ||
|
|
arc.GetSize("sectors") != level.sectors.Size() ||
|
|
arc.GetSize("polyobjs") != (unsigned)po_NumPolyobjs ||
|
|
memcmp(chk, level.md5, 16))
|
|
{
|
|
I_Error("Savegame is from a different level");
|
|
}
|
|
}
|
|
arc("saveversion", SaveVersion);
|
|
|
|
if (arc.isReading())
|
|
{
|
|
DThinker::DestroyAllThinkers();
|
|
interpolator.ClearInterpolations();
|
|
arc.ReadObjects(hubload);
|
|
}
|
|
|
|
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)
|
|
("level.skytexture2", level.skytexture2)
|
|
("level.fogdensity", level.fogdensity)
|
|
("level.outsidefogdensity", level.outsidefogdensity)
|
|
("level.skyfog", level.skyfog);
|
|
|
|
// Hub transitions must keep the current total time
|
|
if (!hubload)
|
|
level.totaltime = i;
|
|
|
|
if (arc.isReading())
|
|
{
|
|
sky1texture = level.skytexture1;
|
|
sky2texture = level.skytexture2;
|
|
R_InitSkyMap();
|
|
G_AirControlChanged();
|
|
}
|
|
|
|
|
|
|
|
// fixme: This needs to ensure it reads from the correct place. Should be one once there's enough of this code converted to JSON
|
|
|
|
FBehavior::StaticSerializeModuleStates(arc);
|
|
// The order here is important: First world state, then portal state, then thinkers, and last polyobjects.
|
|
arc("linedefs", level.lines, level.loadlines);
|
|
arc("sidedefs", level.sides, level.loadsides);
|
|
arc("sectors", level.sectors, level.loadsectors);
|
|
arc("zones", level.Zones);
|
|
arc("lineportals", level.linePortals);
|
|
arc("sectorportals", level.sectorPortals);
|
|
if (arc.isReading()) P_FinalizePortals();
|
|
|
|
// [ZZ] serialize events
|
|
E_SerializeEvents(arc);
|
|
DThinker::SerializeThinkers(arc, hubload);
|
|
arc.Array("polyobjs", polyobjs, po_NumPolyobjs);
|
|
SerializeSubsectors(arc, "subsectors");
|
|
StatusBar->SerializeMessages(arc);
|
|
AM_SerializeMarkers(arc);
|
|
FRemapTable::StaticSerializeTranslations(arc);
|
|
FCanvasTextureInfo::Serialize(arc);
|
|
P_SerializePlayers(arc, hubload);
|
|
P_SerializeSounds(arc);
|
|
|
|
if (arc.isReading())
|
|
{
|
|
for (auto &sec : level.sectors)
|
|
{
|
|
P_Recalculate3DFloors(&sec);
|
|
}
|
|
for (int i = 0; i < MAXPLAYERS; ++i)
|
|
{
|
|
if (playeringame[i] && players[i].mo != NULL)
|
|
{
|
|
players[i].mo->SetupWeaponSlots();
|
|
}
|
|
}
|
|
}
|
|
AActor::RecreateAllAttachedLights();
|
|
InitPortalGroups();
|
|
}
|