og-odamex/common/svc_message.cpp

1715 lines
42 KiB
C++

// Emacs style mode select -*- C++ -*-
//-----------------------------------------------------------------------------
//
// $Id$
//
// Copyright (C) 2021 by Alex Mayfield.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// DESCRIPTION:
// Server message functions.
// - Functions should send exactly one message.
// - Functions should be named after the message they send.
// - Functions should be self-contained and not rely on global state.
// - Functions should take a buf_t& as a first parameter.
//
//-----------------------------------------------------------------------------
#include "odamex.h"
#include <bitset>
#include "svc_message.h"
#include "common.pb.h"
#include "d_main.h"
#include "g_gametype.h"
#include "i_system.h"
#include "i_time.h"
#include "m_strindex.h"
#include "p_lnspec.h"
#include "p_local.h"
#include "p_mobj.h"
#include "p_unlag.h"
/**
* @brief Pack an array of booleans into a bitfield.
*/
static uint32_t PackBoolArray(nonstd::span<const bool> bools)
{
uint32_t out = 0;
for (size_t i = 0; i < bools.size(); i++)
{
if (bools[i])
{
out |= (1 << i);
}
}
return out;
}
odaproto::svc::Disconnect SVC_Disconnect(const char* message)
{
odaproto::svc::Disconnect msg;
if (message != NULL)
{
msg.set_message(message);
}
return msg;
}
/**
* @brief Send information about a player.
*/
odaproto::svc::PlayerInfo SVC_PlayerInfo(const player_t& player)
{
odaproto::svc::PlayerInfo msg;
uint32_t packedweapons = PackBoolArray(player.weaponowned);
msg.mutable_player()->set_weaponowned(packedweapons);
uint32_t packedcards = PackBoolArray(player.cards);
msg.mutable_player()->set_cards(packedcards);
msg.mutable_player()->set_backpack(player.backpack);
for (int i = 0; i < NUMAMMO; i++)
{
msg.mutable_player()->add_ammo(player.ammo[i]);
msg.mutable_player()->add_maxammo(player.maxammo[i]);
}
msg.mutable_player()->set_health(player.health);
msg.mutable_player()->set_armorpoints(player.armorpoints);
msg.mutable_player()->set_armortype(player.armortype);
msg.mutable_player()->set_lives(player.lives);
msg.mutable_player()->set_readyweapon(player.readyweapon);
msg.mutable_player()->set_pendingweapon(player.pendingweapon);
for (int i = 0; i < NUMPOWERS; i++)
{
msg.mutable_player()->add_powers(player.powers[i]);
}
if (!player.spectator)
msg.mutable_player()->set_cheats(player.cheats);
return msg;
}
/**
* @brief Change the location of a player.
*/
odaproto::svc::MovePlayer SVC_MovePlayer(const player_t& player, const int tic)
{
odaproto::svc::MovePlayer msg;
odaproto::Actor* act = msg.mutable_actor();
odaproto::Player* pl = msg.mutable_player();
pl->set_playerid(player.id); // player number
// [SL] 2011-09-14 - the most recently processed ticcmd from the
// client we're sending this message to.
msg.set_tic(tic);
odaproto::Vec3* pos = act->mutable_pos();
pos->set_x(player.mo->x);
pos->set_y(player.mo->y);
pos->set_z(player.mo->z);
act->set_angle(player.mo->angle);
act->set_pitch(player.mo->pitch);
if (player.mo->frame == 32773)
{
msg.set_frame(PLAYER_FULLBRIGHTFRAME);
}
else
{
msg.set_frame(player.mo->frame);
}
// write velocity
odaproto::Vec3* mom = act->mutable_mom();
mom->set_x(player.mo->momx);
mom->set_y(player.mo->momy);
mom->set_z(player.mo->momz);
// Send all available powers information to show the clients
pl->mutable_powers()->Resize(NUMPOWERS + 1, 0);
pl->set_powers(pw_invulnerability, player.powers[pw_invulnerability]);
pl->set_powers(pw_strength, player.powers[pw_strength] > 0 ? 1 : 0);
pl->set_powers(pw_invisibility, player.powers[pw_invisibility]);
pl->set_powers(pw_ironfeet, player.powers[pw_ironfeet]);
pl->set_powers(pw_allmap, player.powers[pw_allmap]);
pl->set_powers(pw_infrared, player.powers[pw_infrared]);
return msg;
}
/**
* @brief Send the local player position for a client.
*/
odaproto::svc::UpdateLocalPlayer SVC_UpdateLocalPlayer(const AActor& mo, const int tic)
{
odaproto::svc::UpdateLocalPlayer msg;
// client player will update his position if packets were missed
odaproto::Actor* act = msg.mutable_actor();
// client-tic of the most recently processed ticcmd for this client
msg.set_tic(tic);
odaproto::Vec3* pos = act->mutable_pos();
pos->set_x(mo.x);
pos->set_y(mo.y);
pos->set_z(mo.z);
odaproto::Vec3* mom = act->mutable_mom();
mom->set_x(mo.momx);
mom->set_y(mo.momy);
mom->set_z(mo.momz);
act->set_waterlevel(mo.waterlevel);
return msg;
}
/**
* @brief Persist level locals to the client.
*
* @param b Buffer to write to.
* @param locals Level locals struct to send.
* @param flags SVC_LL_* bit flags to designate what gets sent.
*/
odaproto::svc::LevelLocals SVC_LevelLocals(const level_locals_t& locals, uint32_t flags)
{
odaproto::svc::LevelLocals msg;
msg.set_flags(flags);
if (flags & SVC_LL_TIME)
{
msg.set_time(locals.time);
}
if (flags & SVC_LL_TOTALS)
{
msg.set_total_secrets(locals.total_secrets);
msg.set_total_items(locals.total_items);
msg.set_total_monsters(locals.total_monsters);
}
if (flags & SVC_LL_SECRETS)
{
msg.set_found_secrets(locals.found_secrets);
}
if (flags & SVC_LL_ITEMS)
{
msg.set_found_items(locals.found_items);
}
if (flags & SVC_LL_MONSTERS)
{
msg.set_killed_monsters(locals.killed_monsters);
}
if (flags & SVC_LL_MONSTER_RESPAWNS)
{
msg.set_respawned_monsters(locals.respawned_monsters);
}
return msg;
}
/**
* @brief Send the server's current time in MS to the client.
*/
odaproto::svc::PingRequest SVC_PingRequest()
{
odaproto::svc::PingRequest msg;
msg.set_ms_time(I_MSTime());
return msg;
}
odaproto::svc::UpdatePing SVC_UpdatePing(const player_t& player)
{
odaproto::svc::UpdatePing msg;
msg.set_pid(player.id);
msg.set_ping(player.ping);
return msg;
}
odaproto::svc::SpawnMobj SVC_SpawnMobj(const AActor* mo)
{
odaproto::svc::SpawnMobj msg;
odaproto::Actor* base = msg.mutable_baseline();
odaproto::Vec3* bpos = base->mutable_pos();
odaproto::Vec3* bmom = base->mutable_mom();
odaproto::Actor* cur = msg.mutable_current();
odaproto::Vec3* apos = cur->mutable_pos();
odaproto::Vec3* amom = cur->mutable_mom();
uint32_t spawnFlags = 0;
// Contents of the baseline
bpos->set_x(mo->baseline.pos.x);
bpos->set_y(mo->baseline.pos.y);
bpos->set_z(mo->baseline.pos.z);
bmom->set_x(mo->baseline.mom.x);
bmom->set_y(mo->baseline.mom.y);
bmom->set_z(mo->baseline.mom.z);
base->set_angle(mo->baseline.angle);
base->set_targetid(mo->baseline.targetid);
base->set_tracerid(mo->baseline.tracerid);
base->set_movecount(mo->baseline.movecount);
base->set_movedir(mo->baseline.movedir);
base->set_rndindex(mo->baseline.rndindex);
// Current location could be different than the baseline
uint32_t bflags = P_GetMobjBaselineFlags(*mo);
msg.set_baseline_flags(bflags);
if (bflags & baseline_t::POSX)
{
apos->set_x(mo->x);
}
if (bflags & baseline_t::POSY)
{
apos->set_y(mo->y);
}
if (bflags & baseline_t::POSZ)
{
apos->set_z(mo->z);
}
if (bflags & baseline_t::ANGLE)
{
cur->set_angle(mo->angle);
}
if (bflags & baseline_t::MOVEDIR)
{
cur->set_movedir(mo->movedir);
}
if (bflags & baseline_t::MOVECOUNT)
{
cur->set_movecount(mo->movecount);
}
if (bflags & baseline_t::RNDINDEX)
{
cur->set_rndindex(mo->rndindex);
}
if (bflags & baseline_t::TARGET)
{
cur->set_targetid(mo->target ? mo->target->netid : 0);
}
if (bflags & baseline_t::TRACER)
{
cur->set_tracerid(mo->tracer ? mo->tracer->netid : 0);
}
if (bflags & baseline_t::MOMX)
{
amom->set_x(mo->momx);
}
if (bflags & baseline_t::MOMY)
{
amom->set_y(mo->momy);
}
if (bflags & baseline_t::MOMZ)
{
amom->set_z(mo->momz);
}
cur->set_type(mo->type);
cur->set_netid(mo->netid);
// denis - sending state fixes monster ghosts appearing under doors
cur->set_statenum(mo->state->statenum);
if (mo->type == MT_FOUNTAIN)
{
msg.add_args(mo->args[0]);
}
if (mo->type == MT_ZDOOMBRIDGE)
{
msg.add_args(mo->args[0]);
msg.add_args(mo->args[1]);
}
if (mo->type == MT_UPPERSTACK || mo->type == MT_LOWERSTACK)
{
msg.add_args(mo->tid);
msg.add_args(mo->args[0]); // boundary flat alpha
}
if (mo->type == MT_SKYVIEWPOINT)
{
msg.add_args(mo->tid);
}
if (mo->type == MT_SKYPICKER)
{
msg.add_args(mo->args[0]);
msg.add_args(mo->args[1]); // which planes to apply the viewpoint to
}
// denis - check type as that is what the client will be spawning
if (mo->flags & MF_MISSILE || mobjinfo[mo->type].flags & MF_MISSILE)
{
msg.set_target_netid(mo->target ? mo->target->netid : 0);
}
else if (mo->flags & MF_AMBUSH || mo->flags & MF_DROPPED)
{
spawnFlags |= SVC_SM_FLAGS;
cur->set_flags(mo->flags);
}
if (mo->flags2 & MF2_DORMANT)
{
spawnFlags |= SVC_SM_FLAGS2;
cur->set_flags2(mo->flags2);
}
// odamex flags - only monster flags for now
if (mo->oflags & hordeBossModMask)
{
spawnFlags |= SVC_SM_OFLAGS;
cur->set_oflags(mo->oflags & hordeBossModMask);
}
// animating corpses
if ((mo->flags & MF_CORPSE) && mo->state->statenum != S_GIBS)
{
// This sets off some additional logic on the client.
spawnFlags |= SVC_SM_CORPSE;
cur->set_frame(mo->frame);
cur->set_tics(mo->tics);
}
msg.set_spawn_flags(spawnFlags);
return msg;
}
odaproto::svc::DisconnectClient SVC_DisconnectClient(const player_t& player)
{
odaproto::svc::DisconnectClient msg;
msg.set_pid(player.id);
return msg;
}
/**
* @brief Sends a message to a player telling them to change to the specified
* WAD and DEH patch files and load a map.
*/
odaproto::svc::LoadMap SVC_LoadMap(const OResFiles& wadnames, const OResFiles& patchnames,
const std::string& mapname, int time)
{
odaproto::svc::LoadMap msg;
// send list of wads (skip over wadnames[0] == odamex.wad)
size_t wadcount = wadnames.size() - 1;
for (size_t i = 1; i < wadcount + 1; i++)
{
odaproto::svc::LoadMap_Resource* wad = msg.add_wadnames();
wad->set_name(wadnames[i].getBasename());
wad->set_hash(wadnames[i].getMD5().getHexStr());
}
// send list of DEH/BEX patches
size_t patchcount = patchnames.size();
for (size_t i = 0; i < patchcount; i++)
{
odaproto::svc::LoadMap_Resource* patch = msg.add_patchnames();
patch->set_name(patchnames[i].getBasename());
patch->set_hash(patchnames[i].getMD5().getHexStr());
}
msg.set_mapname(mapname);
msg.set_time(time);
return msg;
}
odaproto::svc::ConsolePlayer SVC_ConsolePlayer(const player_t& player,
const std::string& digest)
{
odaproto::svc::ConsolePlayer msg;
msg.set_pid(player.id);
msg.set_digest(digest);
return msg;
}
odaproto::svc::ExplodeMissile SVC_ExplodeMissile(const AActor& mobj)
{
odaproto::svc::ExplodeMissile msg;
msg.set_netid(mobj.netid);
return msg;
}
odaproto::svc::RemoveMobj SVC_RemoveMobj(const AActor& mobj)
{
odaproto::svc::RemoveMobj msg;
msg.set_netid(mobj.netid);
return msg;
}
odaproto::svc::UserInfo SVC_UserInfo(const player_t& player, int64_t time)
{
odaproto::svc::UserInfo msg;
msg.set_pid(player.id);
msg.set_netname(player.userinfo.netname);
msg.set_team(player.userinfo.team);
msg.set_gender(player.userinfo.gender);
// [AM] Alpha is always 255.
odaproto::Color* color = msg.mutable_color();
color->set_r(player.userinfo.color[1]);
color->set_g(player.userinfo.color[2]);
color->set_b(player.userinfo.color[3]);
msg.set_join_time(time);
return msg;
}
/**
* @brief Update mobj data on the client compared to the baseline.
*/
odaproto::svc::UpdateMobj SVC_UpdateMobj(const AActor& mobj)
{
odaproto::svc::UpdateMobj msg;
uint32_t flags = P_GetMobjBaselineFlags(mobj);
msg.set_flags(flags);
odaproto::Actor* act = msg.mutable_actor();
odaproto::Vec3* pos = act->mutable_pos();
odaproto::Vec3* mom = act->mutable_mom();
act->set_netid(mobj.netid);
if (flags & baseline_t::POSX)
{
pos->set_x(mobj.x);
}
if (flags & baseline_t::POSY)
{
pos->set_y(mobj.y);
}
if (flags & baseline_t::POSZ)
{
pos->set_z(mobj.z);
}
if (flags & baseline_t::ANGLE)
{
act->set_angle(mobj.angle);
}
if (flags & baseline_t::MOVEDIR)
{
act->set_movedir(mobj.movedir);
}
if (flags & baseline_t::MOVECOUNT)
{
act->set_movecount(mobj.movecount);
}
if (flags & baseline_t::RNDINDEX)
{
act->set_rndindex(mobj.rndindex);
}
if (flags & baseline_t::TARGET)
{
act->set_targetid(mobj.target ? mobj.target->netid : 0);
}
if (flags & baseline_t::TRACER)
{
act->set_tracerid(mobj.tracer ? mobj.tracer->netid : 0);
}
if (flags & baseline_t::MOMX)
{
mom->set_x(mobj.momx);
}
if (flags & baseline_t::MOMY)
{
mom->set_y(mobj.momy);
}
if (flags & baseline_t::MOMZ)
{
mom->set_z(mobj.momz);
}
return msg;
}
EXTERN_CVAR(sv_sharekeys);
odaproto::svc::SpawnPlayer SVC_SpawnPlayer(const player_t& player)
{
odaproto::svc::SpawnPlayer msg;
msg.set_pid(player.id);
odaproto::Actor* act = msg.mutable_actor();
if (player.mo)
{
act->set_netid(player.mo->netid);
act->set_angle(player.mo->angle);
act->mutable_pos()->set_x(player.mo->x);
act->mutable_pos()->set_y(player.mo->y);
act->mutable_pos()->set_z(player.mo->z);
}
else
{
// The client hasn't yet received his own position from the server
// This happens with cl_autorecord
// Just fake a position for now
act->set_netid(limits::MAXSHORT);
}
if (sv_sharekeys)
{
const uint32_t packedcards = PackBoolArray(player.cards);
msg.set_cards(packedcards);
}
return msg;
}
odaproto::svc::DamagePlayer SVC_DamagePlayer(const player_t& player, const AActor* inflictor, int health, int armor)
{
odaproto::svc::DamagePlayer msg;
msg.set_netid(player.mo->netid);
msg.set_inflictorid(inflictor ? inflictor->netid : 0);
msg.set_health_damage(health);
msg.set_armor_damage(armor);
msg.mutable_player()->set_health(player.health);
msg.mutable_player()->set_armorpoints(player.armorpoints);
return msg;
}
/**
* @brief Kill a mobj.
*/
odaproto::svc::KillMobj SVC_KillMobj(const AActor* source, const AActor* target, const AActor* inflictor,
int mod, bool joinkill)
{
odaproto::svc::KillMobj msg;
odaproto::Actor* tgt = msg.mutable_target();
msg.set_source_netid(source ? source->netid : 0);
msg.set_inflictor_netid(inflictor ? inflictor->netid : 0);
msg.set_health(target->health);
msg.set_mod(mod);
msg.set_joinkill(joinkill);
// [AM] Confusingly, we send the lives _before_ we take it away, so
// the lives logic can live in the kill function.
msg.set_lives(target->player ? target->player->lives : -1);
// send death location first
tgt->set_netid(target->netid);
tgt->set_rndindex(target->rndindex);
// [SL] 2012-12-26 - Get real position since this actor is at
// a reconciled position due to unlag
fixed_t xoffs = 0, yoffs = 0, zoffs = 0;
if (target->player)
{
Unlag::getInstance().getReconciliationOffset(target->player->id, xoffs, yoffs,
zoffs);
}
odaproto::Vec3* tgtpos = tgt->mutable_pos();
tgtpos->set_x(target->x + xoffs);
tgtpos->set_y(target->y + yoffs);
tgtpos->set_z(target->z + zoffs);
tgt->set_angle(target->angle);
odaproto::Vec3* tgtmom = tgt->mutable_mom();
tgtmom->set_x(target->momx);
tgtmom->set_y(target->momy);
tgtmom->set_z(target->momz);
return msg;
}
/**
* @brief Resurrect a mobj.
*/
odaproto::svc::RaiseMobj SVC_RaiseMobj(const AActor* source, const AActor* corpse)
{
odaproto::svc::RaiseMobj msg;
odaproto::Actor* cps = msg.mutable_corpse();
// corpse netid
cps->set_netid(corpse->netid);
cps->set_rndindex(corpse->rndindex);
odaproto::Vec3* cpspos = cps->mutable_pos();
cpspos->set_x(corpse->x);
cpspos->set_y(corpse->y);
cpspos->set_z(corpse->z);
cps->set_angle(corpse->angle);
odaproto::Vec3* cpsmom = cps->mutable_mom();
cpsmom->set_x(corpse->momx);
cpsmom->set_y(corpse->momy);
cpsmom->set_z(corpse->momz);
msg.set_source_netid(source ? source->netid : 0);
return msg;
}
odaproto::svc::FireWeapon SVC_FireWeapon(const player_t& player)
{
odaproto::svc::FireWeapon msg;
msg.set_readyweapon(player.readyweapon);
msg.set_servertic(player.tic);
return msg;
}
odaproto::svc::UpdateSector SVC_UpdateSector(const sector_t& sector)
{
odaproto::svc::UpdateSector msg;
msg.set_sectornum(&sector - ::sectors);
odaproto::Sector* secmsg = msg.mutable_sector();
secmsg->set_floor_height(P_FloorHeight(&sector));
secmsg->set_ceiling_height(P_CeilingHeight(&sector));
secmsg->set_floorpic(sector.floorpic);
secmsg->set_ceilingpic(sector.ceilingpic);
secmsg->set_special(sector.special);
return msg;
}
odaproto::svc::Print SVC_Print(printlevel_t level, const std::string& str)
{
odaproto::svc::Print msg;
msg.set_level(level);
msg.set_message(str);
return msg;
}
/**
* @brief Persist player information to the client that is less vital.
*
* @param b Buffer to write to.
* @param player Player to send information about.
* @param flags SVC_PM_* flags to designate what gets sent.
*/
odaproto::svc::PlayerMembers SVC_PlayerMembers(const player_t& player, byte flags)
{
odaproto::svc::PlayerMembers msg;
msg.set_pid(player.id);
msg.set_flags(flags);
if (flags & SVC_PM_SPECTATOR)
{
msg.set_spectator(player.spectator);
}
if (flags & SVC_PM_READY)
{
msg.set_ready(player.ready);
}
if (flags & SVC_PM_LIVES)
{
msg.set_lives(player.lives);
}
if (flags & SVC_PM_DAMAGE)
{
msg.set_monsterdmgcount(player.monsterdmgcount);
}
if (flags & SVC_PM_SCORE)
{
// [AM] Just send everything instead of trying to be clever about
// what we send based on game modes. There's less room for
// breakage when adding game modes, and we have varints now,
// which means we're probably saving bandwidth anyway.
msg.set_roundwins(player.roundwins);
msg.set_points(player.points);
msg.set_fragcount(player.fragcount);
msg.set_deathcount(player.deathcount);
msg.set_killcount(player.killcount);
// [AM] Is there any reason we would ever care about itemcount?
msg.set_secretcount(player.secretcount);
msg.set_totalpoints(player.totalpoints);
msg.set_totaldeaths(player.totaldeaths);
}
if (flags & SVC_PM_CHEATS)
{
if (!player.spectator)
msg.set_cheats(player.cheats);
}
return msg;
}
/**
* Persist mutable team information to the client.
*/
odaproto::svc::TeamMembers SVC_TeamMembers(team_t team)
{
odaproto::svc::TeamMembers msg;
TeamInfo* info = GetTeamInfo(team);
msg.set_team(team);
msg.set_points(info->Points);
msg.set_roundwins(info->RoundWins);
return msg;
}
odaproto::svc::ActivateLine SVC_ActivateLine(const line_t* line, const AActor* mo, int side,
LineActivationType type)
{
odaproto::svc::ActivateLine msg;
msg.set_linenum(line ? line - ::lines : -1);
msg.set_activator_netid(mo ? mo->netid : 0);
msg.set_side(side);
msg.set_activation_type(type);
return msg;
}
odaproto::svc::MovingSector SVC_MovingSector(const sector_t& sector)
{
odaproto::svc::MovingSector msg;
ptrdiff_t sectornum = &sector - ::sectors;
// Determine which moving planes are in this sector - governs which
// fields to send to the other side.
movertype_t floor_mover = SEC_INVALID, ceiling_mover = SEC_INVALID;
if (sector.ceilingdata && sector.ceilingdata->IsA(RUNTIME_CLASS(DCeiling)))
{
ceiling_mover = SEC_CEILING;
}
if (sector.ceilingdata && sector.ceilingdata->IsA(RUNTIME_CLASS(DDoor)))
{
ceiling_mover = SEC_DOOR;
}
if (sector.floordata && sector.floordata->IsA(RUNTIME_CLASS(DFloor)))
{
floor_mover = SEC_FLOOR;
}
if (sector.floordata && sector.floordata->IsA(RUNTIME_CLASS(DPlat)))
{
floor_mover = SEC_PLAT;
}
if (sector.ceilingdata && sector.ceilingdata->IsA(RUNTIME_CLASS(DElevator)))
{
ceiling_mover = SEC_ELEVATOR;
floor_mover = SEC_INVALID;
}
if (sector.ceilingdata && sector.ceilingdata->IsA(RUNTIME_CLASS(DPillar)))
{
ceiling_mover = SEC_PILLAR;
floor_mover = SEC_INVALID;
}
// no moving planes? skip it.
if (ceiling_mover == SEC_INVALID && floor_mover == SEC_INVALID)
{
return msg;
}
// Create bitfield to denote moving planes in this sector
byte movers = byte(ceiling_mover) | (byte(floor_mover) << 4);
msg.set_sector(sectornum);
msg.set_ceiling_height(P_CeilingHeight(&sector));
msg.set_floor_height(P_FloorHeight(&sector));
msg.set_movers(movers);
if (ceiling_mover == SEC_ELEVATOR)
{
DElevator* Elevator = static_cast<DElevator*>(sector.ceilingdata);
odaproto::svc::MovingSector_Snapshot* ceil = msg.mutable_ceiling_mover();
ceil->set_ceil_type(Elevator->m_Type);
ceil->set_ceil_status(Elevator->m_Status);
ceil->set_ceil_dir(Elevator->m_Direction);
ceil->set_floor_dest(Elevator->m_FloorDestHeight);
ceil->set_ceil_dest(Elevator->m_CeilingDestHeight);
ceil->set_ceil_speed(Elevator->m_Speed);
}
if (ceiling_mover == SEC_PILLAR)
{
DPillar* Pillar = static_cast<DPillar*>(sector.ceilingdata);
odaproto::svc::MovingSector_Snapshot* ceil = msg.mutable_ceiling_mover();
ceil->set_ceil_type(Pillar->m_Type);
ceil->set_ceil_status(Pillar->m_Status);
ceil->set_floor_speed(Pillar->m_FloorSpeed);
ceil->set_ceil_speed(Pillar->m_CeilingSpeed);
ceil->set_floor_dest(Pillar->m_FloorTarget);
ceil->set_ceil_dest(Pillar->m_CeilingTarget);
ceil->set_ceil_crush(Pillar->m_Crush);
}
if (ceiling_mover == SEC_CEILING)
{
DCeiling* Ceiling = static_cast<DCeiling*>(sector.ceilingdata);
odaproto::svc::MovingSector_Snapshot* ceil = msg.mutable_ceiling_mover();
ceil->set_ceil_type(Ceiling->m_Type);
ceil->set_ceil_low(Ceiling->m_BottomHeight);
ceil->set_ceil_high(Ceiling->m_TopHeight);
ceil->set_ceil_speed(Ceiling->m_Speed);
ceil->set_crusher_speed_1(Ceiling->m_Speed1);
ceil->set_crusher_speed_2(Ceiling->m_Speed2);
ceil->set_ceil_crush(Ceiling->m_Crush);
ceil->set_silent(Ceiling->m_Silent);
ceil->set_ceil_dir(Ceiling->m_Direction);
ceil->set_ceil_tex(Ceiling->m_Texture);
ceil->set_ceil_new_special(Ceiling->m_NewSpecial);
ceil->set_ceil_tag(Ceiling->m_Tag);
ceil->set_ceil_old_dir(Ceiling->m_OldDirection);
}
if (ceiling_mover == SEC_DOOR)
{
DDoor* Door = static_cast<DDoor*>(sector.ceilingdata);
odaproto::svc::MovingSector_Snapshot* ceil = msg.mutable_ceiling_mover();
ceil->set_ceil_type(Door->m_Type);
ceil->set_ceil_height(Door->m_TopHeight);
ceil->set_ceil_speed(Door->m_Speed);
ceil->set_ceil_wait(Door->m_TopWait);
ceil->set_ceil_counter(Door->m_TopCountdown);
ceil->set_ceil_status(Door->m_Status);
// Check for an invalid m_Line (doors triggered by tag 666)
ceil->set_ceil_line(Door->m_Line ? (Door->m_Line - lines) : -1);
}
if (floor_mover == SEC_FLOOR)
{
DFloor* Floor = static_cast<DFloor*>(sector.floordata);
odaproto::svc::MovingSector_Snapshot* floor = msg.mutable_floor_mover();
floor->set_floor_type(Floor->m_Type);
floor->set_floor_status(Floor->m_Status);
floor->set_floor_crush(Floor->m_Crush);
floor->set_floor_dir(Floor->m_Direction);
floor->set_floor_speed(Floor->m_NewSpecial);
floor->set_floor_tex(Floor->m_Texture);
floor->set_floor_dest(Floor->m_FloorDestHeight);
floor->set_floor_speed(Floor->m_Speed);
floor->set_reset_counter(Floor->m_ResetCount);
floor->set_orig_height(Floor->m_OrgHeight);
floor->set_delay(Floor->m_Delay);
floor->set_pause_time(Floor->m_PauseTime);
floor->set_step_time(Floor->m_StepTime);
floor->set_per_step_time(Floor->m_PerStepTime);
floor->set_floor_line(Floor->m_Line ? (Floor->m_Line - lines) : -1);
}
if (floor_mover == SEC_PLAT)
{
DPlat* Plat = static_cast<DPlat*>(sector.floordata);
odaproto::svc::MovingSector_Snapshot* floor = msg.mutable_floor_mover();
floor->set_floor_speed(Plat->m_Speed);
floor->set_floor_low(Plat->m_Low);
floor->set_floor_high(Plat->m_High);
floor->set_floor_wait(Plat->m_Wait);
floor->set_floor_counter(Plat->m_Count);
floor->set_floor_status(Plat->m_Status);
floor->set_floor_old_status(Plat->m_OldStatus);
floor->set_floor_crush(Plat->m_Crush);
floor->set_floor_tag(Plat->m_Tag);
floor->set_floor_type(Plat->m_Type);
floor->set_floor_offset(Plat->m_Height);
floor->set_floor_lip(Plat->m_Lip);
}
return msg;
}
odaproto::svc::PlaySound SVC_PlaySound(const PlaySoundType& type, int channel, int sfx_id,
float volume, int attenuation)
{
odaproto::svc::PlaySound msg;
msg.set_channel(channel);
msg.set_sfxid(sfx_id);
msg.set_volume(volume);
msg.set_attenuation(attenuation);
switch (type.tag)
{
case PlaySoundType::PS_NONE:
break;
case PlaySoundType::PS_MOBJ:
msg.set_netid(type.data.mo->netid);
break;
case PlaySoundType::PS_POS:
msg.mutable_pos()->set_x(type.data.pos.x);
msg.mutable_pos()->set_y(type.data.pos.y);
break;
}
return msg;
}
odaproto::svc::TouchSpecial SVC_TouchSpecial(const AActor& mo)
{
odaproto::svc::TouchSpecial msg;
msg.set_netid(mo.netid);
return msg;
}
/**
* @brief Send information about a player
*/
odaproto::svc::PlayerState SVC_PlayerState(const player_t& player)
{
odaproto::svc::PlayerState msg;
odaproto::Player* pl = msg.mutable_player();
pl->set_playerid(player.id);
pl->set_health(player.health);
pl->set_armortype(player.armortype);
pl->set_armorpoints(player.armorpoints);
pl->set_lives(player.lives);
pl->set_readyweapon(player.readyweapon);
std::bitset<6> cardBits;
for (int i = 0; i < NUMCARDS; i++)
{
cardBits.set(i, player.cards[i]);
}
pl->set_cards(cardBits.to_ulong());
for (int i = 0; i < NUMAMMO; i++)
{
pl->add_ammo(player.ammo[i]);
}
for (int i = 0; i < NUMPSPRITES; i++)
{
const pspdef_t* psp = &player.psprites[i];
const int32_t state = psp->state ? psp->state->statenum : 0;
odaproto::Player_Psp* plpsp = pl->add_psprites();
plpsp->set_statenum(state);
}
for (int i = 0; i < NUMPOWERS; i++)
{
pl->add_powers(player.powers[i]);
}
if (!player.spectator)
pl->set_cheats(player.cheats);
return msg;
}
/**
* @brief Send information about the server's LevelState to the client.
*/
odaproto::svc::LevelState SVC_LevelState(const SerializedLevelState& sls)
{
odaproto::svc::LevelState msg;
msg.set_state(sls.state);
msg.set_countdown_done_time(sls.countdown_done_time);
msg.set_ingame_start_time(sls.ingame_start_time);
msg.set_round_number(sls.round_number);
msg.set_last_wininfo_type(sls.last_wininfo_type);
msg.set_last_wininfo_id(sls.last_wininfo_id);
return msg;
}
odaproto::svc::PlayerQueuePos SVC_PlayerQueuePos(const player_t& source)
{
odaproto::svc::PlayerQueuePos msg;
msg.set_pid(source.id);
msg.set_queuepos(source.QueuePosition);
return msg;
}
odaproto::svc::ForceTeam SVC_ForceTeam(team_t team)
{
odaproto::svc::ForceTeam msg;
msg.set_team(team);
return msg;
}
odaproto::svc::Switch SVC_Switch(const line_t& line, uint32_t state, uint32_t timer)
{
odaproto::svc::Switch msg;
msg.set_linenum(&line - ::lines);
msg.set_switch_active(line.switchactive);
msg.set_special(line.special);
msg.set_state(state);
msg.set_button_texture(P_GetButtonTexture(&line));
msg.set_timer(timer);
return msg;
}
odaproto::svc::Say SVC_Say(const bool visibility, const byte pid,
const std::string& message)
{
odaproto::svc::Say msg;
msg.set_visibility(visibility);
msg.set_pid(pid);
msg.set_message(message);
return msg;
}
odaproto::svc::CTFRefresh SVC_CTFRefresh(const TeamsView& teams, const bool full)
{
odaproto::svc::CTFRefresh msg;
msg.set_full(full);
for (const auto& team : teams)
{
odaproto::svc::CTFRefresh_TeamInfo* info = msg.add_team_info();
info->set_points(team->Points);
if (full)
{
info->set_flag_state(team->FlagData.state);
info->set_flag_flagger(team->FlagData.flagger);
}
}
return msg;
}
odaproto::svc::CTFEvent SVC_CTFEvent(const flag_score_t event, const team_t target,
const player_t& player)
{
odaproto::svc::CTFEvent msg;
msg.set_event(event);
msg.set_target_team(target);
if (validplayer(player))
{
msg.set_player_team(player.userinfo.team);
msg.set_player_id(player.id);
if (G_IsRoundsGame())
{
msg.set_player_totalpoints(player.totalpoints);
}
else
{
msg.set_player_points(player.points);
}
}
return msg;
}
/**
* @brief Send information about a player who discovered a secret.
*/
odaproto::svc::SecretEvent SVC_SecretEvent(const player_t& player, const sector_t& sector)
{
odaproto::svc::SecretEvent msg;
msg.set_pid(player.id);
msg.set_sectornum(&sector - ::sectors);
msg.mutable_sector()->set_special(sector.special);
return msg;
}
odaproto::svc::ServerSettings SVC_ServerSettings(const cvar_t& var)
{
odaproto::svc::ServerSettings msg;
msg.set_key(var.name());
msg.set_value(var.str());
return msg;
}
odaproto::svc::ConnectClient SVC_ConnectClient(const player_t& player)
{
odaproto::svc::ConnectClient msg;
msg.set_pid(player.id);
return msg;
}
odaproto::svc::MidPrint SVC_MidPrint(const std::string& message, const int time)
{
odaproto::svc::MidPrint msg;
msg.set_message(message);
msg.set_time(time);
return msg;
}
odaproto::svc::ServerGametic SVC_ServerGametic(const byte tic)
{
odaproto::svc::ServerGametic msg;
msg.set_tic(tic);
return msg;
}
odaproto::svc::IntTimeLeft SVC_IntTimeLeft(const unsigned int timeleft)
{
odaproto::svc::IntTimeLeft msg;
msg.set_timeleft(timeleft);
return msg;
}
odaproto::svc::RailTrail SVC_RailTrail(const v3double_t& start, const v3double_t& end)
{
odaproto::svc::RailTrail msg;
odaproto::Vec3* stmsg = msg.mutable_start();
stmsg->set_x(DOUBLE2FIXED(start.x));
stmsg->set_y(DOUBLE2FIXED(start.y));
stmsg->set_z(DOUBLE2FIXED(start.z));
odaproto::Vec3* edmsg = msg.mutable_end();
edmsg->set_x(DOUBLE2FIXED(end.x));
edmsg->set_y(DOUBLE2FIXED(end.y));
edmsg->set_z(DOUBLE2FIXED(end.z));
return msg;
}
odaproto::svc::LineUpdate SVC_LineUpdate(const line_t& line)
{
odaproto::svc::LineUpdate msg;
msg.set_linenum(&line - ::lines);
msg.set_flags(line.flags);
msg.set_lucency(line.lucency);
return msg;
}
odaproto::svc::SectorProperties SVC_SectorProperties(const sector_t& sector)
{
odaproto::svc::SectorProperties msg;
msg.set_sectornum(&sector - ::sectors);
msg.set_changes(sector.SectorChanges);
odaproto::Sector* secmsg = msg.mutable_sector();
for (int i = 0, prop = 1; prop < SPC_Max; i++)
{
prop = 1 << i;
if ((prop & sector.SectorChanges) == 0)
continue;
switch (prop)
{
case SPC_FlatPic:
secmsg->set_floorpic(sector.floorpic);
secmsg->set_ceilingpic(sector.ceilingpic);
break;
case SPC_LightLevel:
secmsg->set_lightlevel(sector.lightlevel);
break;
case SPC_Color: {
odaproto::Color* color = secmsg->mutable_colormap()->mutable_color();
color->set_r(sector.colormap->color.getr());
color->set_g(sector.colormap->color.getg());
color->set_b(sector.colormap->color.getb());
break;
}
case SPC_Fade: {
odaproto::Color* color = secmsg->mutable_colormap()->mutable_fade();
color->set_r(sector.colormap->fade.getr());
color->set_g(sector.colormap->fade.getg());
color->set_b(sector.colormap->fade.getb());
break;
}
case SPC_Gravity:
secmsg->set_gravity(sector.gravity);
break;
case SPC_Panning: {
odaproto::Vec2* mc = secmsg->mutable_ceiling_offs();
mc->set_x(sector.ceiling_xoffs);
mc->set_y(sector.ceiling_yoffs);
odaproto::Vec2* mf = secmsg->mutable_floor_offs();
mf->set_x(sector.floor_xoffs);
mf->set_y(sector.floor_yoffs);
break;
}
case SPC_Scale: {
odaproto::Vec2* cs = secmsg->mutable_ceiling_scale();
cs->set_x(sector.ceiling_xscale);
cs->set_y(sector.ceiling_yscale);
odaproto::Vec2* fs = secmsg->mutable_floor_scale();
fs->set_x(sector.floor_xscale);
fs->set_y(sector.floor_yscale);
break;
}
case SPC_Rotation:
secmsg->set_floor_angle(sector.floor_angle);
secmsg->set_ceiling_angle(sector.ceiling_angle);
break;
case SPC_AlignBase:
secmsg->set_base_ceiling_angle(sector.base_ceiling_angle);
secmsg->set_base_ceiling_yoffs(sector.base_ceiling_yoffs);
secmsg->set_base_floor_angle(sector.base_floor_angle);
secmsg->set_base_floor_yoffs(sector.base_floor_yoffs);
break;
case SPC_Special:
secmsg->set_special(sector.special);
break;
default:
break;
}
}
return msg;
}
odaproto::svc::LineSideUpdate SVC_LineSideUpdate(const line_t& line, const int sideNum)
{
odaproto::svc::LineSideUpdate msg;
side_t* currentSideDef = sides + line.sidenum[sideNum];
msg.set_linenum(&line - ::lines);
msg.set_side(sideNum);
msg.set_changes(currentSideDef->SidedefChanges);
for (int i = 0, prop = 1; prop < SDPC_Max; i++)
{
prop = BIT(i);
if ((prop & currentSideDef->SidedefChanges) == 0)
continue;
switch (prop)
{
case SDPC_TexTop:
msg.set_toptexture(currentSideDef->toptexture);
break;
case SDPC_TexMid:
msg.set_midtexture(currentSideDef->midtexture);
break;
case SDPC_TexBottom:
msg.set_bottomtexture(currentSideDef->bottomtexture);
break;
default:
break;
}
}
return msg;
}
odaproto::svc::MobjState SVC_MobjState(const AActor* mo)
{
odaproto::svc::MobjState msg;
const int32_t mostate = mo->state ? mo->state->statenum : 0;
msg.set_netid(mo->netid);
msg.set_mostate(mostate);
return msg;
}
odaproto::svc::DamageMobj SVC_DamageMobj(const AActor* target, const int pain)
{
odaproto::svc::DamageMobj msg;
msg.set_netid(target->netid);
msg.set_health(target->health);
msg.set_pain(pain);
return msg;
}
odaproto::svc::ExecuteLineSpecial SVC_ExecuteLineSpecial(byte special, const line_t* line,
const AActor* mo, const int (&args)[5])
{
odaproto::svc::ExecuteLineSpecial msg;
msg.set_special(special);
msg.set_linenum(line ? line - ::lines : -1);
msg.set_activator_netid(mo ? mo->netid : 0);
msg.set_arg0(args[0]);
msg.set_arg1(args[1]);
msg.set_arg2(args[2]);
msg.set_arg3(args[3]);
msg.set_arg4(args[4]);
return msg;
}
/**
* @brief Execute an ACS special on the client.
*
* @param special Special ID.
* @param activator Activator pointer, NULL if no activator.
* @param print String to send to client, NULL if no string.
* @param args Arguments to pass to the client.
*/
odaproto::svc::ExecuteACSSpecial SVC_ExecuteACSSpecial(const byte special,
const AActor* activator,
const char* print,
const nonstd::span<const int> args)
{
odaproto::svc::ExecuteACSSpecial msg;
msg.set_special(special);
if (activator != NULL)
{
msg.set_activator_netid(activator->netid);
}
if (print != NULL)
{
msg.set_print(print);
}
for (const auto& arg : args)
{
msg.add_args(arg);
}
return msg;
}
odaproto::svc::ThinkerUpdate SVC_ThinkerUpdate(const DThinker* thinker)
{
odaproto::svc::ThinkerUpdate msg;
if (thinker->IsA(RUNTIME_CLASS(DScroller)))
{
const DScroller* scroller = static_cast<const DScroller*>(thinker);
odaproto::svc::ThinkerUpdate_Scroller* smsg = msg.mutable_scroller();
smsg->set_type(scroller->GetType());
smsg->set_scroll_x(scroller->GetScrollX());
smsg->set_scroll_y(scroller->GetScrollY());
smsg->set_affectee(scroller->GetAffectee());
smsg->set_control(scroller->GetControl());
smsg->set_accel(scroller->GetAccel());
}
else if (thinker->IsA(RUNTIME_CLASS(DFireFlicker)))
{
const DFireFlicker* fireFlicker = static_cast<const DFireFlicker*>(thinker);
odaproto::svc::ThinkerUpdate_FireFlicker* ffmsg = msg.mutable_fire_flicker();
ffmsg->set_sector(fireFlicker->GetSector() - sectors);
ffmsg->set_min_light(fireFlicker->GetMinLight());
ffmsg->set_max_light(fireFlicker->GetMaxLight());
}
else if (thinker->IsA(RUNTIME_CLASS(DFlicker)))
{
const DFlicker* flicker = static_cast<const DFlicker*>(thinker);
odaproto::svc::ThinkerUpdate_Flicker* fmsg = msg.mutable_flicker();
fmsg->set_sector(flicker->GetSector() - sectors);
fmsg->set_min_light(flicker->GetMinLight());
fmsg->set_max_light(flicker->GetMaxLight());
}
else if (thinker->IsA(RUNTIME_CLASS(DLightFlash)))
{
const DLightFlash* lightFlash = static_cast<const DLightFlash*>(thinker);
odaproto::svc::ThinkerUpdate_LightFlash* lfmsg = msg.mutable_light_flash();
lfmsg->set_sector(lightFlash->GetSector() - sectors);
lfmsg->set_min_light(lightFlash->GetMinLight());
lfmsg->set_max_light(lightFlash->GetMaxLight());
}
else if (thinker->IsA(RUNTIME_CLASS(DStrobe)))
{
const DStrobe* strobe = static_cast<const DStrobe*>(thinker);
odaproto::svc::ThinkerUpdate_Strobe* smsg = msg.mutable_strobe();
smsg->set_sector(strobe->GetSector() - sectors);
smsg->set_min_light(strobe->GetMinLight());
smsg->set_max_light(strobe->GetMaxLight());
smsg->set_dark_time(strobe->GetDarkTime());
smsg->set_bright_time(strobe->GetBrightTime());
smsg->set_count(strobe->GetCount());
}
else if (thinker->IsA(RUNTIME_CLASS(DGlow)))
{
const DGlow* glow = static_cast<const DGlow*>(thinker);
odaproto::svc::ThinkerUpdate_Glow* gmsg = msg.mutable_glow();
gmsg->set_sector(glow->GetSector() - sectors);
}
else if (thinker->IsA(RUNTIME_CLASS(DGlow2)))
{
const DGlow2* glow2 = static_cast<const DGlow2*>(thinker);
odaproto::svc::ThinkerUpdate_Glow2* g2msg = msg.mutable_glow2();
g2msg->set_sector(glow2->GetSector() - sectors);
g2msg->set_start(glow2->GetStart());
g2msg->set_end(glow2->GetEnd());
g2msg->set_max_tics(glow2->GetMaxTics());
g2msg->set_one_shot(glow2->GetOneShot());
}
else if (thinker->IsA(RUNTIME_CLASS(DPhased)))
{
const DPhased* phased = static_cast<const DPhased*>(thinker);
odaproto::svc::ThinkerUpdate_Phased* pmsg = msg.mutable_phased();
pmsg->set_sector(phased->GetSector() - sectors);
pmsg->set_base_level(phased->GetBaseLevel());
pmsg->set_phase(phased->GetPhase());
}
return msg;
}
odaproto::svc::VoteUpdate SVC_VoteUpdate(const vote_state_t& state)
{
odaproto::svc::VoteUpdate msg;
msg.set_result(state.result);
msg.set_votestring(state.votestring);
msg.set_countdown(state.countdown);
msg.set_yes(state.yes);
msg.set_yes_needed(state.yes_needed);
msg.set_no(state.no);
msg.set_no_needed(state.no_needed);
msg.set_abs(state.abs);
return msg;
}
odaproto::svc::Maplist SVC_Maplist(const maplist_status_t status)
{
odaproto::svc::Maplist msg;
msg.set_status(status);
return msg;
}
odaproto::svc::MaplistUpdate SVC_MaplistUpdate(const maplist_status_t status,
const maplist_qrows_t* maplist)
{
odaproto::svc::MaplistUpdate msg;
msg.set_status(status);
if (status == MAPLIST_OUTDATED)
{
assert(maplist != NULL && "Maplist pointer must be non-null");
// In order to cut down on bandwidth, this message uses mappings of
// strings to unique ID's to save bandwidth. Some of these mappings
// are already known on the receiving end.
OStringIndexer indexer = OStringIndexer::maplistFactory();
for (const auto& [_, entry] : *maplist)
{
// Create a row and add an indexed map to it.
odaproto::svc::MaplistUpdate::Row* row = msg.add_maplist();
const std::string& map = entry->map;
const uint32_t mapidx = indexer.getIndex(map);
row->set_map(mapidx);
const std::string lastmaps = fmt::format("{}", entry->lastmaps);
const uint32_t lastmapidx = indexer.getIndex(lastmaps);
row->set_lastmap(lastmapidx);
for (const auto& wad : entry->wads)
{
// Push an indexed WAD into the message.
std::string filename = D_CleanseFileName(wad);
const uint32_t wadidx = indexer.getIndex(filename);
row->add_wads(wadidx);
}
}
// Populate the dictionary.
for (const auto& [string, idx] : indexer.indexes)
{
if (!indexer.shouldTransmit(idx))
continue;
typedef google::protobuf::MapPair<uint32_t, std::string> DictPair;
msg.mutable_dict()->insert(DictPair(idx, string));
}
}
return msg;
}
odaproto::svc::MaplistIndex SVC_MaplistIndex(const byte count, const size_t this_index,
const size_t next_index)
{
odaproto::svc::MaplistIndex msg;
msg.set_count(count);
if (count > 0)
{
msg.set_next_index(next_index);
if (count > 1)
{
msg.set_this_index(this_index);
}
}
return msg;
}
odaproto::svc::Toast SVC_Toast(const toast_t& toast)
{
odaproto::svc::Toast msg;
msg.set_flags(toast.flags);
msg.set_left(toast.left);
msg.set_left_pid(toast.left_pid);
msg.set_right(toast.right);
msg.set_right_pid(toast.right_pid);
msg.set_icon(toast.icon);
msg.set_points(toast.points);
msg.set_spree_color(toast.spree_color);
return msg;
}
odaproto::svc::HordeInfo SVC_HordeInfo(const hordeInfo_t& horde)
{
odaproto::svc::HordeInfo msg;
msg.set_state(horde.state);
msg.set_wave(horde.wave);
msg.set_wave_time(horde.waveTime);
msg.set_boss_time(horde.bossTime);
msg.set_define_id(horde.defineID);
msg.set_spawned_health(horde.spawnedHealth);
msg.set_killed_health(horde.killedHealth);
msg.set_boss_health(horde.bossHealth);
msg.set_boss_damage(horde.bossDamage);
msg.set_wave_start_health(horde.waveStartHealth);
return msg;
}
odaproto::svc::Spree SVC_Spree(const SpreeRecord_t& spree)
{
odaproto::svc::Spree msg;
msg.set_pid(spree.playerId);
msg.set_spree_level(spree.spreeLevel);
return msg;
}
odaproto::svc::SpreeBreaker SVC_SpreeBreaker(const SpreeBreaker_t& breaker, const int level, const SpreeBreakerType breakerType)
{
odaproto::svc::SpreeBreaker msg;
msg.set_victim_pid(breaker.spreeEndedPlayerId);
msg.set_victim_name(breaker.spreeEndedName);
msg.set_source_pid(breaker.spreeEnderPlayerId);
msg.set_source_name(breaker.spreeEnderName);
msg.set_spree_level(level);
msg.set_spree_points(breaker.endedPoints);
msg.set_spree_breaker_type(breakerType);
return msg;
}
odaproto::svc::NetdemoCap SVC_NetdemoCap(const player_t* player)
{
odaproto::svc::NetdemoCap msg;
odaproto::Actor* act = msg.mutable_actor();
odaproto::Player* play = msg.mutable_player();
const AActor* mo = player->mo;
player->cmd.serialize(*msg.mutable_player_cmd());
act->set_waterlevel(mo->waterlevel);
act->mutable_pos()->set_x(mo->x);
act->mutable_pos()->set_y(mo->y);
act->mutable_pos()->set_z(mo->z);
act->mutable_mom()->set_x(mo->momx);
act->mutable_mom()->set_y(mo->momy);
act->mutable_mom()->set_z(mo->momz);
act->set_angle(mo->angle);
act->set_pitch(mo->pitch);
play->set_viewz(player->viewz);
play->set_viewheight(player->viewheight);
play->set_deltaviewheight(player->deltaviewheight);
play->set_jumptics(player->jumpTics);
act->set_reactiontime(mo->reactiontime);
play->set_readyweapon(player->readyweapon);
play->set_pendingweapon(player->pendingweapon);
return msg;
}
VERSION_CONTROL(svc_message, "$Id$")