/*
* This file is part of Deliantra, the Roguelike Realtime MMORPG.
*
* Copyright (©) 2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
* Copyright (©) 2001 Mark Wedel
* Copyright (©) 1992 Frank Tore Johansen
*
* Deliantra is free software: you can redistribute it and/or modify it under
* the terms of the Affero GNU General Public License as published by the
* Free Software Foundation, either version 3 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.
*
* You should have received a copy of the Affero GNU General Public License
* and the GNU General Public License along with this program. If not, see
* .
*
* The authors can be reached via e-mail to
*/
/*
* Image related communication
*
* This file deals with the image related communication to the
* client. I've located all the functions in this file - this
* localizes it more, and means that we don't need to declare
* things like all the structures as globals.
*/
#include
#include
#include "crc.h"
#define MAX_IMAGE_SIZE 10000
/**
* client requested an image. send it rate-limited
* before flushing.
*/
void
AskFaceCmd (char *buf, int len, client *ns)
{
int idx = 0, pri = 0;
sscanf (buf, "%d %d", &idx, &pri);
//TODO: somehow fetch default priority from send_fx here
const faceinfo *f = face_info (idx);
if (!f)
return; // doh
int set = ns->tileset;
if (!f->face [set].chksum_len)
set = 0;
/* cfperl_ix calls cf::face::ix which loads the data */
/* and then calls cf::client::send_ix to actually queue the ix */
cfperl_ix (ns, set, idx, pri);
}
void
client::ix_send (faceidx idx, sint16 pri, SV *data_sv)
{
STRLEN size;
char *data = SvPVbyte (data_sv, size);
ixsend ix;
ix.pri = pri;
ix.idx = idx;
ix.ofs = size;
ix.data = (uint8 *)data;
ix.data_sv = SvREFCNT_inc (data_sv);
auto (pos, ixface.end ());
// the by far most common case will be to insert
// near the end, so little looping.
while (pos != ixface.begin ())
{
--pos;
// sort within 2k bins, to slightly prefer smaller images
if (pri > pos->pri || (pri == pos->pri && (ix.ofs >> 11) <= (pos->ofs >> 11)))
{
++pos;
break;
}
}
ixface.insert (pos, ix);
#if 0
for (auto (i, ixface.begin ()); i != ixface.end (); ++i)
fprintf (stderr, "<%d,%d> ", i->pri, i->ofs);
fprintf (stderr, "\n");
#endif
}
void
client::ix_pop ()
{
ixsend &ix = ixface.back ();
SvREFCNT_dec (ix.data_sv);
ixface.pop_back ();
}
/**
* Sends a face offer (fx) to a client.
* If nocache is true (nonzero), ignore the cache setting from the client -
* this is needed for the askface, in which we really do want to send the
* face (and askface is the only place that should be setting it). Otherwise,
* we look at the facecache, and if set, send the image name.
*/
void
client::send_face (faceidx facenum, int pri)
{
// never send face 0. ever. it does not exist.
if (!facenum)
return;
faceinfo *f = face_info (facenum);
if (!f)
{
LOG (llevError | logBacktrace, "client::send_face (%d) out of bounds??\n", facenum);
return;
}
// refuse to send non-image faces unless requested
if (!fx_want [f->type])
return;
if (faces_sent [facenum])
return;
faces_sent [facenum] = true;
fxface.push_back (facenum);
}
void client::flush_fx ()
{
while (!fxface.empty ())
{
packet fx ("fx");
packet sx ("sx");
int type = 0;
do
{
faceidx facenum = fxface.back (); fxface.pop_back ();
if (const faceinfo *f = face_info (facenum))
{
if (f->type != type)
{
type = f->type;
fx << ber32 (0)
<< uint8 (1) << uint8 (type);
}
const facedata *d = f->data (tileset);
fx << ber32 (facenum)
<< data8 (d->chksum, d->chksum_len);
if (smoothing)
{
faceinfo *f = face_info (facenum);
if (f->smooth)
{
send_face (f->smooth, -110);
sx << ber32 (facenum)
<< ber32 (f->smooth)
<< ber32 (f->smoothlevel);
}
}
}
}
while (!fxface.empty ()
&& fx.room () > ber32::size + CHKSUM_MAXLEN + 1 + 3 /* type switch */
&& sx.room () > ber32::size * 3);
send_packet (fx);
if (sx.length () > 3) send_packet (sx);
}
}
// send all faces of this object to the client
// this uses more bandwidth initially, but makes
// animations look much smoother, and every client
// is supposed to do client-side caching anyways.
void
client::send_faces (object *ob)
{
send_face (ob->face, 10);
if (ob->animation_id)
{
const animation &anim = ob->anim ();
for (int i = 0; i < anim.num_animations; i++)
send_face (anim.faces [i], -10);
}
}
/**
* Need to send an animation sequence to the client.
* We will send appropriate face commands to the client if we haven't
* sent them the face yet (this can become quite costly in terms of
* how much we are sending - on the other hand, this should only happen
* when the player logs in and picks stuff up.
*/
void
client::send_animation (short anim_num)
{
/* Do some checking on the anim_num we got. Note that the animations
* are added in contiguous order, so if the number is in the valid
* range, it must be a valid animation.
*/
if (anim_num < 0 || anim_num >= animations.size ())
{
LOG (llevError, "esrv_send_anim (%d) out of bounds??\n", anim_num);
return;
}
packet sl ("anim");
sl << uint16 (anim_num)
<< uint16 (0); /* flags - not used right now */
/* Build up the list of faces. Also, send any information (ie, the
* the face itself) down to the client.
*/
for (int i = 0; i < animations[anim_num].num_animations; i++)
{
send_face (animations[anim_num].faces[i], -20);
sl << uint16 (animations[anim_num].faces[i]);
}
send_packet (sl);
anims_sent[anim_num] = 1;
}
void
client::invalidate_face (faceidx idx)
{
if (!faces_sent [idx])
return;
faces_sent [idx] = false;
send_face (idx);
//TODO: check for active ix and abort it.
}
void
client::invalidate_all_faces ()
{
for (faceidx i = 0; i < faces_sent.size (); ++i)
invalidate_face (i);
}