/* Copyright 2022-2026 Kurt Nienhaus
 *
 * This file is part of VgaGames.
 * VgaGames 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.
 * VgaGames 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 VgaGames.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include "misc.h"
#ifdef SML3_HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif

void init_misc(void);
void dest_misc(void);

static long misc_time_delta(long *);
static int misc_wait_time(int);
static int misc_angle_from_xy(int, int, int *);
static int misc_xy_from_angle(int, int *, int *);
static int misc_sin(int);
static int misc_cos(int);
static VG_BOOL misc_pause(void);
static int misc_moving_one_step(const struct VG_RectCent *, int, int, struct VG_RectCent **);
static int misc_moving_one_step_to_dst(const struct VG_RectCent *, const struct VG_Point *, int, struct VG_RectCent **);
static struct VG_Position * misc_rect2position(struct VG_Position *, const struct VG_Rect *);
static VG_BOOL misc_move_and_check_collisions(void *, unsigned int, struct VG_RectCent *, unsigned int, const struct VG_RectCent *, int);
static int misc_line_positions(const struct VG_Rect *, const struct VG_Rect *, int, struct VG_Rect **);
static int misc_points_on_line(const struct VG_Point *, const struct VG_Point *, int, struct VG_Point **);
static VG_BOOL misc_fadeout(int);
static int misc_colorbrightness(int, int);
static const char * misc_version(int *, int *);

static int get_atan(int);
static void set_rectc(struct VG_RectCent *, int, int);


/* arcus tangens max-values for 0 to 89 degrees multiplied with TAB_ATAN_MUL */
#define TAB_ATAN_ANZ 90
#define TAB_ATAN_MUL 10000
static int tab_atan_max[TAB_ATAN_ANZ] = {
  87, 262, 437, 612, 787, 963, 1139, 1317, 1495, 1673, 1853, 2035, 2217, 2401, 2586,
  2773, 2962, 3153, 3346, 3541, 3739, 3939, 4142, 4348, 4557, 4770, 4986, 5206, 5430, 5658,
  5890, 6128, 6371, 6619, 6873, 7133, 7400, 7673, 7954, 8243, 8541, 8847, 9163, 9490, 9827,
  10176, 10538, 10913, 11303, 11708, 12131, 12572, 13032, 13514, 14019, 14550, 15108, 15697, 16319, 16977,
  17675, 18418, 19210, 20057, 20965, 21943, 22998, 24142, 25386, 26746, 28239, 29887, 31716, 33759, 36059,
  38667, 41653, 45107, 49152, 53955, 59758, 66912, 75958, 87769, 103854, 127062, 163499, 229038, 381885, 1145887
};

/* sinus values for 0 to 90 degrees multiplied with TAB_SIN_MUL */
#define TAB_SIN_ANZ 91
#define TAB_SIN_MUL 10000
static int tab_sin[TAB_SIN_ANZ] = {
  0, 175, 349, 523, 698, 872, 1045, 1219, 1392, 1564, 1736, 1908, 2079, 2250, 2419,
  2588, 2756, 2924, 3090, 3256, 3420, 3584, 3746, 3907, 4067, 4226, 4384, 4540, 4695, 4848,
  5000, 5150, 5299, 5446, 5592, 5736, 5878, 6018, 6157, 6293, 6428, 6561, 6691, 6820, 6947,
  7071, 7193, 7314, 7431, 7547, 7660, 7771, 7880, 7986, 8090, 8192, 8290, 8387, 8480, 8572,
  8660, 8746, 8829, 8910, 8988, 9063, 9135, 9205, 9272, 9336, 9397, 9455, 9511, 9563, 9613,
  9659, 9703, 9744, 9781, 9816, 9848, 9877, 9903, 9925, 9945, 9962, 9976, 9986, 9994, 9998,
  10000
};


/* set functions */
void
init_misc(void)
{
  vg4data.lists.misc.s = SML3_calloc(1, sizeof(*vg4data.lists.misc.s));
  vg4data.lists.misc.s->hset_gc = vg4_hash_create_nolist();
  vg4->misc->time_delta = misc_time_delta;
  vg4->misc->wait_time = misc_wait_time;
  vg4->misc->angle_from_xy = misc_angle_from_xy;
  vg4->misc->xy_from_angle = misc_xy_from_angle;
  vg4->misc->sin = misc_sin;
  vg4->misc->cos = misc_cos;
  vg4->misc->pause = misc_pause;
  vg4->misc->moving_one_step = misc_moving_one_step;
  vg4->misc->moving_one_step_to_dst = misc_moving_one_step_to_dst;
  vg4->misc->rect2position = misc_rect2position;
  vg4->misc->move_and_check_collisions = misc_move_and_check_collisions;
  vg4->misc->line_positions = misc_line_positions;
  vg4->misc->points_on_line = misc_points_on_line;
  vg4->misc->fadeout = misc_fadeout;
  vg4->misc->colorbrightness = misc_colorbrightness;
  vg4->misc->version = misc_version;
  init_misc_str();
  init_misc_bgposition();
  init_misc_saveload();
  init_misc_base64();
  init_misc_utf8();
} /* Ende init_misc */

void
dest_misc(void)
{
  vg4->hash->destroy(vg4data.lists.misc.s->hset_gc);
  free(vg4data.lists.misc.s);
  vg4data.lists.misc.s = NULL;
} /* Ende dest_misc */


/* return atan() value (angle 0 to 90) */
static int
get_atan(int iwert)
{
  int ipos, links, rechts, idir;

  ipos = links = 0; rechts = TAB_ATAN_ANZ - 1;

  while (links <= rechts) {  /* binary search */
    ipos = (links + rechts) / 2;
    if (tab_atan_max[ipos] < iwert) {
      idir = -1;
    } else if (tab_atan_max[ipos] > iwert) {
      idir = 1;
    } else {
      break;
    }
    if (idir > 0) { rechts = ipos - 1; } else { links = ipos + 1; }
  }

  if (rechts < links) {  /* no exact hit */
    if (rechts < 0) {
      ipos = 0;
    } else if (links >= TAB_ATAN_ANZ) {
      ipos = TAB_ATAN_ANZ;
    } else {
      if (tab_atan_max[ipos] < iwert) { ipos++; }
    }
  }

  return ipos;
} /* Ende get_atan */


/* set rectc according to xpos and ypos */
static void
set_rectc(struct VG_RectCent *rectc, int xpos, int ypos)
{
  if (rectc == NULL) { return; }

  rectc->rect.x = xpos / 100;
  rectc->centx = xpos % 100;
  if (rectc->centx > 49) {
    rectc->rect.x++;
    rectc->centx -= 100;
  } else if (rectc->centx < -50) {
    rectc->rect.x--;
    rectc->centx += 100;
  }

  rectc->rect.y = ypos / 100;
  rectc->centy = ypos % 100;
  if (rectc->centy > 49) {
    rectc->rect.y++;
    rectc->centy -= 100;
  } else if (rectc->centy < -50) {
    rectc->rect.y--;
    rectc->centy += 100;
  }
} /* Ende set_rectc */


/* misc_time_delta:
 * return the time-difference in milliseconds since last call
 * @param tlast   address to last time of call, will be updated
 * @return        time-difference in milliseconds
 */
static long
misc_time_delta(long *tlast)
{
  SML3_int64 ztlast, *ztptr;
  long tdiff;

  ztptr = &ztlast;
  if (tlast != NULL) { *ztptr = *tlast; } else { ztptr = NULL; }

  tdiff = (long)SML3_zeitpunkt_diff(ztptr);
  if (tlast != NULL) { *tlast = *ztptr; }

  return tdiff;
} /* Ende misc_time_delta */


/* misc_wait_time:
 * once called a game-loop:
 *   sleep the rest of a defined time so that every game-loop consumes the same time.
 * @param m_sec  time in milliseconds a game-loop should consume
 * @return  actual slept time
 */
static int
misc_wait_time(int m_sec)
{
  static struct timeval tv;
  static int msec_last = 0;
  int msec_nunc, msec_diff, msec_wait;

  m_sec -= SML3_TIME_OF_SELECT;
  if (m_sec < 1) { return 0; }

  gettimeofday(&tv, NULL);

  msec_nunc = (tv.tv_sec % 60) * 1000 + tv.tv_usec / 1000;
  if (msec_nunc < msec_last) { msec_nunc += 60000; }
  msec_diff = msec_nunc - msec_last;

  msec_wait = 0;
  if (msec_diff < m_sec) {
    tv.tv_usec = (m_sec - msec_diff) % 1000 * 1000;
    tv.tv_sec = (m_sec - msec_diff) / 1000;
    msec_wait = tv.tv_sec * 1000 + tv.tv_usec / 1000 + SML3_TIME_OF_SELECT;
    select(0, NULL, NULL, NULL, &tv);
    gettimeofday(&tv, NULL);
  }

  msec_last = (tv.tv_sec % 60) * 1000 + tv.tv_usec / 1000;

  return msec_wait;
} /* Ende misc_wait_time */


/* misc_angle_from_xy:
 * return angle according to given x-/y-direction
 * @param xdelta        x-direction in 1/100 pixels
 * @param ydelta        y-direction in 1/100 pixels
 * @param pixelpercent  for returning percent of pixels on which xdelta/ydelta is based, may be NULL
 * @return  angle in degrees from 0 to 359, or -1 if xdelta and ydelta are both 0
 */
static int
misc_angle_from_xy(int xdelta, int ydelta, int *pixelpercent)
{
  int iwert, angle;

  if (pixelpercent != NULL) { *pixelpercent = 0; }

  if (xdelta > 0) {
    if (ydelta < 0) {  /* 0 - 90 */
      iwert = xdelta * TAB_ATAN_MUL / -ydelta;
      angle = get_atan(iwert);
    } else if (ydelta > 0) {  /* 90 - 180 */
      iwert = xdelta * TAB_ATAN_MUL / ydelta;
      angle = get_atan(iwert);
      angle = 90 + 90 - angle;
    } else {  /* 90 */
      angle = 90;
    }
  } else if (xdelta < 0) {
    if (ydelta > 0) {  /* 180 - 270 */
      iwert = -xdelta * TAB_ATAN_MUL / ydelta;
      angle = get_atan(iwert);
      angle += 180;
    } else if (ydelta < 0) {  /* 270 - 360 */
      iwert = -xdelta * TAB_ATAN_MUL / -ydelta;
      angle = get_atan(iwert);
      angle = 270 + 90 - angle;
    } else {  /* 270 */
      angle = 270;
    }
  } else {
    if (ydelta > 0) {
      angle = 180;
    } else if (ydelta < 0) {
      angle = 0;
    } else {
      return -1;
    }
  }
  angle %= 360;

  if (pixelpercent != NULL) {
    if ((xdelta >= 0 ? xdelta : -xdelta) > (ydelta >= 0 ? ydelta : -ydelta)) {
      vg4->misc->xy_from_angle(angle, &iwert, NULL);
      if (iwert != 0) { *pixelpercent = xdelta * 100 / iwert; }
    } else {
      vg4->misc->xy_from_angle(angle, NULL, &iwert);
      if (iwert != 0) { *pixelpercent = ydelta * 100 / iwert; }
    }
  }

  return angle;
} /* Ende misc_angle_from_xy */


/* misc_xy_from_angle:
 * return x-/y-direction (based on 1 pixel) of a given angle
 * @param angle   angle in degrees
 * @param xdelta  for returning x-direction in 1/100 pixels
 * @param ydelta  for returning y-direction in 1/100 pixels
 * @return  angle (eventually corrected, from 0 to 359)
 */
static int
misc_xy_from_angle(int angle, int *xdelta, int *ydelta)
{
  const int mulpa = 100;
  int rplus, idiv;

  if (TAB_SIN_MUL > mulpa) {
    idiv = TAB_SIN_MUL / mulpa;
    rplus = idiv / 2;
  } else {
    idiv = 1;
    rplus = 0;
  }

  angle %= 360; angle += 360; angle %= 360;

  if (angle <= 90) {
    if (xdelta != NULL) { *xdelta = (tab_sin[angle] + rplus) / idiv; }
    if (ydelta != NULL) { *ydelta = (tab_sin[90 - angle] + rplus) / idiv; *ydelta = -(*ydelta); }
  } else if (angle <= 180) {
    if (xdelta != NULL) { *xdelta = (tab_sin[180 - angle] + rplus) / idiv; }
    if (ydelta != NULL) { *ydelta = (tab_sin[angle - 90] + rplus) / idiv; }
  } else if (angle <= 270) {
    if (xdelta != NULL) { *xdelta = (tab_sin[angle - 180] + rplus) / idiv; *xdelta = -(*xdelta); }
    if (ydelta != NULL) { *ydelta = (tab_sin[270 - angle] + rplus) / idiv; }
  } else {
    if (xdelta != NULL) { *xdelta = (tab_sin[360 - angle] + rplus) / idiv; *xdelta = -(*xdelta); }
    if (ydelta != NULL) { *ydelta = (tab_sin[angle - 270] + rplus) / idiv; *ydelta = -(*ydelta); }
  }

  return angle;
} /* Ende misc_xy_from_angle */


/* misc_sin:
 * return sin()-value from angle as integer multiplied with 10000
 * @param angle  angle in degrees
 * @return  sin()-value as integer multiplied with 10000
 */
static int
misc_sin(int angle)
{
  int rval;

  angle %= 360; angle += 360; angle %= 360;

  if (angle <= 90) {
    rval = tab_sin[angle];
  } else if (angle <= 180) {
    rval = tab_sin[180 - angle];
  } else if (angle <= 270) {
    rval = -tab_sin[angle - 180];
  } else {
    rval = -tab_sin[360 - angle];
  }

  return rval;
} /* Ende misc_sin */


/* misc_cos:
 * return cos()-value from angle as integer multiplied with 10000
 * @param angle  angle in degrees
 * @return  cos()-value as integer multiplied with 10000
 */
static int
misc_cos(int angle)
{
  int rval;

  angle %= 360; angle += 360; angle %= 360;

  if (angle <= 90) {
    rval = tab_sin[90 - angle];
  } else if (angle <= 180) {
    rval = -tab_sin[angle - 90];
  } else if (angle <= 270) {
    rval = -tab_sin[270 - angle];
  } else {
    rval = tab_sin[angle - 270];
  }

  return rval;
} /* Ende misc_cos */


/* misc_pause:
 * pause game (locally)
 * @return  VG_TRUE = OK or VG_FALSE = exit-request
 */
static VG_BOOL
misc_pause(void)
{
  int keyref_cont[3], cont_ilauf;
  VG_BOOL retw;
  struct VG_Image *wclone, *imgp_zzz, *imgp_cont;
  struct VG_ImagecopyAttr iattr, iattr_zzz;
  struct VG_Sprite *sprt_zzz;
  VG_BOOL au_susp;
  struct VG_ImagecopyAttrPixel wattr_pixel;
  struct VG_Position cont_posi;

  VG_IMAGECOPY_ATTR_DEFAULT(&iattr);
  iattr.pixel.brightness = 30;

  keyref_cont[0] = vg4->input->key_insert("CONTINUE", VG_FALSE, VG_FALSE);
  vg4->input->key_setkbd(keyref_cont[0], VG_INPUT_KBDCODE_SPACE);
  keyref_cont[1] = vg4->input->key_insert("CONTINUE", VG_FALSE, VG_FALSE);
  vg4->input->key_setkbd(keyref_cont[1], VG_INPUT_KBDCODE_RETURN);
  keyref_cont[2] = vg4->input->key_insert("CONTINUE", VG_FALSE, VG_FALSE);
  vg4->input->key_setkbd(keyref_cont[2], VG_INPUT_KBDCODE_ESCAPE);

  wattr_pixel = vg4->window->getattr();
  vg4->window->setattr(NULL);

  wclone = vg4_window_clone_nolist(NULL, NULL);
  au_susp = vg4->audio->suspend(VG_TRUE);

  { const char *prefix = vg4_get_prefix(NULL);
    char loadfile[VGI_PATHSIZE];
    int wsize;
    vg4->window->getparameters(&wsize, NULL);
    if (wsize == VG_WINDOW_SIZE_LOW) {
      snprintf(loadfile, sizeof(loadfile), "%s/share/vgagames4/images/zzz-low.sprite", prefix);
    } else {
      snprintf(loadfile, sizeof(loadfile), "%s/share/vgagames4/images/zzz-high.sprite", prefix);
    }
    sprt_zzz = vg4_sprite_load_nolist(loadfile);
  }
  imgp_cont = vg4_intern_ok_cancel_img(&cont_posi, 1);
  cont_ilauf = 0;

  retw = VG_TRUE;
  for (;;) {
    if (!vg4->input->update(VG_FALSE)) { retw = VG_FALSE; break; }

    if (vg4->input->key_newpressed(keyref_cont[0])) { break; }  /* continue */
    if (vg4->input->key_newpressed(keyref_cont[1])) { break; }  /* continue */
    if (vg4->input->key_newpressed(keyref_cont[2])) { break; }  /* continue */

    vg4->window->clear();
    vg4->window->copy(wclone, NULL, &iattr);

    if (vg4->sprite->next(sprt_zzz, &imgp_zzz, &iattr_zzz) && imgp_zzz != NULL) {
      vg4->window->copy(imgp_zzz, NULL, &iattr_zzz);
    }

    cont_ilauf++;
    if (cont_ilauf >= 2000 / VGI_WAIT_TIME + 1) {
      cont_ilauf = 0;
    } else if (cont_ilauf >= 1000 / VGI_WAIT_TIME + 1) {
      vg4->window->copy(imgp_cont, &cont_posi, NULL);
    }

    vg4->window->flush();
    SML3_sleep_msek(VGI_WAIT_TIME);
  }

  vg4->window->clear();
  vg4->window->copy(wclone, NULL, NULL);
  vg4->window->flush();

  vg4->image->destroy(wclone);
  vg4->sprite->destroy(sprt_zzz);
  vg4->image->destroy(imgp_cont);
  vg4->input->key_remove(keyref_cont[0]);
  vg4->input->key_remove(keyref_cont[1]);
  vg4->input->key_remove(keyref_cont[2]);
  vg4->audio->suspend(au_susp);
  vg4->window->setattr(&wattr_pixel);

  return retw;
} /* Ende misc_pause */


/* misc_moving_one_step:
 * return positions when moving in x-/y-direction
 * @param rectc_start  starting position
 * @param angle        moving-angle in degrees
 * @param factor       factor to multiply moving in 1/10 pixel
 * @param rectc_pos    for returning allocated array with moving positions, (must be freed with free())
 * @return  number of elements in rectc_pos
 *
 * Examples:
 *  - angle = 60, factor = 55
 *    moving 0.87 pixels in x-direction and -0.5 pixels in y-direction multiplied with 5.5
 *    => moves 5 pixels in x-direction and 3 pixels in y-direction
 *       and returns an array of 6 positions:
 *        - start-of-x + 1 (0.87 * 1),   start-of-y - 0 (-0.5 * 1)
 *        - start-of-x + 2 (0.87 * 2),   start-of-y - 1 (-0.5 * 2)
 *        - start-of-x + 3 (0.87 * 3),   start-of-y - 1 (-0.5 * 3)
 *        - start-of-x + 3 (0.87 * 4),   start-of-y - 2 (-0.5 * 4)
 *        - start-of-x + 4 (0.87 * 5),   start-of-y - 2 (-0.5 * 5)
 *        - start-of-x + 5 (0.87 * 5.5), start-of-y - 3 (-0.5 * 5.5)
 *  - angle = 150, factor = 50
 *    moving 0.5 pixels in x-direction and 0.87 pixels in y-direction multiplied with 5.0
 *    => moves 3 pixels in x-direction and 4 pixels in y-direction
 *       and returns an array of 4 positions:
 *        - start-of-x + 1 (0.5 * 1), start-of-y + 1 (0.87 * 1)
 *        - start-of-x + 1 (0.5 * 2), start-of-y + 2 (0.87 * 2)
 *        - NOT RETURNING: start-of-x + 2 (0.5 * 3), start-of-y + 3 (0.87 * 3)
 *        - start-of-x + 2 (0.5 * 4), start-of-y + 3 (0.87 * 4)
 *        - start-of-x + 3 (0.5 * 5), start-of-y + 4 (0.87 * 5)
 *    As position 3 and position 4 are equal, only the newest position (4) will be included
 */
static int
misc_moving_one_step(const struct VG_RectCent *rectc_start, int angle, int factor, struct VG_RectCent **rectc_pos)
{
  struct VG_RectCent rcstart, *rcp, rcb;
  int fvorz, xdelta, ydelta, posanz, xpos, ypos;

  if (rectc_pos != NULL) { *rectc_pos = NULL; }
  if (rectc_start == NULL || rectc_pos == NULL) { return 0; }
  if (factor == 0) { return 0; }

  if (factor > 0) { fvorz = 1; } else { fvorz = -1; factor = -factor; }

  vg4->misc->xy_from_angle(angle, &xdelta, &ydelta);

  /* transfer start-position to rcstart and clean it */
  rcstart = *rectc_start;
  xpos = rcstart.rect.x * 100 + rcstart.centx;
  ypos = rcstart.rect.y * 100 + rcstart.centy;
  set_rectc(&rcstart, xpos, ypos);

  posanz = 0;

  while (factor > 0) {
    if (posanz > 0) { rcp = &(*rectc_pos)[posanz - 1]; } else { rcp = &rcstart; }
    rcb = *rcp;

    xpos = rcp->rect.x * 100 + rcp->centx;
    ypos = rcp->rect.y * 100 + rcp->centy;
    factor -= 10;
    if (factor >= 0) {
      xpos += (xdelta * fvorz);
      ypos += (ydelta * fvorz);
    } else {
      xpos += (xdelta * (factor + 10) / 10 * fvorz);
      ypos += (ydelta * (factor + 10) / 10 * fvorz);
    }
    set_rectc(&rcb, xpos, ypos);

    if (rcb.rect.x == rcp->rect.x && rcb.rect.y == rcp->rect.y) {
      /* same position as before */
      *rcp = rcb;
    } else {
      /* new position */
      if (posanz++ == 0) {
        *rectc_pos = SML3_malloc(sizeof(**rectc_pos));
      } else {
        *rectc_pos = SML3_realloc(*rectc_pos, posanz * sizeof(**rectc_pos));
      }
      (*rectc_pos)[posanz - 1] = rcb;
    }
  }

  if (posanz == 0) {
    /* same position as start-position but with other values of centx and centy */
    posanz = 1;
    *rectc_pos = SML3_malloc(sizeof(**rectc_pos));
    (*rectc_pos)[posanz - 1] = rcstart;
  }

  return posanz;
} /* Ende misc_moving_one_step */


/* misc_moving_one_step_to_dst:
 * return positions when moving to a destination point
 * @param rectc_start  starting position
 * @param pt_dest      destination point (centered)
 * @param factor       positive factor to multiply moving in 1/10 pixel
 * @param rectc_pos    for returning allocated array with moving positions, (must be freed with free())
 * @return  number of elements in rectc_pos, or 0 = destination reached
 */
static int
misc_moving_one_step_to_dst(const struct VG_RectCent *rectc_start, const struct VG_Point *pt_dest, int factor, struct VG_RectCent **rectc_pos)
{
  struct VG_RectCent rcstart, *rcp, rcb;
  int angle, xdelta, ydelta, posanz, xpos, ypos;
  VG_BOOL has_reached;

  if (rectc_pos != NULL) { *rectc_pos = NULL; }
  if (rectc_start == NULL || rectc_pos == NULL) { return 0; }
  if (pt_dest == NULL) { return 0; }
  if (factor <= 0) { return 0; }

  /* transfer start-position to rcstart and clean it */
  rcstart = *rectc_start;
  xpos = rcstart.rect.x * 100 + rcstart.centx;
  ypos = rcstart.rect.y * 100 + rcstart.centy;
  set_rectc(&rcstart, xpos, ypos);

  xdelta = (pt_dest->x - rcstart.rect.w / 2) * 100 - xpos;
  ydelta = (pt_dest->y - rcstart.rect.h / 2) * 100 - ypos;
  angle = vg4->misc->angle_from_xy(xdelta, ydelta, NULL);
  if (angle == -1) { return 0; }
  vg4->misc->xy_from_angle(angle, &xdelta, &ydelta);

  has_reached = VG_FALSE;
  posanz = 0;

  while (!has_reached && factor > 0) {
    if (posanz > 0) { rcp = &(*rectc_pos)[posanz - 1]; } else { rcp = &rcstart; }
    rcb = *rcp;

    xpos = rcp->rect.x * 100 + rcp->centx;
    ypos = rcp->rect.y * 100 + rcp->centy;
    factor -= 10;
    if (factor >= 0) {
      xpos += xdelta;
      ypos += ydelta;
    } else {
      xpos += (xdelta * (factor + 10) / 10);
      ypos += (ydelta * (factor + 10) / 10);
    }
    set_rectc(&rcb, xpos, ypos);

    /* destination reached (or overrun)? */
    xpos = rcb.rect.x + rcb.rect.w / 2;
    ypos = rcb.rect.y + rcb.rect.h / 2;
    if (xpos == pt_dest->x || (xdelta < 0 && xpos < pt_dest->x) || (xdelta > 0 && xpos > pt_dest->x)) {
      if (ypos == pt_dest->y || (ydelta < 0 && ypos < pt_dest->y) || (ydelta > 0 && ypos > pt_dest->y)) {
        rcb.rect.x = pt_dest->x - rcb.rect.w / 2;
        rcb.rect.y = pt_dest->y - rcb.rect.h / 2;
        rcb.centx = rcb.centy = 0;
        has_reached = VG_TRUE;
      }
    }

    if (rcb.rect.x == rcp->rect.x && rcb.rect.y == rcp->rect.y) {
      /* same position as before */
      *rcp = rcb;
    } else {
      /* new position */
      if (posanz++ == 0) {
        *rectc_pos = SML3_malloc(sizeof(**rectc_pos));
      } else {
        *rectc_pos = SML3_realloc(*rectc_pos, posanz * sizeof(**rectc_pos));
      }
      (*rectc_pos)[posanz - 1] = rcb;
    }
  }

  if (posanz == 0) {
    /* same position as start-position but with other values of centx and centy */
    posanz = 1;
    *rectc_pos = SML3_malloc(sizeof(**rectc_pos));
    (*rectc_pos)[posanz - 1] = rcstart;
  }

  return posanz;
} /* Ende misc_moving_one_step_to_dst */


/* misc_rect2position:
 * simply set a position from a rectangle for copying,
 * the position will be centered
 * @param posdst  position to be set
 * @param rect    rectangle
 * @return  posdst
 *
 * Example:
 *   struct VG_Position posdst;
 *   struct VG_Rect rect;
 *   struct VG_Image *img;
 *   rect.x = 10; rect.y = 50;
 *   img = vg4->image->load("myimg.bmp");
 *   vg4->image->getsize(img, NULL, &rect.w, &rect.h);
 *   vg4->window->copy(img, vg4->misc->rect2position(&posdst, &rect), NULL);
 */
static struct VG_Position *
misc_rect2position(struct VG_Position *posdst, const struct VG_Rect *rect)
{
  if (posdst == NULL || rect == NULL) { return NULL; }
  posdst->pos = VG_POS_CENTERED;
  posdst->x = rect->x + rect->w / 2;
  posdst->y = rect->y + rect->h / 2;

  return posdst;
} /* Ende misc_rect2position */


/* misc_move_and_check_collisions:
 * move an object-instance step by step along an array of positions
 * and check for collisions
 * @param vgame        private structure of the game, or NULL
 * @param instanceid   instance-ID
 * @param rcnt_actual  actual position of the object-instance
 *                     and returning the new position
 * @param ctag         collision-tag
 * @param rcnt_array   array of positions to move
 * @param npos         number of positions in rcnt_array
 * @return  VG_TRUE = OK, VG_FALSE = object-instance is dead
 */
static VG_BOOL
misc_move_and_check_collisions(void *vgame, unsigned int instanceid, struct VG_RectCent *rcnt_actual, unsigned int ctag, const struct VG_RectCent *rcnt_array, int npos)
{
  struct VG_Object *objp;
  struct VG_RectCent rectc;
  struct VG_Coll *collp;
  int lpos, lposx, lposy, canz, iret;

  if (rcnt_actual == NULL) { return VG_TRUE; }
  if (rcnt_array == NULL || npos <= 0) { return VG_TRUE; }

  objp = vg4->object->instance_getobj(instanceid);

  rectc = *rcnt_actual;
  iret = VG_COLL_RETURN_CONTINUE;
  lposx = lposy = -1;

  for (lpos = 0; lpos < npos; lpos++) {  /* for each step to move */
    lposx++; lposy++;
    rectc.centx = rcnt_array[lposx].centx; rectc.rect.x = rcnt_array[lposx].rect.x; rectc.rect.w = rcnt_array[lposx].rect.w;
    rectc.centy = rcnt_array[lposy].centy; rectc.rect.y = rcnt_array[lposy].rect.y; rectc.rect.h = rcnt_array[lposy].rect.h;
    iret = VG_COLL_RETURN_CONTINUE;

    if (objp != NULL) {
      canz = vg4->collision->setpos(ctag, instanceid, &rectc.rect, &collp);  /* check for collisions at new position */
      iret = vg4->object->collision_call(vgame, instanceid, collp, canz);  /* call collision-functions */
      if (collp != NULL) { free(collp); }
    }

    if (iret == VG_COLL_RETURN_STOP_X) {  /* set to previous x-position and check again */
      lposx--;
      rectc.centx = rcnt_actual->centx; rectc.rect.x = rcnt_actual->rect.x; rectc.rect.w = rcnt_actual->rect.w;
      iret = VG_COLL_RETURN_CONTINUE;
      if (objp != NULL) {
        canz = vg4->collision->setpos(ctag, instanceid, &rectc.rect, &collp);  /* check for collisions at new position */
        iret = vg4->object->collision_call(vgame, instanceid, collp, canz);  /* call collision-functions */
        if (collp != NULL) { free(collp); }
      }
      if (iret == VG_COLL_RETURN_STOP_X || iret == VG_COLL_RETURN_STOP_Y || iret == VG_COLL_RETURN_STOP) {
        iret = VG_COLL_RETURN_STOP;
        break;
      }
    } else if (iret == VG_COLL_RETURN_STOP_Y) {  /* set to previous y-position and check again */
      lposy--;
      rectc.centy = rcnt_actual->centy; rectc.rect.y = rcnt_actual->rect.y; rectc.rect.h = rcnt_actual->rect.h;
      iret = VG_COLL_RETURN_CONTINUE;
      if (objp != NULL) {
        canz = vg4->collision->setpos(ctag, instanceid, &rectc.rect, &collp);  /* check for collisions at new position */
        iret = vg4->object->collision_call(vgame, instanceid, collp, canz);  /* call collision-functions */
        if (collp != NULL) { free(collp); }
      }
      if (iret == VG_COLL_RETURN_STOP_X || iret == VG_COLL_RETURN_STOP_Y || iret == VG_COLL_RETURN_STOP) {
        iret = VG_COLL_RETURN_STOP;
        break;
      }
    }

    if (iret != VG_COLL_RETURN_STOP) { *rcnt_actual = rectc; }  /* actualize position */
    if (iret != VG_COLL_RETURN_CONTINUE) { break; }
  }

  if (iret == VG_COLL_RETURN_STOP) { vg4->collision->setpos(ctag, instanceid, &rcnt_actual->rect, NULL); }
  if (iret == VG_COLL_RETURN_DEAD) { return VG_FALSE; }
  return VG_TRUE;
} /* Ende misc_move_and_check_collisions */


/* misc_line_positions (DEPRECATED):
 * set positions when moving on a line from rect_start to rect_end,
 * (rect_start will not be included)
 * @param rect_start  starting position
 * @param rect_end    ending position
 * @param stepsize    desired distance in pixels between the positions
 * @param rectp       for returning allocated array with positions, (must be freed with free())
 * @return  number of elements in rectp
 */
static int
misc_line_positions(const struct VG_Rect *rect_start, const struct VG_Rect *rect_end, int stepsize, struct VG_Rect **rectp)
{
  struct VG_Rect rect0;
  int ranz, xdiff, ydiff, xneg, yneg, idiff, steppos;

  if (rectp != NULL) { *rectp = NULL; }
  if (rect_start == NULL || rect_end == NULL || rectp == NULL) { return 0; }
  if (stepsize < 1) { stepsize = 1; }

  xdiff = rect_end->x - rect_start->x;
  if (xdiff < 0) { xdiff = -xdiff; xneg = -1; } else { xneg = 1; }
  ydiff = rect_end->y - rect_start->y;
  if (ydiff < 0) { ydiff = -ydiff; yneg = -1; } else { yneg = 1; }

  rect0 = *rect_start;
  rect0.w = rect_end->w;
  rect0.h = rect_end->h;

  steppos = 0;
  ranz = 0;
  for (;;) {
    if (rect0.x == rect_end->x && rect0.y == rect_end->y) { break; }
    if (xdiff > ydiff) {
      rect0.x += xneg;
      idiff = (rect_end->x - rect0.x) * xneg;
      rect0.y = rect_start->y + ((xdiff - idiff) * ydiff / xdiff * yneg);
    } else {
      rect0.y += yneg;
      idiff = (rect_end->y - rect0.y) * yneg;
      rect0.x = rect_start->x + ((ydiff - idiff) * xdiff / ydiff * xneg);
    }
    if (++steppos == stepsize) {
      ranz++;
      if (ranz == 1) {
        *rectp = SML3_malloc(sizeof(**rectp));
      } else {
        *rectp = SML3_realloc(*rectp, ranz * sizeof(**rectp));
      }
      memmove(&(*rectp)[ranz - 1], &rect0, sizeof(struct VG_Rect));
      steppos = 0;
    }
  }

  if (steppos > 0) {
    ranz++;
    if (ranz == 1) {
      *rectp = SML3_malloc(sizeof(**rectp));
    } else {
      *rectp = SML3_realloc(*rectp, ranz * sizeof(**rectp));
    }
    memmove(&(*rectp)[ranz - 1], rect_end, sizeof(struct VG_Rect));
  }

  return ranz;
} /* Ende misc_line_positions */


/* misc_points_on_line:
 * set point-positions when moving on a line from point_start to point_end,
 * (point_start will not be included)
 * @param point_start  starting point-position
 * @param point_end    ending point-position
 * @param stepsize     desired distance in pixels between the point-positions
 * @param pointp       for returning allocated array with point-positions, (must be freed with free())
 * @return  number of elements in pointp
 */
static int
misc_points_on_line(const struct VG_Point *point_start, const struct VG_Point *point_end, int stepsize, struct VG_Point **pointp)
{
  struct VG_Point point0;
  int ranz, xdiff, ydiff, xneg, yneg, idiff, steppos;

  if (pointp != NULL) { *pointp = NULL; }
  if (point_start == NULL || point_end == NULL || pointp == NULL) { return 0; }
  if (stepsize < 1) { stepsize = 1; }

  xdiff = point_end->x - point_start->x;
  if (xdiff < 0) { xdiff = -xdiff; xneg = -1; } else { xneg = 1; }
  ydiff = point_end->y - point_start->y;
  if (ydiff < 0) { ydiff = -ydiff; yneg = -1; } else { yneg = 1; }

  point0 = *point_start;

  steppos = 0;
  ranz = 0;
  for (;;) {
    if (point0.x == point_end->x && point0.y == point_end->y) { break; }
    if (xdiff > ydiff) {
      point0.x += xneg;
      idiff = (point_end->x - point0.x) * xneg;
      point0.y = point_start->y + ((xdiff - idiff) * ydiff / xdiff * yneg);
    } else {
      point0.y += yneg;
      idiff = (point_end->y - point0.y) * yneg;
      point0.x = point_start->x + ((ydiff - idiff) * xdiff / ydiff * xneg);
    }
    if (++steppos == stepsize) {
      ranz++;
      if (ranz == 1) {
        *pointp = SML3_malloc(sizeof(**pointp));
      } else {
        *pointp = SML3_realloc(*pointp, ranz * sizeof(**pointp));
      }
      memmove(&(*pointp)[ranz - 1], &point0, sizeof(struct VG_Point));
      steppos = 0;
    }
  }

  if (steppos > 0) {
    ranz++;
    if (ranz == 1) {
      *pointp = SML3_malloc(sizeof(**pointp));
    } else {
      *pointp = SML3_realloc(*pointp, ranz * sizeof(**pointp));
    }
    memmove(&(*pointp)[ranz - 1], point_end, sizeof(struct VG_Point));
  }

  return ranz;
} /* Ende misc_points_on_line */


/* fade_out:
 * fade out window,
 * if audio-descriptor is given, wait until it ends
 * @param audc       audio-descriptor, or 0 = no audio
 * @return  VG_TRUE = OK or VG_FALSE = exit-request
 */
static VG_BOOL
misc_fadeout(int audc)
{
  const int loop_time = 100;
  struct VG_ImagecopyAttrPixel iattr_pixel;
  VG_BOOL retw;

  retw = VG_TRUE;
  VG_IMAGECOPY_ATTRPIXEL_DEFAULT(&iattr_pixel);

  /* fade out looping playing audio */
  if (audc > 0) {
    VG_BOOL islooping;
    if (vg4->audio->is_playing(audc, &islooping) && islooping) { vg4->audio->stop(audc, VG_TRUE); }
  }

  for (;;) {
    if (!vg4->input->update(VG_FALSE)) { retw = VG_FALSE; break; }

    if (audc > 0) {
      if (!vg4->audio->is_playing(audc, NULL)) { audc = 0; }
    } else {
      if (iattr_pixel.brightness == 0) { break; }
    }

    if (iattr_pixel.brightness < 3) { iattr_pixel.brightness = 0; } else { iattr_pixel.brightness -= 3; }
    vg4->window->setattr(&iattr_pixel);

    vg4->window->flush();
    vg4->misc->wait_time(loop_time);
  }

  VG_IMAGECOPY_ATTRPIXEL_DEFAULT(&iattr_pixel);
  vg4->window->setattr(&iattr_pixel);

  return retw;
} /* Ende misc_fadeout */


/* misc_colorbrightness:
 * change color brightness according to percent-value
 * @param color    one of VG_COLORS or from VG_COLOR_RGB()
 * @param percent  percent-value
 * @return  changed color
 */
static int
misc_colorbrightness(int color, int percent)
{
  unsigned int ri, gi, bi;
  struct VG_PixelColor pxcol;

  if (color > 0xffffff || color < 0) { return 0; }
  if (percent > 25500) { percent = 25500; }
  if (percent < 0) { percent = 0; }

  GET_RGBA(color, &pxcol.r, &pxcol.g, &pxcol.b, &pxcol.a);

  ri = (unsigned int)pxcol.r * percent / 100;
  if (ri > 255) { ri = 255; }
  pxcol.r = ri;

  gi = (unsigned int)pxcol.g * percent / 100;
  if (gi > 255) { gi = 255; }
  pxcol.g = gi;

  bi = (unsigned int)pxcol.b * percent / 100;
  if (bi > 255) { bi = 255; }
  pxcol.b = bi;

  return VG_COLOR_RGB(pxcol.r, pxcol.g, pxcol.b);
} /* Ende misc_colorbrightness */


/* misc_version:
 * return VgaGames4 version
 * @param maior    for returning maior number, or NULL
 * @param minor    for returning minor number, or NULL
 * @return VgaGames4 version (<maior>.<minor>[p<patch>])
 */
static const char *
misc_version(int *maior, int *minor)
{
  const char *kptr = strchr(VGAG_VERSION, '.');
  if (kptr == NULL) { SML3_fehlerexit("VgaGames4 version invalid: %s", VGAG_VERSION); }
  if (maior != NULL) { *maior = atoi(VGAG_VERSION); }
  if (minor != NULL) { *minor = atoi(kptr + 1); }
  return VGAG_VERSION;
} /* Ende misc_version */
