Example #1
0
    void writeSoundTargets()
    {
#if !__JHEXEN__
        if (!IS_SERVER) return; // Not for us.

        // Write the total number.
        int count = 0;
        for (int i = 0; i < numsectors; ++i)
        {
            xsector_t *xsec = P_ToXSector((Sector *)P_ToPtr(DMU_SECTOR, i));
            if (xsec->soundTarget)
            {
                count += 1;
            }
        }

        // beginSegment();
        Writer_WriteInt32(writer, count);

        // Write the mobj references using the mobj archive.
        for (int i = 0; i < numsectors; ++i)
        {
            xsector_t *xsec = P_ToXSector((Sector *)P_ToPtr(DMU_SECTOR, i));
            if (xsec->soundTarget)
            {
                Writer_WriteInt32(writer, i);
                Writer_WriteInt16(writer, thingArchive->serialIdFor(xsec->soundTarget));
            }
        }
        // endSegment();
#endif
    }
Example #2
0
void SaveInfo_Write(SaveInfo *info, Writer *writer)
{
    saveheader_t *hdr;
    DENG_ASSERT(info != 0);

    hdr = &info->header;
    Writer_WriteInt32(writer, hdr->magic);
    Writer_WriteInt32(writer, hdr->version);
    Writer_WriteInt32(writer, hdr->gameMode);
    Str_Write(&info->name, writer);

    Writer_WriteByte(writer, hdr->skill);
    Writer_WriteByte(writer, hdr->episode);
    Writer_WriteByte(writer, hdr->map);
    Writer_WriteByte(writer, hdr->deathmatch);
    Writer_WriteByte(writer, hdr->noMonsters);
#if __JHEXEN__
    Writer_WriteByte(writer, hdr->randomClasses);
#else
    Writer_WriteByte(writer, hdr->respawnMonsters);
    Writer_WriteInt32(writer, hdr->mapTime);

    { int i;
    for(i = 0; i < MAXPLAYERS; ++i)
    {
        Writer_WriteByte(writer, hdr->players[i]);
    }}
#endif
    Writer_WriteInt32(writer, info->gameId);
}
Example #3
0
void playerheader_s::write(Writer1 *writer)
{
    Writer_WriteByte(writer, 2); // version byte

    numPowers       = NUM_POWER_TYPES;
    numKeys         = NUM_KEY_TYPES;
    numFrags        = MAXPLAYERS;
    numWeapons      = NUM_WEAPON_TYPES;
    numAmmoTypes    = NUM_AMMO_TYPES;
    numPSprites     = NUMPSPRITES;
#if __JHERETIC__ || __JHEXEN__ || __JDOOM64__
    numInvItemTypes = NUM_INVENTORYITEM_TYPES;
#endif
#if __JHEXEN__
    numArmorTypes   = NUMARMOR;
#endif

    Writer_WriteInt32(writer, numPowers);
    Writer_WriteInt32(writer, numKeys);
    Writer_WriteInt32(writer, numFrags);
    Writer_WriteInt32(writer, numWeapons);
    Writer_WriteInt32(writer, numAmmoTypes);
    Writer_WriteInt32(writer, numPSprites);
#if __JDOOM64__ || __JHERETIC__ || __JHEXEN__
    Writer_WriteInt32(writer, numInvItemTypes);
#endif
#if __JHEXEN__
    Writer_WriteInt32(writer, numArmorTypes);
#endif
}
Example #4
0
void fireflicker_s::write(MapStateWriter *msw) const
{
    Writer1 *writer = msw->writer();

    Writer_WriteByte(writer, 1); // Write a version byte.

    // Note we don't bother to save a byte to tell if the function
    // is present as we ALWAYS add one when loading.

    Writer_WriteInt32(writer, P_ToIndex(sector));

    Writer_WriteInt32(writer, (int) (255.0f * maxLight));
    Writer_WriteInt32(writer, (int) (255.0f * minLight));
}
Example #5
0
    void writePlayers()
    {
        beginSegment(ASEG_PLAYER_HEADER);
        playerheader_t plrHdr;
        plrHdr.write(writer);

        beginSegment(ASEG_PLAYERS);
        {
#if __JHEXEN__
            for (int i = 0; i < MAXPLAYERS; ++i)
            {
                Writer_WriteByte(writer, players[i].plr->inGame);
            }
#endif

            for (int i = 0; i < MAXPLAYERS; ++i)
            {
                player_t *plr = players + i;
                if (!plr->plr->inGame)
                    continue;

                Writer_WriteInt32(writer, Net_GetPlayerID(i));
                plr->write(writer, plrHdr);
            }
        }
        endSegment();
    }
Example #6
0
    void writeMapHeader()
    {
#if __JHEXEN__
        // Maps have their own version number.
        Writer_WriteByte(writer, MY_SAVE_VERSION);

        // Write the map timer
        Writer_WriteInt32(writer, mapTime);
#endif
    }
Example #7
0
    void writeMisc()
    {
#if __JHEXEN__
        beginSegment(ASEG_MISC);
        for (int i = 0; i < MAXPLAYERS; ++i)
        {
            Writer_WriteInt32(writer, localQuakeHappening[i]);
        }
#endif
#if __JDOOM__
        DENG2_ASSERT(theBossBrain != 0);
        theBossBrain->write(thisPublic);
#endif
    }
Example #8
0
void plat_t::write(MapStateWriter *msw) const
{
    Writer *writer = msw->writer();

    Writer_WriteByte(writer, 1); // Write a version byte.

    Writer_WriteByte(writer, (byte) type);

    Writer_WriteInt32(writer, P_ToIndex(sector));

    Writer_WriteInt32(writer, FLT2FIX(speed));
    Writer_WriteInt16(writer, (int)low);
    Writer_WriteInt16(writer, (int)high);

    Writer_WriteInt32(writer, wait);
    Writer_WriteInt32(writer, count);

    Writer_WriteByte(writer, (byte) state);
    Writer_WriteByte(writer, (byte) oldState);
    Writer_WriteByte(writer, (byte) crush);

    Writer_WriteInt32(writer, tag);
}
Example #9
0
void waggle_s::write(MapStateWriter *msw) const
{
    Writer1 *writer = msw->writer();

    Writer_WriteByte(writer, 1); // Write a version byte.

    // Note we don't bother to save a byte to tell if the function
    // is present as we ALWAYS add one when loading.

    Writer_WriteInt32(writer, P_ToIndex(sector));

    Writer_WriteInt32(writer, FLT2FIX(originalHeight));
    Writer_WriteInt32(writer, FLT2FIX(accumulator));
    Writer_WriteInt32(writer, FLT2FIX(accDelta));
    Writer_WriteInt32(writer, FLT2FIX(targetScale));
    Writer_WriteInt32(writer, FLT2FIX(scale));
    Writer_WriteInt32(writer, FLT2FIX(scaleDelta));
    Writer_WriteInt32(writer, ticker);
    Writer_WriteInt32(writer, state);
}
Example #10
0
    void writePolyobjs()
    {
#if __JHEXEN__
        beginSegment(ASEG_POLYOBJS);

        Writer_WriteInt32(writer, numpolyobjs);
        for (int i = 0; i < numpolyobjs; ++i)
        {
            Polyobj *po = Polyobj_ById(i);
            DENG2_ASSERT(po != 0);
            po->write(thisPublic);
        }

        // endSegment();
#endif
    }
Example #11
0
    /**
     * Serializes thinkers for both client and server.
     *
     * @note Clients do not save data for all thinkers. In some cases the server will send it
     * anyway (so saving it would just bloat client save states).
     *
     * @note Some thinker classes are NEVER saved by clients.
     */
    void writeThinkers()
    {
        beginSegment(ASEG_THINKERS);

#if __JHEXEN__
        Writer_WriteInt32(writer, thingArchive->size()); // number of mobjs.
#endif

        // Serialize qualifying thinkers.
        writethinkerworker_params_t parm; de::zap(parm);
        parm.msw            = thisPublic;
        parm.excludePlayers = thingArchive->excludePlayers();
        Thinker_Iterate(0/*all thinkers*/, writeThinkerWorker, &parm);

        // Mark the end of the thinkers.
        // endSegment();
        Writer_WriteByte(writer, TC_END);
    }
Example #12
0
void pillar_s::write(MapStateWriter *msw) const
{
    Writer1 *writer = msw->writer();

    Writer_WriteByte(writer, 1); // Write a version byte.

    // Note we don't bother to save a byte to tell if the function
    // is present as we ALWAYS add one when loading.

    Writer_WriteInt32(writer, P_ToIndex(sector));

    Writer_WriteInt32(writer, FLT2FIX(ceilingSpeed));
    Writer_WriteInt32(writer, FLT2FIX(floorSpeed));
    Writer_WriteInt32(writer, FLT2FIX(floorDest));
    Writer_WriteInt32(writer, FLT2FIX(ceilingDest));
    Writer_WriteInt32(writer, direction);
    Writer_WriteInt32(writer, crush);
}
Example #13
0
void light_s::write(MapStateWriter *msw) const
{
    Writer1 *writer = msw->writer();

    Writer_WriteByte(writer, 1); // Write a version byte.

    // Note we don't bother to save a byte to tell if the function
    // is present as we ALWAYS add one when loading.

    Writer_WriteByte(writer, (byte)type);

    Writer_WriteInt32(writer, P_ToIndex(sector));

    Writer_WriteInt32(writer, (int) (255.0f * value1));
    Writer_WriteInt32(writer, (int) (255.0f * value2));
    Writer_WriteInt32(writer, tics1);
    Writer_WriteInt32(writer, tics2);
    Writer_WriteInt32(writer, count);
}
Example #14
0
void SV_SaveGameClient(uint /*sessionId*/)
{
    throw de::Error("SV_SaveGameClient", "Not currently implemented");

#if 0
#if !__JHEXEN__ // unsupported in libhexen
    player_t *pl = &players[CONSOLEPLAYER];
    mobj_t *mo   = pl->plr->mo;

    if(!IS_CLIENT || !mo)
        return;

    // Prepare new saved game session session.
    de::game::GameStateFolder *session = new de::game::GameStateFolder(saveNameForClientSessionId(sessionId));
    de::game::GameStateMetadata *metadata = G_CurrentSessionMetadata();
    metadata->set("sessionId", sessionId);
    session->replaceMetadata(metadata);

    de::Path path = de::String("/savegame") / "client" / session->path();
    if(!SV_OpenFileForWrite(path))
    {
        App_Log(DE2_RES_WARNING, "SV_SaveGameClient: Failed opening \"%s\" for writing",
                path.toString().toLatin1().constData());

        // Discard the useless session.
        delete session;
        return;
    }

    Writer1 *writer = SV_NewWriter();
    SV_WriteSessionMetadata(*metadata, writer);

    // Some important information.
    // Our position and look angles.
    Writer_WriteInt32(writer, FLT2FIX(mo->origin[VX]));
    Writer_WriteInt32(writer, FLT2FIX(mo->origin[VY]));
    Writer_WriteInt32(writer, FLT2FIX(mo->origin[VZ]));
    Writer_WriteInt32(writer, FLT2FIX(mo->floorZ));
    Writer_WriteInt32(writer, FLT2FIX(mo->ceilingZ));
    Writer_WriteInt32(writer, mo->angle); /* $unifiedangles */

    Writer_WriteFloat(writer, pl->plr->lookDir); /* $unifiedangles */

    SV_BeginSegment(ASEG_PLAYER_HEADER);
    playerheader_t plrHdr;
    plrHdr.write(writer);

    players[CONSOLEPLAYER].write(writer, plrHdr);

    ThingArchive thingArchive;
    MapStateWriter(thingArchive).write(writer);
    /// @todo No consistency bytes in client saves?

    SV_CloseFile();
    Writer_Delete(writer);
    delete session;
#else
    DENG2_UNUSED(sessionId);
#endif
#endif
}
Example #15
0
void SV_WriteLine(Line *li, MapStateWriter *msw)
{
    xline_t *xli   = P_ToXLine(li);
    Writer1 *writer = msw->writer();

#if !__JHEXEN__
    Writer_WriteByte(writer, xli->xg? 1 : 0); /// @c 1= XG data will follow.
#else
    Writer_WriteByte(writer, 0);
#endif

    // Version.
    // 2: Per surface texture offsets.
    // 2: Surface colors.
    // 3: "Mapped by player" values.
    // 3: Surface flags.
    // 4: Engine-side line flags.
    Writer_WriteByte(writer, 4); // Write a version byte

    Writer_WriteInt16(writer, P_GetIntp(li, DMU_FLAGS));
    Writer_WriteInt16(writer, xli->flags);

    for(int i = 0; i < MAXPLAYERS; ++i)
        Writer_WriteByte(writer, xli->mapped[i]);

#if __JHEXEN__
    Writer_WriteByte(writer, xli->special);
    Writer_WriteByte(writer, xli->arg1);
    Writer_WriteByte(writer, xli->arg2);
    Writer_WriteByte(writer, xli->arg3);
    Writer_WriteByte(writer, xli->arg4);
    Writer_WriteByte(writer, xli->arg5);
#else
    Writer_WriteInt16(writer, xli->special);
    Writer_WriteInt16(writer, xli->tag);
#endif

    // For each side
    float rgba[4];
    for(int i = 0; i < 2; ++i)
    {
        Side *si = (Side *)P_GetPtrp(li, (i? DMU_BACK:DMU_FRONT));
        if(!si) continue;

        Writer_WriteInt16(writer, P_GetIntp(si, DMU_TOP_MATERIAL_OFFSET_X));
        Writer_WriteInt16(writer, P_GetIntp(si, DMU_TOP_MATERIAL_OFFSET_Y));
        Writer_WriteInt16(writer, P_GetIntp(si, DMU_MIDDLE_MATERIAL_OFFSET_X));
        Writer_WriteInt16(writer, P_GetIntp(si, DMU_MIDDLE_MATERIAL_OFFSET_Y));
        Writer_WriteInt16(writer, P_GetIntp(si, DMU_BOTTOM_MATERIAL_OFFSET_X));
        Writer_WriteInt16(writer, P_GetIntp(si, DMU_BOTTOM_MATERIAL_OFFSET_Y));

        Writer_WriteInt16(writer, P_GetIntp(si, DMU_TOP_FLAGS));
        Writer_WriteInt16(writer, P_GetIntp(si, DMU_MIDDLE_FLAGS));
        Writer_WriteInt16(writer, P_GetIntp(si, DMU_BOTTOM_FLAGS));

        Writer_WriteInt16(writer, msw->serialIdFor((world_Material *)P_GetPtrp(si, DMU_TOP_MATERIAL)));
        Writer_WriteInt16(writer, msw->serialIdFor((world_Material *)P_GetPtrp(si, DMU_BOTTOM_MATERIAL)));
        Writer_WriteInt16(writer, msw->serialIdFor((world_Material *)P_GetPtrp(si, DMU_MIDDLE_MATERIAL)));

        P_GetFloatpv(si, DMU_TOP_COLOR, rgba);
        for(int k = 0; k < 3; ++k)
            Writer_WriteByte(writer, (byte)(255 * rgba[k]));

        P_GetFloatpv(si, DMU_BOTTOM_COLOR, rgba);
        for(int k = 0; k < 3; ++k)
            Writer_WriteByte(writer, (byte)(255 * rgba[k]));

        P_GetFloatpv(si, DMU_MIDDLE_COLOR, rgba);
        for(int k = 0; k < 4; ++k)
            Writer_WriteByte(writer, (byte)(255 * rgba[k]));

        Writer_WriteInt32(writer, P_GetIntp(si, DMU_MIDDLE_BLENDMODE));
        Writer_WriteInt16(writer, P_GetIntp(si, DMU_FLAGS));
    }

#if !__JHEXEN__
    // Extended General?
    if(xli->xg)
    {
        SV_WriteXGLine(li, msw);
    }
#endif
}
Example #16
0
static void Net_DoUpdate(void)
{
    static int          lastTime = 0;

    int                 nowTime, newTics;

    /**
     * This timing is only used by the client when it determines if it is
     * time to send ticcmds or coordinates to the server.
     */

    // Check time.
    nowTime = Timer_Ticks();

    // Clock reset?
    if(firstNetUpdate)
    {
        firstNetUpdate = false;
        lastTime = nowTime;
    }
    newTics = nowTime - lastTime;
    if(newTics <= 0)
        return; // Nothing new to update.

    lastTime = nowTime;

    // This is as far as dedicated servers go.
#ifdef __CLIENT__

    /**
     * Clients will periodically send their coordinates to the server so
     * any prediction errors can be fixed. Client movement is almost
     * entirely local.
     */
#ifdef _DEBUG
    if(netGame && verbose >= 2)
    {
        Con_Message("Net_DoUpdate: coordTimer=%i cl:%i shmo:%p", coordTimer,
                    isClient, ddPlayers[consolePlayer].shared.mo);
    }
#endif

    coordTimer -= newTics;
    if(isClient && coordTimer <= 0 &&
       ddPlayers[consolePlayer].shared.mo)
    {
        mobj_t *mo = ddPlayers[consolePlayer].shared.mo;

        coordTimer = 1; //netCoordTime; // 35/2

        Msg_Begin(PKT_COORDS);
        Writer_WriteFloat(msgWriter, gameTime);
        Writer_WriteFloat(msgWriter, mo->origin[VX]);
        Writer_WriteFloat(msgWriter, mo->origin[VY]);
        if(mo->origin[VZ] == mo->floorZ)
        {
            // This'll keep us on the floor even in fast moving sectors.
            Writer_WriteInt32(msgWriter, DDMININT);
        }
        else
        {
            Writer_WriteInt32(msgWriter, FLT2FIX(mo->origin[VZ]));
        }
        // Also include angles.
        Writer_WriteUInt16(msgWriter, mo->angle >> 16);
        Writer_WriteInt16(msgWriter, P_LookDirToShort(ddPlayers[consolePlayer].shared.lookDir));
        // Control state.
        Writer_WriteChar(msgWriter, FLT2FIX(ddPlayers[consolePlayer].shared.forwardMove) >> 13);
        Writer_WriteChar(msgWriter, FLT2FIX(ddPlayers[consolePlayer].shared.sideMove) >> 13);
        Msg_End();

        Net_SendBuffer(0, 0);
    }
Example #17
0
void mobj_s::write(MapStateWriter *msw) const
{
    Writer1 *writer = msw->writer();
    mobj_t const *mo = this;

    // Version.
    // JHEXEN
    // 2: Added the 'translucency' byte.
    // 3: Added byte 'vistarget'
    // 4: Added long 'tracer'
    // 4: Added long 'lastenemy'
    // 5: Added flags3
    // 6: Floor material removed.
    //
    // JDOOM || JHERETIC || JDOOM64
    // 4: Added byte 'translucency'
    // 5: Added byte 'vistarget'
    // 5: Added tracer in jDoom
    // 5: Added dropoff fix in jHeretic
    // 5: Added long 'floorclip'
    // 6: Added proper respawn data
    // 6: Added flags 2 in jDoom
    // 6: Added damage
    // 7: Added generator in jHeretic
    // 7: Added flags3
    //
    // JDOOM
    // 9: Revised mapspot flag interpretation
    //
    // JHERETIC
    // 8: Added special3
    // 9: Revised mapspot flag interpretation
    //
    // JHEXEN
    // 7: Removed superfluous info ptr
    // 8: Added 'onMobj'
    Writer_WriteByte(writer, MOBJ_SAVEVERSION);

#if !__JHEXEN__
    // A version 2 features: archive number and target.
    Writer_WriteInt16(writer, msw->serialIdFor(mo));
    Writer_WriteInt16(writer, msw->serialIdFor(mo->target));

# if __JDOOM__ || __JDOOM64__
    // Ver 5 features: Save tracer (fixes Archvile, Revenant bug)
    Writer_WriteInt16(writer, msw->serialIdFor(mo->tracer));
# endif
#endif

    Writer_WriteInt16(writer, msw->serialIdFor(mo->onMobj));

    // Info for drawing: position.
    Writer_WriteInt32(writer, FLT2FIX(mo->origin[VX]));
    Writer_WriteInt32(writer, FLT2FIX(mo->origin[VY]));
    Writer_WriteInt32(writer, FLT2FIX(mo->origin[VZ]));

    //More drawing info: to determine current sprite.
    Writer_WriteInt32(writer, mo->angle); // Orientation.
    Writer_WriteInt32(writer, mo->sprite); // Used to find patch_t and flip value.
    Writer_WriteInt32(writer, mo->frame);

#if !__JHEXEN__
    // The closest interval over all contacted Sectors.
    Writer_WriteInt32(writer, FLT2FIX(mo->floorZ));
    Writer_WriteInt32(writer, FLT2FIX(mo->ceilingZ));
#endif

    // For movement checking.
    Writer_WriteInt32(writer, FLT2FIX(mo->radius));
    Writer_WriteInt32(writer, FLT2FIX(mo->height));

    // Momentums, used to update position.
    Writer_WriteInt32(writer, FLT2FIX(mo->mom[MX]));
    Writer_WriteInt32(writer, FLT2FIX(mo->mom[MY]));
    Writer_WriteInt32(writer, FLT2FIX(mo->mom[MZ]));

    // If == VALIDCOUNT, already checked.
    Writer_WriteInt32(writer, mo->valid);

    Writer_WriteInt32(writer, mo->type);
    Writer_WriteInt32(writer, mo->tics); // State tic counter.
    Writer_WriteInt32(writer, int(mo->state - STATES) /*PTR2INT(mo->state)*/);

#if __JHEXEN__
    Writer_WriteInt32(writer, mo->damage);
#endif

    Writer_WriteInt32(writer, mo->flags);
#if __JHEXEN__
    Writer_WriteInt32(writer, mo->flags2);
    Writer_WriteInt32(writer, mo->flags3);

    if(mo->type == MT_KORAX)
        Writer_WriteInt32(writer, 0); // Searching index.
    else
        Writer_WriteInt32(writer, mo->special1);

    switch(mo->type)
    {
    case MT_LIGHTNING_FLOOR:
    case MT_LIGHTNING_ZAP:
    case MT_HOLY_TAIL:
    case MT_LIGHTNING_CEILING:
        if(mo->flags & MF_CORPSE)
            Writer_WriteInt32(writer, 0);
        else
            Writer_WriteInt32(writer, msw->serialIdFor(INT2PTR(mobj_t, mo->special2)));
        break;

    default:
        Writer_WriteInt32(writer, mo->special2);
        break;
    }
#endif
    Writer_WriteInt32(writer, mo->health);

    // Movement direction, movement generation (zig-zagging).
    Writer_WriteInt32(writer, mo->moveDir); // 0-7
    Writer_WriteInt32(writer, mo->moveCount); // When 0, select a new dir.

#if __JHEXEN__
    if(mo->flags & MF_CORPSE)
        Writer_WriteInt32(writer, 0);
    else
        Writer_WriteInt32(writer, (int) msw->serialIdFor(mo->target));
#endif

    // Reaction time: if non 0, don't attack yet.
    // Used by player to freeze a bit after teleporting.
    Writer_WriteInt32(writer, mo->reactionTime);

    // If >0, the target will be chased no matter what (even if shot).
    Writer_WriteInt32(writer, mo->threshold);

    // Additional info record for player avatars only (only valid if type is MT_PLAYER).
    {
        int const playerNum = (mo->player? int(mo->player - players + 1) : 0);
        Writer_WriteInt32(writer, playerNum /*PTR2INT(mo->player)*/);
    }

    // Player number last looked for.
    Writer_WriteInt32(writer, mo->lastLook);

#if !__JHEXEN__
    // For nightmare/multiplayer respawn.
    Writer_WriteInt32(writer, FLT2FIX(mo->spawnSpot.origin[VX]));
    Writer_WriteInt32(writer, FLT2FIX(mo->spawnSpot.origin[VY]));
    Writer_WriteInt32(writer, FLT2FIX(mo->spawnSpot.origin[VZ]));
    Writer_WriteInt32(writer, mo->spawnSpot.angle);
    Writer_WriteInt32(writer, mo->spawnSpot.flags);

    Writer_WriteInt32(writer, mo->intFlags); // $dropoff_fix: internal flags.
    Writer_WriteInt32(writer, FLT2FIX(mo->dropOffZ)); // $dropoff_fix
    Writer_WriteInt32(writer, mo->gear); // Used in torque simulation.

    Writer_WriteInt32(writer, mo->damage);
    Writer_WriteInt32(writer, mo->flags2);
    Writer_WriteInt32(writer, mo->flags3);
# ifdef __JHERETIC__
    Writer_WriteInt32(writer, mo->special1);
    Writer_WriteInt32(writer, mo->special2);
    Writer_WriteInt32(writer, mo->special3);
# endif

    Writer_WriteByte(writer,  mo->translucency);
    Writer_WriteByte(writer,  (byte)(mo->visTarget + 1));
#endif

    Writer_WriteInt32(writer, FLT2FIX(mo->floorClip));
#if __JHEXEN__
    Writer_WriteInt32(writer, msw->serialIdFor(mo));
    Writer_WriteInt32(writer, mo->tid);
    Writer_WriteInt32(writer, mo->special);
    Writer_Write(writer,      mo->args, sizeof(mo->args));
    Writer_WriteByte(writer,  mo->translucency);
    Writer_WriteByte(writer,  (byte)(mo->visTarget + 1));

    switch(mo->type)
    {
    case MT_BISH_FX:
    case MT_HOLY_FX:
    case MT_DRAGON:
    case MT_THRUSTFLOOR_UP:
    case MT_THRUSTFLOOR_DOWN:
    case MT_MINOTAUR:
    case MT_SORCFX1:
    case MT_MSTAFF_FX2:
    case MT_HOLY_TAIL:
    case MT_LIGHTNING_CEILING:
        if(mo->flags & MF_CORPSE)
            Writer_WriteInt32(writer, 0);
        else
            Writer_WriteInt32(writer, msw->serialIdFor(mo->tracer));
        break;

    default:
        DENG_ASSERT(mo->tracer == NULL); /// @todo Tracer won't be saved correctly?
        Writer_WriteInt32(writer, PTR2INT(mo->tracer));
        break;
    }

    Writer_WriteInt32(writer, PTR2INT(mo->lastEnemy));
#elif __JHERETIC__
    Writer_WriteInt16(writer, msw->serialIdFor(mo->generator));
#endif
}