--- deliantra/server/socket/image.C 2006/09/10 13:43:33 1.3
+++ deliantra/server/socket/image.C 2008/12/27 01:25:00 1.58
@@ -1,32 +1,26 @@
-
/*
- * static char *rcsid_init_c =
- * "$Id: image.C,v 1.3 2006/09/10 13:43:33 root Exp $";
+ * This file is part of Deliantra, the Roguelike Realtime MMORPG.
+ *
+ * Copyright (©) 2005,2006,2007,2008 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
+ * Copyright (©) 2001,2007 Mark Wedel
+ * Copyright (©) 1992,2007 Frank Tore Johansen
+ *
+ * Deliantra 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 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 GNU General Public License
+ * along with this program. If not, see .
+ *
+ * The authors can be reached via e-mail to
*/
-/*
- CrossFire, A Multiplayer game for X-windows
-
- Copyright (C) 2001 Mark Wedel
- Copyright (C) 1992 Frank Tore Johansen
-
- 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.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
- The author can be reached via e-mail to crossfire-devel@real-time.com
-*/
-
/** \file
* Image related communication
*
@@ -41,391 +35,200 @@
#include
#include
-#include
-#include
-#include
+#include "crc.h"
-#define MAX_FACE_SETS 20 /**< Maximum number of image sets the program will handle */
+#define MAX_IMAGE_SIZE 10000
-/** Information about one image */
-typedef struct FaceInfo
+/**
+ * client requested an image. send it rate-limited
+ * before flushing.
+ */
+void
+AskFaceCmd (char *buf, int len, client *ns)
{
+ int idx = 0, pri = 0;
- uint8 *data; /**< image data */
-
- uint16 datalen; /**< length of the xpm data */
-
- uint32 checksum; /**< Checksum of face data */
-} FaceInfo;
-
-/** Information about one face set */
-typedef struct
-{
+ sscanf (buf, "%d %d", &idx, &pri);
- char *prefix; /**< */
- char *fullname;
+ //TODO: somehow fetch default priority from send_fx here
- uint8 fallback; /**< faceset to use when an image is not found in this faceset */
- char *size;
- char *extension;
- char *comment;
+ const facedata *d = face_data (idx, ns->faceset);
- FaceInfo *faces; /**< images in this faceset */
-} FaceSets;
+ if (!d)
+ return; // doh
-static FaceSets facesets[MAX_FACE_SETS]; /**< All facesets */
+ client::ixsend ix;
-/**
- * Checks specified faceset is valid
- * \param fsn faceset number
- */
-int
-is_valid_faceset (int fsn)
-{
- if (fsn >= 0 && fsn < MAX_FACE_SETS && facesets[fsn].prefix)
- return TRUE;
- return FALSE;
-}
+ ix.pri = pri;
+ ix.idx = idx;
+ ix.ofs = d->data.size ();
-/**
- * Frees all faceset information
- */
-void
-free_socket_images (void)
-{
- int num, q;
+ auto (pos, ns->ixface.end ());
- for (num = 0; num < MAX_FACE_SETS; num++)
+ // the by far most common case will be to insert
+ // near the end, so little looping.
+ while (pos != ns->ixface.begin ())
{
- if (facesets[num].prefix)
+ --pos;
+
+ // sort within 2k bins, to slightly prefer smaller images
+ if (pri > pos->pri || (pri == pos->pri && (ix.ofs >> 11) <= (pos->ofs >> 11)))
{
- for (q = 0; q < nrofpixmaps; q++)
- if (facesets[num].faces[q].data)
- free (facesets[num].faces[q].data);
- free (facesets[num].prefix);
- free (facesets[num].fullname);
- free (facesets[num].size);
- free (facesets[num].extension);
- free (facesets[num].comment);
- free (facesets[num].faces);
+ ++pos;
+ break;
}
}
-}
-/**
- * This returns the set we will actually use when sending
- * a face. This is used because the image files may be sparse.
- * This function is recursive. imageno is the face number we are
- * trying to send
- *
- * If face is not found in specified faceset, tries with 'fallback' faceset.
- *
- * \param faceset faceset to check
- * \param imageno image number
- *
- */
-static int
-get_face_fallback (int faceset, int imageno)
-{
- /* faceset 0 is supposed to have every image, so just return. Doing
- * so also prevents infinite loops in the case if it not having
- * the face, but in that case, we are likely to crash when we try
- * to access the data, but that is probably preferable to an infinite
- * loop.
- */
- if (faceset == 0)
- return 0;
+ ns->ixface.insert (pos, ix);
- if (!facesets[faceset].prefix)
- {
- LOG (llevError, "get_face_fallback called with unused set (%d)?\n", faceset);
- return 0; /* use default set */
- }
- if (facesets[faceset].faces[imageno].data)
- return faceset;
- return get_face_fallback (facesets[faceset].fallback, imageno);
+#if 0
+ for (auto (i, ns->ixface.begin ()); i != ns->ixface.end (); ++i)
+ fprintf (stderr, "<%d,%d> ", i->pri, i->ofs);
+ fprintf (stderr, "\n");
+#endif
}
/**
- * Checks fallback are correctly defined.
- * This is a simple recursive function that makes sure the fallbacks
- * are all proper (eg, the fall back to defined sets, and also
- * eventually fall back to 0). At the top level, togo is set to MAX_FACE_SETS,
- * if togo gets to zero, it means we have a loop.
- * This is only run when we first load the facesets.
+ * Sends a face to a client if they are in pixmap mode
+ * nothing gets sent in bitmap mode.
+ * 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.
*/
-static void
-check_faceset_fallback (int faceset, int togo)
+void
+client::send_face (faceidx facenum, int pri)
{
- int fallback = facesets[faceset].fallback;
-
- /* proper case - falls back to base set */
- if (fallback == 0)
+ // never send face 0. ever. it does not exist.
+ if (!facenum)
return;
- if (!facesets[fallback].prefix)
- {
- LOG (llevError, "Face set %d falls to non set faceset %d\n", faceset, fallback);
- abort ();
- }
- togo--;
- if (togo == 0)
+ faceinfo *f = face_info (facenum);
+
+ if (!f)
{
- LOG (llevError, "Infinite loop found in facesets. aborting.\n");
- abort ();
+ LOG (llevError | logBacktrace, "client::send_face (%d) out of bounds??\n", facenum);
+ return;
}
- check_faceset_fallback (fallback, togo);
-}
-#define MAX_IMAGE_SIZE 10000
+ // refuse to send non-image faces
+ if (!fx_want [f->type])
+ return;
-/**
- * Loads all the image types into memory.
- *
- * This way, we can easily send them to the client. We should really do something
- * better than abort on any errors - on the other hand, these are all fatal
- * to the server (can't work around them), but the abort just seems a bit
- * messy (exit would probably be better.)
- *
- * Couple of notes: We assume that the faces are in a continous block.
- * This works fine for now, but this could perhaps change in the future
- *
- * Function largely rewritten May 2000 to be more general purpose.
- * The server itself does not care what the image data is - to the server,
- * it is just data it needs to allocate. As such, the code is written
- * to do such.
- */
+ if (faces_sent [facenum])
+ return;
-void
-read_client_images (void)
-{
- char filename[400];
- char buf[HUGE_BUF];
- char *cp, *cps[7];
- FILE *infile;
- int num, len, compressed, fileno, i, badline;
-
- memset (facesets, 0, sizeof (facesets));
- sprintf (filename, "%s/image_info", settings.datadir);
- if ((infile = open_and_uncompress (filename, 0, &compressed)) == NULL)
- {
- LOG (llevError, "Unable to open %s\n", filename);
- abort ();
- }
- while (fgets (buf, HUGE_BUF - 1, infile) != NULL)
- {
- badline = 0;
+ faces_sent[facenum] = true;
- if (buf[0] == '#')
- continue;
- if (!(cps[0] = strtok (buf, ":")))
- badline = 1;
- for (i = 1; i < 7; i++)
- {
- if (!(cps[i] = strtok (NULL, ":")))
- badline = 1;
- }
- if (badline)
- {
- LOG (llevError, "Bad line in image_info file, ignoring line:\n %s", buf);
- }
- else
- {
- len = atoi (cps[0]);
- if (len >= MAX_FACE_SETS)
- {
- LOG (llevError, "To high a setnum in image_info file: %d > %d\n", len, MAX_FACE_SETS);
- abort ();
- }
- facesets[len].prefix = strdup_local (cps[1]);
- facesets[len].fullname = strdup_local (cps[2]);
- facesets[len].fallback = atoi (cps[3]);
- facesets[len].size = strdup_local (cps[4]);
- facesets[len].extension = strdup_local (cps[5]);
- facesets[len].comment = strdup_local (cps[6]);
- }
- }
- close_and_delete (infile, compressed);
- for (i = 0; i < MAX_FACE_SETS; i++)
- {
- if (facesets[i].prefix)
- check_faceset_fallback (i, MAX_FACE_SETS);
- }
- /* Loaded the faceset information - now need to load up the
- * actual faces.
- */
+ fxface.push_back (facenum);
+}
- for (fileno = 0; fileno < MAX_FACE_SETS; fileno++)
+void client::flush_fx ()
+{
+ while (!fxface.empty ())
{
- /* if prefix is not set, this is not used */
- if (!facesets[fileno].prefix)
- continue;
- facesets[fileno].faces = (FaceInfo *) calloc (nrofpixmaps, sizeof (FaceInfo));
+ packet fx ("fx");
+ packet sx ("sx");
+ int type = 0;
- sprintf (filename, "%s/crossfire.%d", settings.datadir, fileno);
- LOG (llevDebug, "Loading image file %s\n", filename);
-
- if ((infile = open_and_uncompress (filename, 0, &compressed)) == NULL)
- {
- LOG (llevError, "Unable to open %s\n", filename);
- abort ();
- }
- while (fgets (buf, HUGE_BUF - 1, infile) != NULL)
+ do
{
- if (strncmp (buf, "IMAGE ", 6) != 0)
- {
- LOG (llevError, "read_client_images:Bad image line - not IMAGE, instead\n%s", buf);
- abort ();
- }
- num = atoi (buf + 6);
- if (num < 0 || num >= nrofpixmaps)
- {
- LOG (llevError, "read_client_images: Image num %d not in 0..%d\n%s", num, nrofpixmaps, buf);
- abort ();
- }
- /* Skip accross the number data */
- for (cp = buf + 6; *cp != ' '; cp++);
- len = atoi (cp);
- if (len == 0 || len > MAX_IMAGE_SIZE)
- {
- LOG (llevError, "read_client_images: length not valid: %d > %d \n%s", len, MAX_IMAGE_SIZE, buf);
- abort ();
- }
- /* We don't actualy care about the name if the image that
- * is embedded in the image file, so just ignore it.
- */
- facesets[fileno].faces[num].datalen = len;
- facesets[fileno].faces[num].data = (uint8 *) malloc (len);
- if ((i = fread (facesets[fileno].faces[num].data, len, 1, infile)) != 1)
- {
- LOG (llevError, "read_client_images: Did not read desired amount of data, wanted %d, got %d\n%s", len, i, buf);
- abort ();
- }
- facesets[fileno].faces[num].checksum = 0;
- for (i = 0; i < len; i++)
+ faceidx facenum = fxface.back (); fxface.pop_back ();
+
+ if (const faceinfo *f = face_info (facenum))
{
- ROTATE_RIGHT (facesets[fileno].faces[num].checksum);
- facesets[fileno].faces[num].checksum += facesets[fileno].faces[num].data[i];
- facesets[fileno].faces[num].checksum &= 0xffffffff;
+ if (f->type != type)
+ {
+ type = f->type;
+
+ fx << ber32 (0)
+ << uint8 (1) << uint8 (type);
+ }
+
+ const facedata *d = f->data (faceset);
+
+ fx << ber32 (facenum)
+ << data8 (d->chksum, CHKSUM_SIZE);
+
+ if (smoothing)
+ {
+ faceinfo *f = face_info (facenum);
+
+ if (f->smooth)
+ {
+ send_face (f->smooth, -110);
+
+ sx << ber32 (facenum)
+ << ber32 (f->smooth)
+ << ber32 (f->smoothlevel);
+ }
+ }
}
}
- close_and_delete (infile, compressed);
- } /* For fileno < MAX_FACE_SETS */
-}
+ while (!fxface.empty ()
+ && fx.room () > ber32::size + CHKSUM_SIZE + 1 + 3 /* type switch */
+ && sx.room () > ber32::size * 3);
-/**
- * Client tells us what type of faces it wants. Also sets
- * the caching attribute.
- *
- */
-
-void
-SetFaceMode (char *buf, int len, NewSocket * ns)
-{
- char tmp[256];
-
- int mask = (atoi (buf) & CF_FACE_CACHE), mode = (atoi (buf) & ~CF_FACE_CACHE);
-
- if (mode == CF_FACE_NONE)
- {
- ns->facecache = 1;
- }
- else if (mode != CF_FACE_PNG)
- {
- sprintf (tmp, "drawinfo %d %s", NDI_RED, "Warning - send unsupported face mode. Will use Png");
- Write_String_To_Socket (ns, tmp, strlen (tmp));
-#ifdef ESRV_DEBUG
- LOG (llevDebug, "SetFaceMode: Invalid mode from client: %d\n", mode);
-#endif
- }
- if (mask)
- {
- ns->facecache = 1;
+ send_packet (fx);
+ if (sx.length () > 3) send_packet (sx);
}
}
-/**
- * Client has requested pixmap that it somehow missed getting.
- * This will be called often if the client is
- * caching images.
- */
-
+// 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
-SendFaceCmd (char *buff, int len, NewSocket * ns)
+client::send_faces (object *ob)
{
- long tmpnum = atoi (buff);
- short facenum = tmpnum & 0xffff;
+ send_face (ob->face, 10);
+
+ if (ob->animation_id)
+ {
+ animation &anim = animations [ob->animation_id];
- if (facenum != 0)
- esrv_send_face (ns, facenum, 1);
+ for (int i = 0; i < anim.num_animations; i++)
+ send_face (anim.faces [i], -10);
+ }
}
/**
- * Sends a face to a client if they are in pixmap mode
- * nothing gets sent in bitmap mode.
- * 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.
+ * 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
-esrv_send_face (NewSocket * ns, short face_num, int nocache)
+client::send_animation (short anim_num)
{
- SockList sl;
- char fallback;
-
- if (face_num <= 0 || face_num >= nrofpixmaps)
+ /* Do some checking on the anim_num we got. Note that the animations
+ * are added in contigous 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_face (%d) out of bounds??\n", face_num);
+ LOG (llevError, "esrv_send_anim (%d) out of bounds??\n", anim_num);
return;
}
- sl.buf = (unsigned char *) malloc (MAXSOCKBUF);
- fallback = get_face_fallback (ns->faceset, face_num);
+ packet sl ("anim");
- if (facesets[fallback].faces[face_num].data == NULL)
- {
- LOG (llevError, "esrv_send_face: faces[%d].data == NULL\n", face_num);
- return;
- }
+ sl << uint16 (anim_num)
+ << uint16 (0); /* flags - not used right now */
- if (ns->facecache && !nocache)
- {
- if (ns->image2)
- strcpy ((char *) sl.buf, "face2 ");
- else if (ns->sc_version >= 1026)
- strcpy ((char *) sl.buf, "face1 ");
- else
- strcpy ((char *) sl.buf, "face ");
-
- sl.len = strlen ((const char *) sl.buf);
- SockList_AddShort (&sl, face_num);
- if (ns->image2)
- SockList_AddChar (&sl, fallback);
- if (ns->sc_version >= 1026)
- SockList_AddInt (&sl, facesets[fallback].faces[face_num].checksum);
- strcpy ((char *) sl.buf + sl.len, new_faces[face_num].name);
- sl.len += strlen (new_faces[face_num].name);
- Send_With_Handling (ns, &sl);
- }
- else
+ /* 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++)
{
- if (ns->image2)
- strcpy ((char *) sl.buf, "image2 ");
- else
- strcpy ((char *) sl.buf, "image ");
- sl.len = strlen ((char *) sl.buf);
- SockList_AddInt (&sl, face_num);
- if (ns->image2)
- SockList_AddChar (&sl, fallback);
- SockList_AddInt (&sl, facesets[fallback].faces[face_num].datalen);
- memcpy (sl.buf + sl.len, facesets[fallback].faces[face_num].data, facesets[fallback].faces[face_num].datalen);
- sl.len += facesets[fallback].faces[face_num].datalen;
- Send_With_Handling (ns, &sl);
+ send_face (animations[anim_num].faces[i], -20);
+ sl << uint16 (animations[anim_num].faces[i]); /* flags - not used right now */
}
- ns->faces_sent[face_num] |= NS_FACESENT_FACE;
- free (sl.buf);
+
+ send_packet (sl);
+
+ anims_sent[anim_num] = 1;
}
/**
@@ -433,28 +236,17 @@
* and the image_info file information. See the doc/Developers/protocol
* if you want further detail.
*/
-
void
-send_image_info (NewSocket * ns, char *params)
+send_image_info (client *ns, char *params)
{
- SockList sl;
- int i;
+ packet sl;
- sl.buf = (unsigned char *) malloc (MAXSOCKBUF);
+ //TODO: second parameter is a checksum, but it makes no sense in this current framework
+ sl.printf ("replyinfo image_info\n%d\n%u\n", MAX_FACES, 0);
- sprintf ((char *) sl.buf, "replyinfo image_info\n%d\n%d\n", nrofpixmaps - 1, bmaps_checksum);
- for (i = 0; i < MAX_FACE_SETS; i++)
- {
- if (facesets[i].prefix)
- {
- sprintf ((char *) sl.buf + strlen ((const char *) sl.buf), "%d:%s:%s:%d:%s:%s:%s",
- i, facesets[i].prefix, facesets[i].fullname, facesets[i].fallback,
- facesets[i].size, facesets[i].extension, facesets[i].comment);
- }
- }
- sl.len = strlen ((const char *) sl.buf);
- Send_With_Handling (ns, &sl);
- free (sl.buf);
+ sl << "0:base:standard:0:32x32:none:The old 32x32 faceset.\n";
+
+ ns->send_packet (sl);
}
/**
@@ -467,15 +259,12 @@
* - name
*/
void
-send_image_sums (NewSocket * ns, char *params)
+send_image_sums (client *ns, char *params)
{
int start, stop;
- short i;
- char qq;
- char *cp, buf[MAX_BUF];
- SockList sl;
+ char *cp;
- sl.buf = (unsigned char *) malloc (MAXSOCKBUF);
+ packet sl;
start = atoi (params);
for (cp = params; *cp != '\0'; cp++)
@@ -483,41 +272,49 @@
break;
stop = atoi (cp);
- if (stop < start || *cp == '\0' || (stop - start) > 1000 || stop >= nrofpixmaps)
+ if (stop < start || *cp == '\0' || (stop - start) > 1000 || stop >= MAX_FACES)
{
- sprintf (buf, "replyinfo image_sums %d %d", start, stop);
- cs_write_string (ns, buf, strlen (buf));
+ sl.printf ("replyinfo image_sums %d %d", start, stop);
+ ns->send_packet (sl);
+ sl.reset ();
return;
}
- sprintf ((char *) sl.buf, "replyinfo image_sums %d %d ", start, stop);
- sl.len = strlen ((const char *) sl.buf);
+ sl.printf ("replyinfo image_sums %d %d ", start, stop);
- for (i = start; i <= stop; i++)
- {
- SockList_AddShort (&sl, i);
- ns->faces_sent[i] |= NS_FACESENT_FACE;
+ for (int i = start; i <= stop && i < faces.size (); i++)
+ if (const faceinfo *f = face_info (i))
+ if (ns->fx_want [f->type])
+ {
+ ns->faces_sent[i] = true;
+
+ const facedata *d = f->data (ns->faceset);
+
+ if (sl.room () < 2 + 4 + 1 + d->data.size () + 1)
+ break;
+
+ sl << uint16 (i)
+ << uint32 (0) // checksum
+ << uint8 (ns->faceset);
+
+ for (int i = 0; i < CHKSUM_SIZE; ++i)
+ sl.printf ("%02x", d->chksum [i]);
+
+ sl << uint8 (0);
+ }
- qq = get_face_fallback (ns->faceset, i);
- SockList_AddInt (&sl, facesets[qq].faces[i].checksum);
- SockList_AddChar (&sl, qq);
-
- qq = strlen (new_faces[i].name);
- SockList_AddChar (&sl, (char) (qq + 1));
- strcpy ((char *) sl.buf + sl.len, new_faces[i].name);
- sl.len += qq;
- SockList_AddChar (&sl, 0);
- }
/* It would make more sense to catch this pre-emptively in the code above.
* however, if this really happens, we probably just want to cut down the
* size to less than 1000, since that is what we claim the protocol would
* support.
*/
- if (sl.len >= MAXSOCKBUF)
+ //TODO: taken care of above, should simply abort or make sure the above code is correct
+ if (sl.length () > MAXSOCKBUF)
{
- LOG (llevError, "send_image_send: buffer overrun, %d > %d\n", sl.len, MAXSOCKBUF);
+ LOG (llevError, "send_image_send: buffer overrun, %d > %d\n", sl.length (), MAXSOCKBUF);
abort ();
}
- Send_With_Handling (ns, &sl);
- free (sl.buf);
+
+ ns->send_packet (sl);
}
+